Skip to content

fix(parser_ros): bound-check CDR byte-sequence length to prevent OOB read#183

Merged
pabloinigoblasco merged 1 commit into
mainfrom
fix/ros-cdr-bytesequence-oob
Jul 3, 2026
Merged

fix(parser_ros): bound-check CDR byte-sequence length to prevent OOB read#183
pabloinigoblasco merged 1 commit into
mainfrom
fix/ros-cdr-bytesequence-oob

Conversation

@pabloinigoblasco

Copy link
Copy Markdown
Contributor

Summary

  • Add RosParser::readByteSequence(), a single choke point that validates a CDR uint8[] / sequence<uint8> length against the bytes remaining in the message and throws on overrun (mirroring the ROS1 deserializer).
  • Route all 12 bulk byte-array reads through it (Image, CompressedImage, CompressedVideo, PointCloud ×3 variants, OccupancyGrid ×2, markers, DataTamer).
  • Guard the scalar void-handler route in wrapVoidHandler() with try/catch so a decode throw becomes an Expected error instead of std::terminate across the noexcept C-ABI trampoline (the object handlers already have their own try/catch).
  • Add regression tests: a truncated sensor_msgs/Image (object route) and a truncated scalar message (scalar route).

Why

NanoCDR_Deserializer::deserializeByteSequence() trusts the wire-declared array length and does not clamp it to the buffer — unlike the ROS1 deserializer and NanoCDR's own string reader, both of which bounds-check. A truncated or corrupt message (e.g. a partially-written bag, or a sensor_msgs/msg/Image whose data[] declares more bytes than are present) therefore produced an sdk::Image whose data span extended far past the payload. The 2D image viewer's per-row copy read off the end of the buffer → heap out-of-bounds read / SIGSEGV (reproduced on a 64-bit desktop build, not just 32-bit). The defect lives in the shared byte-sequence primitive, so every builtin-object handler that reads a bulk data[] shared the same exposure; sensor_msgs/msg/Image was the most common trigger. rosx_introspection is a pinned upstream release, so the guard is added on the consumer side in parser_ros.

Testing

  • ctest --test-dir build -R ros_parser (or run ros_parser_test): 72 tests pass, including ImageWithTruncatedDataIsRejected and TruncatedScalarMessageDoesNotTerminate.
  • Manual: load an MCAP carrying a truncated sensor_msgs/msg/Image and open the topic in a 2D image view — previously crashed, now the frame is rejected cleanly.
  • Clean build under -Wall -Wextra -Werror; pre-commit (clang-format) passes.

…read

NanoCDR_Deserializer::deserializeByteSequence() trusts the wire-declared
array length and does not clamp it to the buffer (unlike the ROS1 deserializer
and NanoCDR's own string reader). A truncated or corrupt message therefore
yields a span extending past the payload; reading it downstream is an
out-of-bounds access (verified SIGSEGV decoding a truncated sensor_msgs/Image
in a 2D image view).

Route every bulk uint8[] read through a new RosParser::readByteSequence() that
validates the declared length against the bytes remaining and throws on
overrun, matching the ROS1 path. Since a throw across the noexcept C-ABI
trampoline would call std::terminate, guard the scalar void-handler route in
wrapVoidHandler() (the object handlers already carry their own try/catch); this
also closes the pre-existing deserializeString overrun on that route.

Add regression tests for a truncated Image (object route rejected) and a
truncated scalar message (scalar route fails cleanly, no terminate).
@pabloinigoblasco pabloinigoblasco merged commit 7bee811 into main Jul 3, 2026
30 checks passed
@pabloinigoblasco pabloinigoblasco deleted the fix/ros-cdr-bytesequence-oob branch July 3, 2026 08:51
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