В трекере задач FFmpeg появилось сообщение об уязвимости в демультиплексоре Sony PS2 VPK. Разработчик Дарио Клавихо (Darío Clavijo) нашёл баг с помощью собственного фаззера и подробно задокументировал цепочку срабатывания, вплоть до GDB-трассировки и готового патча.
Hello.
This is a bug found with our fuzzer: https://github.com/daedalus/fuzzer/
File:
libavformat/vpk.c:89
Severity: Medium — crafted 21-byte input crashes any FFmpeg-based application that opens a malicious.vpkfile or stream
Root cause:vpk_read_packetdividesvpk->last_block_sizebypar->ch_layout.nb_channelswithout checking whethernb_channelsis zero. A malformed VPK header can setnb_channels = 0, causingSIGFPEon the division.
Описание
Демультиплексор Sony PS2 VPK (libavformat/vpk.c) читает аудиоблоки из собственного контейнерного формата. В функции vpk_read_packet последний блок потока обрабатывается особым образом:
if (vpk->current_block == vpk->block_count) {
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;
...
}
И size, и skip делятся на par->ch_layout.nb_channels. Когда nb_channels равно нулю, процессор генерирует SIGFPE — исключение целочисленного деления на ноль.
Цепочка срабатывания
- Проверка формата (
vpk_probe) распознаёт big-endian сигнатуруVPKи передаёт входные данные демультиплексору VPK. vpk_read_headerразбирает 24-байтовый заголовок. Фаззинговый вход устанавливаетnb_channels = 0в байтах заголовка0x0e–0x11. Функция действительно проверяет, чтоnb_channels > 0, но в кастомном AVIO-пути фаззера данные пробы/заголовка и данные, читаемые позднее при чтении пакета, могут разойтись: к моменту выполненияvpk_read_packetзначениеpar->ch_layout.nb_channelsвозвращается к0из исходного фаззингового потока, тогда какvpk->last_block_sizeиvpk->block_countбыли вычислены по данным пробы с корректным числом каналов. Таким образом деление выполняется с реальным, но нулевым делителем.vpk_read_packetдоходит до ветки обработки последнего блока и делит на ноль какsize, так иskip.
Входные данные, вызывающие сбой
Hex-дамп 21-байтового входа, вызывающего краш (crash_1787378545_34bc062c_sig_signal8.bin):
00000000 20 4b 50 56 56 50 00 f8 04 00 3b 03 61 39 56 32 | KPVVP....;.a9V2|
00000010 36 36 30 38 50 |6608P|
- Байты 0–3:
20 4b 50 56— ASCII" KPV", что представляет собой big-endian сигнатуру VPKVPK, побайтово перевёрнутую относительно границы слова - Байты 0x0e–0x11:
00 00 00 00—nb_channels = 0, триггер сбоя
Трассировка GDB
Program received signal SIGFPE, Arithmetic exception.
0x00005555557a9877 in vpk_read_packet (s=0x555557fed700, pkt=0x555557fed300)
at libavformat/vpk.c:89
89 unsigned size = vpk->last_block_size / par->ch_layout.nb_channels;
#0 vpk_read_packet
#1 ff_read_packet
#2 read_frame_internal
#3 av_read_frame
#4 fuzz_ffmpeg
#5 main
Метаданные сбоя
| Параметр | Значение |
|---|---|
| Сигнал | SIGFPE (код возврата −8) |
| Адрес сбоя / RIP | 0x7ffff48a66d7 (сама инструкция) |
| RSP | 0x7fffffffcce0 |
| Число запусков до обнаружения | 495 211 |
| Размер корпуса на момент обнаружения | 13 188 записей |
| Затраченное время | 10 ч 43 мин |
| Родительский seed | 36e65f4009ba0cab |
| SHA256 цели | d704c2a52b21bd33 |
Оценка эксплуатируемости
| Фактор | Оценка |
|---|---|
| Детерминированность сбоя | Детерминирован — 21 байт, единственный путь в коде демультиплексора |
| Глубина срабатывания | Небольшая — avformat_open_input автоматически определяет формат по сигнатуре |
| Предусловия | Отсутствуют — вход самодостаточен, без сети, без работы с кучей |
| Тип сигнала | SIGFPE (целочисленное деление на ноль), а не повреждение памяти |
| Безопасность памяти | Нет выхода за границы буфера при чтении/записи, use-after-free, разыменования NULL |
| Область применения | Любое приложение, вызывающее avformat_open_input + av_read_frame на недоверенных данных |
| Серьёзность | Средняя — надёжный отказ в обслуживании, но не прямой примитив выполнения кода |
Деление на ноль — это примитив отказа в обслуживании. Рядом с проблемной инструкцией нет контролируемой записи или произвольного чтения. Такой вход можно встроить в файл .vpk или в контейнер, который представляется как VPK, чтобы вызвать сбой в любом приложении, использующем FFmpeg.
Предлагаемое исправление
В начале vpk_read_packet предлагается добавить проверку, которая корректно отклоняет потоки с нулевым числом каналов:
static int vpk_read_packet(AVFormatContext *s, AVPacket *pkt)
{
AVCodecParameters *par = s->streams[0]->codecpar;
VPKDemuxContext *vpk = s->priv_data;
int ret, i;
if (par->ch_layout.nb_channels == 0)
return AVERROR_INVALIDDATA;
vpk->current_block++;
...
}
Это согласуется с уже существующей проверкой в vpk_read_header (if (st->codecpar->ch_layout.nb_channels <= 0) return AVERROR_INVALIDDATA;) и возвращает корректную ошибку вместо SIGFPE.
Регрессионный тест
/* Trigger: 21-byte VPK stream with nb_channels=0 — SIGFPE in vpk.c:89 */
static const unsigned char vpk_crash[] = {
0x20, 0x4b, 0x50, 0x56, 0x56, 0x50, 0x00, 0xf8,
0x04, 0x00, 0x3b, 0x03, 0x61, 0x39, 0x56, 0x32,
0x36, 0x36, 0x30, 0x38, 0x50
};
/* Expect: av_read_frame returns -22 (AVERROR_INVALIDDATA), does not crash */
Участник проекта Джун Чжао (Jun Zhao) отметил, что проблема, судя по всему, совпадает с той, что уже обсуждалась в рассылке ffmpeg-devel в ноябре 2024 года. Позднее к issue был привязан pull request с исправлением — fix/24290-vpk-div0 (#24297), закрывающий уязвимость.