Skip to content

Commit d84b960

Browse files
authored
Merge branch 'meshtastic:develop' into time-handling
2 parents b140a38 + 9c260ad commit d84b960

7 files changed

Lines changed: 49 additions & 26 deletions

File tree

src/MessageStore.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,12 @@ const StoredMessage *MessageStore::tryAddFromPacket(const meshtastic_MeshPacket
215215
sm.channelIndex = packet.channel;
216216

217217
const char *payload = reinterpret_cast<const char *>(packet.decoded.payload.bytes);
218-
size_t len = strnlen(payload, MAX_MESSAGE_SIZE - 1);
218+
// payload.bytes is not NUL-terminated, so bound by the received size too: a shorter message
219+
// stored after a longer one would otherwise pick up the previous occupant's trailing bytes.
220+
size_t avail = packet.decoded.payload.size;
221+
if (avail > MAX_MESSAGE_SIZE - 1)
222+
avail = MAX_MESSAGE_SIZE - 1;
223+
size_t len = strnlen(payload, avail);
219224
sm.textOffset = storeTextInPool(payload, len);
220225
sm.textLength = len;
221226

src/mesh/NodeDB.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,10 @@ void NodeDB::resetRadioConfig(bool is_fresh_install)
741741
LOG_INFO("Set default channel and radio preferences!");
742742

743743
channels.initDefaults();
744+
// Defaults ship the public PSK, so strip it again before onConfigChanged() publishes hashes;
745+
// loadFromDisk's sanitation is a no-op when the channel file was absent or corrupt.
746+
if (owner.is_licensed)
747+
channels.ensureLicensedOperation();
744748
}
745749

746750
channels.onConfigChanged();
@@ -3504,10 +3508,9 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
35043508
*/
35053509
bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex, bool xeddsaSigned)
35063510
{
3507-
// Only a signed update may change the identity of a node that has proven it signs; our own record is
3508-
// exempt. Checked before getOrCreateMeshNode so a refused update cannot evict or write the warm tier.
3509-
const meshtastic_NodeInfoLite *existing = getMeshNode(nodeId);
3510-
if (nodeId != getNodeNum() && existing && nodeInfoLiteHasXeddsaSigned(existing) && !xeddsaSigned) {
3511+
// Only a signed update may change the identity of a proven signer; our own record is exempt.
3512+
// Checked before getOrCreateMeshNode so a refusal cannot evict; isKnownXeddsaSigner covers the warm tier.
3513+
if (nodeId != getNodeNum() && isKnownXeddsaSigner(nodeId) && !xeddsaSigned) {
35113514
LOG_WARN("Refusing unsigned identity update for node 0x%08x that previously signed", nodeId);
35123515
return false;
35133516
}

src/mesh/PhoneAPI.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1855,7 +1855,12 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
18551855
p.want_ack = true;
18561856
}
18571857

1858-
lastPortNumToRadio[p.decoded.portnum] = millis();
1858+
// Only the rate-limited ports above are ever read back, so recording any other portnum would let
1859+
// a client grow this map without bound by cycling through them.
1860+
if (IS_ONE_OF(p.decoded.portnum, meshtastic_PortNum_TRACEROUTE_APP, meshtastic_PortNum_POSITION_APP,
1861+
meshtastic_PortNum_WAYPOINT_APP, meshtastic_PortNum_ALERT_APP, meshtastic_PortNum_TELEMETRY_APP,
1862+
meshtastic_PortNum_TEXT_MESSAGE_APP))
1863+
lastPortNumToRadio[p.decoded.portnum] = millis();
18591864
service->handleToRadio(p);
18601865
return true;
18611866
}

src/mesh/Router.cpp

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,8 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
465465

466466
if (!(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag ||
467467
p->which_payload_variant == meshtastic_MeshPacket_decoded_tag)) {
468+
// Error returns from here own the packet, as the position-precision path below does.
469+
packetPool.release(p);
468470
return meshtastic_Routing_Error_BAD_REQUEST;
469471
}
470472

