From: Rince Date: Sat, 8 Nov 2025 13:43:54 +0000 (+0300) Subject: опечатка в названии каталога X-Git-Url: http://gitweb.nataraj.world/?a=commitdiff_plain;h=aea597c8b77316f277eb6b6159d2796b9a7d6298;p=articles.git опечатка в названии каталога --- diff --git "a/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\267\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/README.md" "b/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\267\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/README.md" new file mode 120000 index 0000000..6b6c5b4 --- /dev/null +++ "b/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\267\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/README.md" @@ -0,0 +1 @@ +Подготовка окружения для исследуемого процесса с испольнованием btrfs.md \ No newline at end of file diff --git "a/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\267\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/\320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs.md" "b/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\267\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/\320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs.md" new file mode 100644 index 0000000..5a4c820 --- /dev/null +++ "b/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\267\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/\320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs.md" @@ -0,0 +1,264 @@ +# Подготовка окружения для исследуемого процесса с испольнованием btrfs + +**Update:** По состоянию на 2025 год, коллеги из БазАльта предложили более совершенный способ переключения между снепшотами, основанный на обращении к соответствующим системным вызовам ядра прямо из кода исследуемой программы, в самом ее начале. Это позволяет реализовать фаззинг со снепшотами каталога данных на основе свободного AFL++. Описание подхода находится [Здесь](https://www.altlinux.org/PostgreSQL16_fuzzing_technique) ([WebArchive](http://web.archive.org/web/20241203093118/https://www.altlinux.org/PostgreSQL16_fuzzing_technique)) + + +--- + +Часто при фаззинге сложных проектов может оказаться, что для нормальной работы каждому экземпляру исследуемой программы нужна собственная директория с данными, которую не плохо бы перед каждым новым прогоном исследуемой программы возвращать в исходное состояние. В данном посте в формате "заметок" я расскажу как мне удалось сделать восстановление окружения при фаззинге postgres'а, используя btrfs под фаззером Crusher. + +Для фаззига postgres'а система восстановления контекста чрезвычайно важна. Полноценный postgres не может работать без своего личного хранилища, и при этом меняет его практически на каждый чих. Без возвращения хранилища к исходному состоянию при тестировании, ни о какой воспроизводимости и речи быть не может. Поэтому как только заходит речь о фаззинг-тестировании postgres'а в сборе, необходимо сразу думать о системе восстановления контекста. + +## btrfs -- файловая система со снепшотами + +В качестве framework'а для системы восстановления контекста была выбрана файловая система btrfs. Btrfs позволяет эффективно работать со снепшотами на уровне директорий. А именно, позволяет в рамках файловой системы получить клон указанной директории с минимальными затратами. Клонирование происходит на логическом уровне, данные по факту не копируются, и только лишь изменения внесенные в копию или в оригинал занимают дополнительное место на диске. Все это позволяет экономить время и дисковое пространство. + +Фаззинг, при его промышленном применении, имеет практический смысл, будучи запущенным во много потоков на многих процессорах. Для каждого инстанса фаззера, а точнее для исследуемой программы запущенной этим фаззером, предполагается создать свой собственный снепшот с уникальным именем, содержащий данные необходимые для запуска исследуемой программы, и возвращать этот снепшот к исходному состоянию перед каждым новым прогоном исследуемой программы, чтобы следы оставленные в хранилище предыдущими прогонами не оказывали влияния на последующее.. + +Мной был написан скрипт `snapshooter.pl` (код приведен в конце поста) реализующий действия со снепшотами, необходимые для фаззинга. Скрипту первым параметром передается имя команды и далее параметры которые нужны команде для работы. + +Ниже приведены команды и их описания: + +###### `init MOUNT_POINT IMAGE_FILE IMAGE_SIZE IMAGE_COUNT` + +Принимает 4 аргумента: точка монтирования `MOUNT_POINT`, путь к создаваемому образу файловой системы `IMAGE_FILE`, предполагаемый размер одного снепшота `IMAGE_SIZE` и ожидаемое количество снепшотов `IMAGE_COUNT`. + +Скрипт отмонтирует то, что было примантировано к точки монтирования, создаст новый образ файловой системы, примонтирует его к точке монтирования, и создаст в файловой системе директорию `_reference`. + +В эту директорию следует скопировать "эталонный" снепшот, на базе которого будут строиться снепшоты для всех экземпляров + +###### `clone MOUNT_POINT SNAPSHOT_NAME` + +Принимает два аргумента: точку монтирования `IMAGE_COUNT` и имя снэпшота `SNAPSHOT_NAME`. + +Команда `clone` клонирует эталонный снепшот в директорию `[SNAPSHOT_NAME].base`. Если есть потребность внести какие-то специфичные для инстанса изменения в снепшот, то это следует сделать в этой директории. В дальнейшем перед каждым запуском исследуемой программы рабочий снепшот будет приводится к "базововму" состоянию. + +###### `reset MOUNT_POINT SNAPSHOT_NAME` + +Принимает два аргумента: точку монтирования `MOUNT_POINT`и имя снэпшота `SNAPSHOT_NAME`. + +Команда `reset` удаляет снепшот `SNAPSHOT_NAME`, и создает новый с таким же именем клонируя базовый снепшот `[SNAPSHOT_NAME].base`. Таким образом происходит восстановление целевого снепшота исходное состояние. + +В дальнейшем, для ускорения работы, планируется образ файловой системы со снепшотами перенести с диска в память, но я пока еще не разбирался как это делается. + +## Crusher + +В Crusher 2.11.0 появились инструменты позволяющие инициировать подготовку, и восстановления окружения для запускаемых процессов. К сожалению для того чтобы задействовать эти инструменты необходимо написание скриптов на языке python (и ни на каком другом), а я этот python не умею, и честно говоря не очень хочу уметь. К счастью коллеги снабдили меня обертками которые позволяют из непонятного питона запускать понятные `.sh` и прочие скрипты. Поэтому конструкция получилась двухсоставная. + +Предложенное решение использует два "хука" предоставляемые крашером. Первый хук дергается при создании нового инстанса фаззера (см. `--configurator-script` в документации), и используется для создания нового снепшота с данными хранилища, которыми будет пользоваться исследуемая программа. Второй хук дергается перед каждым новым прогоном исследуемой программы (см. `--environment-plugin` в документации), и используется для того чтобы вернуть снепшот с хранилищем в исходное состояние (ибо есть шанс что при предыдущих запусках он мог быть иземенен) + +### Подготовка окружения + +Изначальная подготовка окружения осуществляется через скрипт `conf.py`: + +```python +import json +import os +import sys +import traceback +import subprocess + +def transform_options(ops_json): + try: + jops = json.loads(ops_json) + instance_name = jops['configuration']['instance_name'] + this_dir = os.path.dirname(__file__) + script_path = this_dir + '/init_script.sh' + pr = subprocess.Popen([script_path, instance_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + pr.wait() + assert pr.returncode == 0 + output = pr.stdout.read() + for line in output.split(): + ls = line.split('=') + assert len(ls) == 2 + var_name = ls[0].strip() + var_value = ls[1].strip() + os.environ[var_name] = var_value + return json.dumps(jops) + except Exception as ex: + print("EXCEPTION!") + traceback.print_exc() + return None +``` +Который в свою очередь вызывает скрипт `init_script.sh`: + +```bash +#!/bin/bash + +INSTANCE_NAME=$1 +THIS_DIR=$(dirname $0) + +sudo ./snapshooter.pl clone mnt "${INSTANCE_NAME}" 1>&2 || exit 1 +sudo ./snapshooter.pl reset mnt "${INSTANCE_NAME}" 1>&2 || exit 1 + +echo "PGDATA=${THIS_DIR}/mnt/$INSTANCE_NAME" +``` + +Скрипту `init_script.sh` в качестве аргумента передается уникальное имя инстанса фаззера, это имя скрипт использует в качестве имени создаваемого снепшота. Кроме того на `STDOUT` скрипт выводит пары `NAME=VALUE` переменных окружения которые должны быть установлены при запуске. В нашем случае мы через переменную окружения сообщаем постгресу где находится его хранилище. Вывод скрипта анализируется оберткой `env.py` и значения переменных окружения передаются фаззеру для последующего применения. + +При запуске фаззера необходимо указать расположение скрипта `conf.py` через параметр `--configurator-script ` + +### Восстановление окружения +Идея та же, что и с созданием окружения. Питоновская обертка `env.py`: + +```python +import json +import os + +error_msg = '...' + +def get_error(): + return error_msg + +instance_name = None + +def init(json_options): + jops = json.loads(json_options) + global instance_name + instance_name = jops['configuration']['instance_name'] + return True + +def finish(): + return True + +def setup(): + this_dir = os.path.dirname(__file__) + script_path = this_dir + '/run_script.sh' + cmd = script_path + ' ' + instance_name + r = os.system(cmd) + return r == 0 + +def teardown(): + return True +``` +И `.sh` скрипт выполняющий содержательную работу: +```bash +#!/bin/bash + +INSTANCE_NAME=$1 + +sudo ./snapshooter.pl reset mnt "${INSTANCE_NAME}" 1>&2 || exit 1 +``` +В данном случае мы получаем имя инстанса в качестве аргумента и восстанавливаем снепшот с этим именем в исходное состояние. + +При запуске фаззинга расположение файла `env.py` указывается через параметр конфигурации `--environment-plugin` + +### Запуск фаззинга +Перед запуском фаззинга мы должны инициализировать хранилище снепшотов, раздать эталонному снепшоту правильные права (постгрес требует чтобы хранилище не было доступно на чтение кому либо кроме постгреса) и заполнить правильными данными. Заранее подготовленный снимок хранилища у меня лежит в директории `etalon`, я его просто копирую. Ну а дальше все достаточно просто. + +```bash +#!/bin/bash + +ME=`whoami` +sudo ./snapshooter.pl init mnt image.img 40000000 10 +sudo chown $ME: mnt/_reference +chmod 700 mnt/_reference + +cp -r etalon/* mnt/_reference + +~/crushers/latest/bin_x86-64/fuzz_manager --bitmap-size 300000 --start 4 --eat-cores 1 --dse-cores 0 -i in -o out -T StdIn -F -I StaticNoForkSrv --configurator-script conf.py --environment-plugin env.py -- /home/nataraj/tests/fuzz_psql/pg/bin/postgres --single postgres +``` + +Таким образом удалось успешно запустить простейший параллельный фаззинг SQL-запросов с восстановлением состояния хранилища к исходному перед каждым запуском. На сам по себе прямой фаззинг SQL-запросов я больших надежд не возлагаю, фаззить в лоб сложные синтаксические конструкции -- малопродуктивно, однако система восстановления контекста в дальнейшем будет применена для фаззинг-исследования функциональности потенциально приводящей к модификации хранилища postgresql. + +## Приложения +### snapshooter.pl +```perl +#!/usr/bin/perl + +use strict; + +my $command = shift @ARGV; + +die "Укажите команду первым аргументом" unless $command; + +if ($command eq "init") +{ + my $mount_path = shift @ARGV; + my $image_path = shift @ARGV; + my $snapshot_size = shift @ARGV; + my $snapshots_count = shift @ARGV; + + unless ($image_path && $mount_path && $snapshot_size && $snapshots_count) + { + die "Команде init нужны 4 параметра: точка монтирования,путь к образу, размер снэпшота и из кол-во"; + } + + `mountpoint -q $mount_path`; + unless( $?) + { + print "$mount_path -- примонтирован. Пробуем отмонтировать...\n"; + `umount $mount_path`; + } + + my $min_btrfs_size = 114294784; + if ($snapshot_size * $snapshots_count < $min_btrfs_size) + { + print "Увеличиваем размер образа до минимально возможных $min_btrfs_size байт"; + $snapshot_size = $min_btrfs_size; + $snapshots_count = 1; + } + + print "Создаем и форматируем образ...\n"; + `dd if=/dev/zero of=$image_path count=$snapshots_count bs=$snapshot_size`; + `mkfs.btrfs $image_path`; + + print "Монтируем образ...\n"; + `mkdir -p $mount_path`; + `mount $image_path $mount_path`; + + print "Создаем reference снэпшот...\n"; + `btrfs subvolume create $mount_path/_reference`; +} + +if ($command eq "clone") +{ + my $mount_path = shift @ARGV; + my $target = shift @ARGV; + + unless ($target) + { + die "Команде $command нужны 2 параметра: точка монтирования, имя снэпшота"; + } + + my $name = "$mount_path/$target.base"; + + if (-e $name) + { + die "Снэпшот $name уже существует"; + } + print "Создаю основу для снэпшота $target: $name\n"; + + `btrfs subvolume snapshot $mount_path/_reference $name`; +} + +if ($command eq "reset") +{ + + my $mount_path = shift @ARGV; + my $target = shift @ARGV; + + unless ($target) + { + die "Команде $command нужны 2 параметра: точка монтирования, имя снэпшота"; + } + my $name = "$mount_path/$target"; + unless (-e "$name.base") + { + die "Не существует основы для снэпшота $target: $name.base"; + } + + print "Обновляю снэпшот $target: \n"; + + if (-e $name) + { + `rm -r $name`; + print "Удаляем старый\n"; + } + + print "Создаем новый\n"; + + `btrfs subvolume snapshot $name.base $name`; +} +``` + diff --git "a/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/README.md" "b/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/README.md" deleted file mode 120000 index 6b6c5b4..0000000 --- "a/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/README.md" +++ /dev/null @@ -1 +0,0 @@ -Подготовка окружения для исследуемого процесса с испольнованием btrfs.md \ No newline at end of file diff --git "a/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/\320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs.md" "b/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/\320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs.md" deleted file mode 100644 index 5a4c820..0000000 --- "a/SDL/2022-03 \320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs/\320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\260 \320\276\320\272\321\200\321\203\320\266\320\265\320\275\320\270\321\217 \320\264\320\273\321\217 \320\270\321\201\321\201\320\273\320\265\320\264\321\203\320\265\320\274\320\276\320\263\320\276 \320\277\321\200\320\276\321\206\320\265\321\201\321\201\320\260 \321\201 \320\270\321\201\320\277\320\276\320\273\321\214\320\275\320\276\320\262\320\260\320\275\320\270\320\265\320\274 btrfs.md" +++ /dev/null @@ -1,264 +0,0 @@ -# Подготовка окружения для исследуемого процесса с испольнованием btrfs - -**Update:** По состоянию на 2025 год, коллеги из БазАльта предложили более совершенный способ переключения между снепшотами, основанный на обращении к соответствующим системным вызовам ядра прямо из кода исследуемой программы, в самом ее начале. Это позволяет реализовать фаззинг со снепшотами каталога данных на основе свободного AFL++. Описание подхода находится [Здесь](https://www.altlinux.org/PostgreSQL16_fuzzing_technique) ([WebArchive](http://web.archive.org/web/20241203093118/https://www.altlinux.org/PostgreSQL16_fuzzing_technique)) - - ---- - -Часто при фаззинге сложных проектов может оказаться, что для нормальной работы каждому экземпляру исследуемой программы нужна собственная директория с данными, которую не плохо бы перед каждым новым прогоном исследуемой программы возвращать в исходное состояние. В данном посте в формате "заметок" я расскажу как мне удалось сделать восстановление окружения при фаззинге postgres'а, используя btrfs под фаззером Crusher. - -Для фаззига postgres'а система восстановления контекста чрезвычайно важна. Полноценный postgres не может работать без своего личного хранилища, и при этом меняет его практически на каждый чих. Без возвращения хранилища к исходному состоянию при тестировании, ни о какой воспроизводимости и речи быть не может. Поэтому как только заходит речь о фаззинг-тестировании postgres'а в сборе, необходимо сразу думать о системе восстановления контекста. - -## btrfs -- файловая система со снепшотами - -В качестве framework'а для системы восстановления контекста была выбрана файловая система btrfs. Btrfs позволяет эффективно работать со снепшотами на уровне директорий. А именно, позволяет в рамках файловой системы получить клон указанной директории с минимальными затратами. Клонирование происходит на логическом уровне, данные по факту не копируются, и только лишь изменения внесенные в копию или в оригинал занимают дополнительное место на диске. Все это позволяет экономить время и дисковое пространство. - -Фаззинг, при его промышленном применении, имеет практический смысл, будучи запущенным во много потоков на многих процессорах. Для каждого инстанса фаззера, а точнее для исследуемой программы запущенной этим фаззером, предполагается создать свой собственный снепшот с уникальным именем, содержащий данные необходимые для запуска исследуемой программы, и возвращать этот снепшот к исходному состоянию перед каждым новым прогоном исследуемой программы, чтобы следы оставленные в хранилище предыдущими прогонами не оказывали влияния на последующее.. - -Мной был написан скрипт `snapshooter.pl` (код приведен в конце поста) реализующий действия со снепшотами, необходимые для фаззинга. Скрипту первым параметром передается имя команды и далее параметры которые нужны команде для работы. - -Ниже приведены команды и их описания: - -###### `init MOUNT_POINT IMAGE_FILE IMAGE_SIZE IMAGE_COUNT` - -Принимает 4 аргумента: точка монтирования `MOUNT_POINT`, путь к создаваемому образу файловой системы `IMAGE_FILE`, предполагаемый размер одного снепшота `IMAGE_SIZE` и ожидаемое количество снепшотов `IMAGE_COUNT`. - -Скрипт отмонтирует то, что было примантировано к точки монтирования, создаст новый образ файловой системы, примонтирует его к точке монтирования, и создаст в файловой системе директорию `_reference`. - -В эту директорию следует скопировать "эталонный" снепшот, на базе которого будут строиться снепшоты для всех экземпляров - -###### `clone MOUNT_POINT SNAPSHOT_NAME` - -Принимает два аргумента: точку монтирования `IMAGE_COUNT` и имя снэпшота `SNAPSHOT_NAME`. - -Команда `clone` клонирует эталонный снепшот в директорию `[SNAPSHOT_NAME].base`. Если есть потребность внести какие-то специфичные для инстанса изменения в снепшот, то это следует сделать в этой директории. В дальнейшем перед каждым запуском исследуемой программы рабочий снепшот будет приводится к "базововму" состоянию. - -###### `reset MOUNT_POINT SNAPSHOT_NAME` - -Принимает два аргумента: точку монтирования `MOUNT_POINT`и имя снэпшота `SNAPSHOT_NAME`. - -Команда `reset` удаляет снепшот `SNAPSHOT_NAME`, и создает новый с таким же именем клонируя базовый снепшот `[SNAPSHOT_NAME].base`. Таким образом происходит восстановление целевого снепшота исходное состояние. - -В дальнейшем, для ускорения работы, планируется образ файловой системы со снепшотами перенести с диска в память, но я пока еще не разбирался как это делается. - -## Crusher - -В Crusher 2.11.0 появились инструменты позволяющие инициировать подготовку, и восстановления окружения для запускаемых процессов. К сожалению для того чтобы задействовать эти инструменты необходимо написание скриптов на языке python (и ни на каком другом), а я этот python не умею, и честно говоря не очень хочу уметь. К счастью коллеги снабдили меня обертками которые позволяют из непонятного питона запускать понятные `.sh` и прочие скрипты. Поэтому конструкция получилась двухсоставная. - -Предложенное решение использует два "хука" предоставляемые крашером. Первый хук дергается при создании нового инстанса фаззера (см. `--configurator-script` в документации), и используется для создания нового снепшота с данными хранилища, которыми будет пользоваться исследуемая программа. Второй хук дергается перед каждым новым прогоном исследуемой программы (см. `--environment-plugin` в документации), и используется для того чтобы вернуть снепшот с хранилищем в исходное состояние (ибо есть шанс что при предыдущих запусках он мог быть иземенен) - -### Подготовка окружения - -Изначальная подготовка окружения осуществляется через скрипт `conf.py`: - -```python -import json -import os -import sys -import traceback -import subprocess - -def transform_options(ops_json): - try: - jops = json.loads(ops_json) - instance_name = jops['configuration']['instance_name'] - this_dir = os.path.dirname(__file__) - script_path = this_dir + '/init_script.sh' - pr = subprocess.Popen([script_path, instance_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - pr.wait() - assert pr.returncode == 0 - output = pr.stdout.read() - for line in output.split(): - ls = line.split('=') - assert len(ls) == 2 - var_name = ls[0].strip() - var_value = ls[1].strip() - os.environ[var_name] = var_value - return json.dumps(jops) - except Exception as ex: - print("EXCEPTION!") - traceback.print_exc() - return None -``` -Который в свою очередь вызывает скрипт `init_script.sh`: - -```bash -#!/bin/bash - -INSTANCE_NAME=$1 -THIS_DIR=$(dirname $0) - -sudo ./snapshooter.pl clone mnt "${INSTANCE_NAME}" 1>&2 || exit 1 -sudo ./snapshooter.pl reset mnt "${INSTANCE_NAME}" 1>&2 || exit 1 - -echo "PGDATA=${THIS_DIR}/mnt/$INSTANCE_NAME" -``` - -Скрипту `init_script.sh` в качестве аргумента передается уникальное имя инстанса фаззера, это имя скрипт использует в качестве имени создаваемого снепшота. Кроме того на `STDOUT` скрипт выводит пары `NAME=VALUE` переменных окружения которые должны быть установлены при запуске. В нашем случае мы через переменную окружения сообщаем постгресу где находится его хранилище. Вывод скрипта анализируется оберткой `env.py` и значения переменных окружения передаются фаззеру для последующего применения. - -При запуске фаззера необходимо указать расположение скрипта `conf.py` через параметр `--configurator-script ` - -### Восстановление окружения -Идея та же, что и с созданием окружения. Питоновская обертка `env.py`: - -```python -import json -import os - -error_msg = '...' - -def get_error(): - return error_msg - -instance_name = None - -def init(json_options): - jops = json.loads(json_options) - global instance_name - instance_name = jops['configuration']['instance_name'] - return True - -def finish(): - return True - -def setup(): - this_dir = os.path.dirname(__file__) - script_path = this_dir + '/run_script.sh' - cmd = script_path + ' ' + instance_name - r = os.system(cmd) - return r == 0 - -def teardown(): - return True -``` -И `.sh` скрипт выполняющий содержательную работу: -```bash -#!/bin/bash - -INSTANCE_NAME=$1 - -sudo ./snapshooter.pl reset mnt "${INSTANCE_NAME}" 1>&2 || exit 1 -``` -В данном случае мы получаем имя инстанса в качестве аргумента и восстанавливаем снепшот с этим именем в исходное состояние. - -При запуске фаззинга расположение файла `env.py` указывается через параметр конфигурации `--environment-plugin` - -### Запуск фаззинга -Перед запуском фаззинга мы должны инициализировать хранилище снепшотов, раздать эталонному снепшоту правильные права (постгрес требует чтобы хранилище не было доступно на чтение кому либо кроме постгреса) и заполнить правильными данными. Заранее подготовленный снимок хранилища у меня лежит в директории `etalon`, я его просто копирую. Ну а дальше все достаточно просто. - -```bash -#!/bin/bash - -ME=`whoami` -sudo ./snapshooter.pl init mnt image.img 40000000 10 -sudo chown $ME: mnt/_reference -chmod 700 mnt/_reference - -cp -r etalon/* mnt/_reference - -~/crushers/latest/bin_x86-64/fuzz_manager --bitmap-size 300000 --start 4 --eat-cores 1 --dse-cores 0 -i in -o out -T StdIn -F -I StaticNoForkSrv --configurator-script conf.py --environment-plugin env.py -- /home/nataraj/tests/fuzz_psql/pg/bin/postgres --single postgres -``` - -Таким образом удалось успешно запустить простейший параллельный фаззинг SQL-запросов с восстановлением состояния хранилища к исходному перед каждым запуском. На сам по себе прямой фаззинг SQL-запросов я больших надежд не возлагаю, фаззить в лоб сложные синтаксические конструкции -- малопродуктивно, однако система восстановления контекста в дальнейшем будет применена для фаззинг-исследования функциональности потенциально приводящей к модификации хранилища postgresql. - -## Приложения -### snapshooter.pl -```perl -#!/usr/bin/perl - -use strict; - -my $command = shift @ARGV; - -die "Укажите команду первым аргументом" unless $command; - -if ($command eq "init") -{ - my $mount_path = shift @ARGV; - my $image_path = shift @ARGV; - my $snapshot_size = shift @ARGV; - my $snapshots_count = shift @ARGV; - - unless ($image_path && $mount_path && $snapshot_size && $snapshots_count) - { - die "Команде init нужны 4 параметра: точка монтирования,путь к образу, размер снэпшота и из кол-во"; - } - - `mountpoint -q $mount_path`; - unless( $?) - { - print "$mount_path -- примонтирован. Пробуем отмонтировать...\n"; - `umount $mount_path`; - } - - my $min_btrfs_size = 114294784; - if ($snapshot_size * $snapshots_count < $min_btrfs_size) - { - print "Увеличиваем размер образа до минимально возможных $min_btrfs_size байт"; - $snapshot_size = $min_btrfs_size; - $snapshots_count = 1; - } - - print "Создаем и форматируем образ...\n"; - `dd if=/dev/zero of=$image_path count=$snapshots_count bs=$snapshot_size`; - `mkfs.btrfs $image_path`; - - print "Монтируем образ...\n"; - `mkdir -p $mount_path`; - `mount $image_path $mount_path`; - - print "Создаем reference снэпшот...\n"; - `btrfs subvolume create $mount_path/_reference`; -} - -if ($command eq "clone") -{ - my $mount_path = shift @ARGV; - my $target = shift @ARGV; - - unless ($target) - { - die "Команде $command нужны 2 параметра: точка монтирования, имя снэпшота"; - } - - my $name = "$mount_path/$target.base"; - - if (-e $name) - { - die "Снэпшот $name уже существует"; - } - print "Создаю основу для снэпшота $target: $name\n"; - - `btrfs subvolume snapshot $mount_path/_reference $name`; -} - -if ($command eq "reset") -{ - - my $mount_path = shift @ARGV; - my $target = shift @ARGV; - - unless ($target) - { - die "Команде $command нужны 2 параметра: точка монтирования, имя снэпшота"; - } - my $name = "$mount_path/$target"; - unless (-e "$name.base") - { - die "Не существует основы для снэпшота $target: $name.base"; - } - - print "Обновляю снэпшот $target: \n"; - - if (-e $name) - { - `rm -r $name`; - print "Удаляем старый\n"; - } - - print "Создаем новый\n"; - - `btrfs subvolume snapshot $name.base $name`; -} -``` -