From 762d0be04866060fc48e0ecf7c8af9eddb2ea63f Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Mon, 22 Jun 2026 09:16:41 +1000 Subject: [PATCH] flist/receiver: zero/empty the device & symlink extra slots recv_file_entry (normal and XMIT_HLINKED abbrev branches) and make_file left F_RDEV_P / the symlink-name slot uninitialized when the matching preserve option was off, so a later read walked into adjacent pool memory. Empty the symlink name when !preserve_links, zero F_RDEV_P when !preserve_devices, and mirror both in the abbreviated branch. receiver.c saves/restores the --write-devices S_IFBLK mode flip around receive_data so dest_mode() never sees the mutated mode. Co-authored-by: Greg Kroah-Hartman --- flist.c | 46 +++++++++++++++++++++++++++++++++++++++++++++- receiver.c | 26 ++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/flist.c b/flist.c index fcfbcecb..c78c88b7 100644 --- a/flist.c +++ b/flist.c @@ -889,9 +889,17 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x rdev_major = DEV_MAJOR(devp); rdev = MAKEDEV(rdev_major, DEV_MINOR(devp)); extra_len += DEV_EXTRA_CNT * EXTRA_LEN; + } else if (IS_DEVICE(mode)) { + /* Abbrev-branch counterpart to the !preserve_devices + * stub-alloc below: zeroed F_RDEV_P slots. */ + extra_len += DEV_EXTRA_CNT * EXTRA_LEN; } if (preserve_links && S_ISLNK(mode)) linkname_len = strlen(F_SYMLINK(first)) + 1; + else if (S_ISLNK(mode)) + /* Abbrev-branch counterpart to the !preserve_links + * stub-alloc below: empty linkname. */ + linkname_len = 1; else linkname_len = 0; real_ISREG_entry = S_ISREG(mode) ? 1 : 0; @@ -1013,6 +1021,15 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x if (IS_DEVICE(mode)) extra_len += DEV_EXTRA_CNT * EXTRA_LEN; file_length = 0; + } else if (IS_DEVICE(mode)) { + /* Peer/batch sent an S_IFCHR/S_IFBLK entry but we are not + * preserving devices. A cooperating sender wouldn't do this; + * a crafted batch can. Allocate (and zero, via the memset + * below) the DEV_EXTRA_CNT slots so F_RDEV_P() callers + * (set_stat_xattr under --fake-super, generator IS_DEVICE + * paths) read {0,0} instead of the previous pool slot. */ + extra_len += DEV_EXTRA_CNT * EXTRA_LEN; + file_length = 0; } else if (protocol_version < 28) rdev = MAKEDEV(0, 0); @@ -1033,6 +1050,14 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x #endif if (munge_symlinks) linkname_len += SYMLINK_PREFIX_LEN; + } else if (S_ISLNK(mode)) { + /* Peer/batch sent an S_IFLNK entry but we are not preserving + * links (no -l, and the batch stream-flags didn't set it). A + * cooperating sender wouldn't do this; a crafted batch can. + * Allocate one byte for an empty linkname so F_SYMLINK() + * callers (log.c %L, generator.c) read a valid "" instead of + * the next pool slot's redzone. */ + linkname_len = 1; } else #endif @@ -1228,7 +1253,11 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x #ifdef SUPPORT_LINKS if (linkname_len) { bp += basename_len; - if (first_hlink_ndx >= flist->ndx_start) { + if (!preserve_links) { + /* The empty-linkname case allocated above; nothing on + * the wire to read. Just terminate it. */ + *bp = '\0'; + } else if (first_hlink_ndx >= flist->ndx_start) { struct file_struct *first = flist->files[first_hlink_ndx - flist->ndx_start]; memcpy(bp, F_SYMLINK(first), linkname_len); } else { @@ -1532,6 +1561,18 @@ struct file_struct *make_file(const char *fname, struct file_list *flist, extra_len += SUM_EXTRA_CNT * EXTRA_LEN; } +#ifdef HAVE_STRUCT_STAT_ST_RDEV + /* The sender path historically passes rdev via the tmp_rdev static + * (read by send_file_entry()), so make_file() never reserved + * DEV_EXTRA_CNT in the file_struct itself. But receiver-side callers + * (recv_generator's --inplace --backup back_file, backup.c make_backup) + * hand this struct to set_file_attrs() -> set_stat_xattr(), which reads + * F_RDEV_P(file) under --fake-super. Reserve and populate the slots + * so the struct is self-contained, matching recv_file_entry(). */ + if (IS_DEVICE(st.st_mode)) + extra_len += DEV_EXTRA_CNT * EXTRA_LEN; +#endif + #if EXTRA_ROUNDING > 0 if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN)) extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN; @@ -1565,7 +1606,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist, #ifdef HAVE_STRUCT_STAT_ST_RDEV if (IS_DEVICE(st.st_mode)) { + uint32 *devp = F_RDEV_P(file); tmp_rdev = st.st_rdev; + DEV_MAJOR(devp) = major(st.st_rdev); + DEV_MINOR(devp) = minor(st.st_rdev); st.st_size = 0; } else if (IS_SPECIAL(st.st_mode)) st.st_size = 0; diff --git a/receiver.c b/receiver.c index 00c0d001..7cd6d714 100644 --- a/receiver.c +++ b/receiver.c @@ -704,7 +704,8 @@ int recv_files(int f_in, int f_out, char *local_name) #ifdef SUPPORT_ACLS const char *parent_dirname = ""; #endif - int ndx, recv_ok, one_inplace; + int ndx, recv_ok, one_inplace, write_to_device; + mode_t write_devices_saved_mode = 0; if (DEBUG_GTE(RECV, 1)) rprintf(FINFO, "recv_files(%d) starting\n", cur_flist->used); @@ -1025,11 +1026,10 @@ int recv_files(int f_in, int f_out, char *local_name) continue; } - if (write_devices && IS_DEVICE(st.st_mode)) { + write_to_device = write_devices && IS_DEVICE(st.st_mode); + if (write_to_device) { if (fd1 != -1 && st.st_size == 0) st.st_size = get_device_size(fd1, fname); - /* Mark the file entry as a device so that we don't try to truncate it later on. */ - file->mode = S_IFBLK | (file->mode & ACCESSPERMS); } else if (fd1 != -1 && !(S_ISREG(st.st_mode))) { close(fd1); fd1 = -1; @@ -1103,9 +1103,27 @@ int recv_files(int f_in, int f_out, char *local_name) else if (!am_server && INFO_GTE(NAME, 1) && INFO_EQ(PROGRESS, 1)) rprintf(FINFO, "%s\n", fname); + /* --write-devices writes a regular source file's content into an + * existing destination device. Flip file->mode to S_IFBLK just for + * receive_data()'s ftruncate gate (!IS_DEVICE), then restore it right + * after -- the file_struct was built as a regular file with no + * DEV_EXTRA_CNT, so leaving it S_IFBLK would make set_stat_xattr's + * F_RDEV_P() read past the allocation. Kept tight here (after the + * pre-transfer log and the fd2 bail-out) so no continue skips the + * restore and dest_mode() above ran on the real mode. */ + if (write_to_device) { + write_devices_saved_mode = file->mode; + file->mode = S_IFBLK | (file->mode & ACCESSPERMS); + } + /* recv file data */ recv_ok = receive_data(f_in, fnamecmp, fd1, st.st_size, fname, fd2, file, inplace || one_inplace); + if (write_to_device) { + file->mode = write_devices_saved_mode; + write_devices_saved_mode = 0; + } + log_item(log_code, file, iflags, NULL); if (want_progress_now) instant_progress(fname);