PostgreSQL16 fuzzing technique: различия между версиями
Строка 157: | Строка 157: | ||
Сервер postgresql16 должен работать в один процесс. Патч также отключает вспомогательные службы, напр. SysLogger, WalWriter, BgWriter, кроме Checkpointer. Без службы Checkpointer с некоторыми тесткейсами postgres крашится. Вызов функции pause() нужен для получения SIGCHLD от дочернего процесса, вызванного в макросе StartupDataBase. Не получив SIGCHLD процесс не станет считывать входные данные. Функция maybe_start_bgworkers, предположительно, нужна для вызова пользовательских фоновых процессов, для фаззинга лишняя. | Сервер postgresql16 должен работать в один процесс. Патч также отключает вспомогательные службы, напр. SysLogger, WalWriter, BgWriter, кроме Checkpointer. Без службы Checkpointer с некоторыми тесткейсами postgres крашится. Вызов функции pause() нужен для получения SIGCHLD от дочернего процесса, вызванного в макросе StartupDataBase. Не получив SIGCHLD процесс не станет считывать входные данные. Функция maybe_start_bgworkers, предположительно, нужна для вызова пользовательских фоновых процессов, для фаззинга лишняя. | ||
< | <source> | ||
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c | diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c | ||
index a0f7084018..57fca666a8 100644 | index a0f7084018..57fca666a8 100644 | ||
Строка 265: | Строка 265: | ||
#define HAVE_SYMLINK 1 | #define HAVE_SYMLINK 1 | ||
#endif | #endif | ||
</ | </source> | ||
<span id="подготовка-базы-данных"></span> | <span id="подготовка-базы-данных"></span> | ||
Версия от 13:04, 15 января 2025
Технология фаззинг-тестирования postgresql16
Фаззинг-тестирование реляционной базы данных postgresql версии 16.4 выполняется с использованием инструментирующего фаззера AFL++ (версия 4.21c), адресного санитайзера (ASAN) и библиотеки libdesock. Для создания отчета о покрытии исходного кода postgresql16 используется утилита afl-cov.
Подготовка
Окружение: alt/alt:p10
Пользователь: user
Для фаззинга необходима файловая система btrfs.
Раздел, в котором запускается фаззинг, должнен быть монтирован с опцией ‘user_subvol_rm_allowed’.
sudo apt-get install -y btrfs-progs libbtrfs-devel
sudo mount -o remount,user_subvol_rm_allowed /home
AFL++ собирается с llvm версии 13.0
cd $HOME
sudo apt-get install -y clang13.0 llvm13.0-devel lld13.0 gcc-c++ gcc-plugin-devel
git clone https://github.com/AFLplusplus/AFLplusplus.git -b v4.21c
export ALTWRAP_LLVM_VERSION=13.0
cd $HOME/AFLplusplus
make
sudo make install
Устанавливаются зависимости
sudo apt-get install -y gear rpm-build OpenSP docbook-style-dsssl postgresql-common \
docbook-style-dsssl-utils docbook-style-xsl flex gcc-c++ libicu-devel \
libkrb5-devel libldap-devel liblz4-devel libossp-uuid-devel libpam-devel \
libreadline-devel libselinux-devel libssl-devel libuuid-devel meson \
libxslt-devel libzstd-devel openjade perl-DBI perl-devel clang13.0-devel \
python3-dev setproctitle-devel tcl-devel xsltproc zlib-devel
Сборка цели с инструментацией afl для фаззинга сетевого соединения
cd $HOME
git clone https://github.com/fkie-cad/libdesock.git
git clone http://git.altlinux.org/gears/p/postgresql16.git -b 16.4-alt0.c10f2.2
cd libdesock && meson setup ./build && cd ./build && meson compile
cd $HOME/postgresql16/
gear --commit --rpmbuild -- rpmbuild -bp
cd /home/user/RPM/BUILD/postgresql16-16.4
patch -p1 < $HOME/postgres_main_btrfs_ioctl.patch
patch -p1 < $HOME/no_fork.patch
export AFL_USE_ASAN=1
./configure --enable-cassert --enable-debug --prefix=$HOME/buildroot CC=afl-clang-lto CXX=afl-clang-lto++ LD=afl-clang-lto
make install
Патч postgres_main_btrfs_ioctl.patch
При фаззинге БД должна быть одинакова для всех тесткейсов. В противном случае фаззер не сможет воспроизвести найденные краши. Для восстановления БД используются моментальные снапшоты файловой системы btrfs.
diff --git a/src/backend/main/main.c b/src/backend/main/main.c
index ed11e8be7f..e6a1b72eef 100644
--- a/src/backend/main/main.c
+++ b/src/backend/main/main.c
@@ -40,8 +40,13 @@
#include "utils/memutils.h"
#include "utils/pg_locale.h"
#include "utils/ps_status.h"
+#include <btrfs/ioctl.h>
+#define DPATH "/home/user/"
+#define SPATH "/home/user/snapshots/"
+#define SUBVOL "btrfs_data"
+
const char *progname;
static bool reached_main = false;
@@ -50,6 +55,7 @@ static void startup_hacks(const char *progname);
static void init_locale(const char *categoryname, int category, const char *locale);
static void help(const char *progname);
static void check_root(const char *progname);
+static void use_btrfs(void);
/*
@@ -58,6 +64,8 @@ static void check_root(const char *progname);
int
main(int argc, char *argv[])
{
+ use_btrfs();
+
bool do_check_root = true;
reached_main = true;
@@ -448,3 +456,65 @@ __ubsan_default_options(void)
return getenv("UBSAN_OPTIONS");
}
+
+
+static void
+use_btrfs(void)
+{
+ struct btrfs_ioctl_vol_args args;
+ int fd, ret;
+ char *src_path, *dest_path, *subvol;
+ DIR *src_dir, *dest_dir;
+
+ memset(&args, 0, sizeof(args));
+ memcpy(args.name, SUBVOL, strlen(SUBVOL));
+
+ subvol = DPATH SUBVOL;
+ dest_path = DPATH;
+ src_path = SPATH SUBVOL;
+
+ dest_dir = opendir(dest_path);
+ if (dest_dir == NULL) {
+ fprintf(stderr, "opendir(%s): %s\n", dest_path, strerror(errno));
+ abort();
+ }
+ fd = dirfd(dest_dir);
+ if (fd == -1) {
+ fprintf(stderr, "dirfd(%s): %s\n", dest_path, strerror(errno));
+ abort();
+ }
+
+ fprintf(stdout,"Delete subvolume: '%s'\n", subvol);
+ ret = ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &args);
+ if (ret < 0) {
+ if (errno != ENOENT) {
+ fprintf(stderr, "ioctl(%s, BTRFS_IOC_SNAP_DESTROY): %s\n", dest_path,
+ strerror(errno));
+ abort();
+ }
+ fprintf(stdout,"Subvolume '%s': %s\n", subvol, strerror(errno));
+ }
+
+ src_dir = opendir(src_path);
+ if (src_dir == NULL) {
+ fprintf(stderr, "opendir(%s): %s\n", src_path, strerror(errno));
+ abort();
+ }
+
+ args.fd = dirfd(src_dir);
+ if (args.fd == -1) {
+ fprintf(stderr, "dirfd(%s): %s\n", src_path, strerror(errno));
+ abort();
+ }
+
+ fprintf(stdout,"Create a snapshot of '%s' in '%s'\n", src_path, subvol);
+ ret = ioctl(fd, BTRFS_IOC_SNAP_CREATE, &args);
+ if (ret < 0) {
+ fprintf(stderr, "ioctl(%s, BTRFS_IOC_SNAP_CREATE): %s\n", src_path,
+ strerror(errno));
+ abort();
+ }
+
+ closedir(src_dir);
+ closedir(dest_dir);
+}
Патч no_fork.patch
Сервер postgresql16 должен работать в один процесс. Патч также отключает вспомогательные службы, напр. SysLogger, WalWriter, BgWriter, кроме Checkpointer. Без службы Checkpointer с некоторыми тесткейсами postgres крашится. Вызов функции pause() нужен для получения SIGCHLD от дочернего процесса, вызванного в макросе StartupDataBase. Не получив SIGCHLD процесс не станет считывать входные данные. Функция maybe_start_bgworkers, предположительно, нужна для вызова пользовательских фоновых процессов, для фаззинга лишняя.
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index a0f7084018..57fca666a8 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -201,10 +201,10 @@ retry:
* cycles checking for this very rare condition, and this should cause
* us to exit quickly in most cases.)
*/
- if (event.events & WL_POSTMASTER_DEATH)
- ereport(FATAL,
- (errcode(ERRCODE_ADMIN_SHUTDOWN),
- errmsg("terminating connection due to unexpected postmaster exit")));
+ // if (event.events & WL_POSTMASTER_DEATH)
+ // ereport(FATAL,
+ // (errcode(ERRCODE_ADMIN_SHUTDOWN),
+ // errmsg("terminating connection due to unexpected postmaster exit")));
/* Handle interrupt. */
if (event.events & WL_LATCH_SET)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index b42aae41fc..dc3f14300d 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -247,13 +247,13 @@ bool send_abort_for_kill = false;
/* PIDs of special child processes; 0 when not running */
static pid_t StartupPID = 0,
- BgWriterPID = 0,
+ BgWriterPID = 1,
CheckpointerPID = 0,
- WalWriterPID = 0,
- WalReceiverPID = 0,
- AutoVacPID = 0,
- PgArchPID = 0,
- SysLoggerPID = 0;
+ WalWriterPID = 1,
+ WalReceiverPID = 1,
+ AutoVacPID = 1,
+ PgArchPID = 1,
+ SysLoggerPID = 1;
/* Startup process's status */
typedef enum
@@ -1157,7 +1157,7 @@ PostmasterMain(int argc, char *argv[])
/*
* If enabled, start up syslogger collection subprocess
*/
- SysLoggerPID = SysLogger_Start();
+ // SysLoggerPID = SysLogger_Start();
/*
* Reset whereToSendOutput from DestDebug (its starting state) to
@@ -1459,9 +1459,9 @@ PostmasterMain(int argc, char *argv[])
Assert(StartupPID != 0);
StartupStatus = STARTUP_RUNNING;
pmState = PM_STARTUP;
-
+ pause();
/* Some workers may be scheduled to start now */
- maybe_start_bgworkers();
+ // maybe_start_bgworkers();
status = ServerLoop();
@@ -1849,8 +1849,8 @@ ServerLoop(void)
MaybeStartWalReceiver();
/* Get other worker processes running, if needed */
- if (StartWorkerNeeded || HaveCrashedWorker)
- maybe_start_bgworkers();
+ // if (StartWorkerNeeded || HaveCrashedWorker)
+ // maybe_start_bgworkers();
#ifdef HAVE_PTHREAD_IS_THREADED_NP
@@ -3103,7 +3103,7 @@ process_pm_child_exit(void)
PgArchPID = StartArchiver();
/* workers may be scheduled to start now */
- maybe_start_bgworkers();
+ // maybe_start_bgworkers();
/* at this point we are really open for business */
ereport(LOG,
@@ -4165,7 +4165,8 @@ BackendStartup(Port *port)
#ifdef EXEC_BACKEND
pid = backend_forkexec(port);
#else /* !EXEC_BACKEND */
- pid = fork_process();
+ // pid = fork_process();
+ pid = 0;
if (pid == 0) /* child */
{
free(bn);
diff --git a/src/include/port.h b/src/include/port.h
index a88d403483..cbb3e056f4 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -512,7 +512,7 @@ extern int wait_result_to_exit_code(int exit_status);
#define HAVE_POLL 1
#define HAVE_POLL_H 1
#define HAVE_READLINK 1
-#define HAVE_SETSID 1
+// #define HAVE_SETSID 1
#define HAVE_SHM_OPEN 1
#define HAVE_SYMLINK 1
#endif
Подготовка базы данных
Создается новый кластер БД data. В кластере создается БД dbfuzz. БД заполняется путем поочередного запуска sql скриптов из каталога src/test/regress/sql из исходного кода postgresql.
Подготовка входных данных
В качестве входных данных используются тесткейсы из каталога src/test/regress/sql из исходного кода postgresql, конвертированные в бинарные файлы с помощью wireshark (см. инструкцию AFLNET).
Данные тесткейсы покрывают около 50% кода. Некоторые тесткейсы очень большие и их рекомендуется разделить на несколько тесткейсов.
Запуск фаззинг-тестирования
export AFL_SKIP_CPUFREQ=1
export AFL_PRELOAD="$HOME/libdesock/build/libdesock.so"
mkdir -pv $HOME/snapshots/
btrfs subvolume create $HOME/btrfs_data
tar -xpf $HOME/data.tar.gz -C $HOME/btrfs_data
btrfs subvolume snapshot $HOME/btrfs_data $HOME/snapshots/
afl-fuzz -i $HOME/seeds -o $HOME/out -t 20000 -- $HOME/buildroot/bin/postgres -D $HOME/btrfs_data/data
Артефакты фаззинга находятся в каталоге $HOME/out.
Недостаток данного метода фаззинг-тестирования - низкая скорость фаззинга.
Преимущество - высокое покрытие кода.
Отключение вспомогательных служб WalWriter, BgWriter и пр. - вопрос спорный. Это незначительно увеличивает скорость фаззинга. Предполается, что отключение этих служб может привести к ложным срабатываниям фаззера как в случае Checkpointer. При включении вспомогательных служб наблюдается низкая стабильность фаззинга.
Покрытие кода фаззинг-тестированием
Сборка postgresql16 с инструментацией gcov
cd $HOME/RPM/BUILD/postgresql16-16.4
make clean
./configure --prefix=$HOME/buildroot-cov --enable-coverage
make install
Подготовка
cd $HOME
git clone https://github.com/vanhauser-thc/afl-cov.git
cd afl-cov
patch -p1 < $HOME/afl-cov.patch
Патч afl-cov.patch
Патч добавляет возможность использования переменной окружения LD_PRELOAD для сбора покрытия кода.
diff --git a/afl-cov b/afl-cov index f021ffe..d58189f 100755 --- a/afl-cov +++ b/afl-cov @@ -878,7 +878,7 @@ def run_cmd( cmd = "timeout -s KILL %s %s" % (timeout, cmd) if aflrun is True and len(fn) > 0: - cmd = "cat " + fn + " | " + cmd + cmd = "cat " + fn + " | " + cargs.env + " " + cmd if cargs.verbose: if log_file: @@ -1548,6 +1548,9 @@ def parse_cmdline() -> argparse.Namespace: p.add_argument( "--afl-file", type=str, help="Filepath that is passed to AFL with -f argument", default="" ) + p.add_argument( + "--env", type=str, help="Set environment variable", default="" + ) return p.parse_args()
Сбор покрытия кода
/home/user/afl-cov/afl-cov -e "$HOME/buildroot-cov/bin/postgres -D $HOME/btrfs_data/data" -c $HOME/RPM/BUILD/postgresql16-16.4 -d $HOME/out -T 20 --coverage-at-exit --overwrite --env "LD_PRELOAD=/home/user/libdesock/build/libdesock.so"
Отчет о покрытии кода находится в каталоге $HOME/out/cov
Подготовка фаззинг-оберток
Здесь представлены несколько оберток в качестве примера. В качестве основы используются обертки из проекта oss-fuzz.
Фаззинг-обертка src/backend/fuzzer/raw_parser_fuzzer.c
Фаззинг-обертка для функции raw_parser (src/backend/parser/parser.c:42). Выполняет лексический и грамматический анализ sql запроса. Возвращает список необработанных (непроанализированных) деревьев анализа. В качестве входных данных используются тесткейсы из каталога src/test/regress/sql из исходного кода postgresql.
#include "postgres.h"
#include "mb/pg_wchar.h"
#include "utils/memutils.h"
#include "utils/memdebug.h"
#include "include/miscadmin.h"
#include "include/parser/parser.h"
#include <unistd.h>
#ifndef __AFL_FUZZ_TESTCASE_LEN
ssize_t fuzz_len;
#define __AFL_FUZZ_TESTCASE_LEN fuzz_len
unsigned char fuzz_buf[1024000];
#define __AFL_FUZZ_TESTCASE_BUF fuzz_buf
#define __AFL_FUZZ_INIT() void sync(void);
#define __AFL_LOOP(x) ((fuzz_len = read(0, fuzz_buf, sizeof(fuzz_buf))) > 0 ? 1 : 0)
#define __AFL_INIT() sync()
#endif
__AFL_FUZZ_INIT();
const char *progname = "raw_parser_fuzzer";
int
main(int argc, char **argv)
{
__AFL_INIT();
unsigned char *data = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(1)) {
int size = __AFL_FUZZ_TESTCASE_LEN;
sigjmp_buf local_sigjmp_buf;
char *buffer;
buffer = (char *) calloc(size+1, sizeof(char));
memcpy(buffer, data, size);
MemoryContextInit();
set_stack_base();
if(!sigsetjmp(local_sigjmp_buf,0)){
error_context_stack = NULL;
PG_exception_stack = &local_sigjmp_buf;
raw_parser(buffer, RAW_PARSE_DEFAULT);
}
free(buffer);
FlushErrorState();
//MemoryContextReset(TopMemoryContext);
TopMemoryContext->ident = NULL;
TopMemoryContext->methods->delete_context(TopMemoryContext);
VALGRIND_DESTROY_MEMPOOL(TopMemoryContext);
}
return 0;
}
Фаззинг-обертка src/backend/fuzzer/json_in_fuzzer.c
Фаззинг-обертка для функции json_in (src/backend/utils/adt/json.c:124). Парсит входные данные на наличие json формата. Преобразует строку во внутреннюю форму. Функция может быть вызвана следующим sql запросом: “SELECT ‘{“abc”:1}’::json;”.
#include "postgres.h"
#include "mb/pg_wchar.h"
#include "utils/memutils.h"
#include "utils/memdebug.h"
#include "include/miscadmin.h"
#include <unistd.h>
#ifndef __AFL_FUZZ_TESTCASE_LEN
ssize_t fuzz_len;
#define __AFL_FUZZ_TESTCASE_LEN fuzz_len
unsigned char fuzz_buf[1024000];
#define __AFL_FUZZ_TESTCASE_BUF fuzz_buf
#define __AFL_FUZZ_INIT() void sync(void);
#define __AFL_LOOP(x) ((fuzz_len = read(0, fuzz_buf, sizeof(fuzz_buf))) > 0 ? 1 : 0)
#define __AFL_INIT() sync()
#endif
__AFL_FUZZ_INIT();
const char *progname = "json_in_fuzzer";
int
main(int argc, char **argv)
{
Oid functionId = 321; // 'json_in' function, src/backend/utils/fmgrtab.c
Oid typioparam = 114; // typname => 'json', src/include/catalog/pg_type.dat
int32 typmod = -1; // always -1
__AFL_INIT();
unsigned char *data = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(1)) {
int size = __AFL_FUZZ_TESTCASE_LEN;
sigjmp_buf local_sigjmp_buf;
char *buffer;
buffer = (char *) calloc(size+1, sizeof(char));
memcpy(buffer, data, size);
MemoryContextInit();
set_stack_base();
if(!sigsetjmp(local_sigjmp_buf,0)){
error_context_stack = NULL;
PG_exception_stack = &local_sigjmp_buf;
OidInputFunctionCall(functionId, buffer, typioparam, typmod); // call 'json_in' function
}
free(buffer);
FlushErrorState();
//MemoryContextReset(TopMemoryContext);
TopMemoryContext->ident = NULL;
TopMemoryContext->methods->delete_context(TopMemoryContext);
VALGRIND_DESTROY_MEMPOOL(TopMemoryContext);
}
return 0;
}
Фаззинг-обертка src/backend/fuzzer/timestamp_in_fuzzer.c
Фаззинг-обертка для функции timestamp_in (src/backend/utils/adt/timestamp.c:148). Парсит входные данные на наличие даты и времени. Преобразует строку во внутреннюю форму. Может быть вызвана следующим sql запросом: “SELECT ‘4714-11-24 00:00:00 BC’::timestamp;”.
#include "postgres.h"
#include "mb/pg_wchar.h"
#include "utils/memutils.h"
#include "utils/memdebug.h"
#include "include/miscadmin.h"
#include <unistd.h>
#ifndef __AFL_FUZZ_TESTCASE_LEN
ssize_t fuzz_len;
#define __AFL_FUZZ_TESTCASE_LEN fuzz_len
unsigned char fuzz_buf[1024000];
#define __AFL_FUZZ_TESTCASE_BUF fuzz_buf
#define __AFL_FUZZ_INIT() void sync(void);
#define __AFL_LOOP(x) ((fuzz_len = read(0, fuzz_buf, sizeof(fuzz_buf))) > 0 ? 1 : 0)
#define __AFL_INIT() sync()
#endif
__AFL_FUZZ_INIT();
const char *progname = "timestamp_in_fuzzer";
int
main(int argc, char **argv)
{
pg_timezone_initialize();
Oid functionId = 1312; // 'timestamp_in' function, src/backend/utils/fmgrtab.c
Oid typioparam = 1114; // typname => 'timestamp', src/include/catalog/pg_type.dat
int32 typmod = -1; // always -1
__AFL_INIT();
unsigned char *data = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(1)) {
int size = __AFL_FUZZ_TESTCASE_LEN;
sigjmp_buf local_sigjmp_buf;
char *buffer;
buffer = (char *) calloc(size+1, sizeof(char));
memcpy(buffer, data, size);
MemoryContextInit();
set_stack_base();
if(!sigsetjmp(local_sigjmp_buf,0)){
error_context_stack = NULL;
PG_exception_stack = &local_sigjmp_buf;
OidInputFunctionCall(functionId, buffer, typioparam, typmod); // call 'timestamp_in' function
}
free(buffer);
FlushErrorState();
//MemoryContextReset(TopMemoryContext);
TopMemoryContext->ident = NULL;
TopMemoryContext->methods->delete_context(TopMemoryContext);
VALGRIND_DESTROY_MEMPOOL(TopMemoryContext);
}
return 0;
}
Фаззинг-обертка src/backend/fuzzer/array_in_fuzzer.c
Фаззинг-обертка для функции array_in (src/backend/utils/adt/arrayfuncs.c:176). Парсит входные данные на наличие массива. Конвертирует массив из внешнего string формата во внутренний формат. Используется массив с элементами формата text. Может быть вызвана следующим sql запросом: “select ‘{{{1,2,3,4},{2,3,4,5}},{{3,4,5,6},{4,5,6,7}}}’::text[];”.
#include "postgres.h"
#include "mb/pg_wchar.h"
#include "utils/memutils.h"
#include "utils/memdebug.h"
#include "include/miscadmin.h"
#include "include/fmgr.h"
#include "include/utils/array.h"
#include <unistd.h>
#ifndef __AFL_FUZZ_TESTCASE_LEN
ssize_t fuzz_len;
#define __AFL_FUZZ_TESTCASE_LEN fuzz_len
unsigned char fuzz_buf[1024000];
#define __AFL_FUZZ_TESTCASE_BUF fuzz_buf
#define __AFL_FUZZ_INIT() void sync(void);
#define __AFL_LOOP(x) ((fuzz_len = read(0, fuzz_buf, sizeof(fuzz_buf))) > 0 ? 1 : 0)
#define __AFL_INIT() sync()
#endif
__AFL_FUZZ_INIT();
const char *progname = "array_in_fuzzer";
int
main(int argc, char **argv)
{
Oid functionId = 750; // 'array_in' function, src/backend/utils/fmgrtab.c
Oid typioparam = 25; // typname => 'text', src/include/catalog/pg_type.dat
int32 typmod = -1; // always -1
__AFL_INIT();
unsigned char *data = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(1)) {
int size = __AFL_FUZZ_TESTCASE_LEN;
sigjmp_buf local_sigjmp_buf;
char *buffer;
buffer = (char *) calloc(size+1, sizeof(char));
memcpy(buffer, data, size);
MemoryContextInit();
set_stack_base();
if(!sigsetjmp(local_sigjmp_buf,0)){
error_context_stack = NULL;
PG_exception_stack = &local_sigjmp_buf;
FmgrInfo flinfo;
ArrayMetaState *my_extra;
fmgr_info(functionId, &flinfo);
flinfo.fn_extra = MemoryContextAlloc(flinfo.fn_mcxt,
sizeof(ArrayMetaState));
my_extra = (ArrayMetaState *) flinfo.fn_extra;
my_extra->element_type = typioparam;
my_extra->typlen = -1;
my_extra->typbyval = false;
my_extra->typalign = 'i';
my_extra->typdelim = ',';
my_extra->typioparam = 25; // typname => 'text', src/include/catalog/pg_type.dat
my_extra->typiofunc = 46;
fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc,
flinfo.fn_mcxt);
InputFunctionCall(&flinfo, buffer, typioparam, typmod); // call 'array_in' function
}
free(buffer);
FlushErrorState();
//MemoryContextReset(TopMemoryContext);
TopMemoryContext->ident = NULL;
TopMemoryContext->methods->delete_context(TopMemoryContext);
VALGRIND_DESTROY_MEMPOOL(TopMemoryContext);
}
return 0;
}
src/backend/fuzzer/Makefile
subdir = src/backend/fuzzer
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
subsysfilename = objfiles.txt
SUBDIROBJS = $(SUBDIRS:%=%/$(subsysfilename))
# make function to expand objfiles.txt contents
expand_subsys = $(foreach file,$(1),$(if $(filter %/objfiles.txt,$(file)),$(patsubst ../../src/backend/%,%,$(addprefix $(top_builddir)/,$(shell cat $(file)))),$(file)))
objfiles.txt: Makefile $(SUBDIROBJS) $(OBJS_FUZZERS)
# Don't rebuild the list if only the OBJS have changed.
$(if $(filter-out $(OBJS_FUZZERS),$?),( $(if $(SUBDIROBJS),cat $(SUBDIROBJS); )echo $(addprefix $(subdir)/,$(OBJS_FUZZERS)) ) >$@,touch $@)
SUBDIRS = ../access ../bootstrap ../catalog ../parser ../commands ../executor ../foreign ../lib ../libpq \
../main ../nodes ../optimizer ../partitioning ../port ../postmaster \
../regex ../replication ../rewrite ../backup \
../statistics ../storage ../tcop ../tsearch ../utils $(top_builddir)/src/timezone \
../jit ../archive
OBJS = \
$(LOCALOBJS) \
$(SUBDIROBJS) \
$(top_builddir)/src/common/libpgcommon_srv.a \
$(top_builddir)/src/port/libpgport_srv.a \
LIBS_FUZZER = -lz -ldl -lpthread -lrt -luuid -licui18n -licuuc -licudata
OBJS_FUZZERS = $(filter-out ../main/objfiles.txt, $(OBJS))
TARGETS = $(basename $(wildcard ./*.c))
fuzzer: $(TARGETS)
$(TARGETS): %: %.o $(OBJS_FUZZERS)
$(CXX) $(CFLAGS) $(call expand_subsys,$^) -o $@ $(LIBS_FUZZER)
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $^ -I$(top_builddir)/src
В итоге каталог $HOME/RPM/BUILD/postgresql16-16.4/src/backend/fuzzer содержит следующие файлы:
Makefile
array_in_fuzzer.c
json_in_fuzzer.c
raw_parser_fuzzer.c
timestamp_in_fuzzer.c
Сборка фаззинг-оберток с инструментацией afl
cd $HOME/RPM/BUILD/postgresql16-16.4
make clean
./configure --enable-cassert --enable-debug --prefix=$HOME/buildroot CC=afl-clang-lto CXX=afl-clang-lto++ LD=afl-clang-lto
make install
cd src/backend/fuzzer
make fuzzer
В итоге каталог $HOME/RPM/BUILD/postgresql16-16.4/src/backend/fuzzer содержит следующие исполняемые файлы:
array_in_fuzzer
json_in_fuzzer
raw_parser_fuzzer
timestamp_in_fuzzer
Запуск фаззинг-тестирования оберток
Запуск обертки raw_parser. Остальные обертки запускаются аналогично.
afl-fuzz -i $HOME/RPM/BUILD/postgresql16-16.4/src/test/regress/sql -o $HOME/out-raw_parser -t 5000 -- $HOME/RPM/BUILD/postgresql16-16.4/src/backend/fuzzer/raw_parser_fuzzer
Артефакты фаззинга находятся в каталоге $HOME/out-raw_parser.
Преимущество данного метода фаззинг-тестирования - высокая скорость фаззинга.
Недостаток - низкое покрытие кода.
Полный список внутренних функций, выполняющих преобразование из одного формата postgresql в другой, находится в файле src/backend/utils/fmgrtab.c:27 (файл генерируется при сборке).
Информация о типах данных postgresql, используемая в фаззинг-обертках, находится в файле src/include/catalog/pg_type.dat.
Покрытие кода фаззинг-тестированием оберток
Сборка фаззинг-оберток с инструментацией gcov
cd $HOME/RPM/BUILD/postgresql16-16.4
make clean
./configure --prefix=$HOME/buildroot-cov --enable-coverage
make install
cd src/backend/fuzzer
make fuzzer
Сбор покрытия кода
Запуск сбора покрытия кода для обертки raw_parser. Остальные обертки запускаются аналогично.
/home/user/afl-cov/afl-cov -e "$HOME/RPM/BUILD/postgresql16-16.4/src/backend/fuzzer/raw_parser" -c $HOME/RPM/BUILD/postgresql16-16.4 -d $HOME/out-raw_parser -T 5 --coverage-at-exit --overwrite
Отчет о покрытии кода находится в каталоге $HOME/out-raw_parser/cov
Автор: Евгений Горбанев