fix: guard findChannelIdx == -1 + scope trail_units reset

C1/C2: onChannelMessageRecv and onChannelDataRecv both passed
findChannelIdx() through (uint8_t), so an unknown-secret packet
turned -1 into 255 and the message flowed into the offline queue,
UI history, notification and bot reply with a bogus channel index.
Drop such packets at the recv path; also harden addChannelMsg with
an MAX_GROUP_CHANNELS bounds check so a future caller can't poison
the channel-history ring buffer either.

H4: loadPrefsInt always reset trail_units_idx on sentinel mismatch,
even for jumps where the field was saved correctly (e.g. 0xC0DE0004
→ 0xC0DE0005). Scope the reset to sentinel == 0xC0DE0003, which is
the only case where the byte at that offset is actually the stale
sentinel low byte.

FEATURES.md: mark these three items as  in the audit backlog.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-05-30 23:57:12 +02:00
parent 321d769eb5
commit 9113b7f12e
4 changed files with 187 additions and 9 deletions

View File

@@ -333,9 +333,13 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
_prefs.home_pages_mask |= NodePrefs::HP_FAVOURITES;
}
// 0xC0DE0003 → 0xC0DE0004: trail_units_idx added after trail_min_delta_idx.
// Saves from 0xC0DE0003 have the sentinel bytes where trail_units_idx sits,
// so rd() picks up 0x03 (low byte of the old sentinel) — reset to default 0.
_prefs.trail_units_idx = 0;
// On a 0xC0DE0003 file the sentinel bytes sit where trail_units_idx is now,
// so rd() picks up 0x03 (low byte of the old sentinel) — reset just that
// case to default 0. Newer mismatches (e.g. 0xC0DE0004 → 0xC0DE0005) had
// the field saved correctly and must not be clobbered.
if (sentinel == 0xC0DE0003) {
_prefs.trail_units_idx = 0;
}
}
file.close();

View File

@@ -558,7 +558,17 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
out_frame[i++] = RESP_CODE_CHANNEL_MSG_RECV;
}
uint8_t channel_idx = findChannelIdx(channel);
// findChannelIdx() returns -1 for an unknown secret (e.g. a packet that
// routed to us through a stale hash collision, or a stored channel slot
// whose secret was corrupted). Casting -1 to uint8_t would give 255, and
// every downstream path (offline queue, UI hist, bot reply) would then
// operate on a bogus channel index. Drop the message instead.
int idx = findChannelIdx(channel);
if (idx < 0) {
MESH_DEBUG_PRINTLN("onChannelMessageRecv: unknown channel secret — dropping message");
return;
}
uint8_t channel_idx = (uint8_t)idx;
out_frame[i++] = channel_idx;
uint8_t path_len = out_frame[i++] = pkt->isRouteFlood() ? pkt->path_len : 0xFF;
@@ -606,7 +616,12 @@ void MyMesh::onChannelDataRecv(const mesh::GroupChannel &channel, mesh::Packet *
out_frame[i++] = 0; // reserved1
out_frame[i++] = 0; // reserved2
uint8_t channel_idx = findChannelIdx(channel);
int didx = findChannelIdx(channel);
if (didx < 0) {
MESH_DEBUG_PRINTLN("onChannelDataRecv: unknown channel secret — dropping packet");
return;
}
uint8_t channel_idx = (uint8_t)didx;
out_frame[i++] = channel_idx;
out_frame[i++] = pkt->isRouteFlood() ? pkt->path_len : 0xFF;
out_frame[i++] = (uint8_t)(data_type & 0xFF);

View File

@@ -412,6 +412,10 @@ public:
}
void addChannelMsg(uint8_t ch_idx, const char* text) {
// Guard against bogus channel indices (e.g. findChannelIdx() returned -1
// and was cast to uint8_t → 255). Storing such an entry would burn a ring
// slot for a message that no visible channel can ever surface.
if (ch_idx >= MAX_GROUP_CHANNELS) return;
int pos;
if (_hist_count < CH_HIST_MAX) {
pos = (_hist_head + _hist_count) % CH_HIST_MAX;
@@ -425,10 +429,8 @@ public:
strncpy(_hist[pos].text, text, sizeof(_hist[pos].text) - 1);
_hist[pos].text[sizeof(_hist[pos].text) - 1] = '\0';
if (ch_idx < MAX_GROUP_CHANNELS) {
bool viewing = (_phase == CHANNEL_HIST && _sel_channel_idx == (int)ch_idx);
if (!viewing && _ch_unread[ch_idx] < 99) _ch_unread[ch_idx]++;
}
bool viewing = (_phase == CHANNEL_HIST && _sel_channel_idx == (int)ch_idx);
if (!viewing && _ch_unread[ch_idx] < 99) _ch_unread[ch_idx]++;
}
void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text) {