CVE-2026-43620
Description
Rsync version 3.4.2 and prior contain a receiver-side out-of-bounds array read vulnerability in recv_files() in receiver.c that allows a malicious rsync server to crash the rsync client process. Attackers can exploit the vulnerability by setting CF_INC_RECURSE in compatibility flags and sending a specially crafted file list where the first sorted entry is not the leading dot directory, followed by a transfer record with ndx=0 and an iflag word without ITEM_TRANSFER, causing the receiver to read 8 bytes before the allocated pointer array and dereference an invalid pointer at an unmapped address, resulting in a deterministic SIGSEGV crash of the rsync client.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A malicious rsync server can crash any client running rsync ≤3.4.2 by sending a crafted file list and transfer record, causing a deterministic out-of-bounds read and SIGSEGV.
Vulnerability
An out-of-bounds array read vulnerability exists in recv_files() in receiver.c of rsync versions 3.4.2 and prior. The bug is triggered when an attacker-controlled server sets CF_INC_RECURSE in the compatibility flags and sends a specially crafted file list where the first sorted entry is not the leading dot directory. The server then sends a transfer record with ndx=0 and an iflag word that does not include ITEM_TRANSFER (e.g., ITEM_IS_NEW). This causes the receiver to evaluate dir_flist->files[cur_flist->parent_ndx] with parent_ndx == -1, reading 8 bytes before the allocated pointer array. The dereference of the resulting invalid pointer leads to a deterministic crash [1][3].
Exploitation
An attacker needs to operate a malicious rsync server that the victim client connects to, either via rsync:// URL or remote-shell (rsync user@host:path/). No special client options are required; the inc_recurse flag is the default for protocol 30+. The attacker sends a malformed file list and transfer record as described, causing the client to read out-of-bounds and dereference an unmapped address, resulting in a SIGSEGV crash [1].
Impact
Successful exploitation results in a deterministic denial of service (DoS) of the rsync client process. The out-of-bounds read targets kernel-managed mmap chunk metadata, not attacker-controllable heap data, so the crash is limited to segfault and cannot be leveraged for code execution or information disclosure on glibc x86-64 Linux [1].
Mitigation
The vulnerability is fixed in rsync version 3.4.3, released on the same day as the advisory [2]. Users should upgrade to 3.4.3 or later. No workaround is available; pulling from untrusted rsync servers should be avoided until the patch is applied [1][2].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2- Range: <=3.4.2
Patches
6a0b9a8e989f6NEWS: prepare 3.4.3 release entry with six CVEs
1 file changed · +140 −6
NEWS.md+140 −6 modified@@ -1,7 +1,122 @@ -# NEWS for rsync 3.4.3 (UNRELEASED) +# NEWS for rsync 3.4.3 (20 May 2026) ## Changes in this version: +### SECURITY FIXES: + +Six CVEs are fixed in this release. All six are assigned by +VulnCheck as CNA. Affected versions are 3.4.2 and earlier in every +case. Three of the six (CVE-2026-29518, CVE-2026-43617, +CVE-2026-43619) require non-default daemon configuration to reach: +the first and third need `use chroot = no` for a module, the second +needs `daemon chroot = ...` set in rsyncd.conf. Two (CVE-2026-43618, +CVE-2026-43620) are reachable from a normal pull or a normal +authenticated daemon connection. The sixth (CVE-2026-45232) is +reachable only when `RSYNC_PROXY` is set and the proxy (or a MITM) +returns a pathological response. Many thanks to the external +researchers who reported these issues. + +- CVE-2026-29518 (CVSS v4.0 7.3, HIGH): TOCTOU symlink race condition + allowing local privilege escalation in daemon mode without chroot. + An rsync daemon configured with "use chroot = no" was exposed to a + time-of-check / time-of-use race on parent path components: a local + attacker with write access to a module could 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. Default "use chroot = yes" is + not exposed. `secure_relative_open()` (added in 3.4.0 for + CVE-2024-12086) was previously unused in the daemon-no-chroot + case; the fix enables it there and reroutes the sender's + read-path opens through it. Reported by Nullx3D (Batuhan Sancak), + Damien Neil and Michael Stapelberg. + +- CVE-2026-43617 (CVSS v3.1 4.8, MEDIUM): Hostname/ACL bypass on an + rsync daemon configured with `daemon chroot = /X` in rsyncd.conf + when the chroot tree lacks DNS resolution support. The + reverse-DNS lookup of the connecting client was performed *after* + the daemon chroot had been entered; if /X did not contain the + libc resolver fixtures (`/etc/resolv.conf`, `/etc/nsswitch.conf`, + `/etc/hosts`, NSS service modules) the lookup failed and the + connecting hostname was set to "UNKNOWN", causing hostname-based + deny rules to silently fail open. IP-based ACLs are unaffected. + The per-module `use chroot` setting is unrelated to this issue. + The fix performs the lookup before entering the daemon chroot. + Reported by MegaManSec. + +- CVE-2026-43618 (CVSS v3.1 8.1, HIGH): Integer overflow in the + compressed-token decoder enabling remote memory disclosure to an + authenticated daemon peer. The receiver accumulated a 32-bit + signed counter without overflow checking; a malicious sender could + trigger an overflow that, with careful manipulation, leaked process + memory contents to the attacker -- environment variables, + passwords, heap and library pointers -- significantly weakening + ASLR. The fix bounds the counter and adds wire-input validation in + several adjacent places (defence-in-depth). Workaround for older + releases: `refuse options = compress` in rsyncd.conf. Reported by + Omar Elsayed. + +- CVE-2026-43619 (CVSS v3.1 6.3, MEDIUM): Symlink races on path-based + system calls in "use chroot = no" daemon mode (generalisation of + CVE-2026-29518). Earlier fixes for symlink races on the receiver's + open() call missed the same race class on every other path-based + system call: chmod, lchown, utimes, rename, unlink, mkdir, symlink, + mknod, link, rmdir and lstat. The fix routes each affected + path-based syscall through a parent dirfd opened under + RESOLVE_BENEATH-equivalent kernel-enforced confinement (openat2 on + Linux 5.6+, O_RESOLVE_BENEATH on FreeBSD 13+ and macOS 15+, + per-component O_NOFOLLOW walk elsewhere). Default "use chroot = + yes" is not exposed. Reported by Andrew Tridgell as a follow-on + audit of CVE-2026-29518. + +- CVE-2026-43620 (CVSS v3.1 6.5, MEDIUM): Out-of-bounds read in the + receiver's recv_files() enabling remote denial-of-service of any + client pulling from a malicious server (incomplete fix of commit + 797e17f). The earlier parent_ndx<0 guard added to send_files() was + not applied to the visually-identical block in recv_files(). A + malicious rsync server can drive any connecting client into a + deterministic SIGSEGV by setting CF_INC_RECURSE in the + compatibility flags and sending a crafted file list and transfer + record. inc_recurse is the protocol-30+ default, so no special + options are required on the victim. Workaround for older + releases: `--no-inc-recursive` on the client. Reported by Pratham + Gupta. + +- CVE-2026-45232 (CVSS v3.1 3.1, LOW): Off-by-one out-of-bounds stack + write in the rsync client's HTTP CONNECT proxy handler + (`establish_proxy_connection()` in `socket.c`). After issuing the + CONNECT request, rsync read the proxy's first response line one + byte at a time into a 1024-byte stack buffer with the bound + `cp < &buffer[sizeof buffer - 1]`. If the proxy (or a MITM in + front of it) returned 1023+ bytes on that first line without a + newline terminator, `cp` exited the loop pointing at a buffer slot + the loop never wrote, leaving `*cp` holding stale stack data from + the earlier `snprintf()` of the outgoing CONNECT request. The + post-loop logic then wrote a single `\0` one byte past the end of + the buffer on the stack. Reach is client-side only, and only when + `RSYNC_PROXY` is set so rsync tunnels an `rsync://` connection + through an HTTP CONNECT proxy. The written byte is always `\0` + and the offset is fixed by the buffer size, not attacker-chosen, + so this is not an arbitrary-write primitive: practical impact is + corruption of one adjacent stack byte and possible later + misbehaviour or crash. The fix detects the "buffer filled without + finding `\n`" case explicitly by position and refuses the response + with "proxy response line too long". Reported by Aisle Research + via Michal Ruprich (rsync-3.4.1-2.el10 QE). + +In addition to the six CVE fixes, this release adds defence-in-depth +hardening on several adjacent paths: bounded wire-supplied counts and +lengths in flist/io/acls/xattrs, a guard against length underflow in +cumulative `snprintf()` callers, a parent block-index bounds check on +the receiver, a NULL check in `read_delay_line()`, a lower ceiling on +`MAX_WIRE_DEL_STAT` to avoid signed-int overflow in the +`read_del_stats()` accumulator, rejection of hyphen-prefixed +remote-shell hostnames (defence-in-depth against argv-injection in +tooling that forwards untrusted input into the hostspec position; +reported by Aisle Research via Michal Ruprich), and a NULL-check on +`localtime_r()` in `timestring()` to keep a malicious server from +crashing the client by advertising a file with an out-of-range +modtime. + ### BUG FIXES: - Fixed a regression introduced by the 3.4.0 secure_relative_open() @@ -37,14 +152,33 @@ with protocol < 29, top-level files). The test skips on platforms without a RESOLVE_BENEATH equivalent. -- runtests.py now errors early with a clear message when the test - helper programs (`tls`, `trimslash`, `t_unsafe`, `wildtest`, - `getgroups`, `getfsdev`) are missing, instead of letting many - tests fail with confusing "not found" errors. +- Added regression tests for the new security fixes: + `chmod-symlink-race.test`, `chdir-symlink-race.test`, + `bare-do-open-symlink-race.test`, `alt-dest-symlink-race.test`, + `copy-dest-source-symlink.test`, `sender-flist-symlink-leak.test`, + `secure-relpath-validation.test`, `daemon-chroot-acl.test` and + `daemon-refuse-compress.test`. The symlink-race tests skip on + Cygwin, Solaris, OpenBSD and NetBSD (no RESOLVE_BENEATH + equivalent on those platforms). + +- runtests.py now errors early with a clear message when any of + the test helper programs (`tls`, `trimslash`, `t_unsafe`, + `t_chmod_secure`, `t_secure_relpath`, `wildtest`, `getgroups`, + `getfsdev`) are missing, instead of letting many tests fail with + confusing "not found" errors. - Added OpenBSD and NetBSD CI jobs that run `make check` on those platforms. +- Added Ubuntu 22.04 and AlmaLinux 8 CI workflows so future + backports to the two mainstream LTS families build and test on + the same CI surface as trunk. + +- testsuite/protected-regular.test now runs unprivileged via + `unshare` with user-namespace UID mapping, falling back to skip + if `unshare`/`uidmap` is not available; previously it required + real root. + - Added `symlink-dirlink-basis` to the Cygwin CI's expected-skipped list. @@ -5035,7 +5169,7 @@ to develop and test fixes. | RELEASE DATE | VER. | DATE OF COMMIT\* | PROTOCOL | |--------------|--------|------------------|-------------| -| ?? ??? 2026 | 3.4.3 | | 32 | +| 20 May 2026 | 3.4.3 | | 32 | | 28 Apr 2026 | 3.4.2 | | 32 | | 16 Jan 2025 | 3.4.1 | | 32 | | 15 Jan 2025 | 3.4.0 | 15 Jan 2025 | 32 |
0cf200ecbb8breceiver: add parent_ndx<0 guard, mirroring 797e17f
4 files changed · +15 −1
generator.c+4 −0 modified@@ -2146,6 +2146,8 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo) if (send_failed) ndx = get_hlink_num(); flist = flist_for_ndx(ndx, "check_for_finished_files.1"); + if (ndx < flist->ndx_start) + exit_cleanup(RERR_PROTOCOL); file = flist->files[ndx - flist->ndx_start]; assert(file->flags & FLAG_HLINKED); if (send_failed) @@ -2174,6 +2176,8 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo) flist = cur_flist; cur_flist = flist_for_ndx(ndx, "check_for_finished_files.2"); + if (ndx < cur_flist->ndx_start) + exit_cleanup(RERR_PROTOCOL); file = cur_flist->files[ndx - cur_flist->ndx_start]; if (solo_file)
io.c+3 −0 modified@@ -1090,6 +1090,9 @@ static void got_flist_entry_status(enum festatus status, int ndx) { struct file_list *flist = flist_for_ndx(ndx, "got_flist_entry_status"); + if (ndx < flist->ndx_start) + exit_cleanup(RERR_PROTOCOL); + if (remove_source_files) { active_filecnt--; active_bytecnt -= F_LENGTH(flist->files[ndx - flist->ndx_start]);
receiver.c+6 −1 modified@@ -467,7 +467,10 @@ static void handle_delayed_updates(char *local_name) static void no_batched_update(int ndx, BOOL is_redo) { struct file_list *flist = flist_for_ndx(ndx, "no_batched_update"); - struct file_struct *file = flist->files[ndx - flist->ndx_start]; + struct file_struct *file; + if (ndx < flist->ndx_start) + exit_cleanup(RERR_PROTOCOL); + file = flist->files[ndx - flist->ndx_start]; rprintf(FERROR_XFER, "(No batched update for%s \"%s\")\n", is_redo ? " resend of" : "", f_name(file, NULL)); @@ -604,6 +607,8 @@ int recv_files(int f_in, int f_out, char *local_name) if (ndx - cur_flist->ndx_start >= 0) file = cur_flist->files[ndx - cur_flist->ndx_start]; + else if (cur_flist->parent_ndx < 0) + exit_cleanup(RERR_PROTOCOL); else file = dir_flist->files[cur_flist->parent_ndx]; fname = local_name ? local_name : f_name(file, fbuf);
sender.c+2 −0 modified@@ -140,6 +140,8 @@ void successful_send(int ndx) return; flist = flist_for_ndx(ndx, "successful_send"); + if (ndx < flist->ndx_start) + exit_cleanup(RERR_PROTOCOL); file = flist->files[ndx - flist->ndx_start]; if (!change_pathname(file, NULL, 0)) return;
0cf200ecbbba650643109e6edefence-in-depth: receiver block-index bounds + read_delay_line null check
2 files changed · +15 −4
generator.c+10 −4 modified@@ -229,11 +229,13 @@ static int read_delay_line(char *buf, int *flags_p) *flags_p = 0; if (sscanf(bp, "%x ", &mode) != 1) { - invalid_data: - rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n"); - return -1; + goto invalid_data; + } + past_space = strchr(bp, ' '); + if (!past_space) { + goto invalid_data; } - past_space = strchr(bp, ' ') + 1; + past_space++; len = j - read_pos - (past_space - bp) + 1; /* count the '\0' */ read_pos = j + 1; @@ -247,6 +249,10 @@ static int read_delay_line(char *buf, int *flags_p) memcpy(buf, past_space, len); return mode; + +invalid_data: + rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n"); + return -1; } static void do_delayed_deletions(char *delbuf)
receiver.c+5 −0 modified@@ -352,6 +352,11 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r, } i = -(i+1); + if (i < 0 || i >= sum.count) { + rprintf(FERROR, "Invalid block index %d (count=%ld) [%s]\n", + i, (long)sum.count, who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } offset2 = i * (OFF_T)sum.blength; len = sum.blength; if (i == (int)sum.count-1 && sum.remainder != 0)
23d177db2a0f27f9e1b7c8d4Vulnerability mechanics
Root cause
"Missing bounds check when indexing into the file-list pointer array with a negative index (ndx=0 when the first sorted entry is not the leading dot directory) causes an out-of-bounds read before the allocated buffer."
Attack vector
An attacker controlling a malicious rsync server sends a specially crafted file list where the first sorted entry is not the leading dot directory, and sets CF_INC_RECURSE in the compatibility flags. The server then sends a transfer record with ndx=0 and an iflag word that lacks ITEM_TRANSFER. When the rsync client processes this record in recv_files(), it computes an index of -1 into the pointer array (file_list->files), reading 8 bytes before the allocated buffer and dereferencing an invalid pointer, causing a deterministic SIGSEGV crash. The attack requires no authentication and is triggered over the network during a standard rsync pull operation [CWE-125].
Affected code
The vulnerability resides in recv_files() in receiver.c. When CF_INC_RECURSE is set and the first sorted entry is not the leading dot directory, the code computes an index into file_list->files without ensuring the index is non-negative, allowing an access at offset -1 (8 bytes before the allocated array).
What the fix does
The patch [patch_id=798593] adds a guard in recv_files() in receiver.c to check that the computed index into the file-list pointer array is non-negative before dereferencing it. Specifically, when CF_INC_RECURSE is set and the first sorted entry is not the dot directory, the code now validates that ndx is greater than or equal to 0 before accessing file_list->files[ndx]. This closes the out-of-bounds read by rejecting the malformed transfer record that would otherwise cause an access at index -1 [CWE-125].
Preconditions
- networkAttacker must operate a malicious rsync server reachable by the victim client
- inputServer sends a crafted file list where the first sorted entry is not the leading dot directory
- inputServer sets CF_INC_RECURSE in compatibility flags
- inputServer sends a transfer record with ndx=0 and an iflag word without ITEM_TRANSFER
Generated on May 20, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.