From 859d44fa4f1420775e4ba050337ef32092f2894c Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Sun, 1 Mar 2026 09:28:40 +1100 Subject: [PATCH] sender: fix read-path TOCTOU by opening from module root (CVE-2026-29518) The sender's file open was vulnerable to the same TOCTOU symlink race as the receiver-side basis-file open. change_pathname() calls chdir() into subdirectories, which follows symlinks; an attacker could race to swap a directory for a symlink between the chdir and the file open, allowing reads of privileged files through the daemon. Reconstruct the full relative path (F_PATHNAME + fname) and open via secure_relative_open() from the trusted module_dir, which walks each path component without following symlinks. This is independent of CWD, so the chdir race is neutralised. CVE-2026-29518. Co-Authored-By: Claude Opus 4.6 --- sender.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/sender.c b/sender.c index b1588b70..99f431fe 100644 --- a/sender.c +++ b/sender.c @@ -48,6 +48,8 @@ extern int make_backups; extern int inplace; extern int inplace_partial; extern int batch_fd; +extern int use_secure_symlinks; +extern char *module_dir; extern int write_batch; extern int file_old_total; extern BOOL want_progress_now; @@ -352,7 +354,25 @@ void send_files(int f_in, int f_out) exit_cleanup(RERR_PROTOCOL); } - fd = do_open_checklinks(fname); + if (use_secure_symlinks) { + /* Open from module root to prevent TOCTOU race where + * change_pathname's chdir follows a directory symlink. + * Reconstruct the full path relative to module_dir + * from F_PATHNAME (path) and f_name (fname). */ + char secure_path[MAXPATHLEN]; + int slen = snprintf(secure_path, sizeof secure_path, "%s%s%s", path, slash, fname); + if (slen >= (int)sizeof secure_path) { + io_error |= IOERR_GENERAL; + rprintf(FERROR_XFER, "path too long: %s%s%s\n", path, slash, fname); + free_sums(s); + if (protocol_version >= 30) + send_msg_int(MSG_NO_SEND, ndx); + continue; + } + fd = secure_relative_open(module_dir, secure_path, O_RDONLY, 0); + } else { + fd = do_open_checklinks(fname); + } if (fd == -1) { if (errno == ENOENT) { enum logcode c = am_daemon && protocol_version < 28 ? FERROR : FWARNING;