1. Тема дня
Проверяем parser не только ручными тестами, а миллионами случайных и полуслучайных входов:
случайные байты
битые длины
обрезанные заголовки
неполный COBS
потерянный delimiter
тысячи TLV нулевой длины
произвольный chunking UART-потокаИнструменты:
libFuzzer
AddressSanitizer
UndefinedBehaviorSanitizer
seed corpus
fuzz invariants
crash minimization
regression corpusГлавная мысль: parser должен быть независим от MCU, RTOS и HAL, чтобы запускаться на host под sanitizers миллионы раз в секунду. На плате потом проверяются timing, DMA, ISR и интеграция.
2. Зачем это нужно
Для HIL-протокола обычный тест проверит PING, BAD_CRC и BAD_VERSION. Fuzzer найдет странные комбинации:
header_length = 63
payload_length = UINT32_MAX - 40
TLV length = 65535
фактически кадр 17 байт
следующий кадр приклеился к предыдущемуДля EC25 AT parser fuzzing помогает проверить:
длинный +QMTRECV
embedded NUL
CR без LF
URC внутри ответа
late OK после timeout
payload больше maximum3. Теория
Unit test vs fuzzing
Unit-тест проверяет известный вход. Fuzzer генерирует новые входы, отслеживая coverage, и сохраняет те, которые открывают новые ветви. Ошибкой считается не BAD_FORMAT, а:
out-of-bounds read/write
use-after-free
integer overflow
undefined behavior
assert invariant failure
timeout/hangSanitizers
ASan ловит:
heap/stack/global overflow
use-after-free
double free
invalid freeUBSan ловит:
signed overflow
bad shift
misaligned access
null dereferenceСборка fuzz target:
clang -std=c17 -O1 -g -fno-omit-frame-pointer \
-fno-sanitize-recover=undefined \
-fsanitize=fuzzer,address,undefined \
protocol/*.c tests/fuzz/protocol_stream_fuzz.c \
-Iprotocol -o build/protocol_stream_fuzzChunking invariance
Один и тот же поток должен давать одинаковый результат независимо от границ feed():
feed(all bytes once) == feed(bytes by random chunks)Это критично для UART/TCP. Пример invariant:
fuzz_result_t one = parse_as_one_chunk(data, size);
fuzz_result_t chunks = parse_as_chunks(data, size);
if (one.frame_count != chunks.frame_count || one.digest != chunks.digest) {
__builtin_trap();
}Разделяй fuzz targets
CRC мешает fuzzer пройти глубже, поэтому нужны отдельные targets:
stream fuzz: COBS + CRC + resync
frame fuzz: decoded frame parser
TLV fuzz: payload parser directly
message fuzz: конкретная schema
FSM fuzz: последовательность событий
AT fuzz: stream/line/transaction layers4. Типичные ошибки
- Fuzz-тестируется вся прошивка сразу.
- Sanitizers не включены.
- Глобальный parser не сбрасывается между inputs.
- Malformed input считается ошибкой теста.
- Печатается каждый input.
- CRC закрывает все глубокие ветви parser.
- Input size не ограничен.
- Проверяется только отсутствие crash, но не invariants.
- Crash input не сохраняется.
- Исправленный crash не попадает в regression corpus.
5. Практическое задание на 30-60 минут
Создай структуру:
tests/fuzz/
protocol_stream_fuzz.c
corpus/protocol_stream/
artifacts/
regressions/Добавь fuzz target для proto_rx_feed():
input
→ feed целиком
→ feed chunks
→ сравнить digest принятых кадровСоздай seeds production encoder'ом:
валидный PING
два PING подряд
кадр с optional TLVЗапусти:
./build-fuzz/protocol_stream_fuzz \
tests/fuzz/corpus/protocol_stream \
-artifact_prefix=tests/fuzz/artifacts/ \
-max_len=4096 \
-timeout=2 \
-max_total_time=60 \
-print_final_stats=1Проверь инфраструктуру: временно внеси off-by-one в буфер, дождись ASan report, сохрани crash input как regression и верни исправление.
6. Что почитать или попробовать дальше
- LLVM libFuzzer: corpus, dictionaries, minimization, parallel jobs.
- Clang ASan и UBSan.
- AFL++ для долгих campaigns.
- ESP-IDF host tests и Unity target tests.
Критерии: Отделяйте ожидаемый отказ от нарушения безопасности или согласованности.
Задание
Спроектируйте проверку chunking invariance для proto_rx_feed() и объясните действия с минимизированным входом, обнаружившим дефект.
Критерии самопроверки: Нужны одинаковые байты, чистое состояние parser, сравнение эквивалентных результатов и сохранённый regression input; обычный отказ некорректному входу не считать crash.
Показать ответ автора
Сбросить независимые экземпляры parser, передать один байтовый поток целиком и chunks, сравнить digest принятых кадров. Сохранить минимизированный вход в regression corpus, исправить дефект и повторить тест с sanitizers. Не переносить состояние между inputs.