]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
sender: fix read-path TOCTOU by opening from module root (CVE-2026-29518)
authorAndrew Tridgell <andrew@tridgell.net>
Sat, 28 Feb 2026 22:28:40 +0000 (09:28 +1100)
committerAndrew Tridgell <andrew@tridgell.net>
Thu, 7 May 2026 21:49:13 +0000 (07:49 +1000)
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 <noreply@anthropic.com>
sender.c

index b1588b7011720c9cfaa6c8af9bdf54e712f4c0bd..99f431fec246b2da2961dd91cb7b5089c9b9e175 100644 (file)
--- 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;