Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions src/mesh/NodeDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
21 changes: 10 additions & 11 deletions src/mesh/Router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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;
}
}
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/mesh/udp/UdpMulticastHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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;
Expand Down
7 changes: 3 additions & 4 deletions src/modules/NodeInfoModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading