fix(receiver): confine --temp-dir scratch dir and gate setuid bits
Three receiver security fixes from the audit: 1. --temp-dir symlink escape (High): file_open_temp_dir() opened the client-controlled scratch dir with a bare open(), so a symlink planted under the receive root let a peer redirect receiver scratch files outside the authorized root. The opened dir is now judged by the REAL path of its fd (via /proc/self/fd), and any target outside the authorized receive root is refused with a logged error (EACCES). An in-root symlink (the EXDEV cross-filesystem fallback case) still works, and the no-root local batch path is unchanged. 2. setuid/setgid/sticky under SUPER_MODE_OFF (High): the special bits were applied under --perms (and via --chmod) even when the connection forbade super-user activities. FileAttrPolicy gains super_permitted, set by file_attr_policy_from_config() from privilege_super_mode_permitted(); metadata_mode_for_policy(), the symlink path, the special-node creation path, and the deferred directory-mode apply now strip the special bits when it is false. Exact rsync semantics are preserved when permitted. 3. daemon umask (Low): daemonize() forced umask(0), so implied parent directories created without -p were world-writable 0777. Set the conventional daemon umask 022 instead (rsync never forces 0); -p/-a mode preservation is unaffected because it restores modes via fchmod. Tests: new unit tests for file_open_temp_dir confinement and the masked/unmasked special-bit policy (incl. the --chmod path), a daemon world-writable-dir regression test, an integration escape test, and a root-only integration test asserting special bits are masked without --allow-super. The old cross-filesystem test encoded the vulnerable behavior (symlink target outside the root) and is replaced by the escape test; the EXDEV fallback code is retained for in-root links.
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user