mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-18 17:31:42 +10:00
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:
@@ -210,6 +210,11 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta
|
||||
return;
|
||||
}
|
||||
|
||||
// Same String(float) stack-overflow guard as EnvironmentTelemetry: form_formaldehyde is the only
|
||||
// float rendered here, and its raw bytes are unvalidated on decode.
|
||||
telemetry.variant.air_quality_metrics.form_formaldehyde =
|
||||
UnitConversions::displaySafeFloat(telemetry.variant.air_quality_metrics.form_formaldehyde);
|
||||
|
||||
const auto &m = telemetry.variant.air_quality_metrics;
|
||||
|
||||
// Check if any telemetry field has valid data
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
class UnitConversions
|
||||
{
|
||||
public:
|
||||
@@ -7,4 +9,13 @@ class UnitConversions
|
||||
static float MetersPerSecondToKnots(float metersPerSecond);
|
||||
static float MetersPerSecondToMilesPerHour(float metersPerSecond);
|
||||
static float HectoPascalToInchesOfMercury(float hectoPascal);
|
||||
|
||||
// Bound a float before Arduino String(float) renders it: its fixed char[33] + dtostrf overflow
|
||||
// near FLT_MAX (stack smash). Clamp to +/-1e9 (<=10 digits) and drop non-finite values.
|
||||
static inline float displaySafeFloat(float v)
|
||||
{
|
||||
if (!std::isfinite(v))
|
||||
return 0.0f;
|
||||
return v < -1e9f ? -1e9f : (v > 1e9f ? 1e9f : v);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -452,7 +452,7 @@ void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin,
|
||||
// If there's a route back (or we are the destination as then the route is complete), print it
|
||||
if (r->route_back_count > 0 || origin == nodeDB->getNodeNum()) {
|
||||
route += "\n";
|
||||
if (r->snr_towards_count > 0 && origin == nodeDB->getNodeNum())
|
||||
if (origin == nodeDB->getNodeNum() && r->snr_back_count > 0 && r->snr_back[r->snr_back_count - 1] != INT8_MIN)
|
||||
route += vformat("(%.2fdB) 0x%x <-- ", (float)r->snr_back[r->snr_back_count - 1] / 4, origin);
|
||||
else
|
||||
route += "...";
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "configuration.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "graphics/draw/CompassRenderer.h"
|
||||
#include "meshUtils.h"
|
||||
|
||||
#if HAS_SCREEN
|
||||
#include "gps/RTC.h"
|
||||
@@ -92,6 +93,10 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanitize before these reach the OLED renderer (defense-in-depth vs PB_VALIDATE_UTF8).
|
||||
sanitizeUtf8(wp.name, sizeof(wp.name));
|
||||
sanitizeUtf8(wp.description, sizeof(wp.description));
|
||||
|
||||
// Get timestamp info. Will pass as a field to drawColumns
|
||||
char lastStr[20];
|
||||
getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr));
|
||||
|
||||
Reference in New Issue
Block a user