From f45f48055e548851bc7230f454dfeba139be6c04 Mon Sep 17 00:00:00 2001
From: Andrew Tridgell <andrew@tridgell.net>
Date: Wed, 18 Dec 2024 08:59:42 +1100
Subject: [PATCH] fixed symlink race condition in sender

when we open a file that we don't expect to be a symlink use
O_NOFOLLOW to prevent a race condition where an attacker could change
a file between being a normal file and a symlink
---
 checksum.c  |  2 +-
 flist.c     |  2 +-
 generator.c |  4 ++--
 receiver.c  |  2 +-
 sender.c    |  2 +-
 syscall.c   | 20 ++++++++++++++++++++
 t_unsafe.c  |  3 +++
 tls.c       |  3 +++
 trimslash.c |  2 ++
 util1.c     |  2 +-
 10 files changed, 35 insertions(+), 7 deletions(-)

Index: rsync-3.1.3/checksum.c
===================================================================
--- rsync-3.1.3.orig/checksum.c
+++ rsync-3.1.3/checksum.c
@@ -200,7 +200,7 @@ void file_checksum(const char *fname, co
 
 	memset(sum, 0, MAX_DIGEST_LEN);
 
-	fd = do_open(fname, O_RDONLY, 0);
+	fd = do_open_checklinks(fname);
 	if (fd == -1)
 		return;
 
Index: rsync-3.1.3/generator.c
===================================================================
--- rsync-3.1.3.orig/generator.c
+++ rsync-3.1.3/generator.c
@@ -1828,7 +1828,7 @@ static void recv_generator(char *fname,
 	}
 
 	/* open the file */
-	if ((fd = do_open(fnamecmp, O_RDONLY, 0)) < 0) {
+	if ((fd = do_open_checklinks(fnamecmp)) < 0) {
 		rsyserr(FERROR, errno, "failed to open %s, continuing",
 			full_fname(fnamecmp));
 	  pretend_missing:
Index: rsync-3.1.3/receiver.c
===================================================================
--- rsync-3.1.3.orig/receiver.c
+++ rsync-3.1.3/receiver.c
@@ -758,7 +758,7 @@ int recv_files(int f_in, int f_out, char
 		if (fd1 == -1 && protocol_version < 29) {
 			if (fnamecmp != fname) {
 				fnamecmp = fname;
-				fd1 = do_open(fnamecmp, O_RDONLY, 0);
+				fd1 = do_open_nofollow(fnamecmp, O_RDONLY);
 			}
 
 			if (fd1 == -1 && basis_dir[0]) {
Index: rsync-3.1.3/sender.c
===================================================================
--- rsync-3.1.3.orig/sender.c
+++ rsync-3.1.3/sender.c
@@ -335,7 +335,7 @@ void send_files(int f_in, int f_out)
 			exit_cleanup(RERR_PROTOCOL);
 		}
 
-		fd = do_open(fname, O_RDONLY, 0);
+		fd = do_open_checklinks(fname);
 		if (fd == -1) {
 			if (errno == ENOENT) {
 				enum logcode c = am_daemon
Index: rsync-3.1.3/syscall.c
===================================================================
--- rsync-3.1.3.orig/syscall.c
+++ rsync-3.1.3/syscall.c
@@ -45,6 +45,8 @@ extern int preallocate_files;
 extern int preserve_perms;
 extern int preserve_executability;
 extern int noatime;
+extern int copy_links;
+extern int copy_unsafe_links;
 
 #ifndef S_BLKSIZE
 # if defined hpux || defined __hpux__ || defined __hpux
@@ -659,3 +661,21 @@ cleanup:
 	return retfd;
 #endif // O_NOFOLLOW, O_DIRECTORY
 }
+
+/*
+  varient of do_open/do_open_nofollow which does do_open() if the
+  copy_links or copy_unsafe_links options are set and does
+  do_open_nofollow() otherwise
+
+  This is used to prevent a race condition where an attacker could be
+  switching a file between being a symlink and being a normal file
+
+  The open is always done with O_RDONLY flags
+ */
+int do_open_checklinks(const char *pathname)
+{
+	if (copy_links || copy_unsafe_links) {
+		return do_open(pathname, O_RDONLY, 0);
+	}
+	return do_open_nofollow(pathname, O_RDONLY);
+}
Index: rsync-3.1.3/t_unsafe.c
===================================================================
--- rsync-3.1.3.orig/t_unsafe.c
+++ rsync-3.1.3/t_unsafe.c
@@ -31,6 +31,9 @@ int list_only = 0;
 int human_readable = 0;
 int preserve_perms = 0;
 int preserve_executability = 0;
+int copy_links = 0;
+int copy_unsafe_links = 0;
+
 short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
 
 /* This is to make syscall.o shut up. */
Index: rsync-3.1.3/tls.c
===================================================================
--- rsync-3.1.3.orig/tls.c
+++ rsync-3.1.3/tls.c
@@ -54,6 +54,9 @@ int preserve_executability = 0;
 // int preallocate_files = 0;
 // int inplace = 0;
 int noatime = 0;
+int safe_symlinks = 0;
+int copy_links = 0;
+int copy_unsafe_links = 0;
 
 #ifdef SUPPORT_XATTRS
 
Index: rsync-3.1.3/trimslash.c
===================================================================
--- rsync-3.1.3.orig/trimslash.c
+++ rsync-3.1.3/trimslash.c
@@ -31,6 +31,8 @@ int preserve_executability = 0;
 // int preallocate_files = 0;
 // int inplace = 0;
 int noatime = 0;
+int copy_links = 0;
+int copy_unsafe_links = 0;
 
 int
 main(int argc, char **argv)
Index: rsync-3.1.3/util.c
===================================================================
--- rsync-3.1.3.orig/util.c
+++ rsync-3.1.3/util.c
@@ -462,7 +462,7 @@ int copy_file(const char *source, const
 	int len;   /* Number of bytes read into `buf'. */
 	OFF_T prealloc_len = 0, offset = 0;
 
-	if ((ifd = do_open(source, O_RDONLY, 0)) < 0) {
+	if ((ifd = do_open_nofollow(source, O_RDONLY)) < 0) {
 		int save_errno = errno;
 		rsyserr(FERROR_XFER, errno, "open %s", full_fname(source));
 		errno = save_errno;
