Skip to content

fix(core): packet parsers test the length before reading header fields (0010 finding 1) - #37

Merged
marcelverdult merged 24 commits into
devfrom
fix/dispatcher-parse-len
Oct 26, 2026
Merged

marcelverdult merged 24 commits into
devfrom
fix/dispatcher-parse-len

Conversation

@marcelverdult

Copy link
Copy Markdown
Owner

CI only, not for merge into integration. Based on integration at 07fa9c5c, so the diff against dev also shows the integration commits. This PR's own commits are the last four, from 783e89d2.

Open finding 1 of note 0010: Dispatcher::tryParsePacket() read the transport codes and the path length byte before testing len, and Packet::readFrom() also copied the whole path first. Both now test the length before each read. No packet changes its outcome: every one is accepted or refused as before, accepted ones with the same fields. Note: docs/fixes/packet-parse-len.md.

Evidence:

  • Host, tests first (test/test_packet_parse in native_core, plus CommandTest.SendRawPacketShortFramesOfEveryRouteType): input at the end of a page with a PROT_NONE page behind it, every route type, len 0 to 72; on the old code 288 cases fail exactly where predicted (tryParsePacket route 0/3 len 0..5, route 1/2 len 0..1; readFrom up to len 69/65), after the fix none. Reference comparison against the old code kept verbatim in the test (16 version/route pairs, all 256 path length bytes, len 0..80 and 180..256, two stale fills) for both parsers and for the radio receive path: equal before and after. Frame sweep golden untouched.
  • Mutations: 12 of 13 caught, the 13th is equivalent and explained in the note.
  • ASan probe (companion_asan_probe.patch, PROBE_SKIP=1): the cmdSendRawPacket/tryParsePacket line (code 65, lens 4..6) is gone; the CLI finding 2 stays unchanged.
  • All 22 native envs (the 17 of CI and five more): 793 cases, 792 passed, 1 self skipped (SX1276), on macOS and in a Linux container (python:3.12-slim + pip platformio).
  • 13 firmware builds before and after (repeater, room server, companion on ESP32, nRF52, RP2040, STM32, plus both bridge repeaters; wio-e5 has no room server): all build, RAM unchanged, flash +8 to +32 bytes.
  • Hardware: none (ohne Brett).

…the board description

Inventory of the radio blocks in the 89 target.cpp (tools/refactoring/
variant_target_globals.py), why objects cannot be weak, the opt-in macro
RADIOLIB_TARGET_GLOBALS in target.h, the split into D1 (31 nRF52) and
D2 (22 ESP32 with the shared radio_init()), rejected alternatives and the
open upstream pull requests that touch the same lines.
…riants

A new host suite (native_variant_target_globals) builds the shared variant
code against the target.h of a variant with the real AutoDiscoverRTCClock,
with the radio block of the 31 nRF52 variants of cut C copied verbatim: the
radio is built from a Module on the board pins in SX126x order with SPI
passed, radio_driver wraps it and the board, rtc_clock falls back to and
ticks fallback_clock, and the shared radio_init() starts that radio. The
variant stand-ins move into variant_stubs.h and gain a Module stand-in.
… variants

RadioLibTarget.cpp defines radio, radio_driver, fallback_clock and
rtc_clock for the variants whose target.h defines the new opt-in macro
RADIOLIB_TARGET_GLOBALS, built from the P_LORA_* pins: on nRF52 the radio
is a Module on SPI, the fallback clock a VolatileRTCClock. Not weak: a weak
object would still be constructed into a variant's own one. Other platforms
and the SX1276 pin order stop the build with an #error until described.

The 31 nRF52 variants of cut C delete their block from target.cpp
(deletions only) and add the macro line to target.h (additions only).
The host suite now runs its cases against the shared globals.
…riants

A host suite in four environments, one per board description that occurs
among the 22 ESP32 variants of cut D2 (SX1262 on an own bus on the default
host, without P_LORA_SCLK, SX1276, own bus on another host), with their
blocks and radio_init() bodies copied verbatim: the Module's pins in the
chip's order, its bus (own, on which host, or none), what radio_driver
wraps, the fallback clock, and that radio_init() starts the fallback clock,
the RTC and then the radio on its bus, passing the result through. The SPI
stand-in gets bus numbers, the radio stand-in tells the module's bus apart.
…r 22 ESP32 variants

RadioLibTarget.cpp describes the radio globals for ESP32 as well: the pins
in the chip's order (SX1276: DIO0, RESET, DIO1), an own SPIClass where the
board names P_LORA_SCLK, on P_LORA_SPI_HOST if set, otherwise no bus so that
RadioLib starts its default one, and an ESP32RTCClock as fallback clock. For
those variants with an SX126x or SX1276 it adds a weak radio_init(): start
the fallback clock and the RTC, then the radio on its bus.

The 22 ESP32 variants left by cut C delete their block and radio_init()
from target.cpp (deletions only) and add the opt-in, and five of them
P_LORA_SPI_HOST (FSPI, or 0 on the C6), to target.h (additions only).
test_packet_parse (native_core) runs Dispatcher::tryParsePacket() and
Packet::readFrom() on a buffer with an unreadable page behind len, and
compares both, and the radio receive path, against the code as it is.
A companion test sends short SEND_RAW_PACKET frames of every route type.

The two guarded cases fail on this commit: both parsers read the header
fields before they test the length.
A child that reads the guard page now exits with 3 instead of crashing,
so the system crash reporter is not started for every failing case, and
the death tests use the fork style: 2 s instead of 30 s.
Dispatcher::tryParsePacket() read the transport codes and the path length
byte, and Packet::readFrom() also the whole path, before comparing with
len. Both now test the length first. Every packet is accepted or refused
as before, and an accepted packet has the same fields
(docs/fixes/packet-parse-len.md).
Notes the fix, the callers it is reached from, the host tests, mutations,
the ASan probe before and after, native runs on macOS and Linux and the
builds per platform. Open finding 1 of note 0010 points to it.
@marcelverdult
marcelverdult merged commit d4a838c into dev Oct 26, 2026
15 checks passed
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