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:
@@ -302,10 +302,39 @@ bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Re-decode nested string-bearing payloads before local phone delivery so PB_VALIDATE_UTF8 rejects
|
||||
// malformed NodeInfo/Waypoint data a strict phone decoder could crash on. Mesh relay is unaffected.
|
||||
bool MeshService::phonePayloadIsDecodable(const meshtastic_Data &d)
|
||||
{
|
||||
// User/Waypoint are all-static nanopb messages (no PB_ENABLE_MALLOC/callback fields), so the
|
||||
// decoded scratch owns no heap and needs no pb_release.
|
||||
switch (d.portnum) {
|
||||
case meshtastic_PortNum_NODEINFO_APP: {
|
||||
meshtastic_User u = meshtastic_User_init_zero;
|
||||
return pb_decode_from_bytes(d.payload.bytes, d.payload.size, &meshtastic_User_msg, &u);
|
||||
}
|
||||
case meshtastic_PortNum_WAYPOINT_APP: {
|
||||
meshtastic_Waypoint w = meshtastic_Waypoint_init_zero;
|
||||
return pb_decode_from_bytes(d.payload.bytes, d.payload.size, &meshtastic_Waypoint_msg, &w);
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void MeshService::sendToPhone(meshtastic_MeshPacket *p)
|
||||
{
|
||||
perhapsDecode(p);
|
||||
|
||||
// Withhold decoded nested payloads a strict phone decoder would reject; still-encrypted packets
|
||||
// pass through (the phone may hold the key).
|
||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !phonePayloadIsDecodable(p->decoded)) {
|
||||
LOG_WARN("Dropping undecodable portnum=%d payload from phone delivery (from=0x%08x)", p->decoded.portnum, p->from);
|
||||
releaseToPool(p);
|
||||
fromNum++; // notify observers so the phone can resync
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
|
||||
if (moduleConfig.store_forward.enabled && storeForwardModule->isServer() &&
|
||||
|
||||
Reference in New Issue
Block a user