@@ -633,7 +635,9 @@ bool checkXeddsaReceivePolicy(meshtastic_MeshPacket *p)
633635
if (p->decoded.xeddsa_signature.size == XEDDSA_SIGNATURE_SIZE) {
634636
meshtastic_NodeInfoLite_public_key_t senderKey = {0, {0}};
635637
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->from);
636-
if (nodeDB->copyPublicKey(p->from, senderKey)) {
638+
// Authoritative keys only: verifying against an opportunistic cache key would let a planted
639+
// key mark its own node a signer, the trust loop #11116 closed on the decrypt path.
640+
if (nodeDB->copyPublicKeyAuthoritative(p->from, senderKey)) {
637641
p->xeddsa_signed =
638642
crypto->xeddsa_verify(senderKey.bytes, p->from, p->id, p->decoded.portnum, p->decoded.payload.bytes,
639643
p->decoded.payload.size, p->decoded.xeddsa_signature.bytes);
@@ -681,20 +685,14 @@ bool checkXeddsaReceivePolicy(meshtastic_MeshPacket *p)
681685
if (compatible)
682686
return true;
683687

684-
// In Balanced, preserve legacy unsigned-unicast compatibility and only reject the class a
685-
// signing node always signs: a non-PKI broadcast whose signed encoding would still fit the
686-
// LoRa frame. Canonical sizing removes unknown protobuf fields before mirroring the
687-
// sender-side signedDataFits() gate, so this counts the same fields that gate counted.
688-
// Unicast packets and broadcasts too big to carry a signature are never signed, so they
689-
// must not be hard-failed here even for a known signer (PKI already returned above).
690-
// isKnownXeddsaSigner consults the warm tier too: a signer evicted from the hot store
691-
// must not become impersonatable via unsigned broadcasts until it is re-heard.
692-
if (nodeDB->isKnownXeddsaSigner(p->from) && isBroadcast(p->to)) {
688+
// Balanced rejects only what a signer always signs: non-PKI broadcasts whose signed encoding
689+
// would have fit, plus unicasts on ham where licensed senders sign too. Mirrors perhapsEncode.
690+
if (nodeDB->isKnownXeddsaSigner(p->from) && (isBroadcast(p->to) || owner.is_licensed)) {
693691
size_t canonicalSize;
694692
if (!canonicalSignableSize(&p->decoded, &canonicalSize))
695693
return true; // can't size it; never drop on a sizing failure
696694
if (canonicalSize + XEDDSA_SIGNATURE_FIELD_BYTES + MESHTASTIC_HEADER_LENGTH <= MAX_LORA_PAYLOAD_LEN) {
697-
LOG_WARN("Dropping unsigned broadcast from 0x%08x that previously signed", p->from);
695+
LOG_WARN("Dropping unsigned packet from 0x%08x that previously signed", p->from);
698696
return false;
699697
}
700698
}
@@ -1172,7 +1170,12 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
11721170
*destKey.bytes);
11731171
return meshtastic_Routing_Error_PKI_FAILED;
11741172
}
1175-
crypto->encryptCurve25519(p->to, getFrom(p), destKey, p->id, numbytes, bytes, p->encrypted.bytes);
1173+
// On failure encrypted.bytes holds no ciphertext, so continuing would put the plaintext
1174+
// on the air labelled pki_encrypted.
1175+
if (!crypto->encryptCurve25519(p->to, getFrom(p), destKey, p->id, numbytes, bytes, p->encrypted.bytes)) {
1176+
LOG_WARN("PKI encryption failed for destination node 0x%08x", p->to);
1177+
return meshtastic_Routing_Error_PKI_FAILED;
1178+
}
11761179
numbytes += MESHTASTIC_PKC_OVERHEAD;
11771180
p->channel = 0;
11781181
p->pki_encrypted = true;

src/mesh/udp/UdpMulticastHandler.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ class UdpMulticastHandler final
7979
LOG_WARN("UDP packet with spoofed local from=0x%08x, dropping", mp.from);
8080
return;
8181
}
82+
// Same clamp the MQTT ingress applies: an out-of-range hop count is not relayable.
83+
if (mp.hop_limit > HOP_MAX || mp.hop_start > HOP_MAX) {
84+
LOG_WARN("UDP packet with invalid hop_limit(%u) or hop_start(%u), dropping", mp.hop_limit, mp.hop_start);
85+
return;
86+
}
8287
mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
8388
// Authentication metadata is local-only; Router re-establishes it after successful PKI decryption.
8489
mp.pki_encrypted = false;

src/modules/DropzoneModule.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,17 @@ ProcessMessage DropzoneModule::handleReceived(const meshtastic_MeshPacket &mp)
3333
auto &p = mp.decoded;
3434
char matchCompare[54];
3535
auto incomingMessage = reinterpret_cast<const char *>(p.payload.bytes);
36-
sprintf(matchCompare, "%s conditions", owner.short_name);
37-
if (strncasecmp(incomingMessage, matchCompare, strlen(matchCompare)) == 0) {
36+
// payload.bytes is not NUL-terminated, so a comparison longer than the received size would read
37+
// whatever the previous occupant of the packet left behind.
38+
const size_t received = p.payload.size;
39+
snprintf(matchCompare, sizeof(matchCompare), "%s conditions", owner.short_name);
40+
if (received >= strlen(matchCompare) && strncasecmp(incomingMessage, matchCompare, strlen(matchCompare)) == 0) {
3841
LOG_DEBUG("Received dropzone conditions request");
3942
startSendConditions = millis();
4043
}
4144

42-
sprintf(matchCompare, "%s conditions", owner.long_name);
43-
if (strncasecmp(incomingMessage, matchCompare, strlen(matchCompare)) == 0) {
45+
snprintf(matchCompare, sizeof(matchCompare), "%s conditions", owner.long_name);
46+
if (received >= strlen(matchCompare) && strncasecmp(incomingMessage, matchCompare, strlen(matchCompare)) == 0) {
4447
LOG_DEBUG("Received dropzone conditions request");
4548
startSendConditions = millis();
4649
}

src/modules/NodeInfoModule.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,9 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
5656
return true;
5757
}
5858
NodeNum sourceNum = getFrom(&mp);
59-
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(sourceNum);
60-
// Broadcasts only: senders never sign unicast NodeInfo, so dropping it would break exchanges
61-
// with signer nodes. Backstops ingress that skips Router's downgrade drop (e.g. decoded MQTT).
62-
if (node && nodeInfoLiteHasXeddsaSigned(node) && !mp.xeddsa_signed && isBroadcast(mp.to)) {
59+
// Broadcasts only: unicast NodeInfo is unsigned off ham, so updateUser refuses the identity
60+
// write instead. isKnownXeddsaSigner also covers the warm tier.
61+
if (nodeDB->isKnownXeddsaSigner(sourceNum) && !mp.xeddsa_signed && isBroadcast(mp.to)) {
6362
LOG_WARN("Dropping unsigned NodeInfo broadcast from node 0x%08x that previously signed", sourceNum);
6463
return true;
6564
}

0 commit comments

Comments
 (0)