PostgreSQL16 fuzzing technique

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

Технология фаззинг-тестирования 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 &quot;utils/memutils.h&quot;
 #include &quot;utils/pg_locale.h&quot;
 #include &quot;utils/ps_status.h&quot;
+#include &lt;btrfs/ioctl.h&gt;
 
 
+#define DPATH &quot;/home/user/&quot;
+#define SPATH &quot;/home/user/snapshots/&quot;
+#define SUBVOL &quot;btrfs_data&quot;
+
 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(&quot;UBSAN_OPTIONS&quot;);
 }
+
+
+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(&amp;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, &quot;opendir(%s): %s\n&quot;, dest_path, strerror(errno));
+       abort();
+   }
+   fd = dirfd(dest_dir);
+   if (fd == -1) {
+       fprintf(stderr, &quot;dirfd(%s): %s\n&quot;, dest_path, strerror(errno));
+       abort();
+   }
+
+   fprintf(stdout,&quot;Delete subvolume: '%s'\n&quot;, subvol);
+   ret = ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &amp;args);
+   if (ret &lt; 0) {
+       if (errno != ENOENT) {
+           fprintf(stderr, &quot;ioctl(%s, BTRFS_IOC_SNAP_DESTROY): %s\n&quot;, dest_path,
+               strerror(errno));
+           abort();
+       }
+       fprintf(stdout,&quot;Subvolume '%s': %s\n&quot;, subvol, strerror(errno));
+   }
+
+   src_dir = opendir(src_path);
+   if (src_dir == NULL) {
+       fprintf(stderr, &quot;opendir(%s): %s\n&quot;, src_path, strerror(errno));
+       abort();
+   }
+
+   args.fd = dirfd(src_dir);
+   if (args.fd == -1) {
+       fprintf(stderr, &quot;dirfd(%s): %s\n&quot;, src_path, strerror(errno));
+       abort();
+   }
+
+   fprintf(stdout,&quot;Create a snapshot of '%s' in '%s'\n&quot;, src_path, subvol);
+   ret = ioctl(fd, BTRFS_IOC_SNAP_CREATE, &amp;args);
+   if (ret &lt; 0) {
+       fprintf(stderr, &quot;ioctl(%s, BTRFS_IOC_SNAP_CREATE): %s\n&quot;, 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(); //wait for StartupDataBase() to complete
 	/* 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

Автор: Евгений Горбанев