Технология фаззинга Python3 скриптами

Материал из ALT Linux Wiki

При фаззинге Python3 используются скрипты на Python в качестве входного корпуса, которые потом обрабатывает интерпретатор. Цель состоит в том, чтобы определить, как интерпретатор реагирует на разнообразные конструкции языка.

Это требует наличия инструментации интерпретатора, чтобы можно было отслеживать покрытие кода и эффективность тестирования. Для этого в данной статье будут использоваться такие инструменты, как afl++ и afl-cov. Также следует упомянуть, что все нижеописанные действия будут проводиться в контейнере Podman.

Настройка контейнерного окружения для фаззинга

Перед началом фаззинга Python3 нужно настроить контейнерное окружение. Подготовленный Dockerfile из репозитория [1] позволяет создать окружение, полностью готовое для запуска фаззинга и сбора покрытия кода. Он основан на базе alt:p10. Сначала в нем настраиваются параметры UID и GID. Далее устанавливаются базовые утилиты, такие как bash. Затем выполняется установка инструментов для сборки и анализа: clang, llvm, gcc, ninja, а также пакетов для Python (python3-dev, setuptools, pip). Эти инструменты необходимы для компиляции Python3 и интеграции с AFL для фаззинга. Дополнительно устанавливаются утилиты для сбора покрытия кода (lcov, gcovr) и Rust, необходимый для поддержки некоторых возможностей AFL++.

После установки всех зависимостей создается изолированный пользователь. Далее устанавливаются дополнительные инструменты, такие как capstone и libxdc, для поддержки продвинутых режимов фаззинга. Затем выполняется установка и настройка afl++ и afl-cov. Контейнер настраивается для использования afl-clang-lto, чтобы поддерживать фаззинг на уровне сборки Python3. В конце Dockerfile переключается обратно на непривилегированного пользователя и запускает контейнер в режиме оболочки, создавая готовую к использованию изолированную среду для тестирования.

Для начала работы следует склонировать репозиторий с подготовленным Dockerfile и перейти в его директорию:

git clone https://git.altlinux.org/people/zeff/public/python3-fuzzing.git
cd ./python3-fuzzing

После этого, перейдите к сборке контейнера:

podman build -t cpython-fuzzer .

Запуск фаззинга Python3

Процесс запуска фаззинга Python3 состоит из четырёх основных этапов: сборка Python3 с AFL, сборка Python3 с покрытием (AFL-cov), запуск фаззинга, сбор покрытия кода.

Сборка Python3 с инструментацией AFL

Для сборки Python3 с инструментами фаззинга AFL, необходимо выполнить сборку внутри контейнера. Запустите контейнер с образом cpython-fuzzer:

podman run -it --rm --name build-afl \
    -v "$(pwd)/artifacts:/artifacts" \
    -v "$(pwd):/host:ro" \
    --userns=keep-id:uid=500,gid=500 \
    cpython-fuzzer bash

Установите переменные окружения для компиляторов AFL:

export CC=afl-clang-lto \
       CXX=afl-clang-lto++ \
       LD=afl-ld-lto \
       CFLAGS="-fPIE" \
       CXXFLAGS="-fPIE" \
       AFL_USE_ASAN=1 \
       AFL_USE_UBSAN=1 \
       ASAN_OPTIONS="detect_leaks=0:abort_on_error=1:symbolize=0"

Затем склонируйте исходный код Python3 и приготовьте его к сборке:

git clone --branch 3.9.20-alt1 --depth 1 git://git.altlinux.org/gears/p/python3.git python3.9.20-alt1
cd python3.9.20-alt1
git checkout 3.9.20-alt1
sed -i 's&--enable-ipv6&--enable-ipv6 ax_cv_c_float_words_bigendian=no&' python3.spec
gear --commit --rpmbuild -- rpmbuild --buildroot=/home/user/fuzz_target -bp

Перейдите в директорию сборки и настройте параметры компиляции:

cd /home/user/RPM/BUILD/
rm -rf  /artifacts/python3-3.9.20-alt1
mv python3-3.9.20 /artifacts/python3-3.9.20-alt1
cd /artifacts/python3-3.9.20-alt1
mkdir build && cd build
../configure ax_cv_c_float_words_bigendian=no \
  --enable-ipv6 \
  --with-dbmliborder=gdbm:ndbm:bdb \
  --with-system-expat \
  --with-system-ffi \
  --enable-loadable-sqlite-extensions \
  --with-lto \
  --with-ssl-default-suites=openssl \
  --without-ensurepip

Запустите сборку:

make -j$(nproc) 2>&1 | tee /artifacts/python3-3.9.20-alt1/build.log
exit

После завершения сборки Python3 будет готов для запуска фаззинга с использованием AFL.

Сборка Python3 с инструментацией для покрытия (AFL-cov)

Для анализа покрытия кода при фаззинге необходимо собрать Python3 с дополнительной инструментализацией для покрытия. Для этого сначала запустите контейнер:

podman run -it --rm --name build-cov \
    -v "$(pwd)/artifacts:/artifacts" \
    -v "$(pwd):/host:ro" \
    --userns=keep-id:uid=500,gid=500 \
    cpython-fuzzer bash

Внутри контейнера настройте переменные окружения для сборки с поддержкой покрытия:

