Skip to content

[low] Truncated/garbage fast-path buffer is silently routed to pickle.loads on the raw bytes #50

@toloco

Description

@toloco

Severity: ⚪ low • Category: correctness
Location: src/serde.rs : 157-218

What's wrong

On any malformed fast-path payload (too short for the declared width, e.g. a TAG_I64 with <8 trailing bytes, a TAG_STR/TAG_BYTES whose length exceeds the remaining data, or a TAG_TUPLE whose element decode hits a short buffer) deserialize_one returns Ok(None). The caller deserialize_value (shared_store.rs:281-287) interprets Ok(None) as 'this is a pickle-wrapped value' and feeds pickle_payload(data) = &data[1..] to pickle.loads. For a buffer that actually started with a fast-path tag (not TAG_PICKLE), this hands arbitrary non-pickle bytes to pickle.loads, which will typically raise an opaque UnpicklingError rather than signalling cache corruption. This is only reachable with corrupt/format-skewed mmap contents (same trust domain), so low severity, but the conflation of 'malformed fast-path' and 'is pickle' is a latent footgun. Note: pickle.loads on attacker-controlled bytes is an arbitrary-code-execution primitive, but the shm file is per-user/per-machine with a derived name, so this is not a new external attack surface — it does mean a corrupted cache file can execute code on load, which is worth documenting.

Trigger

Corrupt or format-skewed value bytes in the mmap whose leading byte is a fast-path tag but whose body is truncated; deserialize_value then pickle.loads()es the tail of the buffer.

Suggested fix

Distinguish 'definitely pickle' (data[0]==TAG_PICKLE) from 'malformed fast-path' (a recognized tag but short/invalid body). Only fall through to pickle when the leading tag is TAG_PICKLE; for a recognized-but-malformed fast-path tag, return a clear error. Document that a corrupted shared-cache file can trigger pickle.loads of untrusted bytes.

Adversarial verification note

The mechanism is confirmed in the real code. In src/serde.rs, the public deserialize() (lines 32-43) returns Ok(None) in two distinct cases: (a) data[0]==TAG_PICKLE (line 36-38, legitimate pickle), and (b) deserialize_one() returns None, which happens for truncated/malformed fast-path payloads — TAG_I64/TAG_F64 with <9 bytes (lines 158-159, 166-167), TAG_STR/TAG_BYTES with insufficient length bytes or body (lines 174-179, 187-192), and TAG_TUPLE whose element decode hits a short buffer (line 213). The caller deserialize_value() in src/shared_store.rs:281-287 treats Ok(None) uniformly as "this is pickle-wrapped" and feeds pickle_payload(data) = &data[1..] (serde.rs:54-56) to pickle.loads. So a buffer beginning with a fast-path tag (not TAG_PICKLE) but with a truncated body gets its tail handed to pickle.loads, which raises an opaque UnpicklingError rather than signaling cache corruption. The conflation is real.

However, this is not reachable through the safe Python API under normal operation: ShmCache.insert (shm/mod.rs:311-419) writes the full value_bytes and stores value_len in the slot header, and get/read_value (lines 179-202) reads back exactly value_len bytes — what is serialized is what is deserialized. Reaching a malformed fast-path payload requires external corruption or format-skew of the mmap file, which is the same trust domain (per-user/per-machine derived name). The reviewer correctly identified this trigger constraint and self-rated it low. No data corruption, memory unsafety, or crash arises from this within the safe API — worst case is an opaque exception on an already-corrupt cache file. Severity 'low' is appropriate and not overstated; it is fundamentally a robustness/footgun issue rather than a bug exploitable via the documented interface.


Filed from a multi-agent code review (finder → adversarial verification → synthesis). Confirmed real after a skeptic re-read the code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfrom-reviewFiled from the multi-agent code reviewrustPull requests that update rust codesecuritySecurity / trust-boundary concernseverity:lowMinor issue or nit

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions