Главная » Хабрахабр » [Перевод] Фаззинг в стиле 1989 года

[Перевод] Фаззинг в стиле 1989 года

С наступлением 2019 года хорошо вспомнить прошлое и подумать о будущем. Оглянемся на 30 лет назад и поразмышляем над первыми научными статьями по фаззингу: «Эмпирическое исследование надёжности утилит UNIX» и последующей работой 1995 года «Пересмотр фаззинга» того же автора Бартона Миллера.

Вы должны прочитать оригинальные документы не только для контекста, но и для понимания. В этой статье попытаемся найти баги в современных версиях Ubuntu Linux, используя те же самые инструменты, что и в оригинальных работах по фаззингу. Внимательные читатели могут заметить дату публикации оригинальной статьи: 1990 год. Они оказались весьма пророческими в отношении уязвимостей и эксплоитов на десятилетия вперёд. Ещё более внимательные заметят копирайт в комментариях исходников: 1989.

Для тех, кто не читал документы (хотя это реально следует сделать), в данном разделе краткое резюме и некоторые избранные цитаты.

Она использует некое начальное значение (seed), обеспечивая воспроизводимость результатов, чего современным фаззерам часто не хватает. Программа фаззинга генерирует случайные потоки символов, с возможностью генерировать только печатные или непечатные символы. Зависания выявляются вручную. Набор скриптов запускается на тестируемых программах и проверяет наличие основных дампов. Адаптеры выдают случайные входные данные для интерактивных программ (статья 1990 года), сетевых сервисов (1995) и графических X-приложений (1995).

3 BSD, SunOS, AIX, Xenix, Dynix). В статье 1990 года тестируются четыре процессорные архитектуры (i386, CVAX, Sparc, 68020) и пять операционных систем (4. В первой статье удаётся добиться сбоя 25-33% утилит, в зависимости от платформы. В статье 1995 года аналогичный выбор платформ. В последующей статье эти цифры варьируются от 9% до 33%, причем у GNU (на SunOS) и Linux наименьший процент сбоев.

Особого упоминания удостоены экстремально небезопасная функция gets и система типов C. В статье 1990 года делается вывод, что 1) программисты не проверяют границы массива или коды ошибок, 2) макросы затрудняют чтение и отладку кода и 3) язык C весьма небезопасен. Статья завершается опросом пользователей о том, как часто они исправляют баги или сообщают о них. В ходе тестирования авторы нашли уязвимости Format String за годы до их массовой эксплуатации. Оказалось, что сообщать о багах трудно и не было особого интереса в их исправлении.

Цитата: В статье 1995 года упоминается ПО с открытым исходным кодом и обсуждается, почему в нём меньше ошибок.

… Когда мы исследовали причины сбоев, то проявилось тревожное явление: многие из багов (около 40%), о которых сообщалось в 1990 году, по-прежнему присутствуют в своей точной форме в 1995 году.

Трудно понять, почему разработчики не используют этот лёгкий и бесплатный источник повышения надёжности. Используемые здесь методы просты и в основном автоматизированы.

Только через 15-20 лет техника фаззинга станет стандартной практикой у крупных вендоров.

Ещё мне кажется, что это заявление 1990 году предвидит будущие события:

Возможность переполнения входного буфера — потенциальная дыра в безопасности, как показал недавний интернет-червь. Часто лаконичность стиля программирования С доводится до крайности, форма превалирует над правильной функцией.

К счастью, спустя 30 лет д-р Бартон по-прежнему предоставляет полный исходный код, скрипты и данные для воспроизведения своих результатов: похвальный пример, которому должны следовать другие исследователи. Скрипты без проблем работают, а в инструмент фаззинга потребовалось внести лишь незначительные изменения для компиляции и запуска.

Согласно README, здесь те же самые случайные входные, что и в оригинальном исследовании. Для этих тестов мы использовали скрипты и входные данные из репозитория fuzz-1995-basic, потому что там самый свежий список тестируемых приложений. Изменился только список утилит для тестирования. Результаты ниже для современного Linux получены точно на том же коде фаззинга и входных данных, что и в оригинальных статьях.

Очевидно, что за последние 30 лет произошли некоторые изменения в программных пакетах Linux, хотя довольно много проверенных утилит по-прежнему ведут свою родословную на протяжении десятилетий. Где возможно, мы взяли современные версии тех же программ из статьи 1995 года. Некоторые программы больше не доступны, их мы заменили. Обоснование всех замен:

  • cfecc1: Эквивалент препроцессору C из статьи 1995 года.
  • dbxgdb: Эквивалент дебаггера 1995 года.
  • ditroffgroff: ditroff больше не доступен.
  • dtblgtbl: Эквивалент GNU Troff старой утилиты dtbl.
  • lispclisp: Стандартная реализация lisp.
  • moreless: Less is more!
  • prologswipl: Есть два варианта prolog: SWI Prolog и GNU Prolog. SWI Prolog предпочтительнее, потому что это более старая и полная реализация.
  • awkgawk: GNU версия awk.
  • ccgcc: Стандартный компилятор C.
  • compressgzip: GZip это идейный наследник старой Unix-утилиты compress.
  • lintsplint: Переписанный lint под лицензией GPL.
  • /bin/mail/usr/bin/mail: Эквивалентная утилита по другому пути.
  • f77fort77: Есть два варианнта компилятора Fortan77: GNU Fortran и Fort77. Первый рекомендуется для Fortran 90, а второй для поддержки Fortran77. Программа f2c активно поддерживается, её список изменений ведётся с 1989 года.

Техника фаззинга 1989 года по-прежнему находит ошибки в 2018 году. Но есть определённый прогресс.

К счастью, для утилит Linux такая база существует. Чтобы измерить прогресс, нужна некая база. 1. Хотя во времена оригинальной статьи 1990 года ОС Linux ещё не существовала, но повторный тест 1995 года запустил тот же код фаззинга на утилитах из дистрибутива Slackware 2. Соответствующие результаты приводятся в таблице 3 статьи 1995 года (стр. 0 от 1995 года. По сравнению с коммерческими конкурентами GNU/Linux выглядит очень хорошо: 7-9).

Процент сбоев утилит в свободно распространяемой 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 слишком легко написать небезопасный код и по возможности его следует избегать. Автор оригинальных статей предсказал проблемы безопасности, которые вызовет С в течение ближайших десятилетий. К сожалению, этому совету не следовали десятилетиями. В частности, статьи демонстрируют: баги проявляются даже при самом простом фазинге, и такое тестирование следует включить в стандартную практику разработки программного обеспечения.

Ждите следующей статьи «Фаззинг в 2000 году», где мы исследуем, насколько устойчивы приложения Windows 10 по сравнению с их эквивалентами Windows NT/2000 при проверке фаззером. Надеюсь, вам понравилась эта 30-летняя ретроспектива. Думаю, ответ предсказуем.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Из песочницы] Прибыльность сайтов и сервисов

Эта статья будет полезна всем, кто хочет сделать собственный бизнес на веб-сайтах и сервисах. Здесь приведены примеры прибыльности и средней посещаемости сайтов через год работы. Основная цель статьи — дать приблизительное представление о сложности развития проектов для тех, кто задумал ...

Прошлое и будущее Java в интервью с Саймоном Риттером из Azul

Представляем вам интервью с Саймоном Риттером — человеком, который работал над Java с самого начала и продолжает делать это в роли заместителя технического директора Azul — компании, работающей над виртуальной машиной Zing JVM и одним из лучших сборщиков мусора, C4 ...