From b8e332cd2a31c315435df8e4723cccb31f5dcc62 Mon Sep 17 00:00:00 2001 From: Dragan Spiridonov Date: Thu, 16 Apr 2026 15:01:57 +0200 Subject: [PATCH] fix(firmware): defensive copy of filter_mac to prevent callback crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CSI callback reads g_nvs_config.filter_mac_set and filter_mac on every invocation (100-500 Hz). If wifi_init_sta() corrupts g_nvs_config (same root cause as the node_id clobber), the callback reads garbage from the struct, leading to Core 0 LoadProhibited panic after ~2400 callbacks (~70 seconds of operation). Extends the early-capture pattern from the node_id fix to also copy filter_mac_set and filter_mac into module-local statics before WiFi init runs. Adds canary logging to detect filter_mac corruption. Observed on device 80:b5:4e:c1:be:b8 via serial: CSI cb #2400 → Guru Meditation Error: Core 0 panic'ed (LoadProhibited) → TG0WDT_SYS_RST → reboot → crash again at ~2900 callbacks Refs #232 #375 #385 #386 #390 Co-Authored-By: Ruflo & AQE --- firmware/esp32-csi-node/main/csi_collector.c | 52 +++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/firmware/esp32-csi-node/main/csi_collector.c b/firmware/esp32-csi-node/main/csi_collector.c index 903fcbb9..ae23065e 100644 --- a/firmware/esp32-csi-node/main/csi_collector.c +++ b/firmware/esp32-csi-node/main/csi_collector.c @@ -25,14 +25,21 @@ /* ADR-060: Access the global NVS config for MAC filter and channel override. */ extern nvs_config_t g_nvs_config; -/* Defensive fix (#232, #375, #385, #386, #390): capture node_id into a - * module-local static BEFORE wifi_init_sta() runs, because WiFi driver init - * can corrupt g_nvs_config.node_id (confirmed on device 80:b5:4e:c1:be:b8). +/* Defensive fix (#232, #375, #385, #386, #390): capture NVS config fields into + * module-local statics BEFORE wifi_init_sta() runs, because WiFi driver init + * can corrupt g_nvs_config (confirmed on device 80:b5:4e:c1:be:b8). * main.c calls csi_collector_set_node_id() immediately after nvs_config_load(), - * and csi_serialize_frame() uses s_node_id exclusively. */ + * and all runtime paths use the local copies exclusively. */ static uint8_t s_node_id = 1; static bool s_node_id_early_set = false; +/* Defensive copy of MAC filter config — the CSI callback fires at 100-500 Hz + * and reads filter_mac_set + filter_mac on every invocation. If wifi_init_sta() + * corrupts g_nvs_config, the callback would read garbage, potentially causing + * LoadProhibited panics (observed: Core 0 panic after ~2400 callbacks). */ +static uint8_t s_filter_mac[6] = {0}; +static bool s_filter_mac_set = false; + /* ADR-057: Build-time guard — fail early if CSI is not enabled in sdkconfig. * Without this, the firmware compiles but crashes at runtime with: * "E (xxxx) wifi:CSI not enabled in menuconfig!" @@ -165,9 +172,11 @@ static void wifi_csi_callback(void *ctx, wifi_csi_info_t *info) { (void)ctx; - /* ADR-060: MAC address filtering — drop frames from non-matching sources. */ - if (g_nvs_config.filter_mac_set) { - if (memcmp(info->mac, g_nvs_config.filter_mac, 6) != 0) { + /* ADR-060: MAC address filtering — drop frames from non-matching sources. + * Uses defensively-copied s_filter_mac instead of g_nvs_config (which can + * be corrupted by wifi_init_sta — same root cause as the node_id clobber). */ + if (s_filter_mac_set) { + if (memcmp(info->mac, s_filter_mac, 6) != 0) { return; /* Source MAC doesn't match filter — skip frame. */ } } @@ -228,6 +237,17 @@ void csi_collector_set_node_id(uint8_t node_id) s_node_id_early_set = true; ESP_LOGI(TAG, "Early capture node_id=%u (before WiFi init, #232/#390)", (unsigned)node_id); + + /* Also capture MAC filter config now — same struct, same corruption risk. + * The CSI callback reads filter_mac_set on every invocation (100-500 Hz), + * so a corrupted value could cause erratic filtering or crash. */ + s_filter_mac_set = (g_nvs_config.filter_mac_set != 0); + if (s_filter_mac_set) { + memcpy(s_filter_mac, g_nvs_config.filter_mac, 6); + ESP_LOGI(TAG, "Early capture filter_mac=%02x:%02x:%02x:%02x:%02x:%02x", + s_filter_mac[0], s_filter_mac[1], s_filter_mac[2], + s_filter_mac[3], s_filter_mac[4], s_filter_mac[5]); + } } void csi_collector_init(void) @@ -248,6 +268,24 @@ void csi_collector_init(void) (unsigned)s_node_id); } + /* Canary for filter_mac: check if WiFi init corrupted the filter fields. */ + if (s_node_id_early_set) { + bool mac_set_now = (g_nvs_config.filter_mac_set != 0); + if (mac_set_now != s_filter_mac_set) { + ESP_LOGW(TAG, "filter_mac_set clobber CONFIRMED: early=%d g_nvs_config=%d", + (int)s_filter_mac_set, (int)mac_set_now); + } else if (s_filter_mac_set && + memcmp(s_filter_mac, g_nvs_config.filter_mac, 6) != 0) { + ESP_LOGW(TAG, "filter_mac clobber CONFIRMED: bytes differ after WiFi init"); + } + } else { + /* No early capture — grab filter config now (may already be corrupted). */ + s_filter_mac_set = (g_nvs_config.filter_mac_set != 0); + if (s_filter_mac_set) { + memcpy(s_filter_mac, g_nvs_config.filter_mac, 6); + } + } + /* ADR-060: Determine the CSI channel. * Priority: 1) NVS override (--channel), 2) connected AP channel, 3) Kconfig default. */ uint8_t csi_channel = (uint8_t)CONFIG_CSI_WIFI_CHANNEL;