Harden against crafted-packet crashes + adversarial fuzzing (#10862)

Audit and fuzzing of the RF-packet decode -> dispatch -> display/phone paths for
the "crash a node or phone with a crafted packet" surface, beyond the XEdDSA
authenticity work.

Crash fixes (reproduced under AddressSanitizer / UBSan):
- GeoCoord::latLongToUTM/latLongToMGRS read fixed letter tables out of bounds on
  extreme latitude_i/longitude_i from a received Position, and narrowed
  out-of-range easting/northing doubles to unsigned (float-cast-overflow UB).
  Clamp the UTM zone, the easting/northing narrowing, and the band/col/row
  indices. Regression: test_geocoord_extreme_coords_no_oob.
- EnvironmentTelemetry/AirQualityTelemetry render attacker floats via
  String(float), which on nRF52/RP2040/STM32/portduino formats into a fixed
  char[33] (dtostrf) and overflows near FLT_MAX. Clamp the rendered metrics via
  UnitConversions::displaySafeFloat (finite + magnitude <= 1e9), unit-tested in
  test_type_conversions.

Defense-in-depth + robustness:
- TraceRouteModule::printRoute: fix an snr_back[-1] OOB read (wrong count in the
  guard) and stop formatting the INT8_MIN "unknown SNR" sentinel as a dB value.
- WaypointModule/NodeDB: sanitize untrusted strings before the OLED renderer and
  the phone-facing ClientNotification (belt-and-suspenders vs PB_VALIDATE_UTF8).
- MeshService::sendToPhone: withhold NODEINFO/WAYPOINT packets whose nested string
  won't cleanly decode, protecting strict phone protobuf decoders without
  affecting mesh relay.

Tests: new test_fuzz_decode (protobuf decode + UTF-8 sanitizer fuzz) and
test_fuzz_packets (perhapsDecode / module-handler / traceroute / phone-gate fuzz),
all under AddressSanitizer; native-suite-count 25 -> 27. Full suite 515/515 green.
This commit is contained in:
Ben Meadors
2026-07-02 16:50:49 -05:00
committed by GitHub
parent 510e9796f9
commit b4dd76a4db
14 files changed
+945 -20

No files matched your search

@@ -375,6 +375,22 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
return;
}
// Bound the float metrics before String(float) renders them (see UnitConversions::displaySafeFloat);
// the stored packet is always the environment variant.
{
auto &e = telemetry.variant.environment_metrics;
e.temperature = UnitConversions::displaySafeFloat(e.temperature);
e.relative_humidity = UnitConversions::displaySafeFloat(e.relative_humidity);
e.barometric_pressure = UnitConversions::displaySafeFloat(e.barometric_pressure);
e.voltage = UnitConversions::displaySafeFloat(e.voltage);
e.current = UnitConversions::displaySafeFloat(e.current);
e.lux = UnitConversions::displaySafeFloat(e.lux);
e.white_lux = UnitConversions::displaySafeFloat(e.white_lux);
e.weight = UnitConversions::displaySafeFloat(e.weight);
e.distance = UnitConversions::displaySafeFloat(e.distance);
e.radiation = UnitConversions::displaySafeFloat(e.radiation);
}
const auto &m = telemetry.variant.environment_metrics;
// Check if any telemetry field has valid data