On Fri Jun 12, 2026 at 2:13 PM CEST, Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org wrote: > From: Ashishkumar Parmar <[email protected]> > > Pick the upstream backport [1] for CVE-2026-29518 as mentioned in [3], > where a non-chrooted rsync daemon could be exposed to a parent path > TOCTOU race that allowed file access outside the module. > > Also include the dependent upstream fix that followed the CVE fix: > - CVE-2026-29518_p2.patch [2] secures sender read-path opens by > opening files from the module root, closing the same race on the > read side. > > [1] > https://github.com/RsyncProject/rsync/commit/1a5ad81add1004354a3d8ba841b94ffe19cd2505 > [2] > https://github.com/RsyncProject/rsync/commit/99b36291d06ca66229942c7a525a1f5566f10c85 > [3] https://www.cve.org/CVERecord?id=CVE-2026-29518 > > Signed-off-by: Ashishkumar Parmar <[email protected]> > ---
Hello, It looks like this whole series is needed for wrynose (rsync 3.4.1). Can you send fixes there first, and, then, ping here? Thanks! > .../rsync/files/CVE-2026-29518_p1.patch | 330 ++++++++++++++++++ > .../rsync/files/CVE-2026-29518_p2.patch | 73 ++++ > meta/recipes-devtools/rsync/rsync_3.2.7.bb | 2 + > 3 files changed, 405 insertions(+) > create mode 100644 meta/recipes-devtools/rsync/files/CVE-2026-29518_p1.patch > create mode 100644 meta/recipes-devtools/rsync/files/CVE-2026-29518_p2.patch > > diff --git a/meta/recipes-devtools/rsync/files/CVE-2026-29518_p1.patch > b/meta/recipes-devtools/rsync/files/CVE-2026-29518_p1.patch > new file mode 100644 > index 0000000000..227ca56dd3 > --- /dev/null > +++ b/meta/recipes-devtools/rsync/files/CVE-2026-29518_p1.patch > @@ -0,0 +1,330 @@ > +From c5192e125999130b7e15c621989839da31b15a05 Mon Sep 17 00:00:00 2001 > +From: Andrew Tridgell <[email protected]> > +Date: Wed, 31 Dec 2025 10:01:23 +1100 > +Subject: [PATCH] syscall+clientserver: am_chrooted and use_secure_symlinks > for > + daemon-no-chroot (CVE-2026-29518) > + > +CVE-2026-29518: an rsync daemon configured with "use chroot = no" > +is exposed to a TOCTOU race on parent path components. A local > +attacker with write access to a module can replace a parent > +directory component with a symlink between the receiver's check > +and its open(), redirecting reads (basis-file disclosure) and > +writes (file overwrite) outside the module. Under elevated daemon > +privilege this allows privilege escalation. Default > +"use chroot = yes" is not exposed. > + > +Add secure_relative_open() in syscall.c. It walks the parent > +components under RESOLVE_BENEATH (Linux 5.6+) / > +O_RESOLVE_BENEATH (FreeBSD 13+, macOS 15+) / per-component > +O_NOFOLLOW elsewhere, anchored at a trusted dirfd, so a parent- > +symlink swap is rejected by the kernel. Route the receiver's > +basis-file open in receiver.c through it when use_secure_symlinks > +is set in clientserver.c rsync_module(). > + > +Reporters: Nullx3D (Batuhan SANCAK); Damien Neil; Michael Stapelberg. > + > +Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> > + > +CVE: CVE-2026-29518 > +Upstream-Status: Backport > [https://github.com/RsyncProject/rsync/commit/1a5ad81add1004354a3d8ba841b94ffe19cd2505] > + > +(cherry picked from commit 1a5ad81add1004354a3d8ba841b94ffe19cd2505) > +Signed-off-by: Ashishkumar Parmar <[email protected]> > +--- > + clientserver.c | 25 +++++++++ > + options.c | 9 ++++ > + receiver.c | 22 ++++++-- > + syscall.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++ > + 4 files changed, 192 insertions(+), 3 deletions(-) > + > +diff --git a/clientserver.c b/clientserver.c > +index 7c897abc..b6eba098 100644 > +--- a/clientserver.c > ++++ b/clientserver.c > +@@ -30,6 +30,7 @@ extern int list_only; > + extern int am_sender; > + extern int am_server; > + extern int am_daemon; > ++extern int am_chrooted; > + extern int am_root; > + extern int msgs2stderr; > + extern int rsync_port; > +@@ -38,6 +39,7 @@ extern int ignore_errors; > + extern int preserve_xattrs; > + extern int kluge_around_eof; > + extern int munge_symlinks; > ++extern int use_secure_symlinks; > + extern int open_noatime; > + extern int sanitize_paths; > + extern int numeric_ids; > +@@ -981,6 +983,7 @@ static int rsync_module(int f_in, int f_out, int i, > const char *addr, const char > + io_printf(f_out, "@ERROR: chroot failed\n"); > + return -1; > + } > ++ am_chrooted = 1; > + module_chdir = module_dir; > + } > + > +@@ -1003,6 +1006,15 @@ static int rsync_module(int f_in, int f_out, int i, > const char *addr, const char > + } > + } > + > ++ /* Enable secure symlink handling for any non-chrooted daemon module. > ++ * This prevents TOCTOU race attacks where an attacker could switch a > ++ * directory to a symlink between path validation and file open. > ++ * Match the gate used by the do_*_at() wrappers in syscall.c > ++ * (am_daemon && !am_chrooted) -- the protection has nothing to do > ++ * with symlink munging, so a module configured with > ++ * "munge symlinks = false" must still get the secure-open path. */ > ++ use_secure_symlinks = am_daemon && !am_chrooted; > ++ > + if (gid_list.count) { > + gid_t *gid_array = gid_list.items; > + if (setgid(gid_array[0])) { > +@@ -1305,6 +1317,19 @@ int start_daemon(int f_in, int f_out) > + rsyserr(FLOG, errno, "daemon chroot(\"%s\") failed", p); > + return -1; > + } > ++ /* Deliberately do NOT set am_chrooted here. am_chrooted > ++ * gates the per-module symlink-race defenses > ++ * (secure_relative_open() and the do_*_at() wrappers in > ++ * syscall.c) and means "the kernel is enforcing path > ++ * confinement at the module boundary". The daemon chroot > ++ * confines path resolution to the daemon-chroot directory, > ++ * not to any individual module path -- modules sharing the > ++ * daemon chroot are still distinguishable filesystem > ++ * subtrees and a sender-controlled symlink in module A > ++ * could redirect a syscall to module B (or to other files > ++ * inside the daemon chroot) without the per-module > ++ * defenses. Leave am_chrooted=0 here so secure_relative_open() > ++ * still fires for "use chroot = no" modules. */ > + if (chdir("/") < 0) { > + rsyserr(FLOG, errno, "daemon chdir(\"/\") failed"); > + return -1; > +diff --git a/options.c b/options.c > +index d38bbe8d..d4ca5396 100644 > +--- a/options.c > ++++ b/options.c > +@@ -113,11 +113,20 @@ int mkpath_dest_arg = 0; > + int allow_inc_recurse = 1; > + int xfer_dirs = -1; > + int am_daemon = 0; > ++/* Set after a successful per-module chroot ("use chroot = yes") in > ++ * clientserver.c. NOT set for the daemon-level "daemon chroot = /X" > ++ * chroot: that confines path resolution to /X, but module paths > ++ * /X/modA, /X/modB, etc. are not chroot boundaries, so the per-module > ++ * symlink-race defenses (secure_relative_open() / do_*_at() in > ++ * syscall.c, gated by `am_daemon && !am_chrooted`) must still fire > ++ * even when the daemon is inside a daemon chroot. */ > ++int am_chrooted = 0; > + int connect_timeout = 0; > + int keep_partial = 0; > + int safe_symlinks = 0; > + int copy_unsafe_links = 0; > + int munge_symlinks = 0; > ++int use_secure_symlinks = 0; > + int size_only = 0; > + int daemon_bwlimit = 0; > + int bwlimit = 0; > +diff --git a/receiver.c b/receiver.c > +index 77de8697..cbe18196 100644 > +--- a/receiver.c > ++++ b/receiver.c > +@@ -70,6 +70,7 @@ extern int fuzzy_basis; > + > + extern struct name_num_item *xfer_sum_nni; > + extern int xfer_sum_len; > ++extern int use_secure_symlinks; > + > + static struct bitbag *delayed_bits = NULL; > + static int phase = 0, redoing = 0; > +@@ -214,7 +215,12 @@ int open_tmpfile(char *fnametmp, const char *fname, > struct file_struct *file) > + * access to ensure that there is no race condition. They will be > + * correctly updated after the right owner and group info is set. > + * (Thanks to [email protected] for pointing this out.) */ > +- fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS); > ++ /* When use_secure_symlinks is on (non-chroot daemon with > munge_symlinks), > ++ * use secure_mkstemp to prevent symlink race attacks on parent > directories. */ > ++ if (use_secure_symlinks) > ++ fd = secure_mkstemp(fnametmp, (file->mode|added_perms) & > INITACCESSPERMS); > ++ else > ++ fd = do_mkstemp(fnametmp, (file->mode|added_perms) & > INITACCESSPERMS); > + > + #if 0 > + /* In most cases parent directories will already exist because their > +@@ -854,11 +860,21 @@ int recv_files(int f_in, int f_out, char *local_name) > + /* We now check to see if we are writing the file "inplace" */ > + if (inplace || one_inplace) { > + fnametmp = one_inplace ? partialptr : fname; > +- fd2 = do_open(fnametmp, O_WRONLY|O_CREAT, 0600); > ++ /* When use_secure_symlinks is on (non-chroot daemon), > ++ * use secure open to prevent symlink race attacks > where an > ++ * attacker could switch a directory to a symlink > between > ++ * path validation and file open. */ > ++ if (use_secure_symlinks) > ++ fd2 = secure_relative_open(NULL, fnametmp, > O_WRONLY|O_CREAT, 0600); > ++ else > ++ fd2 = do_open(fnametmp, O_WRONLY|O_CREAT, 0600); > + #ifdef linux > + if (fd2 == -1 && errno == EACCES) { > + /* Maybe the error was due to protected_regular > setting? */ > +- fd2 = do_open(fname, O_WRONLY, 0600); > ++ if (use_secure_symlinks) > ++ fd2 = secure_relative_open(NULL, fname, > O_WRONLY, 0600); > ++ else > ++ fd2 = do_open(fname, O_WRONLY, 0600); > + } > + #endif > + if (fd2 == -1) { > +diff --git a/syscall.c b/syscall.c > +index 8aab2cc0..8b39a6e2 100644 > +--- a/syscall.c > ++++ b/syscall.c > +@@ -882,6 +882,145 @@ cleanup: > + #endif // O_NOFOLLOW, O_DIRECTORY > + } > + > ++/* Fill buf with len random bytes. Prefers /dev/urandom for cryptographic > ++ * quality; falls back to rand() if /dev/urandom cannot be opened or read > ++ * (e.g. inside a chroot or container without /dev populated). */ > ++static void rand_bytes(unsigned char *buf, size_t len) > ++{ > ++#ifndef O_CLOEXEC > ++#define O_CLOEXEC 0 > ++#endif > ++ int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); > ++ if (fd >= 0) { > ++ ssize_t n = read(fd, buf, len); > ++ close(fd); > ++ if (n == (ssize_t)len) { > ++ return; > ++ } > ++ } > ++ for (size_t i = 0; i < len; i++) { > ++ buf[i] = (unsigned char)rand(); > ++ } > ++} > ++ > ++/* > ++ Secure version of mkstemp that prevents symlink attacks on parent > directories. > ++ Like secure_relative_open(), this walks the path checking each component > ++ with O_NOFOLLOW to prevent TOCTOU race conditions. > ++ > ++ The template may be relative or absolute, but must not contain ../ > components. > ++ Returns fd on success, -1 on error. > ++*/ > ++int secure_mkstemp(char *template, mode_t perms) > ++{ > ++#if !defined(O_NOFOLLOW) || !defined(O_DIRECTORY) || !defined(AT_FDCWD) > ++ /* Fall back to regular mkstemp on old systems */ > ++ return do_mkstemp(template, perms); > ++#else > ++ char *lastslash; > ++ int dirfd = AT_FDCWD; > ++ int fd = -1; > ++ > ++ if (!template) { > ++ errno = EINVAL; > ++ return -1; > ++ } > ++ if (strncmp(template, "../", 3) == 0 || strstr(template, "/../")) { > ++ errno = EINVAL; > ++ return -1; > ++ } > ++ > ++ /* For absolute paths, start the secure walk from "/" rather than CWD. > */ > ++ if (template[0] == '/') { > ++ dirfd = open("/", O_RDONLY | O_DIRECTORY | O_NOFOLLOW); > ++ if (dirfd < 0) > ++ return -1; > ++ } > ++ > ++ /* Find the last slash to separate directory from filename */ > ++ lastslash = strrchr(template, '/'); > ++ if (lastslash) { > ++ char *path_copy = my_strdup(template, __FILE__, __LINE__); > ++ if (!path_copy) > ++ return -1; > ++ > ++ /* Null-terminate at the last slash to get directory part */ > ++ path_copy[lastslash - template] = '\0'; > ++ > ++ /* Walk the directory path securely */ > ++ for (const char *part = strtok(path_copy, "/"); > ++ part != NULL; > ++ part = strtok(NULL, "/")) > ++ { > ++ int next_fd = openat(dirfd, part, O_RDONLY | > O_DIRECTORY | O_NOFOLLOW); > ++ if (next_fd == -1) { > ++ int save_errno = errno; > ++ free(path_copy); > ++ if (dirfd != AT_FDCWD) close(dirfd); > ++ errno = (save_errno == ELOOP) ? ELOOP : > save_errno; > ++ return -1; > ++ } > ++ if (dirfd != AT_FDCWD) close(dirfd); > ++ dirfd = next_fd; > ++ } > ++ free(path_copy); > ++ } > ++ > ++ /* Now create the temp file in the securely-opened directory */ > ++ perms |= S_IWUSR; > ++ > ++ /* Generate unique filename - we need to modify the template in place */ > ++ char *filename = lastslash ? lastslash + 1 : template; > ++ size_t filename_len = strlen(filename); > ++ > ++ if (filename_len < 6) { > ++ if (dirfd != AT_FDCWD) close(dirfd); > ++ errno = EINVAL; > ++ return -1; > ++ } > ++ char *suffix = filename + filename_len - 6; /* Points to XXXXXX */ > ++ if (strcmp(suffix, "XXXXXX") != 0) { > ++ if (dirfd != AT_FDCWD) close(dirfd); > ++ errno = EINVAL; > ++ return -1; > ++ } > ++ > ++ /* Try random suffixes until we find one that works */ > ++ static const char letters[] = > "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; > ++ for (int tries = 0; tries < 100; tries++) { > ++ unsigned char rbytes[6]; > ++ rand_bytes(rbytes, sizeof(rbytes)); > ++ for (int i = 0; i < 6; i++) > ++ suffix[i] = letters[rbytes[i] % (sizeof(letters) - 1)]; > ++ > ++ fd = openat(dirfd, filename, O_RDWR | O_CREAT | O_EXCL | > O_NOFOLLOW, perms); > ++ if (fd >= 0) > ++ break; > ++ if (errno != EEXIST) { > ++ if (dirfd != AT_FDCWD) close(dirfd); > ++ return -1; > ++ } > ++ } > ++ > ++ if (fd >= 0) { > ++ if (fchmod(fd, perms) != 0 && preserve_perms) { > ++ int errno_save = errno; > ++ close(fd); > ++ unlinkat(dirfd, filename, 0); > ++ if (dirfd != AT_FDCWD) close(dirfd); > ++ errno = errno_save; > ++ return -1; > ++ } > ++#if defined HAVE_SETMODE && O_BINARY > ++ setmode(fd, O_BINARY); > ++#endif > ++ } > ++ > ++ if (dirfd != AT_FDCWD) close(dirfd); > ++ return fd; > ++#endif > ++} > ++ > + /* > + varient of do_open/do_open_nofollow which does do_open() if the > + copy_links or copy_unsafe_links options are set and does > +-- > +2.35.6 > diff --git a/meta/recipes-devtools/rsync/files/CVE-2026-29518_p2.patch > b/meta/recipes-devtools/rsync/files/CVE-2026-29518_p2.patch > new file mode 100644 > index 0000000000..4ce3d03248 > --- /dev/null > +++ b/meta/recipes-devtools/rsync/files/CVE-2026-29518_p2.patch > @@ -0,0 +1,73 @@ > +From 37d459f837868cf5dc6a3f4962c8c9d9bcd2d4b8 Mon Sep 17 00:00:00 2001 > +From: Andrew Tridgell <[email protected]> > +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 <[email protected]> > + > +CVE: CVE-2026-29518 > +Upstream-Status: Backport > [https://github.com/RsyncProject/rsync/commit/99b36291d06ca66229942c7a525a1f5566f10c85] > + > +(cherry picked from commit 99b36291d06ca66229942c7a525a1f5566f10c85) > +Signed-off-by: Ashishkumar Parmar <[email protected]> > +--- > + 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; > +-- > +2.35.6 > diff --git a/meta/recipes-devtools/rsync/rsync_3.2.7.bb > b/meta/recipes-devtools/rsync/rsync_3.2.7.bb > index 2a1c3d9d56..fdbee387e3 100644 > --- a/meta/recipes-devtools/rsync/rsync_3.2.7.bb > +++ b/meta/recipes-devtools/rsync/rsync_3.2.7.bb > @@ -29,6 +29,8 @@ SRC_URI = > "https://download.samba.org/pub/${BPN}/src/${BP}.tar.gz \ > file://CVE-2024-12747.patch \ > file://CVE-2025-10158.patch \ > file://CVE-2026-41035.patch \ > + file://CVE-2026-29518_p1.patch \ > + file://CVE-2026-29518_p2.patch \ > " > SRC_URI[sha256sum] = > "4e7d9d3f6ed10878c58c5fb724a67dacf4b6aac7340b13e488fb2dc41346f2bb" > -- Yoann Congal Smile ECS
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#239281): https://lists.openembedded.org/g/openembedded-core/message/239281 Mute This Topic: https://lists.openembedded.org/mt/119772227/21656 Group Owner: [email protected] Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
