VYPR
Low severity3.1NVD Advisory· Published May 20, 2026· Updated May 20, 2026

CVE-2026-45232

CVE-2026-45232

Description

Rsync versions before 3.4.3 contain an off-by-one out-of-bounds stack write vulnerability in the establish_proxy_connection() function in socket.c that allows network attackers to corrupt stack memory by sending a malformed HTTP proxy response. Attackers can exploit this by positioning themselves between the client and proxy or controlling the proxy server to send a response line of 1023 or more bytes without a newline terminator, causing a null byte to be written to an out-of-bounds stack address when the RSYNC_PROXY environment variable is set.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Rsync versions before 3.4.3 contain an off-by-one stack OOB write in establish_proxy_connection() that can corrupt stack memory when a malicious proxy sends a long response line without a newline.

Vulnerability

Rsync versions before 3.4.3 contain an off-by-one out-of-bounds stack write vulnerability in the establish_proxy_connection() function in socket.c. The client reads the HTTP proxy's first response line one byte at a time into a 1024-byte stack buffer with the bound cp < &buffer[sizeof buffer - 1], so the loop only ever writes buffer[0..sizeof-2]. If the proxy (or a man-in-the-middle in front of it) returns 1023+ bytes on the first response line without a \n terminator, the loop exits with cp == &buffer[sizeof buffer - 1] — a slot the loop never wrote, so *cp holds stale stack bytes left there by the earlier snprintf() that formatted the outgoing CONNECT request. The post-loop code then checks if (*cp != '\n') cp++; and then *cp-- = '\0';, which writes a null byte one byte past the end of the on-stack buffer, corrupting the adjacent stack slot. The vulnerability is reachable only when the RSYNC_PROXY environment variable is set, directing rsync to tunnel an rsync:// connection through an HTTP CONNECT proxy. [1][2]

Exploitation

An attacker must be able to control the HTTP proxy response sent to the rsync client. This can be achieved by positioning themselves as a man-in-the-middle between the client and the proxy, or by controlling the proxy server itself. The attacker sends an HTTP CONNECT response with a status line of 1023 or more bytes that does not contain a newline terminator. When the rsync client processes this response with RSYNC_PROXY set, the bug triggers and a null byte is written one byte past the end of the 1024-byte stack buffer. No authentication or special prerequisite is required from the attacker beyond network access to inject the malicious response. The user must have configured the RSYNC_PROXY environment variable for the vulnerable code path to execute. [1][2]

Impact

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 in the rsync client process, potentially leading to misbehaviour or a crash (denial of service). No information disclosure beyond what the existing rprintf(FERROR, "bad response from proxy -- %s\n", buffer) path might print, and no server-side exposure. The CVSS v3 base score is 3.1 (Low), with the vector AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:L. [1][2]

Mitigation

The vulnerability is fixed in rsync version 3.4.3, released on 2026-05-20 [3]. The fix detects the "buffer filled without newline" condition and avoids the out-of-bounds write [1]. Users should upgrade to version 3.4.3 or later. For those unable to upgrade, avoiding the use of the RSYNC_PROXY environment variable eliminates the attack surface. No workaround is available for scenarios that require HTTP CONNECT proxy support. The CVE is not listed on CISA's Known Exploited Vulnerabilities catalog. [1][3]

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

Patches

2
a5fc5ebe7a8e

socket: reject over-long proxy response line

