Merge branch 'fix/audit-receiver' into fix/audit-cycle
This commit is contained in:
+43
-9
@@ -1136,17 +1136,51 @@ int file_open_private_dir(const char* dir_path) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* Open a --temp-dir scratch directory exactly as rsync does: the directory must
|
||||
* already exist and is used as given (an absolute path is used verbatim, a
|
||||
* relative one was already resolved against the destination root by the
|
||||
* caller). Unlike file_open_private_dir this neither creates it nor confines
|
||||
* it below the receive root, because rsync accepts any temp dir -- including
|
||||
* one outside the destination tree or on another filesystem. Returns an
|
||||
* O_DIRECTORY|O_CLOEXEC fd, or -1 on error. */
|
||||
/* Open a --temp-dir scratch directory. The directory must already exist (rsync
|
||||
* never creates it); a relative path was already resolved against the
|
||||
* destination root by the caller. Unlike file_open_private_dir this neither
|
||||
* creates it nor requires it to be a direct child of the receive root, because
|
||||
* rsync permits a scratch dir that (via a symlink) lands on another filesystem
|
||||
* -- but it MUST resolve inside the authorized receive root. The directory is
|
||||
* opened following symlinks and then judged by the REAL path of the opened fd
|
||||
* (through /proc/self/fd), so a client-planted symlink under the receive root
|
||||
* can never redirect receiver scratch files outside the sandbox while an
|
||||
* in-root link to another filesystem (the EXDEV fallback case) still works.
|
||||
* Returns an O_DIRECTORY|O_CLOEXEC fd, or -1 on error (errno set; an escaping
|
||||
* target is reported as EACCES with a logged reason). */
|
||||
int file_open_temp_dir(const char* dir_path) {
|
||||
if (!dir_path)
|
||||
return -1;
|
||||
return open(dir_path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
int fd = open(dir_path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
const char* root = utils_get_authorized_root_path();
|
||||
if (!root) {
|
||||
/* No authorized root (e.g. a local batch apply): nothing to confine
|
||||
against, so preserve the historical open-as-given behavior. */
|
||||
return fd;
|
||||
}
|
||||
char fd_path[64];
|
||||
int fd_path_length = snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", fd);
|
||||
char resolved[PATH_MAX];
|
||||
if (fd_path_length < 0 || (size_t)fd_path_length >= sizeof(fd_path) ||
|
||||
!realpath(fd_path, resolved)) {
|
||||
int saved_errno = errno;
|
||||
close(fd);
|
||||
errno = saved_errno;
|
||||
return -1;
|
||||
}
|
||||
if (!path_is_within_root(root, resolved)) {
|
||||
char* escaped = output_escape(dir_path, log_get_8_bit_output());
|
||||
log_message(LOG_LEVEL_ERROR,
|
||||
"--temp-dir '%s' resolves outside the authorized receive root; refusing",
|
||||
escaped ? escaped : "<allocation failed>");
|
||||
free(escaped);
|
||||
close(fd);
|
||||
errno = EACCES;
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* After the content and mode/times are restored on the just-written file, apply
|
||||
@@ -1859,6 +1893,6 @@ bool file_write_to_disk(const char* path, const void* data, unsigned long long d
|
||||
bool inplace, bool sparse) {
|
||||
if (!path || (!data && data_size != 0) || has_path_traversal(path))
|
||||
return false;
|
||||
FileAttrPolicy policy = {false, false, false, false};
|
||||
FileAttrPolicy policy = {0};
|
||||
return file_to_disk_secure(path, data, data_size, inplace, sparse, false, NULL, policy, NULL);
|
||||
}
|
||||
|
||||
+6
-2
@@ -103,8 +103,12 @@ bool file_remove_tree_secure(const char* path);
|
||||
the authorized root. Used for the --delay-updates staging directory. */
|
||||
int file_open_private_dir(const char* dir_path);
|
||||
|
||||
/* Open an existing --temp-dir scratch directory as-is (absolute or relative;
|
||||
no creation, no root confinement), matching rsync's --temp-dir handling. */
|
||||
/* Open an existing --temp-dir scratch directory (relative or absolute; no
|
||||
creation). When an authorized receive root is configured the directory's
|
||||
REAL path (symlinks resolved) must lie within it, so a client-planted
|
||||
symlink cannot redirect receiver scratch files outside the sandbox; an
|
||||
in-root symlink to another filesystem is still allowed for rsync's EXDEV
|
||||
fallback. */
|
||||
int file_open_temp_dir(const char* dir_path);
|
||||
|
||||
/* The file_to_disk_secure* variants write a temporary copy in the destination
|
||||
|
||||
@@ -29,6 +29,12 @@ typedef struct FileAttrPolicy {
|
||||
bool times; /* config->preserve_times: apply the source mtime */
|
||||
bool atimes; /* config->preserve_atimes (-U): apply the source atime */
|
||||
bool executability; /* config->use_executability (-E): exec-bits-only mode */
|
||||
/* privilege_super_mode_permitted(): when false (SUPER_MODE_OFF / --no-super,
|
||||
or a daemon that did not grant `client owner = yes`), the setuid/setgid/
|
||||
sticky bits are stripped from every applied mode (source mode and any
|
||||
--chmod result) even under --perms. When true, rsync's exact semantics are
|
||||
preserved: -p copies the special bits and the kernel decides. */
|
||||
bool super_permitted;
|
||||
} FileAttrPolicy;
|
||||
|
||||
/* Build the per-attribute policy from a connection's Config. A NULL config
|
||||
|
||||
@@ -474,9 +474,13 @@ static FileSaveResult file_save_special_to_disk(const char* root_directory, cons
|
||||
/* Under -p/--perms rsync copies the source's permission and special bits; a
|
||||
* kernel that denies setuid/setgid/sticky reports the failure rather than
|
||||
* having them masked here. Without -p the node is created like any other new
|
||||
* entry: source_mode & 0777 & ~umask. */
|
||||
* entry: source_mode & 0777 & ~umask. When super-user activities are
|
||||
* forbidden, the special bits are stripped even under -p (they are
|
||||
* super-user activities just like device-node creation). */
|
||||
mode_t perms = config->preserve_perms ? (mode & (mode_t)(S_ISUID | S_ISGID | S_ISVTX | 0777))
|
||||
: (mode & 0777 & ~(mode_t)file_process_umask());
|
||||
if (!privilege_super_mode_permitted(config->super_mode))
|
||||
perms &= ~(mode_t)(S_ISUID | S_ISGID | S_ISVTX);
|
||||
|
||||
int rc = is_fifo ? mkfifoat(parent_fd, leaf, perms)
|
||||
: mknodat(parent_fd, leaf, create_mode | perms, rdev);
|
||||
@@ -2949,8 +2953,12 @@ void dir_metadata_list_apply(const DirTimeList* list, const char* root_directory
|
||||
}
|
||||
if (mode_ready) {
|
||||
/* rsync -p copies the source directory mode exactly, including
|
||||
* group/other write and the setgid/sticky bits. */
|
||||
* group/other write and the setgid/sticky bits. Setuid/setgid/sticky
|
||||
* are super-user activities: when the connection forbade them
|
||||
* (SUPER_MODE_OFF / --no-super), strip them even under -p. */
|
||||
mode_t safe_mode = dir_mode & (mode_t)(S_ISUID | S_ISGID | S_ISVTX | 0777);
|
||||
if (!privilege_super_mode_permitted(config->super_mode))
|
||||
safe_mode &= ~(mode_t)(S_ISUID | S_ISGID | S_ISVTX);
|
||||
if (dir_fd < 0) {
|
||||
char* escaped_path = output_escape(dir_path, log_get_8_bit_output());
|
||||
log_message(LOG_LEVEL_WARNING, "Failed to open directory %s to set its mode: %s",
|
||||
|
||||
+20
-5
@@ -211,13 +211,22 @@ FileMetadata* metadata_receive(int file_descriptor, int* ok) {
|
||||
|
||||
bool metadata_mode_for_policy(mode_t source_mode, mode_t current_mode, FileAttrPolicy policy,
|
||||
mode_t* out_mode) {
|
||||
const mode_t special_bits = (mode_t)(S_ISUID | S_ISGID | S_ISVTX);
|
||||
const mode_t execute_bits = S_IXUSR | S_IXGRP | S_IXOTH;
|
||||
if (policy.perms) {
|
||||
/* rsync --perms copies the source's permission and special bits exactly,
|
||||
* including group/other write and setuid/setgid/sticky. The kernel may
|
||||
* still clear setgid when the receiver is not in the file's group; the
|
||||
* caller logs a failed chmod rather than silently masking the bits here. */
|
||||
*out_mode = source_mode & (mode_t)(S_ISUID | S_ISGID | S_ISVTX | 0777);
|
||||
* caller logs a failed chmod rather than silently masking the bits here.
|
||||
* Setuid/setgid/sticky are super-user activities: when the connection did
|
||||
* not permit them (SUPER_MODE_OFF / --no-super) they are stripped, so a
|
||||
* client can never install a privileged bit on a receiver that forbade
|
||||
* super-user activities. This also covers bits introduced by --chmod,
|
||||
* whose result is fed in as source_mode. */
|
||||
mode_t bits = source_mode & (mode_t)(special_bits | 0777);
|
||||
if (!policy.super_permitted)
|
||||
bits &= ~special_bits;
|
||||
*out_mode = bits;
|
||||
return true;
|
||||
}
|
||||
if (policy.executability) {
|
||||
@@ -227,8 +236,11 @@ bool metadata_mode_for_policy(mode_t source_mode, mode_t current_mode, FileAttrP
|
||||
* execute); otherwise clear every execute bit. This runs on the
|
||||
* destination-derived base (pre-existing dest mode, or source&~umask for a
|
||||
* new file), and leaves the special bits untouched. --perms wins when both
|
||||
* are set (handled above). */
|
||||
mode_t base = current_mode & (mode_t)(S_ISUID | S_ISGID | S_ISVTX | 0777);
|
||||
* are set (handled above). The destination's own special bits survive
|
||||
* unless super-user activities are forbidden. */
|
||||
mode_t base = current_mode & (mode_t)(special_bits | 0777);
|
||||
if (!policy.super_permitted)
|
||||
base &= ~special_bits;
|
||||
if (source_mode & 0111)
|
||||
*out_mode = base | ((base & 0444) >> 2);
|
||||
else
|
||||
@@ -240,12 +252,13 @@ bool metadata_mode_for_policy(mode_t source_mode, mode_t current_mode, FileAttrP
|
||||
}
|
||||
|
||||
FileAttrPolicy file_attr_policy_from_config(const Config* config) {
|
||||
FileAttrPolicy policy = {false, false, false, false};
|
||||
FileAttrPolicy policy = {0};
|
||||
if (config) {
|
||||
policy.perms = config->preserve_perms;
|
||||
policy.times = config->preserve_times;
|
||||
policy.atimes = config->preserve_atimes;
|
||||
policy.executability = config->use_executability;
|
||||
policy.super_permitted = privilege_super_mode_permitted(config->super_mode);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
@@ -314,6 +327,8 @@ bool file_restore_symlink_metadata(const char* path, const FileMetadata* metadat
|
||||
transfer never fails over it. */
|
||||
if (policy.perms) {
|
||||
mode_t link_mode = metadata->mode & (mode_t)(S_ISUID | S_ISGID | S_ISVTX | 0777);
|
||||
if (!policy.super_permitted)
|
||||
link_mode &= ~(mode_t)(S_ISUID | S_ISGID | S_ISVTX);
|
||||
if (fchmodat(parent_fd, leaf, link_mode, AT_SYMLINK_NOFOLLOW) != 0 && errno != EOPNOTSUPP &&
|
||||
errno != ENOTSUP && errno != ENOSYS) {
|
||||
log_message(LOG_LEVEL_DEBUG, "Could not set symlink mode on %s: %s", path, strerror(errno));
|
||||
|
||||
Reference in New Issue
Block a user