@@ -96,6 +96,8 @@ static bool routingAuthCacheMatches(const meshtastic_MeshPacket &packet)
9696
9797static void storeRoutingAuthCache (const meshtastic_MeshPacket &wire, const meshtastic_MeshPacket &authenticated)
9898{
99+ if (!routingAuthCacheLock)
100+ return ;
99101 concurrency::LockGuard guard (routingAuthCacheLock);
100102 routingAuthCache.wire = wire;
101103 routingAuthCache.authenticated = authenticated;
@@ -688,55 +690,60 @@ bool checkXeddsaReceivePolicy(meshtastic_MeshPacket *p)
688690}
689691#endif
690692
691- RoutingAuthVerdict passesRoutingAuthGate (const meshtastic_MeshPacket *p )
693+ static RoutingAuthVerdict evaluateRoutingAuth (const meshtastic_MeshPacket &wire, meshtastic_MeshPacket &authenticated )
692694{
693- // Routing still needs the original encrypted representation for byte-for-byte relay and for
694- // MQTT uplink. Authenticate a copy here; handleReceived() performs the normal in-place decode
695- // only after stateful routing filters have completed.
696- if (routingAuthCacheMatches (*p))
697- return RoutingAuthVerdict::ACCEPT ;
698-
699- meshtastic_MeshPacket wire = *p;
700- meshtastic_MeshPacket authCandidate = *p;
695+ authenticated = wire;
701696 routingAuthEvaluations++;
702- if (authCandidate .which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
697+ if (authenticated .which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
703698 // Already-decoded remote ingress (notably Portduino SimRadio) did not pass through a
704699 // decryptor. Never trust serialized local authentication metadata on that boundary.
705- authCandidate .pki_encrypted = false ;
706- authCandidate .public_key .size = 0 ;
707- authCandidate .xeddsa_signed = false ;
700+ authenticated .pki_encrypted = false ;
701+ authenticated .public_key .size = 0 ;
702+ authenticated .xeddsa_signed = false ;
708703#if !(MESHTASTIC_EXCLUDE_PKI) && !(MESHTASTIC_EXCLUDE_XEDDSA)
709704 concurrency::LockGuard g (cryptLock);
710- if (!checkXeddsaReceivePolicy (&authCandidate )) {
711- LOG_WARN (" Already-decoded packet rejected by signature policy" );
705+ if (!checkXeddsaReceivePolicy (&authenticated )) {
706+ LOG_WARN (" Already-decoded packet rejected by routing signature policy" );
712707 return RoutingAuthVerdict::REJECT ;
713708 }
714709#endif
715- wire = *p;
716- storeRoutingAuthCache (wire, authCandidate);
717710 return RoutingAuthVerdict::ACCEPT ;
718711 }
719- const DecodeState state = perhapsDecode (&authCandidate );
712+ const DecodeState state = perhapsDecode (&authenticated );
720713 if (state == DecodeState::DECODE_POLICY_REJECT ) {
721- LOG_WARN (" Packet rejected by signature policy" );
714+ LOG_WARN (" Packet rejected by routing signature policy" );
722715 return RoutingAuthVerdict::REJECT ;
723716 }
724717 if (state == DecodeState::DECODE_FATAL ) {
725- LOG_WARN (" Fatal decode error, dropping packet " );
718+ LOG_WARN (" Fatal decode error during routing authentication " );
726719 return RoutingAuthVerdict::REJECT ;
727720 }
728721 if (state == DecodeState::DECODE_FAILURE ) {
729- LOG_WARN (" Decryptable packet failed decoding, dropping packet " );
722+ LOG_WARN (" Decryptable packet failed routing authentication " );
730723 return RoutingAuthVerdict::REJECT ;
731724 }
732725
733726 // Only an explicit unknown-channel result remains eligible for opaque relay.
734727 if (state == DecodeState::DECODE_OPAQUE )
735728 return RoutingAuthVerdict::OPAQUE_RELAY_ONLY ;
736- storeRoutingAuthCache (wire, authCandidate);
737729 return RoutingAuthVerdict::ACCEPT ;
738730}
739731
732+ RoutingAuthVerdict passesRoutingAuthGate (const meshtastic_MeshPacket *p)
733+ {
734+ // Routing still needs the original encrypted representation for byte-for-byte relay and for
735+ // MQTT uplink. Authenticate a copy here; handleReceived() performs the normal in-place decode
736+ // only after stateful routing filters have completed.
737+ if (routingAuthCacheMatches (*p))
738+ return RoutingAuthVerdict::ACCEPT ;
739+
740+ meshtastic_MeshPacket authenticated = meshtastic_MeshPacket_init_zero;
741+ const RoutingAuthVerdict verdict = evaluateRoutingAuth (*p, authenticated);
742+ if (verdict == RoutingAuthVerdict::ACCEPT )
743+ storeRoutingAuthCache (*p, authenticated);
744+ return verdict;
745+ }
746+
740747#if !(MESHTASTIC_EXCLUDE_PKI)
741748// The fallback costs three X25519 ops before the AEAD tag is checked. Budget is global because p->from is
742749// attacker-controlled; successful runs refund, and their key is then persisted for the fast path.
@@ -1206,7 +1213,7 @@ NodeNum Router::getNodeNum()
12061213 * Handle any packet that is received by an interface on this node.
12071214 * Note: some packets may merely being passed through this node and will be forwarded elsewhere.
12081215 */
1209- void Router::handleReceived (meshtastic_MeshPacket *p, RxSource src)
1216+ void Router::handleReceived (meshtastic_MeshPacket *p, RxSource src, bool routingAuthRequired )
12101217{
12111218 bool skipHandle = false ;
12121219
@@ -1221,8 +1228,15 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
12211228
12221229 // Consume the decoded/authenticated handoff after preserving the exact encrypted packet and
12231230 // before mutating any packet fields that participate in the exact cache match.
1224- if (src == RX_SRC_RADIO )
1225- applyRoutingAuthCache (p);
1231+ if (routingAuthRequired && !applyRoutingAuthCache (p)) {
1232+ meshtastic_MeshPacket authenticated = meshtastic_MeshPacket_init_zero;
1233+ if (evaluateRoutingAuth (*p, authenticated) != RoutingAuthVerdict::ACCEPT ) {
1234+ LOG_WARN (" Routing authentication handoff was lost and packet re-verification failed" );
1235+ packetPool.release (p_encrypted);
1236+ return ;
1237+ }
1238+ *p = authenticated;
1239+ }
12261240
12271241 // Also, we should set the time from the ISR and it should have msec level resolution.
12281242 // Keep the decoded working packet and encrypted MQTT copy on the same local arrival timestamp.
@@ -1426,6 +1440,6 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
14261440
14271441 // Note: we avoid calling shouldFilterReceived if we are supposed to ignore certain nodes - because some overrides might
14281442 // cache/learn of the existence of nodes (i.e. FloodRouter) that they should not
1429- handleReceived (p);
1443+ handleReceived (p, RX_SRC_RADIO , true );
14301444 packetPool.release (p);
14311445}
0 commit comments