С наступлением 2019 года хорошо вспомнить прошлое и подумать о будущем. Оглянемся на 30 лет назад и поразмышляем над первыми научными статьями по фаззингу: «Эмпирическое исследование надёжности утилит UNIX» и последующей работой 1995 года «Пересмотр фаззинга» того же автора Бартона Миллера.
В этой статье попытаемся найти баги в современных версиях Ubuntu Linux, используя те же самые инструменты, что и в оригинальных работах по фаззингу. Вы должны прочитать оригинальные документы не только для контекста, но и для понимания. Они оказались весьма пророческими в отношении уязвимостей и эксплоитов на десятилетия вперёд. Внимательные читатели могут заметить дату публикации оригинальной статьи: 1990 год. Ещё более внимательные заметят копирайт в комментариях исходников: 1989.
Для тех, кто не читал документы (хотя это реально следует сделать), в данном разделе краткое резюме и некоторые избранные цитаты.
Программа фаззинга генерирует случайные потоки символов, с возможностью генерировать только печатные или непечатные символы. Она использует некое начальное значение (seed), обеспечивая воспроизводимость результатов, чего современным фаззерам часто не хватает. Набор скриптов запускается на тестируемых программах и проверяет наличие основных дампов. Зависания выявляются вручную. Адаптеры выдают случайные входные данные для интерактивных программ (статья 1990 года), сетевых сервисов (1995) и графических X-приложений (1995).
В статье 1990 года тестируются четыре процессорные архитектуры (i386, CVAX, Sparc, 68020) и пять операционных систем (4.3 BSD, SunOS, AIX, Xenix, Dynix). В статье 1995 года аналогичный выбор платформ. В первой статье удаётся добиться сбоя 25-33% утилит, в зависимости от платформы. В последующей статье эти цифры варьируются от 9% до 33%, причем у GNU (на SunOS) и Linux наименьший процент сбоев.
В статье 1990 года делается вывод, что 1) программисты не проверяют границы массива или коды ошибок, 2) макросы затрудняют чтение и отладку кода и 3) язык C весьма небезопасен. Особого упоминания удостоены экстремально небезопасная функция
В статье 1995 года упоминается ПО с открытым исходным кодом и обсуждается, почему в нём меньше ошибок. Цитата:
Только через 15-20 лет техника фаззинга станет стандартной практикой у крупных вендоров.
Ещё мне кажется, что это заявление 1990 году предвидит будущие события:
К счастью, спустя 30 лет д-р Бартон по-прежнему предоставляет полный исходный код, скрипты и данные для воспроизведения своих результатов: похвальный пример, которому должны следовать другие исследователи. Скрипты без проблем работают, а в инструмент фаззинга потребовалось внести лишь незначительные изменения для компиляции и запуска.
Для этих тестов мы использовали скрипты и входные данные из репозитория fuzz-1995-basic, потому что там самый свежий список тестируемых приложений. Согласно README, здесь те же самые случайные входные, что и в оригинальном исследовании. Результаты ниже для современного Linux получены точно на том же коде фаззинга и входных данных, что и в оригинальных статьях. Изменился только список утилит для тестирования.
Очевидно, что за последние 30 лет произошли некоторые изменения в программных пакетах Linux, хотя довольно много проверенных утилит по-прежнему ведут свою родословную на протяжении десятилетий. Где возможно, мы взяли современные версии тех же программ из статьи 1995 года. Некоторые программы больше не доступны, их мы заменили. Обоснование всех замен:
Техника фаззинга 1989 года по-прежнему находит ошибки в 2018 году. Но есть определённый прогресс.
Чтобы измерить прогресс, нужна некая база. К счастью, для утилит Linux такая база существует. Хотя во времена оригинальной статьи 1990 года ОС Linux ещё не существовала, но повторный тест 1995 года запустил тот же код фаззинга на утилитах из дистрибутива Slackware 2.1.0 от 1995 года. Соответствующие результаты приводятся в таблице 3 статьи 1995 года (стр. 7-9). По сравнению с коммерческими конкурентами GNU/Linux выглядит очень хорошо:
Итак, сравним утилиты Linux 1995 и 2018 года инструментами для фаззинга 1989 года:
Удивительно, но количество сбоев и зависаний Linux всё ещё больше нуля, даже на последней версии Ubuntu. Так,
Я смог вручную выяснить корневую причину некоторых багов. Одни результаты, такие как ошибка в glibc, стали неожиданными, в то время как другие, такие как sprintf с буфером фиксированного размера, были предсказуемы.
Ошибка в ul — это на самом деле баг в glibc. В частности, о нём сообщалось здесь и здесь (другой человек нашёл её в
Программа
Похоже, что этот код сохранился с момента создания
Отличный пример классической взаимоблокировки. Программа
На первый взгляд похоже, что присутствует условие бесконечного цикла. Зависание вроде бы в
Для полноты картины хочу упомянуть сбой
Аварийное завершение всегда плохо, но здесь хотя бы программа может заявить об ошибке, обваливаясь рано и громко.
В последние 30 лет фаззинг оставался простым и надёжным способом поиска багов. Хотя в этой области идут активные исследования, даже фаззер 30-летней давности успешно находит ошибки в современных утилитах Linux.
Автор оригинальных статей предсказал проблемы безопасности, которые вызовет С в течение ближайших десятилетий. Он убедительно доказывает, что на C слишком легко написать небезопасный код и по возможности его следует избегать. В частности, статьи демонстрируют: баги проявляются даже при самом простом фазинге, и такое тестирование следует включить в стандартную практику разработки программного обеспечения. К сожалению, этому совету не следовали десятилетиями.
Надеюсь, вам понравилась эта 30-летняя ретроспектива. Ждите следующей статьи «Фаззинг в 2000 году», где мы исследуем, насколько устойчивы приложения Windows 10 по сравнению с их эквивалентами Windows NT/2000 при проверке фаззером. Думаю, ответ предсказуем.
В этой статье попытаемся найти баги в современных версиях Ubuntu Linux, используя те же самые инструменты, что и в оригинальных работах по фаззингу. Вы должны прочитать оригинальные документы не только для контекста, но и для понимания. Они оказались весьма пророческими в отношении уязвимостей и эксплоитов на десятилетия вперёд. Внимательные читатели могут заметить дату публикации оригинальной статьи: 1990 год. Ещё более внимательные заметят копирайт в комментариях исходников: 1989.
Краткий обзор
Для тех, кто не читал документы (хотя это реально следует сделать), в данном разделе краткое резюме и некоторые избранные цитаты.
Программа фаззинга генерирует случайные потоки символов, с возможностью генерировать только печатные или непечатные символы. Она использует некое начальное значение (seed), обеспечивая воспроизводимость результатов, чего современным фаззерам часто не хватает. Набор скриптов запускается на тестируемых программах и проверяет наличие основных дампов. Зависания выявляются вручную. Адаптеры выдают случайные входные данные для интерактивных программ (статья 1990 года), сетевых сервисов (1995) и графических X-приложений (1995).
В статье 1990 года тестируются четыре процессорные архитектуры (i386, CVAX, Sparc, 68020) и пять операционных систем (4.3 BSD, SunOS, AIX, Xenix, Dynix). В статье 1995 года аналогичный выбор платформ. В первой статье удаётся добиться сбоя 25-33% утилит, в зависимости от платформы. В последующей статье эти цифры варьируются от 9% до 33%, причем у GNU (на SunOS) и Linux наименьший процент сбоев.
В статье 1990 года делается вывод, что 1) программисты не проверяют границы массива или коды ошибок, 2) макросы затрудняют чтение и отладку кода и 3) язык C весьма небезопасен. Особого упоминания удостоены экстремально небезопасная функция
gets
и система типов C. В ходе тестирования авторы нашли уязвимости Format String за годы до их массовой эксплуатации. Статья завершается опросом пользователей о том, как часто они исправляют баги или сообщают о них. Оказалось, что сообщать о багах трудно и не было особого интереса в их исправлении.В статье 1995 года упоминается ПО с открытым исходным кодом и обсуждается, почему в нём меньше ошибок. Цитата:
Когда мы исследовали причины сбоев, то проявилось тревожное явление: многие из багов (около 40%), о которых сообщалось в 1990 году, по-прежнему присутствуют в своей точной форме в 1995 году. …
Используемые здесь методы просты и в основном автоматизированы. Трудно понять, почему разработчики не используют этот лёгкий и бесплатный источник повышения надёжности.
Только через 15-20 лет техника фаззинга станет стандартной практикой у крупных вендоров.
Ещё мне кажется, что это заявление 1990 году предвидит будущие события:
Часто лаконичность стиля программирования С доводится до крайности, форма превалирует над правильной функцией. Возможность переполнения входного буфера — потенциальная дыра в безопасности, как показал недавний интернет-червь.
Методология тестирования
К счастью, спустя 30 лет д-р Бартон по-прежнему предоставляет полный исходный код, скрипты и данные для воспроизведения своих результатов: похвальный пример, которому должны следовать другие исследователи. Скрипты без проблем работают, а в инструмент фаззинга потребовалось внести лишь незначительные изменения для компиляции и запуска.
Для этих тестов мы использовали скрипты и входные данные из репозитория fuzz-1995-basic, потому что там самый свежий список тестируемых приложений. Согласно README, здесь те же самые случайные входные, что и в оригинальном исследовании. Результаты ниже для современного Linux получены точно на том же коде фаззинга и входных данных, что и в оригинальных статьях. Изменился только список утилит для тестирования.
Изменения в утилитах за 30 лет
Очевидно, что за последние 30 лет произошли некоторые изменения в программных пакетах Linux, хотя довольно много проверенных утилит по-прежнему ведут свою родословную на протяжении десятилетий. Где возможно, мы взяли современные версии тех же программ из статьи 1995 года. Некоторые программы больше не доступны, их мы заменили. Обоснование всех замен:
cfe
?cc1
: Эквивалент препроцессору C из статьи 1995 года.dbx
?gdb
: Эквивалент дебаггера 1995 года.ditroff
?groff
:ditroff
больше не доступен.dtbl
?gtbl
: Эквивалент GNU Troff старой утилитыdtbl
.lisp
?clisp
: Стандартная реализация lisp.more
?less
: Less is more!prolog
?swipl
: Есть два варианта prolog: SWI Prolog и GNU Prolog. SWI Prolog предпочтительнее, потому что это более старая и полная реализация.awk
?gawk
: GNU версияawk
.cc
?gcc
: Стандартный компилятор C.compress
?gzip
: GZip это идейный наследник старой Unix-утилитыcompress
.lint
?splint
: Переписанныйlint
под лицензией GPL./bin/mail
?/usr/bin/mail
: Эквивалентная утилита по другому пути.f77
?fort77
: Есть два варианнта компилятора Fortan77: GNU Fortran и Fort77. Первый рекомендуется для Fortran 90, а второй для поддержки Fortran77. Программаf2c
активно поддерживается, её список изменений ведётся с 1989 года.
Результаты
Техника фаззинга 1989 года по-прежнему находит ошибки в 2018 году. Но есть определённый прогресс.
Чтобы измерить прогресс, нужна некая база. К счастью, для утилит Linux такая база существует. Хотя во времена оригинальной статьи 1990 года ОС Linux ещё не существовала, но повторный тест 1995 года запустил тот же код фаззинга на утилитах из дистрибутива Slackware 2.1.0 от 1995 года. Соответствующие результаты приводятся в таблице 3 статьи 1995 года (стр. 7-9). По сравнению с коммерческими конкурентами GNU/Linux выглядит очень хорошо:
Процент сбоев утилит в свободно распространяемой Linux-версии UNIX была второй по величине: 9%.
Итак, сравним утилиты Linux 1995 и 2018 года инструментами для фаззинга 1989 года:
Ubuntu 18.10 (2018) | Ubuntu 18.04 (2018) | Ubuntu 16.04 (2016) | Ubuntu 14.04 (2014) | Slackware 2.1.0 (1995) | |
---|---|---|---|---|---|
Сбои | 1 (f77) | 1 (f77) | 2 (f77, ul) | 2 (swipl, f77) | 4 (ul, flex, indent, gdb) |
Зависания | 1 (spell) | 1 (spell) | 1 (spell) | 2 (spell, units) | 1 (ctags) |
Всего протестировано | 81 | 81 | 81 | 81 | 55 |
Сбои/зависания, % | 2% | 2% | 4% | 5% | 9% |
Удивительно, но количество сбоев и зависаний Linux всё ещё больше нуля, даже на последней версии Ubuntu. Так,
f77
вызывает программу f2c
с ошибкой сегментации, а программа spell
зависает на двух вариантах тестовых входных данных.Какие баги?
Я смог вручную выяснить корневую причину некоторых багов. Одни результаты, такие как ошибка в glibc, стали неожиданными, в то время как другие, такие как sprintf с буфером фиксированного размера, были предсказуемы.
Сбой ul
Ошибка в ul — это на самом деле баг в glibc. В частности, о нём сообщалось здесь и здесь (другой человек нашёл её в
ul
) в 2016 году. Согласно баг-трекеру, ошибка по-прежнему не исправлена. Поскольку баг невозможно воспроизвести на Ubuntu 18.04 и новее, то он исправлен на уровне дистрибутива. Судя по комментариям к баг-трекеру, основная проблема может быть очень серьёзной.Сбой f77
Программа
f77
идёт в пакете fort77, который сам является скриптом-оболочкой вокруг f2c
, транслятора исходного кода с Fortran77 на C. Отладка f2c
показывает, что сбой происходит, когда функция errstr
печатает слишком длинное сообщение об ошибке. По исходниккам f2c видно, что там используется функция sprintf для записи строки переменной длины в буфер фиксированного размера:errstr(const char *s, const char *t)
#endif
{
char buff[100];
sprintf(buff, s, t);
err(buff);
}
Похоже, что этот код сохранился с момента создания
f2c
. Программа ведёт историю изменений, по крайней мере, с 1989 года. В 1995 году при повторном фаззинге компилятор Fortran77 не тестировали, иначе проблему нашли бы раньше.Зависание spell
Отличный пример классической взаимоблокировки. Программа
spell
делегирует проверку орфографии программе ispell
через канал. spell
читает текст строка за строкой и выдаёт блокирующую запись размера строки в ispell
. Однако ispell
читает максимум BUFSIZ/2
байт за раз (4096 байт в моей системе) и выдаёт блокирующую запись для гарантии, что клиент получил данные о проверке, обработанные до сих пор. Два различных тестовых входных данных заставили spell
записать строку более 4096 символов для ispell
, что привело к взаимоблокировке: spell
ждёт, пока ispell
прочитает всю строку, в то время как ispell
ждёт от spell
подтверждения о прочтении изначальных орфографических правок.Зависание units
На первый взгляд похоже, что присутствует условие бесконечного цикла. Зависание вроде бы в
libreadline
, а не в units
, хотя более новые версии units
не страдают от этой ошибки. Журнал изменений указывает, что была добавлена фильтрация входных данных, которая могла случайно устранить эту проблему. При этом тщательное расследование причин выходит за рамки этого блога. Возможно, способ повесить libreadline
ещё остался.Сбой swipl
Для полноты картины хочу упомянуть сбой
swipl
, хотя я тщательно его не изучал, так как баг давно исправлен и вроде бы довольно качественно. Сбой на самом деле является утверждением (т. е. тем, что никогда не должно происходить), которое вызывается при преобразовании символов:[Thread 1] pl-fli.c:2495: codeToAtom: Assertion failed: chrcode >= 0
C-stack trace labeled "crash":
[0] __assert_fail+0x41
[1] PL_put_term+0x18e
[2] PL_unify_text+0x1c4
…
Аварийное завершение всегда плохо, но здесь хотя бы программа может заявить об ошибке, обваливаясь рано и громко.
Заключение
В последние 30 лет фаззинг оставался простым и надёжным способом поиска багов. Хотя в этой области идут активные исследования, даже фаззер 30-летней давности успешно находит ошибки в современных утилитах Linux.
Автор оригинальных статей предсказал проблемы безопасности, которые вызовет С в течение ближайших десятилетий. Он убедительно доказывает, что на C слишком легко написать небезопасный код и по возможности его следует избегать. В частности, статьи демонстрируют: баги проявляются даже при самом простом фазинге, и такое тестирование следует включить в стандартную практику разработки программного обеспечения. К сожалению, этому совету не следовали десятилетиями.
Надеюсь, вам понравилась эта 30-летняя ретроспектива. Ждите следующей статьи «Фаззинг в 2000 году», где мы исследуем, насколько устойчивы приложения Windows 10 по сравнению с их эквивалентами Windows NT/2000 при проверке фаззером. Думаю, ответ предсказуем.
Комментарии (9)
AEP
09.01.2019 14:29Напереводили… «проверяет наличие основных дампов» — скорее имелось в виду «проверяет наличие файлов core».
amarao
09.01.2019 15:36Странная подборка утилит с багами. Ни одну из них я не использую. Я верю, что где-то там лежит давно забытый бинарь, делающий Что-то-странное в котором баг. Но зачем?
ul у меня в системе есть, но он приехал из комплекта bsdmainutils по зависимости от quilt.
Command 'spell' not found
/usr/bin/f77 вообще симлинк от update-alternatives и у меня указывает на /usr/bin/gfortran, который приехал как зависимость от pitivi, который… зачем я его ставил? O_o
Lucky_Starr
10.01.2019 15:24+1Спасибо за перевод, интересная статья. Аж захотелось самому поиграться с фаззерами в используемом открытом софте и отправить парочку баг-репортов (:
Ждём продолжение.
gecube
Очень интересно почитать, но было бы вообще идеально, если сделать полномасштабную проверку всей кодовой базы GNU/Linux. Уверен, что там еще много проблем найдется.
atrosinenko
Был The Fuzzing Project с похожими целями, ему, вроде, даже грант от Linux Foundation выдали на это, но конкретно сейчас сайт, похоже, лежит...
encyclopedist
Вроде сейчас блог нормально работает: https://blog.fuzzing-project.org/
А вот список проверенного софта с информацией о найденных проблемах больше не доступен: https://fuzzing-project.org/software.html
atrosinenko
Правда, боюсь, проблематично "успешно пройти" проверку фаззингом — её, скорее, можно "не завалить" за определённое время под определённым типом тестовых данных. Это же не формальное доказательство.
encyclopedist
Google ведет постоянный фаззинг многих open-source проектов: https://github.com/google/oss-fuzz
Вот тут список найденных багов: https://bugs.chromium.org/p/oss-fuzz/issues/list
Более того, они приглашают все открытые проекты присоединяться, и Гугл будет автоматически их проверять. https://security.googleblog.com/2018/11/a-new-chapter-for-oss-fuzz.html
encyclopedist
Вот ещё John Regehr сравнивал надёжность старой версии GCC с новыми версиями GCC и Clang. https://blog.regehr.org/archives/1036