exclude: exempt the daemon's own filter parameters from the confinement

Confining every parse_filter_file() open to the module root also caught
"filter", "include from" and "exclude from" from rsyncd.conf.  Those name
operator-configured paths and pointing them outside the module -- at
/etc/rsync/excludes, say -- is the ordinary way to write them; rsyncd.conf(5)
puts no constraint on where the file lives.  The result was not a refused
rule but a refused connection:

    failed to open exclude file /etc/rsync/excludes:
        Too many levels of symbolic links (40)
    rsync error: error in file IO (code 11) at exclude.c(1582)

with no symlink involved anywhere -- just a regular file outside the module.

Mark the window in which the daemon loads its own parameters and skip the
confinement there.  Everything else, in particular the peer-driven dir-merge
the leak test exercises, is still confined.  Also fix the trailing whitespace
in the original hunk.

(cherry picked from commit 5eb99bb6b2)
This commit is contained in:
Andrew Tridgell committed 2026-08-02 21:33:25 +10:00
1 parent 12d133c625
commit 76b54cdba4
2 files changed
+26 -3

No files matched your search

+8
View File
@@ -42,6 +42,7 @@ extern int munge_symlinks;
extern int use_secure_symlinks;
extern int open_noatime;
extern int sanitize_paths;
extern int daemon_config_filter_file;
extern int numeric_ids;
extern int filesfrom_fd;
extern int remote_protocol;
@@ -899,6 +900,11 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
} else
set_filter_dir(module_dir, module_dirlen);
/* Everything loaded from here to the end of the exclude block is the
* operator's own configuration, so it keeps the ownership walk without the
* module-confinement parse_filter_file() applies to peer-driven merges. */
daemon_config_filter_file = 1;
p = lp_filter(module_id);
parse_filter_str(&daemon_filter_list, p, rule_template(FILTRULE_WORD_SPLIT),
XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3);
@@ -920,6 +926,8 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
parse_filter_str(&daemon_filter_list, p, rule_template(FILTRULE_WORD_SPLIT),
XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES);
daemon_config_filter_file = 0;
log_init(1);
#if defined HAVE_SETENV || defined HAVE_PUTENV
+18 -3
View File
@@ -43,6 +43,9 @@ extern int trust_sender_args;
extern int module_id;
extern int operator_path_resolve;
/* Set while the daemon loads its own filter parameters; see parse_filter_file(). */
int daemon_config_filter_file = 0;
extern char curr_dir[MAXPATHLEN];
extern unsigned int curr_dir_len;
extern unsigned int module_dirlen;
@@ -1554,12 +1557,24 @@ void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_
open_path = line;
} else
open_path = fname;
/* Confine the open to the module root. The ownership walk on its own
* is not enough for a peer-driven merge file: a non-chrooted daemon
* writes --backup-dir entries as root, so a raced backup symlink is
* ROOT-owned -- exactly what open_no_attacker_symlinks() treats as
* trusted -- and naming it in a dir-merge rule would read an
* out-of-module file in as filter rules (their text comes back to the
* peer in "Unknown filter rule" errors).
*
* The daemon's own "filter"/"include from"/"exclude from" parameters
* are exempt: those are operator-configured and legitimately live
* outside the module (/etc/rsync/excludes and the like). */
int save_opr = operator_path_resolve;
operator_path_resolve = 1;
if (!daemon_config_filter_file)
operator_path_resolve = 1;
fd = open_no_attacker_symlinks(open_path, O_RDONLY, 0);
operator_path_resolve = save_opr;
if (fd < 0)
fp = NULL;
else if (!(fp = fdopen(fd, "rb")))