Severity: High — memory safety, 1-byte heap out-of-bounds read on attacker-controlled UDP payload.
Affected function / file: parse_udprelay_header (static), src/udprelay.c:268-271 (v3.3.6, commit 8ad3036).
Root cause
In the ATYP == 3 (domain-name) branch, the code reads the length byte before verifying that buf_len >= 2:
} else if ((atyp & ADDRTYPE_MASK) == 3) {
// Domain name
uint8_t name_len = *(uint8_t *)(buf + offset); // OOB when buf_len == 1
if (name_len + 4 <= buf_len) {
...
memcpy(tmp, buf + offset + 1, name_len);
...
}
}
At this point offset == 1 (it was incremented past the ATYP byte). For any decrypted UDP payload of length 1 whose low nibble is 0x3, the load at buf[1] reads one byte past the end of the heap allocation.
Reachability
parse_udprelay_header is invoked on every decrypted UDP datagram on ss-server and ss-redir (UDP mode). The bug is post-AEAD (a valid MAC is required), but the inner payload is fully attacker-controlled — any party with the shared key and network path to the UDP port can trigger it. Impact: denial-of-service on page-boundary allocations; 1-byte adjacent-heap disclosure is consumed by the subsequent length check and does not propagate to an attacker-visible channel, so RCE is not an obvious primitive.
Reproduction
Discovered with a libFuzzer harness (clang 17 + ASan) mirroring parse_udprelay_header verbatim.
printf '\x03' > repro.bin
./fuzz_ss_header_udp repro.bin
ASan output (abridged):
==*==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x... READ of size 1
#0 parse_udprelay_header src/udprelay.c:270
16 distinct 1-byte inputs (all 0x?3) were saved from a 300 s fuzzing round; all collapse to this root cause.
Suggested fix
Add the missing bounds check before the load:
} else if ((atyp & ADDRTYPE_MASK) == 3) {
if (buf_len < offset + 1)
return 0;
uint8_t name_len = *(uint8_t *)(buf + offset);
if (name_len + 4 <= buf_len) {
...
}
}
Equivalently, combine into one check: if (buf_len < offset + 1 + name_len + 2) return 0; after a bounded load.
Context
Found as part of an independent fuzzing audit of shadowsocks-libev v3.3.6. Not filed privately because the issue was requested to be filed here; happy to redact the PoC if the maintainers would prefer coordinated disclosure for this and the sibling src/json.c issues.
Severity: High — memory safety, 1-byte heap out-of-bounds read on attacker-controlled UDP payload.
Affected function / file:
parse_udprelay_header(static),src/udprelay.c:268-271(v3.3.6, commit8ad3036).Root cause
In the ATYP == 3 (domain-name) branch, the code reads the length byte before verifying that
buf_len >= 2:At this point
offset == 1(it was incremented past the ATYP byte). For any decrypted UDP payload of length 1 whose low nibble is0x3, the load atbuf[1]reads one byte past the end of the heap allocation.Reachability
parse_udprelay_headeris invoked on every decrypted UDP datagram onss-serverandss-redir(UDP mode). The bug is post-AEAD (a valid MAC is required), but the inner payload is fully attacker-controlled — any party with the shared key and network path to the UDP port can trigger it. Impact: denial-of-service on page-boundary allocations; 1-byte adjacent-heap disclosure is consumed by the subsequent length check and does not propagate to an attacker-visible channel, so RCE is not an obvious primitive.Reproduction
Discovered with a libFuzzer harness (clang 17 + ASan) mirroring
parse_udprelay_headerverbatim.ASan output (abridged):
16 distinct 1-byte inputs (all
0x?3) were saved from a 300 s fuzzing round; all collapse to this root cause.Suggested fix
Add the missing bounds check before the load:
Equivalently, combine into one check:
if (buf_len < offset + 1 + name_len + 2) return 0;after a bounded load.Context
Found as part of an independent fuzzing audit of shadowsocks-libev v3.3.6. Not filed privately because the issue was requested to be filed here; happy to redact the PoC if the maintainers would prefer coordinated disclosure for this and the sibling
src/json.cissues.