What's the vulnerability?

rsync can connect to a remote daemon through an HTTP proxy. The function establish_proxy_connection() in socket.c performs an off-by-one out-of-bounds write on a stack buffer while handling the proxy server's CONNECT response. A malicious proxy server that returns an overlong CONNECT response corrupts memory adjacent to that stack buffer.

The write is a single byte past the buffer end, so impact is limited (NVD rates the issue Low), but it is still out-of-bounds stack corruption driven by attacker-controlled input.

Root Cause Analysis

# RCA Report: CVE-2026-45232

## Summary

CVE-2026-45232 is an off-by-one out-of-bounds stack write vulnerability in the `rsync` utility. The bug resides in `establish_proxy_connection()` within `socket.c`. When rsync connects through an HTTP proxy (configured via `RSYNC_PROXY`), it reads the proxy's `CONNECT` response into a 1024-byte stack buffer. If the proxy returns an overlong response (1023+ bytes without a newline terminator), post-loop null-termination logic writes one byte past the buffer's end, corrupting adjacent stack memory.

## Impact

- **Package**: `rsync` (C CLI tool)
- **Affected versions**: `< 3.4.2`
- **Fixed version**: `3.4.3`
- **Risk level**: Low (NVD CVSS 3.1 base 3.1)
- **Consequences**: A malicious or MITM'd HTTP proxy can trigger a single-byte stack overwrite, potentially leading to a crash or undefined behavior. The overwritten byte is always a null terminator (`\0`), and the attacker does not control the offset, limiting exploitability.

## Root Cause

In `socket.c`, `establish_proxy_connection()` declares a stack buffer `char buffer[1024]` and reads the proxy response one byte at a time into `buffer[0]` through `buffer[1022]`. The loop condition is `cp < &buffer[sizeof buffer - 1]`.

If no newline arrives before the buffer is filled, `cp` is left at `&buffer[1023]` (the last valid index). The post-loop code then executes:

```c
if (*cp != '\n')
    cp++;
*cp-- = '\0';
```

Because `*cp` was never written by the loop (it contains stale stack data), the `if` is usually true, so `cp++` advances past the array to `&buffer[1024]`. The subsequent `*cp = '\0'` writes one byte out of bounds.

**Fix commit**: `a5fc5ebe7a8ef1aa72f6e344599f97fd4427ecba` ("socket: reject over-long proxy response line")

The fix:
1. Increases the buffer size to `PROXY_BUF_SIZE + 1` (1025 bytes) for safe null-termination
2. Adds an explicit check: if `cp == &buffer[PROXY_BUF_SIZE - 1]`, reject with `"proxy response line too long"` instead of writing past the buffer
3. Correctly null-terminates within bounds for shorter responses

## Reproduction Steps

1. `repro/reproduction_steps.sh` clones the rsync repository, checks out `v3.4.2` (vulnerable) and `v3.4.3` (fixed), and builds both with AddressSanitizer (`-fsanitize=address`).
2. It starts a minimal Python TCP mock proxy that binds to a local ephemeral port, accepts one connection, reads the `CONNECT` request headers, and sends exactly 1023 bytes of `'X'` with no newline terminator.
3. It runs `rsync` via `RSYNC_PROXY=127.0.0.1:<port>` against each build.
4. Expected behavior:
   - **v3.4.2 (vulnerable)**: AddressSanitizer reports `stack-buffer-overflow` at `establish_proxy_connection` (`socket.c:95`), and rsync aborts.
   - **v3.4.3 (fixed)**: rsync prints `"proxy response line too long"` and exits with error code 10 (non-zero but no crash).

## Evidence

- **Log files**:
  - `logs/rsync_v3.4.2_proxy.log` — ASan output for the vulnerable build
  - `logs/rsync_v3.4.3_proxy.log` — stderr for the fixed build
- **ASan excerpt** (vulnerable build):
  ```
  ==7440==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7f747c600940
  WRITE of size 1 at 0x7f747c600940 thread T0
      #0 establish_proxy_connection /tmp/.../rsync/socket.c:95
      ...
  Memory access at offset 2368 overflows this variable
      [1344, 2368) 'buffer' (line 55)
  ```
- **Fixed build excerpt**:
  ```
  proxy response line too long
  rsync error: error in socket IO (code 10) at clientserver.c(141) [Receiver=3.4.3]
  ```
- **Runtime manifest**: `repro/runtime_manifest.json` records build versions, exit codes, ASan excerpt, payload size (1023 bytes), and confirmation flags.

## Recommendations / Next Steps

1. **Upgrade** to rsync `>= 3.4.3` to obtain the bounds-check fix.
2. **Network controls**: Restrict proxy configurations (`RSYNC_PROXY`) to trusted infrastructure to reduce exposure to malicious proxies.
3. **Testing**: The upstream test `testsuite/proxy-response-line-too-long.test` should be included in CI to prevent regression.
4. **Build hardening**: Compile with `-fstack-protector-strong` and ASan in QA pipelines to detect similar stack overwrites early.

## Additional Notes

- **Idempotency**: The reproduction script was run twice consecutively; both runs produced identical ASan crashes on `v3.4.2` and clean rejection on `v3.4.3`.
- **Payload specifics**: 1023 bytes of `'X'` without a newline is the exact boundary that triggers the off-by-one. A shorter payload does not trigger the bug; a newline-terminated payload exits the read loop early.
- **Build environment**: Tested on `x86_64-pc-linux-gnu` with GCC and AddressSanitizer. The bug is architecture-agnostic, but ASan makes the one-byte overwrite reliably observable.
One Command

Verify with pruva-verify

Run the Pruva CLI to automatically fetch and execute the reproduction script.

pruva-verify REPRO-2026-00141
or pruva-verify CVE-2026-45232
Install: curl -fsSL https://pruva.dev/install.sh | sh

Or Run Manually

1

Download the script

curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00141/artifacts/reproduction_steps.sh
2

Make executable

chmod +x reproduction_steps.sh
3

Run the script

./reproduction_steps.sh
Run in a VM, container, or disposable environment. This exploits a real vulnerability.

How Pruva Reproduced This

Watch the AI agent's step-by-step process.

Loading session...

Artifacts

No artifacts available