mirror of
https://github.com/meshtastic/firmware.git
synced 2026-08-01 10:58:30 -04:00
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.
222 lines
8.6 KiB
C++
222 lines
8.6 KiB
C++
#include "WaypointModule.h"
|
|
#include "NodeDB.h"
|
|
#include "PowerFSM.h"
|
|
#include "configuration.h"
|
|
#include "graphics/SharedUIDisplay.h"
|
|
#include "graphics/draw/CompassRenderer.h"
|
|
#include "meshUtils.h"
|
|
|
|
#if HAS_SCREEN
|
|
#include "gps/RTC.h"
|
|
#include "graphics/Screen.h"
|
|
#include "graphics/TimeFormatters.h"
|
|
#include "graphics/draw/NodeListRenderer.h"
|
|
#include "main.h"
|
|
#endif
|
|
|
|
WaypointModule *waypointModule;
|
|
|
|
ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp)
|
|
{
|
|
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
|
auto &p = mp.decoded;
|
|
LOG_INFO("Received waypoint msg from=0x%08x, id=0x%08x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes);
|
|
#endif
|
|
// We only store/display messages destined for us.
|
|
// Keep a copy of the most recent text message.
|
|
devicestate.rx_waypoint = mp;
|
|
devicestate.has_rx_waypoint = true;
|
|
|
|
powerFSM.trigger(EVENT_RECEIVED_MSG);
|
|
|
|
#if HAS_SCREEN
|
|
|
|
UIFrameEvent e;
|
|
|
|
// New or updated waypoint: focus on this frame next time Screen::setFrames runs
|
|
if (shouldDraw()) {
|
|
requestFocus();
|
|
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
|
|
}
|
|
|
|
// Deleting an old waypoint: remove the frame quietly, don't change frame position if possible
|
|
else
|
|
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND;
|
|
|
|
notifyObservers(&e);
|
|
|
|
#endif
|
|
|
|
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
|
|
}
|
|
|
|
#if HAS_SCREEN
|
|
bool WaypointModule::shouldDraw()
|
|
{
|
|
#if !MESHTASTIC_EXCLUDE_WAYPOINT
|
|
if (!screen || !devicestate.has_rx_waypoint)
|
|
return false;
|
|
|
|
meshtastic_Waypoint wp{}; // <- replaces memset
|
|
if (pb_decode_from_bytes(devicestate.rx_waypoint.decoded.payload.bytes, devicestate.rx_waypoint.decoded.payload.size,
|
|
&meshtastic_Waypoint_msg, &wp)) {
|
|
return wp.expire > getTime();
|
|
}
|
|
return false; // no LOG_ERROR, no flag writes
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/// Draw the last waypoint we received
|
|
void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
{
|
|
if (!screen)
|
|
return;
|
|
display->clear();
|
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
display->setFont(FONT_SMALL);
|
|
int line = 1;
|
|
|
|
// === Set Title
|
|
const char *titleStr = "Waypoint";
|
|
|
|
// === Header ===
|
|
graphics::drawCommonHeader(display, x, y, titleStr);
|
|
const int *textPos = graphics::getTextPositions(display);
|
|
|
|
// Decode the waypoint
|
|
const meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
|
|
meshtastic_Waypoint wp{};
|
|
if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) {
|
|
devicestate.has_rx_waypoint = false;
|
|
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));
|
|
|
|
// Will contain distance information, passed as a field to drawColumns
|
|
char distStr[20] = "";
|
|
|
|
// Get our node, to use our own position
|
|
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
|
|
|
// Match compass sizing/placement to favorite node screen logic.
|
|
const int w = display->getWidth();
|
|
int16_t compassRadius = 8;
|
|
int16_t compassX = x + w - compassRadius - 8;
|
|
int16_t compassY = y + display->getHeight() / 2;
|
|
|
|
if (SCREEN_WIDTH > SCREEN_HEIGHT) {
|
|
const int16_t topY = textPos[1];
|
|
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
|
|
const int16_t usableHeight = bottomY - topY - 5;
|
|
compassRadius = usableHeight / 2;
|
|
if (compassRadius < 8)
|
|
compassRadius = 8;
|
|
compassX = x + SCREEN_WIDTH - compassRadius - 8;
|
|
compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
|
|
} else {
|
|
// Waypoint content uses rows 1..4, so place the compass below that block.
|
|
const int yBelowContent = textPos[4] + FONT_HEIGHT_SMALL + 2;
|
|
const int margin = 4;
|
|
#if defined(USE_EINK)
|
|
const int iconSize = (graphics::currentResolution == graphics::ScreenResolution::High) ? 16 : 8;
|
|
const int navBarHeight = iconSize + 6;
|
|
#else
|
|
const int navBarHeight = 0;
|
|
#endif
|
|
const int availableHeight = SCREEN_HEIGHT - yBelowContent - navBarHeight - margin;
|
|
if (availableHeight > 0) {
|
|
compassRadius = availableHeight / 2;
|
|
if (compassRadius < 8)
|
|
compassRadius = 8;
|
|
if (compassRadius * 2 > SCREEN_WIDTH - 16)
|
|
compassRadius = (SCREEN_WIDTH - 16) / 2;
|
|
if (compassRadius < 8)
|
|
compassRadius = 8;
|
|
compassX = x + SCREEN_WIDTH / 2;
|
|
compassY = yBelowContent + availableHeight / 2;
|
|
}
|
|
}
|
|
const uint16_t compassDiam = compassRadius * 2;
|
|
|
|
const bool hasOwnPositionFix = (ourNode && nodeDB->hasValidPosition(ourNode));
|
|
const char *statusLine1 = nullptr;
|
|
const char *statusLine2 = nullptr;
|
|
|
|
// Distance only needs our own position fix; compass/bearing additionally needs heading.
|
|
meshtastic_PositionLite ownPos;
|
|
const bool haveOwnPos = ourNode && nodeDB->copyNodePosition(ourNode->num, ownPos);
|
|
if (hasOwnPositionFix && haveOwnPos) {
|
|
const meshtastic_PositionLite &op = ownPos;
|
|
const float d =
|
|
GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
|
|
|
// Always show distance once we have an own-position fix, even without heading.
|
|
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
|
float feet = d * METERS_TO_FEET;
|
|
snprintf(distStr, sizeof(distStr), feet < (2 * MILES_TO_FEET) ? "%.0fft" : "%.1fmi",
|
|
feet < (2 * MILES_TO_FEET) ? feet : feet / MILES_TO_FEET);
|
|
} else {
|
|
snprintf(distStr, sizeof(distStr), d < 2000 ? "%.0fm" : "%.1fkm", d < 2000 ? d : d / 1000);
|
|
}
|
|
|
|
float myHeading = 0.0f;
|
|
const bool hasHeading =
|
|
graphics::CompassRenderer::getHeadingRadians(DegD(op.latitude_i), DegD(op.longitude_i), myHeading);
|
|
if (hasHeading) {
|
|
// Draw compass circle
|
|
display->drawCircle(compassX, compassY, compassRadius);
|
|
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
|
|
|
|
// Compass bearing to waypoint
|
|
float bearingToOther =
|
|
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
|
|
bearingToOther = graphics::CompassRenderer::adjustBearingForCompassMode(bearingToOther, myHeading);
|
|
graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
|
|
|
|
const float bearingToOtherDegrees = graphics::CompassRenderer::radiansToDegrees360(bearingToOther);
|
|
|
|
// Distance to waypoint with relative bearing when heading is available.
|
|
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
|
float feet = d * METERS_TO_FEET;
|
|
snprintf(distStr, sizeof(distStr), feet < (2 * MILES_TO_FEET) ? "%.0fft %.0f°" : "%.1fmi %.0f°",
|
|
feet < (2 * MILES_TO_FEET) ? feet : feet / MILES_TO_FEET, bearingToOtherDegrees);
|
|
} else {
|
|
snprintf(distStr, sizeof(distStr), d < 2000 ? "%.0fm %.0f°" : "%.1fkm %.0f°", d < 2000 ? d : d / 1000,
|
|
bearingToOtherDegrees);
|
|
}
|
|
|
|
} else {
|
|
statusLine1 = "No";
|
|
statusLine2 = "Heading";
|
|
}
|
|
} else {
|
|
// No own fix yet, so compass/bearing data would be misleading.
|
|
statusLine1 = "No";
|
|
statusLine2 = "Fix";
|
|
}
|
|
|
|
if (statusLine1) {
|
|
display->drawCircle(compassX, compassY, compassRadius);
|
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
|
display->drawString(compassX, compassY - FONT_HEIGHT_SMALL, statusLine1);
|
|
display->drawString(compassX, compassY, statusLine2);
|
|
}
|
|
|
|
display->setTextAlignment(TEXT_ALIGN_LEFT); // Something above me changes to a different alignment, forcing a fix here!
|
|
display->drawString(0, textPos[line++], lastStr);
|
|
display->drawString(0, textPos[line++], wp.name);
|
|
display->drawString(0, textPos[line++], wp.description);
|
|
if (distStr[0])
|
|
display->drawString(0, textPos[line++], distStr);
|
|
}
|
|
#endif
|