From fc75a8a5c83b1a54084be7e1bb7f3e12877ead13 Mon Sep 17 00:00:00 2001 From: ruv Date: Fri, 22 May 2026 23:00:09 -0400 Subject: [PATCH] test(fuzz): extend csi_serialize fuzz harness for ADR-110 byte 18-19 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The libFuzzer harness was compiled without CONFIG_CSI_FRAME_HE_TAGGING, so the new byte 18/19 path in csi_collector.c was zero-filled at compile time and never fuzzed. Three changes to fix that: 1. test/stubs/esp_stubs.h: wifi_pkt_rx_ctrl_t gains both branch families - HE branch (CONFIG_SOC_WIFI_HE_SUPPORT path): cur_bb_format, second - Legacy branch (S3 / pre-HE chips): sig_mode, cwb, stbc A single stub compiles for either branch; the Makefile picks which one is active via -D flags. Both sets are declared so a build for the unselected branch still compiles cleanly. 2. test/Makefile: CFLAGS now defines CONFIG_CSI_FRAME_HE_TAGGING=1 so the new code path is reachable. CONFIG_SOC_WIFI_HE_SUPPORT stays UNSET (default — exercises the legacy S3 branch). Add it to CFLAGS for a parallel HE-stub run if you want coverage of the C6 branch. 3. test/fuzz_csi_serialize.c: parses 3 more control bytes from fuzz input (he_inputs[2] + legacy_inputs) and writes them through info.rx_ctrl.{cur_bb_format,second,sig_mode,cwb,stbc} so the serializer's PpduType switch and Adr018Flags computation are reached on every iteration. Result: the existing libFuzzer corpus + ASAN/UBSAN now covers the ADR-110 wire encoding paths on every run. No more zero-fill silent skip. Co-Authored-By: claude-flow --- firmware/esp32-csi-node/test/Makefile | 6 ++++ .../esp32-csi-node/test/fuzz_csi_serialize.c | 15 ++++++++++ .../esp32-csi-node/test/stubs/esp_stubs.h | 28 ++++++++++++++----- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/firmware/esp32-csi-node/test/Makefile b/firmware/esp32-csi-node/test/Makefile index c794edd9..28ef8da9 100644 --- a/firmware/esp32-csi-node/test/Makefile +++ b/firmware/esp32-csi-node/test/Makefile @@ -20,6 +20,11 @@ # FUZZ_JOBS=4 # Parallel fuzzing jobs CC = clang +# ADR-110: -DCONFIG_CSI_FRAME_HE_TAGGING=1 enables the byte-18/19 HE path +# in csi_collector.c so the fuzzer exercises that code as well as the +# legacy zero-fill path. CONFIG_SOC_WIFI_HE_SUPPORT is left UNSET to +# exercise the legacy S3 branch (sig_mode/cwb/stbc). Add it to CFLAGS for +# a parallel HE-stub build if you want fuzz coverage of the C6 branch. CFLAGS = -fsanitize=fuzzer,address,undefined -g -O1 \ -Istubs -I../main \ -DCONFIG_CSI_NODE_ID=1 \ @@ -28,6 +33,7 @@ CFLAGS = -fsanitize=fuzzer,address,undefined -g -O1 \ -DCONFIG_CSI_TARGET_IP=\"192.168.1.1\" \ -DCONFIG_CSI_TARGET_PORT=5500 \ -DCONFIG_ESP_WIFI_CSI_ENABLED=1 \ + -DCONFIG_CSI_FRAME_HE_TAGGING=1 \ -Wno-unused-function STUBS_SRC = stubs/esp_stubs.c diff --git a/firmware/esp32-csi-node/test/fuzz_csi_serialize.c b/firmware/esp32-csi-node/test/fuzz_csi_serialize.c index 67cf4523..1eb3af85 100644 --- a/firmware/esp32-csi-node/test/fuzz_csi_serialize.c +++ b/firmware/esp32-csi-node/test/fuzz_csi_serialize.c @@ -60,6 +60,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) uint8_t channel; int8_t noise_floor; uint8_t out_buf_scale; /* Controls output buffer size: 0-255. */ + /* ADR-110: fuzz the new HE-branch + legacy-branch input fields too so + * the byte 18/19 encoding code path is exercised. */ + uint8_t he_inputs[2] = {0}; /* cur_bb_format (4 bits) + second (4 bits) packed */ + uint8_t legacy_inputs = 0; /* sig_mode (2) + cwb (1) + stbc (1) packed */ fuzz_read(&cursor, &remaining, &test_case, 1); fuzz_read(&cursor, &remaining, &iq_len_raw, 2); @@ -67,6 +71,8 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) fuzz_read(&cursor, &remaining, &channel, 1); fuzz_read(&cursor, &remaining, &noise_floor, 1); fuzz_read(&cursor, &remaining, &out_buf_scale, 1); + fuzz_read(&cursor, &remaining, he_inputs, 2); + fuzz_read(&cursor, &remaining, &legacy_inputs, 1); /* --- Test case 0: Normal operation with fuzz-controlled values --- */ @@ -75,6 +81,15 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) info.rx_ctrl.rssi = rssi; info.rx_ctrl.channel = channel & 0x0F; /* 4-bit field */ info.rx_ctrl.noise_floor = noise_floor; + /* ADR-110: feed both branch families. Only the active branch (chosen + * at compile time by CONFIG_SOC_WIFI_HE_SUPPORT) will read its fields; + * the other set is set-but-not-read. Both must be assignable without + * triggering UBSAN bitfield-overflow. */ + info.rx_ctrl.cur_bb_format = he_inputs[0] & 0x0F; /* 0..15 valid input space */ + info.rx_ctrl.second = he_inputs[1] & 0x0F; + info.rx_ctrl.sig_mode = legacy_inputs & 0x03; + info.rx_ctrl.cwb = (legacy_inputs >> 2) & 0x01; + info.rx_ctrl.stbc = (legacy_inputs >> 3) & 0x01; /* Use remaining fuzz data as I/Q buffer content. */ uint16_t iq_len; diff --git a/firmware/esp32-csi-node/test/stubs/esp_stubs.h b/firmware/esp32-csi-node/test/stubs/esp_stubs.h index be96f689..3d849a89 100644 --- a/firmware/esp32-csi-node/test/stubs/esp_stubs.h +++ b/firmware/esp32-csi-node/test/stubs/esp_stubs.h @@ -62,14 +62,28 @@ static inline esp_err_t esp_timer_delete(esp_timer_handle_t h) { (void)h; return /* ---- esp_wifi_types.h ---- */ -/** Minimal rx_ctrl fields needed by csi_serialize_frame. */ +/** Minimal rx_ctrl fields needed by csi_serialize_frame. + * + * ADR-110: the HE-tagging path in csi_collector.c references either + * (CONFIG_SOC_WIFI_HE_SUPPORT branch) cur_bb_format, second + * (legacy / S3 branch) sig_mode, cwb, stbc + * + * Both sets are unconditionally declared here so a single stub builds + * for either branch — the Makefile picks which side via -D flags. */ typedef struct { - signed rssi : 8; - unsigned channel : 4; - unsigned noise_floor : 8; - unsigned rx_ant : 2; - /* Padding to fill out the struct so it compiles. */ - unsigned _pad : 10; + signed rssi : 8; + unsigned channel : 4; + unsigned noise_floor : 8; + unsigned rx_ant : 2; + /* ADR-110 HE-branch fields (CONFIG_SOC_WIFI_HE_SUPPORT path) */ + unsigned cur_bb_format : 4; /**< 0=11b 1=11g/a 2=HT 3=VHT 4=HE-SU 5=HE-MU 6=HE-ER-SU 7=HE-TB */ + unsigned second : 4; /**< secondary 40 MHz channel offset */ + /* ADR-110 legacy-branch fields (pre-HE chips) */ + unsigned sig_mode : 2; /**< 0=non-HT 1=HT 3=VHT */ + unsigned cwb : 1; /**< 0=20 MHz 1=40 MHz */ + unsigned stbc : 1; /**< STBC flag */ + /* Padding to keep alignment predictable. */ + unsigned _pad : 18; } wifi_pkt_rx_ctrl_t; /** Minimal wifi_csi_info_t needed by csi_serialize_frame. */