export CC=clang-15 \
    CXX=clang++-15 \
    LD=clang-15 \
    CFLAGS="-fprofile-arcs -ftest-coverage -fPIE" \
    CXXFLAGS="-fprofile-arcs -ftest-coverage -fPIE" \
    LDFLAGS="-fprofile-arcs -ftest-coverage -fPIE" \
    ALTWRAP_LLVM_VERSION=15.0

Склонируйте исходный код и приготовьте его к сборке:

git clone --branch 3.9.20-alt1 --depth 1 git://git.altlinux.org/gears/p/python3.git python3.9.20-alt1
cd python3.9.20-alt1
git checkout 3.9.20-alt1
sed -i 's&--enable-ipv6&--enable-ipv6 ax_cv_c_float_words_bigendian=no&' python3.spec
gear --commit --rpmbuild -- rpmbuild --buildroot=/home/user/cov_target -bp

Перенестие собранные файлы и выполните настройку:

mv /home/user/RPM/BUILD/python3-3.9.20 /artifacts/python3-3.9.20-alt1-cov
cd /artifacts/python3-3.9.20-alt1-cov
./configure ax_cv_c_float_words_bigendian=no \
  --enable-ipv6 \
  --with-dbmliborder=gdbm:ndbm:bdb \
  --with-system-expat \
  --with-system-ffi \
  --enable-loadable-sqlite-extensions \
  --with-lto \
  --with-ssl-default-suites=openssl \
  --without-ensurepip

Запустите сборку:

make -j$(nproc) 2>&1 | tee /artifacts/python3-3.9.20-alt1-cov/build.log
exit

Запуск фаззинга

Для запуска фаззинга на собранной версии Python3, выполните следующие шаги. Сначала запустите контейнер:

podman run -it --rm --name run-fuzzing \
    -v "$(pwd)/artifacts:/artifacts" \
    -v "$(pwd):/host:ro" \
    --userns=keep-id:uid=500,gid=500 \
    cpython-fuzzer bash

Далее следует подготовить входной корпус, но остановимся здесь поподробнее. В процессе фаззинга Python3, входной корпус представляет собой набор скриптов на Python, которые используются в качестве тестовых данных. Желательно, чтобы входной корпус был разнообразным и содержал как простые, так и сложные конструкции Python, чтобы максимально покрыть возможные пути выполнения.

В качестве входного корпуса в данной статье будет использоваться заранее подготовленный репозиторий [2], содержащий набор тестовых Python-скриптов:

git clone https://github.com/OMGfox/python_input_corpuses
mv python_input_corpuses/input /home/user/indir
rm -rf python_input_corpuses

Настройте окружение для AFL:

export AFL_AUTORESUME=1 AFL_SKIP_CPUFREQ=1 AFL_TRY_AFFINITY=1
mkdir -p /artifacts/outdir

И запустите фаззинг:

afl-fuzz -i /home/user/indir -o /artifacts/outdir \
         -m 1000 -t 1000+ \
         -M master -- /artifacts/python3-3.9.20-alt1/build/python

Эта команда запускает фаззинг с помощью afl-fuzz, используя входные данные из директории /home/user/indir и сохраняя результаты в /artifacts/outdir. Параметр -m 1000 устанавливает лимит памяти в 1000 МБ (его можно удалить, если ограничение не требуется), а -t 1000+ задаёт максимальное время выполнения одного теста в миллисекундах. Опция -M указывает, что данный процесс будет работать в режиме мастера. Также возможна опция -S, которая указывает, что данный процесс будет работать в slave-режиме. После -- указывается путь к исполняемому файлу.

Сбор покрытия кода

Для оценки покрытия кода запустите контейнер:

podman run -it --rm --name run-coverage \
    -v "$(pwd)/artifacts:/artifacts" \
    -v "$(pwd):/host:ro" \
    --userns=keep-id:uid=500,gid=500 \
    cpython-fuzzer bash

Внутри контейнера создайте директорию для отчетов покрытия:

cd ~
mkdir /artifacts/coverage

Запустите afl-cov для анализа покрытия:

afl-cov --overwrite --clang --cover-corpus -v -d /artifacts/coverage \
    --coverage-cmd "/artifacts/python3-3.9.20-alt1-cov/python @@" \
    --code-dir "/artifacts/python3-3.9.20-alt1-cov"

Эта команда запускает afl-cov для анализа покрытия кода, достигнутого во время фаззинга. Параметр --overwrite позволяет перезаписать старые отчёты. Флаг --clang указывает использовать компилятор Clang для точного анализа покрытия. Опция --cover-corpus заставляет afl-cov проверять весь сгенерированный корпус. Параметр -v включает подробный вывод, а -d /artifacts/coverage указывает, куда сохранять результаты анализа. Опция --coverage-cmd "/artifacts/python3-3.9.20-alt1-cov/python @@" указывает команду для запуска исполняемого файла с каждым тестовым случаем, где @@ заменяется на файл из корпуса. Наконец, --code-dir указывает на директорию с исходным кодом для сопоставления сгенерированного покрытия, что позволяет увидеть, какие участки кода были задействованы во время фаззинга.


Ссылки

[1] Репозиторий с Dockerfile

[2] Репозиторий с входным корпусом для фаззинга