Фаззеры безопасности

Если вы делаете что-то важное с точки зрения безопасности с помощью C и C ++, вы, должно быть, слышали об American Fuzzy Lop, самом известном из фаззеров. Он использует заданный набор данных для подпитки вашей программы, немного изменяет его и видит, какие мутации приводят к другому пути кода. Учитывая, что в вашей программе скомпилированы утверждения, или если вы используете AddressSanitizer (-fsanitize=address), эти мутации, как правило, быстро приводят к обнаружению уязвимого места в вашем коде.

Что ж, проект LLVM разрабатывает свою собственную, немного менее функциональную, но предположительно более быструю систему фаззинга, LibFuzzer. Предпосылка та же: вы компилируете свой код с -fsanitizer=fuzzer, и он генерирует исполняемый файл, который, учитывая корпус, многократно вызывает вашу функцию int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size), каким-то образом приводя ее к сбою. Кроме того, он делает это быстрее, поскольку, в отличие от AFL, выполняет фаззинг входных данных в рамках одного процесса. Это может пригодиться, если вы пишете всевозможные парсеры, которые не тратят много времени на обработку для завершения одного цикла обработки входных данных.

Проект LibFuzzer развивается довольно быстро, поэтому старые инструкции не работают. Например, ожидается, что поддержка среды выполнения для LibFuzzer будет упакована как libFuzzer.a, libLLVMFuzzer.a, libcompiler-rt.a и некоторые другие имена, в зависимости от версии clang.

Компиляция нового clang + libFuzzer с нуля

Наиболее разумным способом использования фаззера LLVM является создание проекта LLVM / Clang и его поддержки во время выполнения с нуля. Ниже приведена автоматическая процедура, которая работала для меня на последней версии macOS Sierra на момент публикации.

  • Ожидается, что Homebrew установлен, так как ему необходимо установить или обновить cmake и ninja.
  • Он использует ниндзя для управления параллельной компиляцией, потому что он строится быстрее и обеспечивает несколько лучшую индикацию прогресса.

Вот сценарий для создания недавнего лязга с поддержкой фаззера:

