В $dayjob я писал о том, как мы исправили ряд сбоев и зависаний при подаче поврежденных данных в различные кодеки путем фаззинга FFmpeg с использованием пары процессоров. С тех пор мы значительно увеличили ресурсы ЦП для наших заданий фаззинга, чтобы фаззить как FFmpeg, так и Upipe, и исправили больше проблем в процессе. Большая часть фаззинга была сделана с помощью American Fuzzy Lop (AFL).

Метод фаззинга имел ряд проблем:

  • Требуется повторное выполнение исполняемого файла ffmpeg, дорогостоящее использование системного вызова fork. Это уменьшило общую скорость фазза.
  • Зависимость от ffmpeg.c, сложного инструмента из 4000+ строк, который мог скрывать проблемы, замеченные пользователями API.
  • Возможность построить подходящий тестовый корпус. С чем-то вроде H.264 легко создавать образцы с использованием x264 со многими функциями кодирования, но мы не можем использовать все, особенно когда остаемся в пределах ограничения файла AFL в 1 МБ. Например, для правильного использования нарезанных потоков необходимо создать файл, совместимый с нарезанными потоками.

Итак, с некоторыми дополнениями от Андреаса Кадхалпуна я изменил стандартный пример декодирования, чтобы использовать постоянный режим AFL. Этот режим использует LLVM/clang, чтобы разрешить несколько запусков фаззинга во время одного выполнения, что значительно ускоряет работу:

в то время как (__AFL_LOOP (1000)) {

/* Чтение входных данных. */
/* Вызов кода библиотеки для фаззинга. */
/* Сбросить состояние. */

}

/* Выход в обычном режиме */

Эта реализация была тем случаем, когда мы съели нашу экспериментальную пищу и протестировали API напрямую, основываясь на работе, проделанной моей предыдущей студенткой Outreachy Людмилой Глинских. Однако в основном он просидел на Гитхабе 6 месяцев из-за нехватки времени для реализации проекта. На недавнем собрании ассоциации VideoLAN в Вене мне посчастливилось встретить Павла Голински, который написал несколько небольших дополнений к fffuzz в рамках своего приложения Google Summer of Code. Это напомнило мне, что я не документировал это и не применял его патчи :)

fffuzz прост в использовании, буквально

./ffuzz файл.h264 /dev/null

Запустив его в AFL (не забывая использовать clang/LLVM и устанавливая afl-clang-fast), вы получите очень приятный результат:

Этот подход оказался успешным, но также имеет существенный недостаток:

Он не может отслеживать изменения в кодеках, поэтому мы не знаем, приводит ли конкретная фиксация к сбою фаззинга. В наборе регрессионных тестов FATE мы можем увидеть, нарушают ли коммиты рабочиеобразцы, но намного сложнее увидеть, возникают ли другие сбои. В идеале мы хотели бы видеть информацию, извлекаемую из каждого коммита (например, из сообщения коммита и измененных файлов), а также фаззинг подходящего корпуса. Тогда это будет означать, что изменения в H.264 вызовут фаззинг корпуса H.264 вместо JPEG, который не будет иметь никакого отношения. Это то, что мы хотели бы сделать внутри $dayjob или с помощью таких программ, как GSoC и Outreachy. (Если вы заинтересованы в том, чтобы вас наняли напрямую или через схему с открытым исходным кодом для работы над этим, сообщите мне об этом)

Существуют также проблемы с такими кодеками, как FFv1 и телетекст, которые не имеют необработанного формата (т. е. должны быть мультиплексированы в контейнер, такой как AVI или TS), потому что процесс фаззинга завершается раньше в результате обнаружения ошибок в контейнере и кодек никогда бы не стал фаззить. Ни один из этих фаззингов не пытался фаззить демультиплексирование, это отдельная проблема. Тем не менее, фреймеры Upipe, которые представляют собой компоненты, предназначенные для создания полных кадров из разрозненных данных, были изменены, а проблемы исправлены.

Так что теперь фаззить FFmpeg легко, и я надеюсь, что с помощью fffuzz будет найдено много ошибок. Он доступен здесь: http://github.com/openbroadcastsystems/fffuzz