diff --git a/src/server/server.c b/src/server/server.c index f68615e..9c0058b 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -55,9 +55,15 @@ static CredentialStore* g_credentials = NULL; /* Opaque context threaded through to the config-frame gate: the connection's * SSL object (NULL over plaintext) so the gate can warn when a credential - * exchange is not encrypted. */ + * exchange is not encrypted, plus the super-mode override the gate decides on. + * The gate never mutates the received (const) Config; it records a forced + * SUPER_MODE_OFF here and the handler applies it exactly once after acceptance. */ typedef struct ModuleGateContext { SSL* ssl; + /* SUPER_MODE_OFF when this connection must not attempt any super-user + activity (operator --no-super, or a daemon module without the + `client owner = yes` opt-in); -1 when the config's own mode stands. */ + int super_mode_override; } ModuleGateContext; /* Aggregate payload bytes the multithreaded receiver may buffer ahead of the @@ -182,11 +188,15 @@ static const char* server_module_gate(const Config* config, void* context) { if (!config) return "missing config frame"; /* Operator veto: --no-super forces SUPER_MODE_OFF for this connection before - the copy-as gate is evaluated, and the caller clamps the accepted config - again after this returns so the ownership/device gates see it too. */ - Config* effective = (Config*)config; - if (server_no_super) - effective->super_mode = SUPER_MODE_OFF; + the copy-as gate is evaluated. The received config is const, so the gates + below evaluate a shallow effective copy (only super_mode differs); the + handler applies the recorded override to the accepted config exactly once. */ + Config effective = *config; + if (server_no_super) { + effective.super_mode = SUPER_MODE_OFF; + if (gate_ctx) + gate_ctx->super_mode_override = SUPER_MODE_OFF; + } /* --copy-as (P7 Wave E, protocol 2.18.0): FastSync's safe subset forces the ownership of every written entry to the requested ids, which needs a privileged (root) receiver. An unprivileged receiver REFUSES the whole @@ -195,7 +205,7 @@ static const char* server_module_gate(const Config* config, void* context) { The daemon's per-module client-chosen-ownership refusal is enforced after the module lookup below (it needs the module's opt-in) and covers --copy-as like every other ownership flag. */ - if (identity_copy_as_refused(effective)) { + if (identity_copy_as_refused(&effective)) { if (geteuid() != 0) log_message(LOG_LEVEL_ERROR, "--copy-as requires a privileged receiver (root); refusing"); else @@ -247,10 +257,10 @@ static const char* server_module_gate(const Config* config, void* context) { standalone/SSH server has a single operator-authorized root and keeps honoring these. */ if (!module->client_owner) { - /* Ownership: refuse the whole transfer up front (a clear failure). Uses the - original config so an explicit --super is caught even though super_mode is - clamped to OFF below. */ - if (identity_ownership_requested(config)) { + /* Ownership: refuse the whole transfer up front (a clear failure). Evaluated + against the effective copy (so an operator --no-super has already + neutralized an explicit --super), exactly as before. */ + if (identity_ownership_requested(&effective)) { log_message(LOG_LEVEL_ERROR, "daemon module '%s' refuses client-chosen ownership/super-user activities " "(no `client owner = yes` opt-in); refusing", @@ -258,13 +268,14 @@ static const char* server_module_gate(const Config* config, void* context) { return "client-chosen ownership is not permitted by this daemon module"; } /* Super-user DEVICE activities (char/block mknod and --write-devices) are - permitted under the default AUTO mode, so without this clamp a root daemon - would still let a non-opted module create arbitrary device nodes and write - raw devices. Force them off for this connection: those entries are - skipped (never mknod'ed) while an ordinary `-a` push still succeeds + permitted under the default AUTO mode, so without this override a root + daemon would still let a non-opted module create arbitrary device nodes + and write raw devices. Force them off for this connection: those entries + are skipped (never mknod'ed) while an ordinary `-a` push still succeeds without device nodes, matching the operator's least-privilege choice. The operator-level --no-super veto is already folded into this. */ - effective->super_mode = SUPER_MODE_OFF; + if (gate_ctx) + gate_ctx->super_mode_override = SUPER_MODE_OFF; } if (module->auth_user_count > 0) { /* Auth-required module (Wave B): verify the presented credentials against @@ -322,6 +333,7 @@ void handler(int file_descriptor) { protocol_session_bind(&session); ModuleGateContext gate_ctx; gate_ctx.ssl = ssl; + gate_ctx.super_mode_override = -1; Config* config = config_receive_with_validate(file_descriptor, server_module_gate, &gate_ctx); if (config == NULL) { log_message(LOG_LEVEL_ERROR, "Failed to receive config"); @@ -329,12 +341,13 @@ void handler(int file_descriptor) { protocol_session_unbind(); return; } - /* Operator --no-super veto: clamp the accepted config so every downstream - * gate (identity_apply_ownership via privilege_super_permitted, device-node - * creation) sees SUPER_MODE_OFF even if the gate callback did not already - * mutate a copy of it. */ - if (server_no_super) - config->super_mode = SUPER_MODE_OFF; + /* Apply the super-mode veto the gate decided on (operator --no-super, or a + * daemon module without the `client owner = yes` opt-in) exactly once, so + * every downstream gate (identity_apply_ownership via privilege_super_permitted, + * device-node creation) sees SUPER_MODE_OFF. The gate never mutated the + * received config. */ + if (gate_ctx.super_mode_override != -1) + config->super_mode = gate_ctx.super_mode_override; protocol_set_8_bit_output(config->eight_bit_output); if (!authorized_root) { log_message(LOG_LEVEL_ERROR, "No server-side destination root configured"); @@ -417,8 +430,10 @@ void handler(int file_descriptor) { before anything else; without it the root must pre-exist. A failure here aborts the connection cleanly before any file data is exchanged. */ if (!ensure_receive_root(config)) { + char* escaped_root = output_escape(config->receive_root_directory, log_get_8_bit_output()); log_message(LOG_LEVEL_ERROR, "destination root is not available: %s", - config->receive_root_directory); + escaped_root ? escaped_root : ""); + free(escaped_root); config_delete(config); close(file_descriptor); protocol_session_unbind(); @@ -440,8 +455,16 @@ void handler(int file_descriptor) { } /* Preserve the negotiated identity policy for the fd-relative ownership apply path. Each connection is its own forked process, so this - per-process snapshot never races another connection. */ - identity_set_active(config); + per-process snapshot never races another connection. A failed deep copy + (allocation failure) leaves the snapshot cleared, so refuse the connection + rather than silently applying the wrong ownership policy. */ + if (!identity_set_active(config)) { + log_message(LOG_LEVEL_ERROR, "Failed to activate identity policy"); + config_delete(config); + close(file_descriptor); + protocol_session_unbind(); + return; + } /* Persist the negotiated --keep-dirlinks policy once, here at config-accept, before any multithreaded receiver/writer threads are spawned, so the fd-walk reads a stable value during the whole transfer (and never bleeds @@ -485,6 +508,7 @@ void handler(int file_descriptor) { config_delete(config); close(file_descriptor); protocol_session_unbind(); + identity_clear_active(); return; } PipelineContextReceiver* context = diff --git a/src/shared/file_receive.c b/src/shared/file_receive.c index 7059596..d29bf77 100644 --- a/src/shared/file_receive.c +++ b/src/shared/file_receive.c @@ -350,7 +350,10 @@ static FileSaveResult file_save_special_to_disk(const char* root_directory, cons } if (is_sock) { /* No standard filesystem call recreates a socket; best-effort unsupported. */ - log_message(LOG_LEVEL_WARNING, "socket not recreated: %s (unsupported; skipped)", file->path); + char* escaped_path = output_escape(file->path, log_get_8_bit_output()); + log_message(LOG_LEVEL_WARNING, "socket not recreated: %s (unsupported; skipped)", + escaped_path ? escaped_path : ""); + free(escaped_path); return FILE_SAVE_SKIPPED; } if (is_char || is_blk) { @@ -363,9 +366,11 @@ static FileSaveResult file_save_special_to_disk(const char* root_directory, cons so the policy does not depend on a prior identity_set_active(). Pure FIFO creation is unprivileged and deliberately NOT gated here. */ if (!privilege_super_mode_permitted(config->super_mode)) { + char* escaped_path = output_escape(file->path, log_get_8_bit_output()); log_message(LOG_LEVEL_WARNING, "skipping %s: super-user device-node creation is not permitted on this receiver", - file->path); + escaped_path ? escaped_path : ""); + free(escaped_path); return FILE_SAVE_SKIPPED; } } else if (is_fifo) { @@ -438,18 +443,26 @@ static FileSaveResult file_save_special_to_disk(const char* root_directory, cons free(destination); return FILE_SAVE_SKIPPED; } + char* escaped_path = output_escape(file->path, log_get_8_bit_output()); log_message(LOG_LEVEL_WARNING, "refusing to replace existing entry with %s: %s (skipped)", - is_fifo ? "FIFO" : "device", file->path); + is_fifo ? "FIFO" : "device", escaped_path ? escaped_path : ""); + free(escaped_path); } else if (errno == EPERM || errno == EACCES) { /* Missing CAP_MKNOD / parent write permission: the environment cannot create the node, so skip instead of failing the whole run. */ + char* escaped_path = output_escape(file->path, log_get_8_bit_output()); log_message(LOG_LEVEL_WARNING, "skipping %s: cannot create %s node (%s)\n" " --devices/--specials node creation needs privilege (CAP_MKNOD)", - file->path, is_fifo ? "FIFO" : "device", strerror(errno)); + escaped_path ? escaped_path : "", is_fifo ? "FIFO" : "device", + strerror(errno)); + free(escaped_path); } else { + char* escaped_path = output_escape(file->path, log_get_8_bit_output()); log_message(LOG_LEVEL_WARNING, "failed to create %s %s: %s (skipped)", - is_fifo ? "FIFO" : "device", file->path, strerror(errno)); + is_fifo ? "FIFO" : "device", escaped_path ? escaped_path : "", + strerror(errno)); + free(escaped_path); } close(parent_fd); free(leaf); @@ -512,22 +525,28 @@ static FileSaveResult file_save_write_device(const char* root_directory, const F close(parent_fd); if (fd < 0) { free(destination); + char* escaped_path = output_escape(file->path, log_get_8_bit_output()); + const char* shown_path = escaped_path ? escaped_path : ""; if (saved_errno == ENXIO || saved_errno == EAGAIN) { /* A FIFO with no reader / an unreadable special: skip like every other unusable write-devices target instead of blocking or failing. */ - log_message(LOG_LEVEL_WARNING, "write-devices: %s not writable (%s); skipped", file->path, + log_message(LOG_LEVEL_WARNING, "write-devices: %s not writable (%s); skipped", shown_path, strerror(saved_errno)); } else { - log_message(LOG_LEVEL_WARNING, "write-devices: cannot open %s (%s); skipped", file->path, + log_message(LOG_LEVEL_WARNING, "write-devices: cannot open %s (%s); skipped", shown_path, strerror(saved_errno)); } + free(escaped_path); return FILE_SAVE_SKIPPED; } struct stat st; if (fstat(fd, &st) != 0 || !(S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode))) { close(fd); free(destination); - log_message(LOG_LEVEL_WARNING, "write-devices: %s is not a device node; skipped", file->path); + char* escaped_path = output_escape(file->path, log_get_8_bit_output()); + log_message(LOG_LEVEL_WARNING, "write-devices: %s is not a device node; skipped", + escaped_path ? escaped_path : ""); + free(escaped_path); return FILE_SAVE_SKIPPED; } bool ok = true; @@ -596,10 +615,12 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi open below keeps its own confinement and best-effort skip semantics). */ if (config && config->write_devices) { if (!privilege_super_mode_permitted(config->super_mode)) { + char* escaped_path = output_escape(file->path, log_get_8_bit_output()); log_message(LOG_LEVEL_WARNING, "write-devices: %s skipped: super-user activities are not permitted on this " "receiver", - file->path ? file->path : "(null)"); + escaped_path ? escaped_path : "(null)"); + free(escaped_path); return FILE_SAVE_SKIPPED; } return file_save_write_device(root_directory, file); diff --git a/src/shared/identity.c b/src/shared/identity.c index b30f8d7..5a39f0e 100644 --- a/src/shared/identity.c +++ b/src/shared/identity.c @@ -64,10 +64,10 @@ void identity_clear_active(void) { identity_active_reset(); } -void identity_set_active(const Config* config) { +bool identity_set_active(const Config* config) { identity_active_reset(); if (!config) - return; + return true; g_identity.numeric_ids = config->numeric_ids; g_identity.chown_uid_set = config->chown_uid_set; g_identity.chown_uid = config->chown_uid; @@ -79,19 +79,19 @@ void identity_set_active(const Config* config) { g_identity.copy_as_gid = config->copy_as_gid; if (config->usermap_count > 0) { g_identity.usermap = calloc((size_t)config->usermap_count, sizeof(IdentityMap)); - if (g_identity.usermap) { - memcpy(g_identity.usermap, config->usermap, - (size_t)config->usermap_count * sizeof(IdentityMap)); - g_identity.usermap_count = config->usermap_count; - } + if (!g_identity.usermap) + goto alloc_failed; + memcpy(g_identity.usermap, config->usermap, + (size_t)config->usermap_count * sizeof(IdentityMap)); + g_identity.usermap_count = config->usermap_count; } if (config->groupmap_count > 0) { g_identity.groupmap = calloc((size_t)config->groupmap_count, sizeof(IdentityMap)); - if (g_identity.groupmap) { - memcpy(g_identity.groupmap, config->groupmap, - (size_t)config->groupmap_count * sizeof(IdentityMap)); - g_identity.groupmap_count = config->groupmap_count; - } + if (!g_identity.groupmap) + goto alloc_failed; + memcpy(g_identity.groupmap, config->groupmap, + (size_t)config->groupmap_count * sizeof(IdentityMap)); + g_identity.groupmap_count = config->groupmap_count; } g_identity.set = true; /* A root receiver would honor any client-supplied ownership request (a @@ -113,6 +113,15 @@ void identity_set_active(const Config* config) { "--super requested but the receiver is not privileged; super-user " "activities (ownership, device nodes) will be attempted but refused " "by the kernel and skipped per entry"); + return true; + +alloc_failed: + /* Never proceed with a partial (count-left-zero) map: that would silently + apply the WRONG ownership policy. Fail closed and let the caller refuse + the connection. */ + log_message(LOG_LEVEL_ERROR, "memory allocation failed while activating identity policy"); + identity_active_reset(); + return false; } bool privilege_super_permitted(void) { @@ -440,6 +449,25 @@ static bool identity_id_fits_int32(unsigned long id) { return id <= (unsigned long)INT32_MAX; } +/* Resolve one --copy-as id token. A '*' token means the caller's current + * effective uid (user) or gid (group). Returns 0 on success. On failure sets + * *overflow when a '*' id was wider than int32 so the caller can log the + * specific message; otherwise the token was simply unresolvable. */ +static int identity_resolve_copy_as_id(const char* token, bool is_group, int32_t* out, + bool* overflow) { + *overflow = false; + if (strcmp(token, "*") == 0) { + unsigned long current = is_group ? (unsigned long)getegid() : (unsigned long)geteuid(); + if (!identity_id_fits_int32(current)) { + *overflow = true; + return -1; + } + *out = (int32_t)current; + return 0; + } + return identity_resolve_token(token, is_group, out); +} + int identity_parse_copy_as(Config* config, const char* value) { if (!config || !value || *value == '\0') { log_message(LOG_LEVEL_ERROR, "--copy-as requires USER[:GROUP]"); @@ -465,7 +493,7 @@ int identity_parse_copy_as(Config* config, const char* value) { log_message(LOG_LEVEL_ERROR, "memory allocation failed for --copy-as"); return -1; } - char* user_token = spec; + const char* user_token = spec; const char* group_token = NULL; char* colon = strchr(spec, ':'); if (colon) { @@ -477,57 +505,39 @@ int identity_parse_copy_as(Config* config, const char* value) { * (8-bit-safe) so a control byte cannot forge a log line. */ char* escaped_spec = output_escape(value, false); const char* shown = escaped_spec ? escaped_spec : ""; + int ret = -1; - int32_t uid; if (*user_token == '\0') { log_message(LOG_LEVEL_ERROR, "--copy-as is missing the user (got '%s')", shown); - free(escaped_spec); - free(spec); - return -1; + goto done; } - if (strcmp(user_token, "*") == 0) { - /* '*' means the current/root user: the client's euid. */ - if (!identity_id_fits_int32((unsigned long)geteuid())) { + bool overflow = false; + int32_t uid; + if (identity_resolve_copy_as_id(user_token, false, &uid, &overflow) != 0) { + if (overflow) log_message(LOG_LEVEL_ERROR, "--copy-as: current user id %lu exceeds INT32_MAX", (unsigned long)geteuid()); - free(escaped_spec); - free(spec); - return -1; - } - uid = (int32_t)geteuid(); - } else if (identity_resolve_token(user_token, false, &uid) != 0) { - log_message(LOG_LEVEL_ERROR, - "--copy-as could not resolve user (use a name that exists on the " - "source, '*', or @N): %s", - shown); - free(escaped_spec); - free(spec); - return -1; + else + log_message(LOG_LEVEL_ERROR, + "--copy-as could not resolve user (use a name that exists on the " + "source, '*', or @N): %s", + shown); + goto done; } int32_t gid; if (group_token) { if (*group_token == '\0') { log_message(LOG_LEVEL_ERROR, "--copy-as group is empty (got '%s')", shown); - free(escaped_spec); - free(spec); - return -1; + goto done; } - if (strcmp(group_token, "*") == 0) { - if (!identity_id_fits_int32((unsigned long)getegid())) { + if (identity_resolve_copy_as_id(group_token, true, &gid, &overflow) != 0) { + if (overflow) log_message(LOG_LEVEL_ERROR, "--copy-as: current group id %lu exceeds INT32_MAX", (unsigned long)getegid()); - free(escaped_spec); - free(spec); - return -1; - } - gid = (int32_t)getegid(); - } else if (identity_resolve_token(group_token, true, &gid) != 0) { - log_message(LOG_LEVEL_ERROR, "--copy-as could not resolve group (got '%s'): %s", shown, - shown); - free(escaped_spec); - free(spec); - return -1; + else + log_message(LOG_LEVEL_ERROR, "--copy-as could not resolve group (got '%s')", shown); + goto done; } } else { /* Group omitted: use the user's primary gid. A numeric id with no local @@ -539,9 +549,7 @@ int identity_parse_copy_as(Config* config, const char* value) { log_message(LOG_LEVEL_ERROR, "--copy-as: primary group id %lu for the requested user exceeds INT32_MAX", (unsigned long)pw->pw_gid); - free(escaped_spec); - free(spec); - return -1; + goto done; } gid = (int32_t)pw->pw_gid; } else { @@ -553,12 +561,8 @@ int identity_parse_copy_as(Config* config, const char* value) { * identity_resolve_token. */ if (uid < 0 || gid < 0) { log_message(LOG_LEVEL_ERROR, "--copy-as resolved id does not fit in int32 (got '%s')", shown); - free(escaped_spec); - free(spec); - return -1; + goto done; } - free(escaped_spec); - free(spec); config->copy_as_set = true; config->copy_as_uid = uid; @@ -566,7 +570,12 @@ int identity_parse_copy_as(Config* config, const char* value) { /* Ownership application needs the metadata path (the source uid/gid must be * transmitted); imply it exactly like --chown/--usermap/--groupmap. */ config->use_metadata = true; - return 0; + ret = 0; + +done: + free(escaped_spec); + free(spec); + return ret; } /* ---- Receiver-side ownership application ---- */ diff --git a/src/shared/identity.h b/src/shared/identity.h index 8b07e0c..592c5e7 100644 --- a/src/shared/identity.h +++ b/src/shared/identity.h @@ -67,8 +67,13 @@ bool identity_copy_as_active(void); /* Receiver-side snapshot of the negotiated identity config. The server calls * identity_set_active() once per connection (before any file write) using the * config received over the wire; the snapshot is a deep copy so the caller may - * free its Config immediately. identity_clear_active() releases it. */ -void identity_set_active(const Config* config); + * free its Config immediately. identity_clear_active() releases it. + * + * Returns true on success. On an allocation failure while deep-copying a + * requested usermap/groupmap it logs a LOG_LEVEL_ERROR, leaves the snapshot + * cleared (never a partial/wrong policy) and returns false; the caller must + * refuse the connection. */ +bool identity_set_active(const Config* config); void identity_clear_active(void); /* True when any ownership-affecting identity option is present in the active diff --git a/src/shared/protocol.c b/src/shared/protocol.c index 8f9b7f1..a840ca3 100644 --- a/src/shared/protocol.c +++ b/src/shared/protocol.c @@ -459,10 +459,14 @@ static char* protocol_receive_str_impl(ProtocolSession* session, bool redact) { return NULL; } data[size] = '\0'; - if (redact) + if (redact) { log_debug_message(LOG_DEBUG_PROTO, "Received String: "); - else - log_debug_message(LOG_DEBUG_PROTO, "Received String: %s", data); + } else { + char* escaped_data = output_escape(data, log_get_8_bit_output()); + log_debug_message(LOG_DEBUG_PROTO, "Received String: %s", + escaped_data ? escaped_data : ""); + free(escaped_data); + } return data; } diff --git a/tests/test_config.c b/tests/test_config.c index 77a7ba0..ade0564 100644 --- a/tests/test_config.c +++ b/tests/test_config.c @@ -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); diff --git a/tests/test_server.c b/tests/test_server.c index 6fc70ba..82b842e 100644 --- a/tests/test_server.c +++ b/tests/test_server.c @@ -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(); diff --git a/tests/test_xattr.c b/tests/test_xattr.c index c5e4316..4997ae2 100644 --- a/tests/test_xattr.c +++ b/tests/test_xattr.c @@ -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);