1. Тема дня

Unit test проверяет конкретный сценарий. Property-based test- ing проверяет законы системы на множестве автоматически сгенерированных сценариев.

text
random / coverage-guided bytes
    -> semantic decoder
    -> MODEM / PHASE / CAN events
    -> production FSM
    -> invariants
    -> PASS / FAIL

Главная мысль: fuzzer не обязан знать правильный output для каждого сценария; ему достаточно знать свойства, которые никогда не должны нарушаться.

2. Зачем это нужно

Для EC25 fuzzer генерирует последовательности AT_OK, AT_ERROR, CEREG, TIMEOUT, POWER, MQTT_LOST и ищет stale-event bugs: поздний OK, старый timeout, timeout после generation change. Для phase FSM генерирует RED/GREEN/YELLOW edges, debounce timers, stale input, ADC fault и проверяет, что RED && GREEN не остаётся quality VALID. Для DMA/pool генерирует DMA_COMPLETE, CPU_ACQUIRE, CPU_RELEASE и ловит double release, stale handle, ownership corruption.

3. Теория

Типы properties

Safety:

text
RED && GREEN && quality == NORMAL never happens

Bounds:

text
retry_count <= MAX_RETRY
queue_count <= capacity

Monotonic:

text
transaction_id never decreases
config_generation never decreases

Consistency:

text
mqtt == CONNECTED -> tcp == CONNECTED
state == ONLINE -> registration == REGISTERED

Idempotency:

text
duplicate CEREG=1 does not start PDP twice

Stale-event:

text
AT_OK tx != current_tx does not finish current transaction

Semantic decoder

FSM fuzzer лучше кормить не raw bytes, а нормализованными событиями. Например 4 bytes per event:

text
byte 0: event type
byte 1: argument
byte 2: transaction relation
byte 3: generation relation

Важно генерировать интересные значения: current, current-1, current+1, 0, UINT32_MAX.

Разделяй parser fuzz и FSM fuzz

Parser fuzz получает arbitrary bytes. FSM fuzz получает semantic events.

Shrinking

Найденный crash из 87 событий нужно минимизировать до короткой последовательности. LibFuzzer умеет -minimize_crash=1, а semantic shrinker дополнительно может удалять события и упрощать аргументы.

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

  1. Fuzzing всей прошивки вместо отдельного core.
  2. Нет invariants.
  3. Неверная property даёт ложные баги.
  4. Генерировать только happy-path events.
  5. Генерировать полностью случайные events и не доходить до глубоких состояний.
  6. Не использовать boundary values.
  7. Не генерировать stale transaction/generation IDs.
  8. Использовать real clock.
  9. Оставлять global state между LLVMFuzzerTestOneInput() calls.
  10. Огромный max_len с самого начала.
  11. Не ограничивать action count/internal transitions.
  12. Не использовать ASan/UBSan.
  13. Не минимизировать reproducer.
  14. После fix удалить crash input вместо regression corpus.

5. Практическое задание

Создай tests/fuzz/modem/modem_fuzz.c. События:

c
typedef enum {
    FUZZ_EV_POWER = 0,
    FUZZ_EV_AT_OK,
    FUZZ_EV_AT_ERROR,
    FUZZ_EV_CEREG,
    FUZZ_EV_TIMER,
    FUZZ_EV_MQTT_LOST,
    FUZZ_EV_COUNT,
} fuzz_event_type_t;

Сделай transaction generator:

c
static uint32_t fuzz_transaction_id(const modem_core_t *modem, uint8_t selector)
{
    switch (selector % 5U) {
    case 0: return modem->transaction_id;
    case 1: return modem->transaction_id - 1U;
    case 2: return modem->transaction_id + 1U;
    case 3: return 0U;
    default: return UINT32_MAX;
    }
}

Проверь invariants:

text
retry_count <= retry_limit
MODEM_OFF -> mqtt != CONNECTED
mqtt == CONNECTED -> modem == ONLINE
action_count <= ACTION_CAPACITY
stale AT_OK не завершает текущую transaction

Сборка:

text
clang -std=c17 -O1 -g -fno-omit-frame-pointer \
  -fsanitize=fuzzer,address,undefined \
  modem_core.c tests/fuzz/modem/modem_fuzz.c \
  -Icomponents/modem_core/include \
  -o build/modem_fuzz

Запуск:

text
build/modem_fuzz tests/fuzz/modem/corpus -max_len=256 -max_total_time=60

6. Что попробовать дальше

  • Конвертировать .evlog из урока 54 в seed corpus.
  • Добавить fuzz target для phase_fsm.
  • Сделать differential fuzzing старой и новой реализации FSM.
  • Генерировать HIL-сценарии из минимальных fuzz cases.
Какой input непосредственно проверяет, что stale AT_OK не завершает текущую transaction?

Задание

Сбой требует 87 semantic events. Как превратить его в regression input? Почему для конечного счётчика свойству «transaction_id никогда не уменьшается» нужно условие epoch/wrap?

Критерии самопроверки: Сохраните минимальный regression input и явно оговорите монотонность при wrap счётчика.

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

Минимизируйте последовательность с сохранением сбоя, затем оставьте reproducer в regression corpus и проверяйте те же invariants. Конечный счётчик может перейти с UINT32_MAX на 0, поэтому безусловная числовая монотонность даст ложный bug; задайте epoch или ограниченный интервал без wrap, отдельно проверяя wrap handling.