#!/usr/bin/env bash
set -x
set -e
INSTALL_PREFIX=/usr/local/clang-devel
mkdir -p clang-src
cd clang-src
function brew_install() {
    local pkgname=$1
    echo Ensuring $pkgname is installed
    test -f .has-$pkgname && return
    brew install $pkgname || brew update $pkgname
    touch .has-$pkgname
}
brew_install ninja  # Faster make replacement
brew_install cmake
CLONE_DEPTH="--depth 1"
echo Cloning LLVM/Clang sources
test ! -d llvm && git clone ${CLONE_DEPTH} http://llvm.org/git/llvm.git
test ! -d llvm/tools/clang && \
    (cd llvm/tools && git clone ${CLONE_DEPTH} http://llvm.org/git/clang.git)
test ! -d llvm/projects/libcxx && \
    (cd llvm/projects && git clone ${CLONE_DEPTH} http://llvm.org/git/libcxx.git)
test ! -d compiler-rt && \
    (git clone ${CLONE_DEPTH} http://llvm.org/git/compiler-rt.git)
mkdir -p build
cd build
echo Configuring LLVM/CLang for ninja
cmake -G Ninja                                  \
    -DCMAKE_BUILD_TYPE=Release                  \
    -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX}    \
    -DLLVM_TARGETS_TO_BUILD=X86                 \
    --build ../llvm
echo Building LLVM/Clang
ninja
cd ../compiler-rt
# Suppress x86_64h arch
sed -i '' -e 's/set(X86_64 x86_64 x86_64h)/set(X86_64 x86_64)/' cmake/config-ix.cmake
sed -i '' -e 's/set(X86_64 x86_64 x86_64h)/set(X86_64 x86_64)/' cmake/builtin-config-ix.cmake
sed -i '' -e 's/set(DARWIN_osx_ARCHS i386 x86_64 x86_64h)/set(DARWIN_osx_ARCHS i386 x86_64)/' cmake/builtin-config-ix.cmake
HOST_TARGET=$(../build/bin/llvm-config --host-target)
echo Configuring compiler-rt which contains fuzzer runtime.
PATH=../build/bin:$PATH cmake -G Ninja              \
    -DLLVM_CONFIG_PATH=../build/bin/llvm-config     \
    -DCMAKE_BUILD_TYPE=Release                      \
    -DCMAKE_C_COMPILER_TARGET=${HOST_TARGET}        \
    -DCMAKE_C_COMPILER=clang                        \
    -DCMAKE_CXX_FLAGS=-I$(pwd)/../build/include     \
    -DCMAKE_CXX_COMPILER=clang++                    \
    -DCMAKE_INSTALL_PREFIX=/usr/local/clang-devel/lib/clang/6.0.0   \
    -DCOMPILER_RT_ENABLE_IOS=OFF                    \
    -DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON            \
    --build .
# Some weird recursive dependency
sed -i '' -E 's/CUSTOM_COMMAND incl.*\.h$/CUSTOM_COMMAND/' build.ninja
echo Building compiler-rt
ninja
cd ..
echo "Install clang into ${INSTALL_PREFIX}? Enter \"yes\":"
if read install && test "$install" == yes; then
    (cd build && sudo ninja install)
    (cd compiler-rt && sudo ninja install)
else
    echo "Not installing. Execute:"
    echo "   (cd clang-src/build && ninja install)"
    echo "   (cd clang-src/compiler-rt && ninja install)"
    echo "to install into ${INSTALL_PREFIX}"
fi

Компиляция и завершение установки в /usr/local/bin/clang-devel может занять пару часов. Убедитесь, что он у вас на пути, если вы хотите использовать его сразу:

t='export PATH=/usr/local/clang-devel/bin:$PATH'
eval "$t"
echo "$t" >> ~/.bash_profile

Пример фаззинга

Теперь давайте представим короткий, но работающий пример неработающей программы для тестирования нашего недавно скомпилированного инструментария фаззинга на:

#include <string.h>
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
    /* Whatever broken logic you want to execute on Data... */
    return Data && strlen((const char *)Data) == Size;
}

Скомпилируйте и запустите это прямо до сбоя:

/usr/local/clang-devel/bin/clang -o fuzz fuzz.c \
            -fsanitize=undefined,address,fuzzer
./fuzz

Где undefined и address - это формы дезинфекции инструментов (чтобы выяснить, если что-то не так, именно тогда, когда это происходит, а не когда происходит сбой). И fuzzer здесь главный гость, сама аппаратура фаззера. Вы можете удалить один или оба undefined и address дезинфицирующих средств или даже заменить их на memory (который обычно несовместим с address дезинфицирующим средством).

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

Неурегулированные проблемы

Хотя этот рецепт, похоже, работает, я все равно считаю его довольно неприятным решением:

  1. Бинарный файл clang-6.0 отлично работает с -fsanitize=fuzzer, тогда как clang и clang++, несмотря на то, что они являются символическими ссылками, не поддерживают эту опцию. Они просто терпят неудачу, как если бы эта опция игнорировалась. Я пока не мог понять, почему. Полный путь работает для /usr/local/clang-devel/bin/clang, но не для …clang++.
  2. Полезное дезинфицирующее средство памяти (-fsanitize=memory) не работает с этим рецептом.
  3. Мне не удалось превзойти библиотеку compiler-rt, чтобы скомпилировать ее обычным способом, когда ожидается, что она будет находиться в каталоге clang-src/llvm/projects. Поэтому мне пришлось переместить его и скомпилировать автономно.

Скажите, есть ли у вас более короткий способ построить его, или вы можете помочь с самым разочаровывающим первым элементом.