Как фуззер нашёл баг, который укладывал ffmpeg
Читаю свежий 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 нужно возвращать обычную ошибку разбора.
Ещё один важный шаг — добавить регрессионный тест, чтобы баг не вернулся.
Выводы
- Фаззинг окупается: он быстро находит критичные ошибки в зрелом коде.
- Проверка на ноль обязательна, если значение пришло извне.
- Даже нишевый формат может стать системной проблемой, если его читает массовая библиотека.
Главный вывод простой: в больших проектах баги живут в неожиданных местах, а фаззер — это не роскошь, а необходимый инструмент.
Ссылки
- Issue #24290 на FFmpeg Forgejo — первичный источник
- Daedalus Fuzzer на GitHub — инструмент, нашедший баг
- libavformat/vpk.c — уязвимый код
Дмитрий Полухин — продуктовый дизайнер. Пишу про разработку, AI и дизайн интерфейсов. Обо мне, контакты и профили.