https://github.com/rsyncproject/rsyncAndrew TridgellMay 13, 2026Fixed in 3.4.3via llm-release-walk
2 files changed · +145 13
  • socket.c+17 13 modified
    @@ -47,21 +47,23 @@ static struct sigaction sigact;
     
     static int sock_exec(const char *prog);
     
    +#define PROXY_BUF_SIZE 1024
    +
     /* Establish a proxy connection on an open socket to a web proxy by using the
      * CONNECT method.  If proxy_user and proxy_pass are not NULL, they are used to
      * authenticate to the proxy using the "Basic" proxy-authorization protocol. */
     static int establish_proxy_connection(int fd, char *host, int port, char *proxy_user, char *proxy_pass)
     {
    -	char *cp, buffer[1024];
    -	char *authhdr, authbuf[1024];
    +	char *cp, buffer[PROXY_BUF_SIZE + 1];
    +	char *authhdr, authbuf[PROXY_BUF_SIZE + 1];
     	int len;
     
     	if (proxy_user && proxy_pass) {
    -		stringjoin(buffer, sizeof buffer,
    +		stringjoin(buffer, PROXY_BUF_SIZE,
     			 proxy_user, ":", proxy_pass, NULL);
     		len = strlen(buffer);
     
    -		if ((len*8 + 5) / 6 >= (int)sizeof authbuf - 3) {
    +		if ((len*8 + 5) / 6 >= PROXY_BUF_SIZE - 3) {
     			rprintf(FERROR,
     				"authentication information is too long\n");
     			return -1;
    @@ -74,14 +76,14 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
     		authhdr = "";
     	}
     
    -	len = snprintf(buffer, sizeof buffer, "CONNECT %s:%d HTTP/1.0%s%s\r\n\r\n", host, port, authhdr, authbuf);
    -	assert(len > 0 && len < (int)sizeof buffer);
    +	len = snprintf(buffer, PROXY_BUF_SIZE, "CONNECT %s:%d HTTP/1.0%s%s\r\n\r\n", host, port, authhdr, authbuf);
    +	assert(len > 0 && len < PROXY_BUF_SIZE);
     	if (write(fd, buffer, len) != len) {
     		rsyserr(FERROR, errno, "failed to write to proxy");
     		return -1;
     	}
     
    -	for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
    +	for (cp = buffer; cp < &buffer[PROXY_BUF_SIZE - 1]; cp++) {
     		if (read(fd, cp, 1) != 1) {
     			rsyserr(FERROR, errno, "failed to read from proxy");
     			return -1;
    @@ -90,11 +92,13 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
     			break;
     	}
     
    -	if (*cp != '\n')
    -		cp++;
    -	*cp-- = '\0';
    -	if (*cp == '\r')
    -		*cp = '\0';
    +	if (cp == &buffer[PROXY_BUF_SIZE - 1]) {
    +		rprintf(FERROR, "proxy response line too long\n");
    +		return -1;
    +	}
    +	*cp = '\0';
    +	if (cp > buffer && cp[-1] == '\r')
    +		cp[-1] = '\0';
     	if (strncmp(buffer, "HTTP/", 5) != 0) {
     		rprintf(FERROR, "bad response from proxy -- %s\n",
     			buffer);
    @@ -110,7 +114,7 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
     	}
     	/* throw away the rest of the HTTP header */
     	while (1) {
    -		for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
    +		for (cp = buffer; cp < &buffer[PROXY_BUF_SIZE]; cp++) {
     			if (read(fd, cp, 1) != 1) {
     				rsyserr(FERROR, errno,
     					"failed to read from proxy");
    
  • testsuite/proxy-response-line-too-long.test+128 0 added
    @@ -0,0 +1,128 @@
    +#!/bin/sh
    +
    +# Copyright (C) 2026 by Andrew Tridgell
    +
    +# This program is distributable under the terms of the GNU GPL (see
    +# COPYING).
    +
    +# Regression test for the off-by-one stack OOB write in
    +# establish_proxy_connection() in socket.c when a malicious or
    +# man-in-the-middle HTTP proxy returns a first response line of
    +# 1023+ bytes without a '\n' terminator.
    +#
    +# Pre-fix: the read loop walked buffer[0..sizeof-2] one byte at a
    +# time, then post-loop logic did "if (*cp != '\n') cp++; *cp-- =
    +# '\0';".  If no newline arrived before the loop filled the buffer,
    +# cp was left at &buffer[sizeof-1] (never written by the loop),
    +# *cp held stale stack bytes, and cp++ pushed cp one past the array.
    +# The null-termination then wrote one byte out of bounds on the
    +# stack.  AddressSanitizer reports stack-buffer-overflow at the
    +# null-termination site.
    +#
    +# Post-fix: the bound-exhaustion case is detected by position and
    +# rejected with an "proxy response line too long" message, so no
    +# OOB write occurs and rsync exits with a non-signal status.
    +
    +. "$suitedir/rsync.fns"
    +
    +command -v python3 >/dev/null 2>&1 || test_skipped "python3 not available"
    +
    +workdir="$scratchdir/workdir"
    +mkdir -p "$workdir"
    +cd "$workdir"
    +
    +port_file="$workdir/port"
    +proxy_log="$workdir/proxy.log"
    +
    +# A minimal TCP listener: binds to an ephemeral port on 127.0.0.1,
    +# writes the chosen port to $port_file *before* accept() so the test
    +# can synchronise without a sleep, accepts one connection, reads
    +# until end-of-headers or 64 KiB, sends exactly 1023 bytes of 'X'
    +# with no '\n', then closes.
    +python3 - "$port_file" >"$proxy_log" 2>&1 <<'PYEOF' &
    +import socket, sys, os
    +port_file = sys.argv[1]
    +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    +s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    +s.bind(("127.0.0.1", 0))
    +port = s.getsockname()[1]
    +tmp = port_file + ".tmp"
    +with open(tmp, "w") as fp:
    +    fp.write("%d\n" % port)
    +os.rename(tmp, port_file)   # atomic visibility to the shell side
    +s.listen(1)
    +conn, _ = s.accept()
    +conn.settimeout(5)
    +try:
    +    data = b""
    +    while b"\r\n\r\n" not in data and len(data) < 65536:
    +        chunk = conn.recv(8192)
    +        if not chunk:
    +            break
    +        data += chunk
    +except socket.timeout:
    +    pass
    +conn.sendall(b"X" * 1023)    # exactly the buffer-1 trigger size
    +try:
    +    conn.shutdown(socket.SHUT_RDWR)
    +except OSError:
    +    pass
    +conn.close()
    +s.close()
    +PYEOF
    +proxy_pid=$!
    +
    +# Wait up to ~10s for the listener to publish its port.
    +i=0
    +while [ ! -s "$port_file" ] && [ $i -lt 10 ]; do
    +    sleep 1
    +    i=$((i + 1))
    +done
    +
    +if [ ! -s "$port_file" ]; then
    +    kill "$proxy_pid" 2>/dev/null
    +    cat "$proxy_log" >&2 2>/dev/null
    +    test_fail "proxy listener never published a port"
    +fi
    +
    +port=`cat "$port_file"`
    +case "$port" in
    +    *[!0-9]*|"") kill "$proxy_pid" 2>/dev/null; test_fail "bogus port from listener: '$port'" ;;
    +esac
    +
    +# Run rsync through the malicious proxy.  Any rsync:// URL works:
    +# the proxy intercepts the CONNECT and never forwards anywhere.
    +rsync_err="$workdir/rsync.err"
    +
    +# rsync MUST exit non-zero here (the proxy is misbehaving).
    +# Use `|| status=$?` so we capture the real exit code under `sh -e`;
    +# `if ! cmd; then status=$?` would only ever see 0 because the `!`
    +# is the last command before `$?`.
    +status=0
    +RSYNC_PROXY="127.0.0.1:$port" \
    +    $RSYNC rsync://example.invalid:873/whatever/ "$workdir/out/" \
    +    >/dev/null 2>"$rsync_err" || status=$?
    +
    +# Reap the listener.
    +wait "$proxy_pid" 2>/dev/null || true
    +
    +# 1. rsync must not have crashed (SIGSEGV/SIGABRT report >= 128).
    +if [ "$status" -ge 128 ]; then
    +    cat "$rsync_err" >&2
    +    test_fail "rsync killed by signal (status=$status) -- possible stack OOB regression"
    +fi
    +
    +# 2. rsync must have actually exited non-zero (i.e. saw the bad proxy).
    +if [ "$status" -eq 0 ]; then
    +    cat "$rsync_err" >&2
    +    test_fail "rsync returned success despite malformed proxy response"
    +fi
    +
    +# 3. The new error message must appear.
    +if ! grep -q "proxy response line too long" "$rsync_err"; then
    +    cat "$rsync_err" >&2
    +    test_fail "expected 'proxy response line too long' in rsync stderr"
    +fi
    +
    +echo "OK: over-long proxy response line rejected cleanly without crashing"
    +exit 0
    
32b9a8e989f6
https://github.com/rsyncproject/rsyncFixed in 3.4.3via llm-release-walk

Vulnerability mechanics

Root cause

"Off-by-one error in establish_proxy_connection() in socket.c: when the proxy response line fills the entire 1024-byte buffer without a newline, the null-termination writes one byte past the end of the stack buffer."

Attack vector

An attacker who can control or man-in-the-middle the HTTP proxy (when RSYNC_PROXY is set) sends a first response line of exactly 1023 or more bytes without a newline terminator. The read loop in establish_proxy_connection() fills buffer[0..1022] and leaves cp at &buffer[1023]; the post-loop logic then writes a null byte at buffer[1024], which is one byte past the end of the stack array [CWE-193]. The attacker does not control the value written (always '\0') nor the exact offset, so the impact is limited to corruption of one adjacent stack byte, potentially causing a crash or misbehaviour.

Affected code

The vulnerability is in the `establish_proxy_connection()` function in `socket.c`. The stack buffer `buffer[1024]` is filled byte-by-byte in a loop that stops at `&buffer[sizeof buffer - 1]` (index 1023). When no newline is received, the post-loop code unconditionally does `cp++; *cp-- = '\0'`, writing a null byte at buffer[1024] — one byte past the array boundary.

What the fix does

The patch [patch_id=893724] introduces a PROXY_BUF_SIZE constant (1024) and changes the buffer declaration to `buffer[PROXY_BUF_SIZE + 1]` to reserve space for the null terminator. After the read loop, the code now checks whether cp reached the last valid position (`&buffer[PROXY_BUF_SIZE - 1]`); if so, it prints "proxy response line too long" and returns -1 instead of writing past the end. The null-termination logic is also corrected to write `*cp = '\0'` at the current position and adjust for a trailing '\r' using `cp[-1]`, eliminating the out-of-bounds write entirely.

Preconditions

  • configThe RSYNC_PROXY environment variable must be set, directing rsync to use an HTTP proxy.
  • networkThe attacker must be able to control the proxy server or perform a man-in-the-middle attack between the client and the proxy.
  • inputThe proxy must send a first response line of 1023 or more bytes without a newline (' ') terminator.

Generated on May 20, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.