fix(p8h-core): escape log paths, fail closed on identity activation, tidy server gate

- A6: escape attacker-controlled file paths and the receive root in log
  lines (file_receive, server, protocol DEBUG) with output_escape()
- A8: identity_set_active() returns bool and fails closed when a requested
  usermap/groupmap cannot be deep-copied; handler refuses the connection
- remove the const cast and duplicate super_mode clamp from
  server_module_gate via an explicit override the handler applies once
- release the identity snapshot on the queue_create failure path
- refactor identity_parse_copy_as to a single cleanup tail and drop the
  duplicated group error format specifier
This commit is contained in:
2026-09-12 15:03:54 +02:00
parent b8db810ee5
commit 6d32bc795b
8 changed files with 207 additions and 106 deletions
+5 -5
View File
@@ -1883,13 +1883,13 @@ static void test_privilege_super_permitted_modes() {
Config* c = config_create();
EXPECT_NOT_NULL(c);
c->super_mode = SUPER_MODE_OFF;
identity_set_active(c);
EXPECT_TRUE(identity_set_active(c));
EXPECT_FALSE(privilege_super_permitted());
c->super_mode = SUPER_MODE_ON;
identity_set_active(c);
EXPECT_TRUE(identity_set_active(c));
EXPECT_TRUE(privilege_super_permitted());
c->super_mode = SUPER_MODE_AUTO;
identity_set_active(c);
EXPECT_TRUE(identity_set_active(c));
EXPECT_TRUE(privilege_super_permitted());
config_delete(c);
@@ -1952,10 +1952,10 @@ static void test_super_does_not_imply_numeric() {
EXPECT_NOT_NULL(c);
c->super_mode = SUPER_MODE_ON;
c->use_metadata = true;
identity_set_active(c);
EXPECT_TRUE(identity_set_active(c));
EXPECT_FALSE(identity_active_enabled());
c->numeric_ids = true;
identity_set_active(c);
EXPECT_TRUE(identity_set_active(c));
EXPECT_TRUE(identity_active_enabled());
identity_clear_active();
config_delete(c);
+38
View File
@@ -2,6 +2,7 @@
#include "config.h"
#include "delta.h"
#include "file.h"
#include "log.h"
#include "protocol.h"
#include "test_utils.h"
#include "utils.h"
@@ -724,7 +725,44 @@ static void test_receiver_pending_commits_missing_args() {
free(root);
}
/* A6: an attacker-controlled file path appearing in a log line must be escaped
so a control byte cannot forge a second log record. The socket special-node
branch logs file->path before touching the filesystem, making it a cheap way
to exercise an escaped site. The captured line must contain the escaped path
(`\#012` for the newline), never the raw control byte. */
static void test_special_socket_path_log_escaped() {
set_log_level(LOG_LEVEL_WARNING);
log_set_8_bit_output(false);
FILE* capture = tmpfile();
EXPECT_NOT_NULL(capture);
log_set_file(capture);
File* file = file_create("evil\npath");
EXPECT_NOT_NULL(file);
file->is_special = true;
file->metadata = calloc(1, sizeof(FileMetadata));
EXPECT_NOT_NULL(file->metadata);
file->metadata->mode = S_IFSOCK | 0644;
FileSaveResult result = file_save_to_disk_full("/tmp/dst", file, NULL);
EXPECT_EQ_INT(result, FILE_SAVE_SKIPPED);
fflush(capture);
rewind(capture);
char output[512] = {0};
size_t length = fread(output, 1, sizeof(output) - 1, capture);
output[length] = '\0';
log_set_file(NULL);
fclose(capture);
file_destroy(file);
EXPECT_NOT_NULL(strstr(output, "socket not recreated: evil\\#012path"));
}
void test_server() {
test_special_socket_path_log_escaped();
if (!is_running_under_valgrind()) {
test_receive_files_finished();
test_receive_files_single_file();
+4 -4
View File
@@ -311,7 +311,7 @@ static void test_fake_super_owner_gate() {
/* --no-super: the owner leg is skipped even as root. */
c->super_mode = SUPER_MODE_OFF;
identity_set_active(c);
EXPECT_TRUE(identity_set_active(c));
EXPECT_TRUE(fake_super_restore_fd(fd));
struct stat st;
EXPECT_EQ_INT(fstat(fd, &st), 0);
@@ -320,7 +320,7 @@ static void test_fake_super_owner_gate() {
/* AUTO with an identity policy: the recorded source owner is applied. */
c->super_mode = SUPER_MODE_AUTO;
identity_set_active(c);
EXPECT_TRUE(identity_set_active(c));
EXPECT_TRUE(fake_super_restore_fd(fd));
EXPECT_EQ_INT(fstat(fd, &st), 0);
EXPECT_EQ_INT((int)st.st_uid, 12345);
@@ -331,7 +331,7 @@ static void test_fake_super_owner_gate() {
EXPECT_EQ_INT(fchown(fd, 0, 0), 0);
c->numeric_ids = false;
c->super_mode = SUPER_MODE_ON;
identity_set_active(c);
EXPECT_TRUE(identity_set_active(c));
EXPECT_TRUE(fake_super_restore_fd(fd));
EXPECT_EQ_INT(fstat(fd, &st), 0);
EXPECT_EQ_INT((int)st.st_uid, 0);
@@ -342,7 +342,7 @@ static void test_fake_super_owner_gate() {
c->copy_as_set = true;
c->copy_as_uid = 777;
c->copy_as_gid = 778;
identity_set_active(c);
EXPECT_TRUE(identity_set_active(c));
EXPECT_TRUE(fake_super_restore_fd(fd));
EXPECT_EQ_INT(fstat(fd, &st), 0);
EXPECT_EQ_INT((int)st.st_uid, 0);