diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 32e851d8b..7b1263147 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -3504,10 +3504,9 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact) */ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex, bool xeddsaSigned) { - // Only a signed update may change the identity of a node that has proven it signs; our own record is - // exempt. Checked before getOrCreateMeshNode so a refused update cannot evict or write the warm tier. - const meshtastic_NodeInfoLite *existing = getMeshNode(nodeId); - if (nodeId != getNodeNum() && existing && nodeInfoLiteHasXeddsaSigned(existing) && !xeddsaSigned) { + // Only a signed update may change the identity of a proven signer; our own record is exempt. + // Checked before getOrCreateMeshNode so a refusal cannot evict; isKnownXeddsaSigner covers the warm tier. + if (nodeId != getNodeNum() && isKnownXeddsaSigner(nodeId) && !xeddsaSigned) { LOG_WARN("Refusing unsigned identity update for node 0x%08x that previously signed", nodeId); return false; } diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index cf0e05c9a..3abcf64b3 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -681,20 +681,14 @@ bool checkXeddsaReceivePolicy(meshtastic_MeshPacket *p) if (compatible) return true; - // In Balanced, preserve legacy unsigned-unicast compatibility and only reject the class a - // signing node always signs: a non-PKI broadcast whose signed encoding would still fit the - // LoRa frame. Canonical sizing removes unknown protobuf fields before mirroring the - // sender-side signedDataFits() gate, so this counts the same fields that gate counted. - // Unicast packets and broadcasts too big to carry a signature are never signed, so they - // must not be hard-failed here even for a known signer (PKI already returned above). - // isKnownXeddsaSigner consults the warm tier too: a signer evicted from the hot store - // must not become impersonatable via unsigned broadcasts until it is re-heard. - if (nodeDB->isKnownXeddsaSigner(p->from) && isBroadcast(p->to)) { + // Balanced rejects only what a signer always signs: non-PKI broadcasts whose signed encoding + // would have fit, plus unicasts on ham where licensed senders sign too. Mirrors perhapsEncode. + if (nodeDB->isKnownXeddsaSigner(p->from) && (isBroadcast(p->to) || owner.is_licensed)) { size_t canonicalSize; if (!canonicalSignableSize(&p->decoded, &canonicalSize)) return true; // can't size it; never drop on a sizing failure if (canonicalSize + XEDDSA_SIGNATURE_FIELD_BYTES + MESHTASTIC_HEADER_LENGTH <= MAX_LORA_PAYLOAD_LEN) { - LOG_WARN("Dropping unsigned broadcast from 0x%08x that previously signed", p->from); + LOG_WARN("Dropping unsigned packet from 0x%08x that previously signed", p->from); return false; } } @@ -1172,7 +1166,12 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) *destKey.bytes); return meshtastic_Routing_Error_PKI_FAILED; } - crypto->encryptCurve25519(p->to, getFrom(p), destKey, p->id, numbytes, bytes, p->encrypted.bytes); + // On failure encrypted.bytes holds no ciphertext, so continuing would put the plaintext + // on the air labelled pki_encrypted. + if (!crypto->encryptCurve25519(p->to, getFrom(p), destKey, p->id, numbytes, bytes, p->encrypted.bytes)) { + LOG_WARN("PKI encryption failed for destination node 0x%08x", p->to); + return meshtastic_Routing_Error_PKI_FAILED; + } numbytes += MESHTASTIC_PKC_OVERHEAD; p->channel = 0; p->pki_encrypted = true; diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h index 60967ccef..4a05d1292 100644 --- a/src/mesh/udp/UdpMulticastHandler.h +++ b/src/mesh/udp/UdpMulticastHandler.h @@ -79,6 +79,11 @@ class UdpMulticastHandler final LOG_WARN("UDP packet with spoofed local from=0x%08x, dropping", mp.from); return; } + // Same clamp the MQTT ingress applies: an out-of-range hop count is not relayable. + if (mp.hop_limit > HOP_MAX || mp.hop_start > HOP_MAX) { + LOG_WARN("UDP packet with invalid hop_limit(%u) or hop_start(%u), dropping", mp.hop_limit, mp.hop_start); + return; + } mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP; // Authentication metadata is local-only; Router re-establishes it after successful PKI decryption. mp.pki_encrypted = false; diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index cf6180ca2..91ec9d633 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -53,10 +53,9 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes return true; } NodeNum sourceNum = getFrom(&mp); - const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(sourceNum); - // Broadcasts only: senders never sign unicast NodeInfo, so dropping it would break exchanges - // with signer nodes. Backstops ingress that skips Router's downgrade drop (e.g. decoded MQTT). - if (node && nodeInfoLiteHasXeddsaSigned(node) && !mp.xeddsa_signed && isBroadcast(mp.to)) { + // Broadcasts only: unicast NodeInfo is unsigned off ham, so updateUser refuses the identity + // write instead. isKnownXeddsaSigner also covers the warm tier. + if (nodeDB->isKnownXeddsaSigner(sourceNum) && !mp.xeddsa_signed && isBroadcast(mp.to)) { LOG_WARN("Dropping unsigned NodeInfo broadcast from node 0x%08x that previously signed", sourceNum); return true; }