From 8d85ab5c7a9ea6f045578b3bfbb813461d245cf0 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 10 Oct 2026 15:27:28 -0700 Subject: [PATCH 1/2] Forget a stored peer path whose length byte is invalid ClientACL::load() and the companion's DataStore::loadContacts() keep the stored out_path_len as read. Any value but OUT_PATH_UNKNOWN counts as a known path, so replies to that peer go DIRECT. 136 of the 256 values fail Packet::isValidPathLen(): hash size 4 is reserved, and the path may not exceed MAX_PATH_SIZE. Mesh::sendDirect() keeps the value as the header's path_len, and Dispatcher::sendPacket() then frees the packet as invalid, so the peer gets no reply and nothing is logged outside a debug build. The firmware stores only paths it has validated, so this takes a damaged file. Reset an invalid value to OUT_PATH_UNKNOWN on load, so replies to that peer flood until the path is learned again. ZephCore made the same check in liquidraver/ZephCore@fcfee67. Co-Authored-By: Claude Opus 5.5 --- examples/companion_radio/DataStore.cpp | 4 ++++ src/helpers/ClientACL.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 06c56a7a44..c5559ec1e0 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -280,6 +280,10 @@ File file = openRead(_getContactsChannelsFS(), "/contacts3"); if (!success) break; // EOF + if (c.out_path_len != OUT_PATH_UNKNOWN && !mesh::Packet::isValidPathLen(c.out_path_len)) { + c.out_path_len = OUT_PATH_UNKNOWN; // corrupt entry: rediscover the path rather than send DIRECT on a bad one + } + c.id = mesh::Identity(pub_key); if (!host->onContactLoaded(c)) full = true; } diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 1282382737..e5bfc3f71b 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -39,6 +39,10 @@ void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) { if (!success) break; // EOF + if (c.out_path_len != OUT_PATH_UNKNOWN && !mesh::Packet::isValidPathLen(c.out_path_len)) { + c.out_path_len = OUT_PATH_UNKNOWN; // corrupt entry: rediscover the path rather than send DIRECT on a bad one + } + c.id = mesh::Identity(pub_key); self_id.calcSharedSecret(c.shared_secret, pub_key); // recalculate shared secrets in case our private key changed if (num_clients < MAX_CLIENTS) { From 89f732faa4f869bfa5151756006c92a18be4c7b3 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 10 Oct 2026 15:35:14 -0700 Subject: [PATCH 2/2] Apply the stored path check in the secure-chat loader too simple_secure_chat reads /contacts with the same unchecked out_path_len byte before addContact(). Found by Copilot review on fork PR #28. Co-Authored-By: Claude Opus 5.5 --- examples/simple_secure_chat/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 2e6ba7126d..dc9dd23c48 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -116,6 +116,10 @@ class MyMesh : public BaseChatMesh, ContactVisitor { if (!success) break; // EOF + if (c.out_path_len != OUT_PATH_UNKNOWN && !mesh::Packet::isValidPathLen(c.out_path_len)) { + c.out_path_len = OUT_PATH_UNKNOWN; // corrupt entry: rediscover the path rather than send DIRECT on a bad one + } + c.id = mesh::Identity(pub_key); c.lastmod = 0; if (!addContact(c)) full = true;