Skip to content

fix(parser_data_tamer): bound snapshot lengths before consuming them#184

Open
pabloinigoblasco wants to merge 2 commits into
mainfrom
fix/datatamer-snapshot-length-bounds
Open

fix(parser_data_tamer): bound snapshot lengths before consuming them#184
pabloinigoblasco wants to merge 2 commits into
mainfrom
fix/datatamer-snapshot-length-bounds

Conversation

@pabloinigoblasco

Copy link
Copy Markdown
Contributor

Summary

The DataTamer snapshot framing carries two wire-declared uint32 lengths (mask_size, payload_size). parseScalarsImpl consumed both without checking them against the bytes actually remaining in the payload. Two shared primitives make that unsafe on their own:

  • Deserialize<T> runs std::memcpy(&var, buffer.data, N) before its N > buffer.size bounds check.
  • BufferSpan::trimFront(n) does data += n; size -= n; with no floor, so an oversized n underflows size_t.

An inflated length therefore underflows size and walks the read cursor into a wild pointer → SIGSEGV on 64-bit desktop builds, reachable from any truncated or corrupt DataTamer source (MCAP with a DataTamer schema, or a live ZMQ/MQTT/UDP channel).

Fix: validate each length against the remaining buffer before consuming it, and require the 4 header bytes to be present before each Deserialize<uint32_t>. Reject with PJ::unexpected instead of reading past the end. This is the only bounds check between untrusted wire bytes and the readers, so the guard lives here (the data_tamer dependency is a pinned upstream release).

Why

A single truncated or corrupt DataTamer snapshot crashes the whole application with a wild-pointer read — no bad actor required, just a bag cut mid-write. Same defect class as the ROS2-CDR byte-sequence OOB (#183), in a different parser.

Testing

ctest -R data_tamer — three new regression tests, all rejecting cleanly with no crash:

  • OversizedMaskSizeIsRejectedmask_size = 0xFFFFFFFF with only the 4 header bytes present
  • TruncatedBeforeMaskSizeIsRejected — payload shorter than the 4-byte header
  • OversizedPayloadSizeIsRejectedpayload_size = 0xFFFFFFFF with nothing following the mask

The DataTamer snapshot framing carries two wire-declared uint32 lengths
(mask_size, payload_size). parseScalarsImpl consumed them without checking
against the bytes actually remaining. Deserialize<> memcpy's before its
bounds check and BufferSpan::trimFront subtracts without a floor, so an
inflated length underflows size_t and walks the read cursor into a wild
pointer -> SIGSEGV on 64-bit desktop builds, reachable from any truncated or
corrupt DataTamer source (MCAP or live channel).

Validate each length against the remaining buffer before consuming it, and
require the 4 header bytes to be present before each Deserialize<uint32_t>.
Add regression tests for oversized mask_size, truncated header, and oversized
payload_size.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant