1. Тема дня

Проверяем parser не только ручными тестами, а миллионами случайных и полуслучайных входов:

text
случайные байты
битые длины
обрезанные заголовки
неполный COBS
потерянный delimiter
тысячи TLV нулевой длины
произвольный chunking UART-потока

Инструменты:

text
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 найдет странные комбинации:

text
header_length = 63
payload_length = UINT32_MAX - 40
TLV length = 65535
фактически кадр 17 байт
следующий кадр приклеился к предыдущему

Для EC25 AT parser fuzzing помогает проверить:

text
длинный +QMTRECV
embedded NUL
CR без LF
URC внутри ответа
late OK после timeout
payload больше maximum

3. Теория

Unit test vs fuzzing

Unit-тест проверяет известный вход. Fuzzer генерирует новые входы, отслеживая coverage, и сохраняет те, которые открывают новые ветви. Ошибкой считается не BAD_FORMAT, а:

text
out-of-bounds read/write
use-after-free
integer overflow
undefined behavior
assert invariant failure
timeout/hang

Sanitizers

ASan ловит:

text
heap/stack/global overflow
use-after-free
double free
invalid free

UBSan ловит:

text
signed overflow
bad shift
misaligned access
null dereference

Сборка fuzz target:

text
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_fuzz

Chunking invariance

Один и тот же поток должен давать одинаковый результат независимо от границ feed():

text
feed(all bytes once) == feed(bytes by random chunks)

Это критично для UART/TCP. Пример invariant:

c
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:

text
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 layers

4. Типичные ошибки

  • Fuzz-тестируется вся прошивка сразу.
  • Sanitizers не включены.
  • Глобальный parser не сбрасывается между inputs.
  • Malformed input считается ошибкой теста.
  • Печатается каждый input.
  • CRC закрывает все глубокие ветви parser.
  • Input size не ограничен.
  • Проверяется только отсутствие crash, но не invariants.
  • Crash input не сохраняется.
  • Исправленный crash не попадает в regression corpus.

5. Практическое задание на 30-60 минут

Создай структуру:

text
tests/fuzz/
  protocol_stream_fuzz.c
  corpus/protocol_stream/
  artifacts/
  regressions/

Добавь fuzz target для proto_rx_feed():

text
input
→ feed целиком
→ feed chunks
→ сравнить digest принятых кадров

Создай seeds production encoder'ом:

text
валидный PING
два PING подряд
кадр с optional TLV

Запусти:

text
./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.
Fuzzer передаёт некорректные байты, parser возвращает BAD_FORMAT без crash. Является ли это само по себе провалом fuzz-теста?

Критерии: Отделяйте ожидаемый отказ от нарушения безопасности или согласованности.

Задание

Спроектируйте проверку chunking invariance для proto_rx_feed() и объясните действия с минимизированным входом, обнаружившим дефект.

Критерии самопроверки: Нужны одинаковые байты, чистое состояние parser, сравнение эквивалентных результатов и сохранённый regression input; обычный отказ некорректному входу не считать crash.

Показать ответ автора

Сбросить независимые экземпляры parser, передать один байтовый поток целиком и chunks, сравнить digest принятых кадров. Сохранить минимизированный вход в regression corpus, исправить дефект и повторить тест с sanitizers. Не переносить состояние между inputs.