fix: evaluate pre-hop hop_start bitfield guard post-decode instead of before decryption

This commit is contained in:
Thomas Göttgens
2026-06-22 11:02:09 +02:00
parent 3501f620a3
commit 35c237d634
3 changed files with 22 additions and 9 deletions
+6 -8
View File
@@ -2835,14 +2835,12 @@ HopStartStatus classifyHopStart(const meshtastic_MeshPacket &p)
return HopStartStatus::INVALID;
if (p.hop_start == 0) {
// hop_start == hop_limit == 0: intentional zero-hop broadcast (e.g. beacon). Valid by definition —
// the packet was never meant to travel any hops, so no hop_start ambiguity applies.
if (p.hop_limit == 0)
return HopStartStatus::VALID;
// Firmware prior to 2.3.0 (585805c) lacked a hop_start field. Firmware version 2.5.0 (bf34329) introduced a
// bitfield that is always present. Use the presence of the bitfield to determine if the origin's firmware
// version is guaranteed to have hop_start populated. Note that this can only be done for decoded packets as
// the bitfield is encrypted under the channel encryption key.
// hop_start == 0 is either a modern zero-hop broadcast (e.g. beacon) or pre-2.3.0 firmware (585805c)
// that never populated hop_start. Firmware 2.5.0 (bf34329) introduced a bitfield that is always
// present; use it to tell the two apart. The bitfield is encrypted under the channel key, so this can
// only be resolved for decoded packets — until then the status stays MISSING_OR_UNKNOWN. Callers
// acting before decode must therefore not treat UNKNOWN as a drop (the bitfield isn't readable yet);
// the verdict is re-checked post-decode in Router::handleReceived.
if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag && p.decoded.has_bitfield)
return HopStartStatus::VALID;
return HopStartStatus::MISSING_OR_UNKNOWN;
+4 -1
View File
@@ -147,7 +147,10 @@ inline bool shouldDropPacketForPreHop(const meshtastic_MeshPacket &p)
if (isFromUs(&p)) {
return false; // local-originated packets should never be dropped by pre-hop drop policy
}
return classifyHopStart(p) != HopStartStatus::VALID;
// Pre-decode: the channel-encrypted bitfield isn't readable yet, so a missing/unknown hop_start can't be
// distinguished from a modern packet. Only drop the provably-corrupt case here; the bitfield-dependent
// verdict is re-checked post-decode in Router::handleReceived.
return classifyHopStart(p) == HopStartStatus::INVALID;
#endif
}
+12
View File
@@ -823,6 +823,18 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
else
printPacket("handleReceived(REMOTE)", p);
#if MESHTASTIC_PREHOP_DROP
// Pre-hop firmware drop, post-decode half: the bitfield that proves the origin populated hop_start is
// encrypted under the channel key, so it can only be evaluated now that the packet is decoded. A packet
// whose hop_start is still missing/unknown comes from pre-hop firmware — keep it out of module
// processing, admin handling, phone delivery, MQTT and rebroadcast. Local-origin packets are exempt.
if (!isFromUs(p) && classifyHopStart(*p) != HopStartStatus::VALID) {
logHopStartDrop(*p, "post-decode pre-hop drop");
cancelSending(p->from, p->id);
skipHandle = true;
}
#endif
// Neighbor info module is disabled, ignore expensive neighbor info packets
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
p->decoded.portnum == meshtastic_PortNum_NEIGHBORINFO_APP &&