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]]
-=-=-=-=-=-=-=-=-=-=-=-

  • ... Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
    • ... Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
    • ... Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
    • ... Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
    • ... Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
    • ... Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
    • ... Yoann Congal via lists.openembedded.org
      • ... Yoann Congal via lists.openembedded.org
        • ... Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org

Reply via email to