Skip to content

[Fuzzing] Heap OOB read: parse_udprelay_header reads length byte before bounds check (src/udprelay.c:270) #3035

@parasol-aser

Description

@parasol-aser

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions