From f45f48055e548851bc7230f454dfeba139be6c04 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell 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 Backported-By: Samuel Henrique * Refresh patch to remove offset. --- 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/checksum.c =================================================================== --- rsync.orig/checksum.c +++ rsync/checksum.c @@ -406,7 +406,7 @@ void file_checksum(const char *fname, co int32 remainder; int fd; - fd = do_open(fname, O_RDONLY, 0); + fd = do_open_checklinks(fname); if (fd == -1) { memset(sum, 0, file_sum_len); return; Index: rsync/flist.c =================================================================== --- rsync.orig/flist.c +++ rsync/flist.c @@ -1390,7 +1390,7 @@ struct file_struct *make_file(const char if (copy_devices && am_sender && IS_DEVICE(st.st_mode)) { if (st.st_size == 0) { - int fd = do_open(fname, O_RDONLY, 0); + int fd = do_open_checklinks(fname); if (fd >= 0) { st.st_size = get_device_size(fd, fname); close(fd); Index: rsync/generator.c =================================================================== --- rsync.orig/generator.c +++ rsync/generator.c @@ -1798,7 +1798,7 @@ static void recv_generator(char *fname, if (write_devices && IS_DEVICE(sx.st.st_mode) && sx.st.st_size == 0) { /* This early open into fd skips the regular open below. */ - if ((fd = do_open(fnamecmp, O_RDONLY, 0)) >= 0) + if ((fd = do_open_nofollow(fnamecmp, O_RDONLY)) >= 0) real_sx.st.st_size = sx.st.st_size = get_device_size(fd, fnamecmp); } @@ -1867,7 +1867,7 @@ static void recv_generator(char *fname, } /* open the file */ - if (fd < 0 && (fd = do_open(fnamecmp, O_RDONLY, 0)) < 0) { + if (fd < 0 && (fd = do_open_checklinks(fnamecmp)) < 0) { rsyserr(FERROR, errno, "failed to open %s, continuing", full_fname(fnamecmp)); pretend_missing: Index: rsync/receiver.c =================================================================== --- rsync.orig/receiver.c +++ rsync/receiver.c @@ -775,7 +775,7 @@ int recv_files(int f_in, int f_out, char if (fnamecmp != fname) { fnamecmp = fname; fnamecmp_type = FNAMECMP_FNAME; - fd1 = do_open(fnamecmp, O_RDONLY, 0); + fd1 = do_open_nofollow(fnamecmp, O_RDONLY); } if (fd1 == -1 && basis_dir[0]) { Index: rsync/sender.c =================================================================== --- rsync.orig/sender.c +++ rsync/sender.c @@ -350,7 +350,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 && protocol_version < 28 ? FERROR : FWARNING; Index: rsync/syscall.c =================================================================== --- rsync.orig/syscall.c +++ rsync/syscall.c @@ -45,6 +45,8 @@ extern int preallocate_files; extern int preserve_perms; extern int preserve_executability; extern int open_noatime; +extern int copy_links; +extern int copy_unsafe_links; #ifndef S_BLKSIZE # if defined hpux || defined __hpux__ || defined __hpux @@ -793,3 +795,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/t_unsafe.c =================================================================== --- rsync.orig/t_unsafe.c +++ rsync/t_unsafe.c @@ -28,6 +28,9 @@ int am_root = 0; int am_sender = 1; int read_only = 0; int list_only = 0; +int copy_links = 0; +int copy_unsafe_links = 0; + short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG]; int Index: rsync/tls.c =================================================================== --- rsync.orig/tls.c +++ rsync/tls.c @@ -49,6 +49,9 @@ int list_only = 0; int link_times = 0; int link_owner = 0; int nsec_times = 0; +int safe_symlinks = 0; +int copy_links = 0; +int copy_unsafe_links = 0; #ifdef SUPPORT_XATTRS Index: rsync/trimslash.c =================================================================== --- rsync.orig/trimslash.c +++ rsync/trimslash.c @@ -26,6 +26,8 @@ int am_root = 0; int am_sender = 1; int read_only = 1; int list_only = 0; +int copy_links = 0; +int copy_unsafe_links = 0; int main(int argc, char **argv) Index: rsync/util1.c =================================================================== --- rsync.orig/util1.c +++ rsync/util1.c @@ -365,7 +365,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;