Как фуззер нашёл баг, который укладывал ffmpeg

28.08.2026 · 5 мин

Читаю свежий issue на FFmpeg Forgejo — и голова слегка идёт кругом. Один 21-байтовый файл. Целочисленное деление на ноль. И всё — любое приложение, собранное с FFmpeg, падает с SIGFPE. Без RCE, без memory corruption, просто роняет процесс.

Коротко о баге

В VPK demuxer — компоненте FFmpeg для разбора формата Sony PS2 — нашёлся классический division by zero. Проблема в функции vpk_read_packet (файл libavformat/vpk.c, строка 89):

unsigned size = vpk->last_block_size / par->ch_layout.nb_channels;
unsigned skip = (par->block_align - vpk->last_block_size) / par->ch_layout.nb_channels;

Код делит last_block_size на nb_channels без проверки на ноль. Если nb_channels равен нулю, процесс получает сигнал SIGFPE и падает.

Цепочка триггера бага
───────────────────
FUZZ INPUT (21 байт)
        │
        ▼
┌───────────────────┐
│  vpk_probe()      │
│  (парсит 24 байта)│
└────────┬──────────┘
         │
         ▼
┌───────────────────┐
│ vpk_read_header() │
│  валидация данных │
└────────┬──────────┘
         │
         ▼
┌───────────────────┐
│ vpk_read_packet() │
│  ▶ SIGFPE         │
└───────────────────┘
Цепочка от входных данных до падения процесса

Как это произошло

При фаззинге данные в AVIO-потоке могут рассинхронизироваться. Probe-буфер парсит одно, а данные для чтения пакетов — другое. В итоге nb_channels может обнулиться между проверкой в header и фактическим чтением.

Чем это грозит

Severity: Medium. Это не RCE и не corruption памяти, но это стабильный DoS: любое приложение, открывающее злонамеренный .vpk-файл, падает.

На практике это серьёзно, потому что FFmpeg используют медиаплееры, браузеры, транскодеры и серверы обработки медиа.

Риск для приложения
───────────────────
┌─────────────────────┐
│  .vpk файл          │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│  FFmpeg-парсер      │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│  SIGFPE и падение   │
└─────────────────────┘
Маленький файл способен уронить процесс, который его открыл

Как нашли баг

Автор использовал фуззер из проекта Daedalus. Автоматическая генерация мутантных входных данных нашла проблему примерно за 10 часов 43 минуты и 495 211 запусков. Это хороший пример того, как фаззинг находит дефекты, которые трудно поймать вручную.

Что стоит исправить

Решение простое: добавить проверку перед делением.

if (par->ch_layout.nb_channels == 0)
return AVERROR_INVALIDDATA;

Это должно быть согласовано с уже существующей валидацией в vpk_read_header. Вместо SIGFPE нужно возвращать обычную ошибку разбора.

Ещё один важный шаг — добавить регрессионный тест, чтобы баг не вернулся.

Выводы

Главный вывод простой: в больших проектах баги живут в неожиданных местах, а фаззер — это не роскошь, а необходимый инструмент.

Ссылки

Дмитрий Полухин — продуктовый дизайнер. Пишу про разработку, AI и дизайн интерфейсов. Обо мне, контакты и профили.