fix(p7-privilege): close re-review gaps (implicit dir ownership, daemon --super, write-devices gate)

This commit is contained in:
2026-09-12 12:54:45 +02:00
parent fdc0f238c9
commit 938886829e
6 changed files with 109 additions and 13 deletions
+13 -1
View File
@@ -16,6 +16,7 @@
#include "data.h"
#include "delta.h"
#include "file.h"
#include "identity.h"
#include "log.h"
#include "metadata.h"
#include "utils.h"
@@ -572,8 +573,19 @@ int file_open_secure_parent(const char* path, char** leaf_out, bool create_dirs)
if (strcmp(component, ".") != 0) {
int next = openat(fd, component, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (next < 0 && create_dirs && errno == ENOENT) {
if (mkdirat(fd, component, 0755) == 0 || errno == EEXIST)
bool created = mkdirat(fd, component, 0755) == 0;
if (created || errno == EEXIST) {
/* P7 Wave E: --copy-as owns EVERY entry, including the intermediate
directories this walk creates implicitly. Its target ids are a
global policy, so they are available here without per-entry source
metadata. Only a directory this walk actually created is chowned
(a pre-existing destination directory is left alone, matching
rsync's transferred-entry scope); the helper is a no-op unless an
identity policy is active. */
if (created && identity_copy_as_active())
identity_apply_ownership_link(fd, component, 0, 0);
next = openat(fd, component, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
}
}
/* --keep-dirlinks (-K): a path component that is an existing symlink to
an in-root directory is used as THAT directory rather than failing the
+19 -7
View File
@@ -357,13 +357,15 @@ static FileSaveResult file_save_special_to_disk(const char* root_directory, cons
if (!config || !config->preserve_devices)
return FILE_SAVE_SKIPPED;
/* --super / --no-super (P7 Wave E): char/block device-node creation is a
super-user activity. --no-super forbids it even for a root receiver; the
default AUTO only attempts it when already root. Pure FIFO creation is
unprivileged and deliberately NOT gated here. */
if (!privilege_super_permitted()) {
super-user activity. --no-super forbids it even for a root receiver;
AUTO and --super attempt it (an unprivileged attempt is refused by the
kernel and skipped). The helper is evaluated against THIS config's mode
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)) {
log_message(LOG_LEVEL_WARNING,
"skipping %s: super-user device-node creation is not permitted "
"(--no-super, or the receiver is not privileged)",
"(super-user activities disabled by --no-super)",
file->path);
return FILE_SAVE_SKIPPED;
}
@@ -586,9 +588,19 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
writing content (privilege-gated, confined, rdev-validated). */
if (file->is_special)
return file_save_special_to_disk(root_directory, file, config);
/* --write-devices: write straight into an existing device node. */
if (config && config->write_devices)
/* --write-devices: write straight into an existing device node. Writing
into a device is a super-user activity, so --no-super must suppress it just
like device-node creation; the default AUTO/--super attempt it (the wide
open below keeps its own confinement and best-effort skip semantics). */
if (config && config->write_devices) {
if (!privilege_super_mode_permitted(config->super_mode)) {
log_message(LOG_LEVEL_WARNING,
"write-devices: %s skipped: super-user activities disabled by --no-super",
file->path ? file->path : "(null)");
return FILE_SAVE_SKIPPED;
}
return file_save_write_device(root_directory, file);
}
/* Explicit directory entries (--dirs) carry an empty payload; the entry is
created as a directory under the receive root, applying the same secure
+2 -3
View File
@@ -93,7 +93,6 @@ void identity_set_active(const Config* config) {
g_identity.groupmap_count = config->groupmap_count;
}
}
g_identity.super_mode = config->super_mode;
g_identity.set = true;
/* A root receiver would honor any client-supplied ownership request (a
--usermap/--groupmap/--chown/--copy-as, or raw ids under --numeric-ids).
@@ -467,7 +466,7 @@ int identity_parse_copy_as(Config* config, const char* value) {
return -1;
}
char* user_token = spec;
char* group_token = NULL;
const char* group_token = NULL;
char* colon = strchr(spec, ':');
if (colon) {
*colon = '\0';
@@ -726,5 +725,5 @@ void identity_apply_ownership_link(int parent_fd, const char* leaf, int32_t sour
if (!identity_resolve_targets(&st, source_uid, source_gid, &uid, &gid))
return;
if (fchownat(parent_fd, leaf, uid, gid, AT_SYMLINK_NOFOLLOW) != 0)
identity_log_chown_failure("symlink", uid, gid);
identity_log_chown_failure("no-follow entry", uid, gid);
}