Revert "Add built-in fuzzing harnesses for security testing."

This reverts commit 4a18907b41.

inadvertenly pushed, mea culpa
This commit is contained in:
Andrew Dunstan
2026-04-10 09:53:58 -04:00
parent 3f8913f683
commit eec8e234bd
15 changed files with 0 additions and 1862 deletions
-3
View File
@@ -43,9 +43,6 @@ option('cassert', type: 'boolean', value: false,
option('tap_tests', type: 'feature', value: 'auto',
description: 'Enable TAP tests')
option('fuzzing', type: 'boolean', value: false,
description: 'Build fuzz testing targets')
option('injection_points', type: 'boolean', value: false,
description: 'Enable injection points')
-98
View File
@@ -1,98 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_b64decode.c
* Fuzzing harness for pg_b64_decode()
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_b64decode.c
*
* This harness feeds arbitrary byte sequences to pg_b64_decode(),
* which decodes base64-encoded data per RFC 4648. The function is
* a pure computation with no global state.
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <stdio.h>
#include "common/base64.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
int dstlen;
uint8 *dst;
if (size == 0)
return 0;
/* Allocate a buffer large enough for any valid decoding */
dstlen = pg_b64_dec_len((int) size);
dst = malloc(dstlen);
if (!dst)
return 0;
(void) pg_b64_decode((const char *) data, (int) size, dst, dstlen);
free(dst);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-105
View File
@@ -1,105 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_conninfo.c
* Fuzzing harness for libpq connection string parsing
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_conninfo.c
*
* This harness feeds arbitrary byte sequences to PQconninfoParse(),
* which parses both key=value connection strings and PostgreSQL URIs
* (postgresql://...). The function is completely standalone and
* requires no database connection or other initialization.
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <stdio.h>
#include "libpq-fe.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *str;
char *errmsg = NULL;
PQconninfoOption *opts;
if (size == 0)
return 0;
/* PQconninfoParse expects a NUL-terminated string */
str = malloc(size + 1);
if (!str)
return 0;
memcpy(str, data, size);
str[size] = '\0';
opts = PQconninfoParse(str, &errmsg);
if (opts)
PQconninfoFree(opts);
if (errmsg)
PQfreemem(errmsg);
free(str);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-104
View File
@@ -1,104 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_json.c
* Fuzzing harness for the non-incremental JSON parser
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_json.c
*
* This harness feeds arbitrary byte sequences to pg_parse_json() via
* makeJsonLexContextCstringLen(). It uses the null semantic action so
* that only lexing and structural validation are exercised.
*
* Build with a fuzzing engine (e.g. libFuzzer via -fsanitize=fuzzer)
* or in standalone mode, which reads files named on the command line.
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <stdio.h>
#include "common/jsonapi.h"
#include "mb/pg_wchar.h"
/*
* Entry point for libFuzzer and other engines that call
* LLVMFuzzerTestOneInput().
*/
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
JsonLexContext lex;
if (size == 0)
return 0;
makeJsonLexContextCstringLen(&lex, (const char *) data, size,
PG_UTF8, true);
setJsonLexContextOwnsTokens(&lex, true);
(void) pg_parse_json(&lex, &nullSemAction);
freeJsonLexContext(&lex);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-127
View File
@@ -1,127 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_json_incremental.c
* Fuzzing harness for the incremental JSON parser
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_json_incremental.c
*
* This harness feeds arbitrary byte sequences to
* pg_parse_json_incremental() in small chunks, exercising the
* incremental lexer's boundary handling. The first byte of the input
* is used to vary the chunk size so that the fuzzer can explore
* different splitting strategies.
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <stdio.h>
#include "common/jsonapi.h"
#include "mb/pg_wchar.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
JsonLexContext lex;
size_t chunk_size;
size_t offset;
if (size < 2)
return 0;
/*
* Use the first byte to select a chunk size between 1 and 128. This lets
* the fuzzer explore different ways of splitting the same input across
* incremental parse calls.
*/
chunk_size = (data[0] % 128) + 1;
data++;
size--;
makeJsonLexContextIncremental(&lex, PG_UTF8, true);
setJsonLexContextOwnsTokens(&lex, true);
offset = 0;
while (offset < size)
{
size_t remaining = size - offset;
size_t to_feed = (remaining < chunk_size) ? remaining : chunk_size;
bool is_last = (offset + to_feed >= size);
JsonParseErrorType result;
result = pg_parse_json_incremental(&lex, &nullSemAction,
(const char *) data + offset,
to_feed, is_last);
offset += to_feed;
if (result != JSON_SUCCESS && result != JSON_INCOMPLETE)
break;
if (result == JSON_SUCCESS)
break;
}
freeJsonLexContext(&lex);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-102
View File
@@ -1,102 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_parsepgarray.c
* Fuzzing harness for parsePGArray()
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_parsepgarray.c
*
* This harness feeds arbitrary byte sequences to parsePGArray(),
* which parses PostgreSQL array literal syntax ({elem,"elem",...})
* including nested arrays, quoted elements, and backslash escaping.
* The function is standalone and requires no database connection.
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <stdio.h>
#include "fe_utils/string_utils.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *str;
char **items = NULL;
int nitems = 0;
if (size == 0)
return 0;
/* parsePGArray expects a NUL-terminated string */
str = malloc(size + 1);
if (!str)
return 0;
memcpy(str, data, size);
str[size] = '\0';
(void) parsePGArray(str, &items, &nitems);
free(items);
free(str);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-211
View File
@@ -1,211 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_pgbench_expr.c
* Fuzzing harness for the pgbench expression parser
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_pgbench_expr.c
*
* This harness feeds arbitrary byte sequences to the pgbench expression
* parser (expr_yyparse). The parser exercises a Bison grammar and Flex
* lexer that handle arithmetic expressions, function calls, boolean
* operators, and CASE expressions.
*
* The pgbench expression parser normally calls syntax_error() on any
* parse error, which calls exit(1). This harness provides replacement
* definitions of syntax_error(), strtoint64(), and strtodouble() so
* that the generated parser and lexer objects can link without pulling
* in pgbench.c. Our syntax_error() uses longjmp to recover rather
* than exiting.
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <setjmp.h>
#include <stdio.h>
#include "pgbench.h"
#include "fe_utils/psqlscan.h"
static sigjmp_buf fuzz_jmp_buf;
static void free_pgbench_expr(PgBenchExpr *expr);
static const PsqlScanCallbacks fuzz_callbacks = {
NULL, /* no variable lookup needed */
};
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
/*
* Replacement for pgbench.c's syntax_error(). Instead of calling exit(),
* we longjmp back to the fuzzer's recovery point.
*/
void
syntax_error(const char *source, int lineno, const char *line,
const char *command, const char *msg,
const char *more, int column)
{
siglongjmp(fuzz_jmp_buf, 1);
}
/*
* Replacement for pgbench.c's strtoint64().
*/
bool
strtoint64(const char *str, bool errorOK, int64 *result)
{
char *end;
errno = 0;
*result = strtoi64(str, &end, 10);
if (errno == ERANGE || errno != 0 || end == str || *end != '\0')
return false;
return true;
}
/*
* Replacement for pgbench.c's strtodouble().
*/
bool
strtodouble(const char *str, bool errorOK, double *dv)
{
char *end;
errno = 0;
*dv = strtod(str, &end);
if (errno == ERANGE || errno != 0 || end == str || *end != '\0')
return false;
return true;
}
/*
* Recursively free a PgBenchExpr tree.
*/
static void
free_pgbench_expr(PgBenchExpr *expr)
{
PgBenchExprLink *link;
PgBenchExprLink *next;
if (expr == NULL)
return;
switch (expr->etype)
{
case ENODE_CONSTANT:
break;
case ENODE_VARIABLE:
pg_free(expr->u.variable.varname);
break;
case ENODE_FUNCTION:
for (link = expr->u.function.args; link != NULL; link = next)
{
next = link->next;
free_pgbench_expr(link->expr);
pg_free(link);
}
break;
}
pg_free(expr);
}
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *str;
PsqlScanState sstate;
yyscan_t yyscanner;
PgBenchExpr *result = NULL;
if (size == 0)
return 0;
/* expr_yyparse needs a NUL-terminated string */
str = malloc(size + 1);
if (!str)
return 0;
memcpy(str, data, size);
str[size] = '\0';
sstate = psql_scan_create(&fuzz_callbacks);
psql_scan_setup(sstate, str, (int) size, 0, true);
yyscanner = expr_scanner_init(sstate, "fuzz", 1, 0, "\\set");
if (sigsetjmp(fuzz_jmp_buf, 0) == 0)
{
(void) expr_yyparse(&result, yyscanner);
}
/* Clean up regardless of success or longjmp */
expr_scanner_finish(yyscanner);
psql_scan_finish(sstate);
psql_scan_destroy(sstate);
if (result)
free_pgbench_expr(result);
free(str);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-127
View File
@@ -1,127 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_pglz.c
* Fuzzing harness for the PostgreSQL LZ decompressor
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_pglz.c
*
* This harness feeds arbitrary byte sequences to pglz_decompress(),
* which decompresses PostgreSQL's native LZ-compressed data. The
* decompressor is a pure function with no global state, making it
* ideal for fuzzing.
*
* The first 4 bytes of the fuzzer input are interpreted as the
* claimed raw (uncompressed) size in little-endian byte order,
* capped at 1 MB. The remaining bytes are the compressed payload.
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <stdio.h>
#include <string.h>
#include "common/pg_lzcompress.h"
#define MAX_RAW_SIZE (1024 * 1024)
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
int32 rawsize;
char *dest;
/* Need at least 4 bytes for the raw size, plus some compressed data */
if (size < 5)
return 0;
/* Extract claimed raw size from first 4 bytes (little-endian) */
rawsize = (int32) data[0] |
((int32) data[1] << 8) |
((int32) data[2] << 16) |
((int32) data[3] << 24);
/* Reject nonsensical sizes */
if (rawsize <= 0 || rawsize > MAX_RAW_SIZE)
return 0;
dest = malloc(rawsize);
if (!dest)
return 0;
/* Try decompression with completeness check */
(void) pglz_decompress((const char *) data + 4,
(int32) (size - 4),
dest,
rawsize,
true);
/* Also try without completeness check to exercise that path */
(void) pglz_decompress((const char *) data + 4,
(int32) (size - 4),
dest,
rawsize,
false);
free(dest);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-162
View File
@@ -1,162 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_rawparser.c
* Fuzzing harness for the PostgreSQL raw SQL parser
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_rawparser.c
*
* This harness feeds arbitrary byte sequences to raw_parser(), which
* performs lexical and grammatical analysis of SQL statements. It
* performs minimal backend initialization (just the memory-context
* subsystem) and catches all parser errors via PG_TRY/PG_CATCH.
*
* The harness links against postgres_lib using archive semantics.
* It provides stub definitions for symbols normally supplied by
* main/main.c (progname, parse_dispatch_option) so that the linker
* does not pull in main.o and conflict with the harness's own main().
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <stdio.h>
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "parser/parser.h"
#include "postmaster/postmaster.h"
#include "utils/memutils.h"
#include "utils/palloc.h"
/*
* Stub definitions for symbols that main/main.c normally provides.
* By defining them here we prevent the archive linker from pulling in
* main.o (which defines its own main()).
*/
const char *progname = "fuzz_rawparser";
DispatchOption
parse_dispatch_option(const char *name)
{
return DISPATCH_POSTMASTER;
}
static bool initialized = false;
static void fuzz_initialize(void);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
/*
* One-time initialization: set up memory contexts and encoding.
*/
static void
fuzz_initialize(void)
{
MemoryContextInit();
SetDatabaseEncoding(PG_UTF8);
SetMessageEncoding(PG_UTF8);
}
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *str;
MemoryContext fuzz_context;
MemoryContext oldcontext;
if (!initialized)
{
fuzz_initialize();
initialized = true;
}
if (size == 0)
return 0;
/*
* Create a temporary memory context for each parse attempt so that all
* allocations made by the parser are freed afterwards.
*/
fuzz_context = AllocSetContextCreate(TopMemoryContext,
"Fuzz Context",
ALLOCSET_DEFAULT_SIZES);
oldcontext = MemoryContextSwitchTo(fuzz_context);
/* raw_parser() expects a NUL-terminated string */
str = palloc(size + 1);
memcpy(str, data, size);
str[size] = '\0';
PG_TRY();
{
(void) raw_parser(str, RAW_PARSE_DEFAULT);
}
PG_CATCH();
{
FlushErrorState();
}
PG_END_TRY();
MemoryContextSwitchTo(oldcontext);
MemoryContextDelete(fuzz_context);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-193
View File
@@ -1,193 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_regex.c
* Fuzzing harness for the PostgreSQL regular expression engine
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_regex.c
*
* This harness feeds arbitrary byte sequences to pg_regcomp() and
* pg_regexec(), exercising the full POSIX/ARE regex compiler and
* executor. The first byte selects regex flags; the remaining bytes
* are split between the regex pattern and a test subject string.
*
* The harness links against postgres_lib using archive semantics.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <stdio.h>
#include "catalog/pg_collation.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "regex/regex.h"
#include "utils/memutils.h"
#include "utils/palloc.h"
/* Stubs for symbols from main/main.c */
const char *progname = "fuzz_regex";
DispatchOption
parse_dispatch_option(const char *name)
{
return DISPATCH_POSTMASTER;
}
static bool initialized = false;
static void fuzz_initialize(void);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
static void
fuzz_initialize(void)
{
MemoryContextInit();
SetDatabaseEncoding(PG_UTF8);
SetMessageEncoding(PG_UTF8);
}
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
uint8_t flags_byte;
int re_flags;
size_t pat_len;
size_t subj_len;
const char *pat_start;
const char *subj_start;
pg_wchar *pat_wchar;
pg_wchar *subj_wchar;
int pat_wlen;
int subj_wlen;
regex_t re;
regmatch_t matches[10];
MemoryContext fuzz_context;
MemoryContext oldcontext;
if (!initialized)
{
fuzz_initialize();
initialized = true;
}
/* Need at least flags byte + 1 byte of pattern */
if (size < 2)
return 0;
/*
* First byte selects regex flags. We map bits to useful flag combinations
* to get good coverage of different regex modes.
*/
flags_byte = data[0];
re_flags = REG_ADVANCED;
if (flags_byte & 0x01)
re_flags = REG_EXTENDED; /* ERE instead of ARE */
if (flags_byte & 0x02)
re_flags |= REG_ICASE;
if (flags_byte & 0x04)
re_flags |= REG_NEWLINE;
if (flags_byte & 0x08)
re_flags |= REG_NOSUB;
data++;
size--;
/* Split remaining input: first half pattern, second half subject */
pat_len = size / 2;
if (pat_len == 0)
pat_len = 1;
subj_len = size - pat_len;
pat_start = (const char *) data;
subj_start = (const char *) data + pat_len;
fuzz_context = AllocSetContextCreate(TopMemoryContext,
"Fuzz Context",
ALLOCSET_DEFAULT_SIZES);
oldcontext = MemoryContextSwitchTo(fuzz_context);
/* Convert to pg_wchar for the regex API */
pat_wchar = palloc((pat_len + 1) * sizeof(pg_wchar));
pat_wlen = pg_mb2wchar_with_len(pat_start, pat_wchar, (int) pat_len);
if (pg_regcomp(&re, pat_wchar, pat_wlen, re_flags, C_COLLATION_OID) == 0)
{
/* Compile succeeded — try executing against the subject */
if (subj_len > 0)
{
subj_wchar = palloc((subj_len + 1) * sizeof(pg_wchar));
subj_wlen = pg_mb2wchar_with_len(subj_start, subj_wchar,
(int) subj_len);
(void) pg_regexec(&re, subj_wchar, subj_wlen, 0, NULL,
lengthof(matches), matches, 0);
}
pg_regfree(&re);
}
MemoryContextSwitchTo(oldcontext);
MemoryContextDelete(fuzz_context);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-104
View File
@@ -1,104 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_saslprep.c
* Fuzzing harness for pg_saslprep()
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_saslprep.c
*
* This harness feeds arbitrary byte sequences to pg_saslprep(),
* which performs SASLprep normalization (RFC 4013) on UTF-8 strings.
* This involves Unicode NFKC normalization, character mapping, and
* prohibited character detection. The function is standalone and
* requires no database connection.
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <stdio.h>
#include "common/saslprep.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *str;
char *output = NULL;
if (size == 0)
return 0;
/* pg_saslprep expects a NUL-terminated string */
str = malloc(size + 1);
if (!str)
return 0;
memcpy(str, data, size);
str[size] = '\0';
(void) pg_saslprep(str, &output);
if (output)
free(output);
free(str);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-218
View File
@@ -1,218 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_typeinput.c
* Fuzzing harness for PostgreSQL type input functions
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_typeinput.c
*
* This harness feeds arbitrary byte sequences to the backend's type
* input functions: numeric_in, date_in, timestamp_in, timestamptz_in,
* and interval_in. These functions parse textual representations of
* data types and are a key part of PostgreSQL's input validation.
*
* The first byte of input selects which type parser to call; the
* remaining bytes are the type-input string. All functions support
* soft error handling via ErrorSaveContext, so errors are caught
* without ereport/PG_TRY. PG_TRY/PG_CATCH is used as a safety net
* for any unexpected hard errors.
*
* The harness links against postgres_lib using archive semantics.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <stdio.h>
#include "fmgr.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/miscnodes.h"
#include "pgtime.h"
#include "postmaster/postmaster.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/memutils.h"
#include "utils/numeric.h"
#include "utils/palloc.h"
#include "utils/timestamp.h"
/* Stubs for symbols from main/main.c */
const char *progname = "fuzz_typeinput";
DispatchOption
parse_dispatch_option(const char *name)
{
return DISPATCH_POSTMASTER;
}
/* Type selector values */
#define FUZZ_NUMERIC 0
#define FUZZ_DATE 1
#define FUZZ_TIMESTAMP 2
#define FUZZ_TIMESTAMPTZ 3
#define FUZZ_INTERVAL 4
#define FUZZ_NTYPES 5
static bool initialized = false;
static void fuzz_initialize(void);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
static void
fuzz_initialize(void)
{
MemoryContextInit();
SetDatabaseEncoding(PG_UTF8);
SetMessageEncoding(PG_UTF8);
/*
* Initialize timezone subsystem. Use "GMT" because it is resolved
* without filesystem access (the timezone data directory may not exist in
* a fuzzing build).
*/
pg_timezone_initialize();
}
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *str;
int type_sel;
LOCAL_FCINFO(fcinfo, 3);
ErrorSaveContext escontext;
MemoryContext fuzz_context;
MemoryContext oldcontext;
if (!initialized)
{
fuzz_initialize();
initialized = true;
}
/* Need at least type selector + 1 byte of input */
if (size < 2)
return 0;
type_sel = data[0] % FUZZ_NTYPES;
data++;
size--;
fuzz_context = AllocSetContextCreate(TopMemoryContext,
"Fuzz Context",
ALLOCSET_DEFAULT_SIZES);
oldcontext = MemoryContextSwitchTo(fuzz_context);
/* Build a NUL-terminated string from the input */
str = palloc(size + 1);
memcpy(str, data, size);
str[size] = '\0';
/* Set up ErrorSaveContext for soft error handling */
memset(&escontext, 0, sizeof(escontext));
escontext.type = T_ErrorSaveContext;
escontext.error_occurred = false;
escontext.details_wanted = false;
/* Set up FunctionCallInfo */
memset(fcinfo, 0, SizeForFunctionCallInfo(3));
fcinfo->nargs = 3;
fcinfo->args[0].value = CStringGetDatum(str);
fcinfo->args[0].isnull = false;
fcinfo->args[1].value = ObjectIdGetDatum(InvalidOid); /* typelem */
fcinfo->args[1].isnull = false;
fcinfo->args[2].value = Int32GetDatum(-1); /* typmod */
fcinfo->args[2].isnull = false;
fcinfo->context = (Node *) &escontext;
PG_TRY();
{
switch (type_sel)
{
case FUZZ_NUMERIC:
(void) numeric_in(fcinfo);
break;
case FUZZ_DATE:
(void) date_in(fcinfo);
break;
case FUZZ_TIMESTAMP:
(void) timestamp_in(fcinfo);
break;
case FUZZ_TIMESTAMPTZ:
(void) timestamptz_in(fcinfo);
break;
case FUZZ_INTERVAL:
(void) interval_in(fcinfo);
break;
}
}
PG_CATCH();
{
FlushErrorState();
}
PG_END_TRY();
MemoryContextSwitchTo(oldcontext);
MemoryContextDelete(fuzz_context);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-103
View File
@@ -1,103 +0,0 @@
/*-------------------------------------------------------------------------
*
* fuzz_unescapebytea.c
* Fuzzing harness for PQunescapeBytea()
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/fuzzing/fuzz_unescapebytea.c
*
* This harness feeds arbitrary byte sequences to PQunescapeBytea(),
* which decodes bytea escape formats: hex (\xDEAD...) and legacy
* backslash-octal (\352\273\276...). The function is completely
* standalone and requires no database connection.
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <stdio.h>
#include "libpq-fe.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *str;
size_t resultlen;
unsigned char *result;
if (size == 0)
return 0;
/* PQunescapeBytea expects a NUL-terminated string */
str = malloc(size + 1);
if (!str)
return 0;
memcpy(str, data, size);
str[size] = '\0';
result = PQunescapeBytea((const unsigned char *) str, &resultlen);
if (result)
PQfreemem(result);
free(str);
return 0;
}
#ifdef STANDALONE_FUZZ_TARGET
int
main(int argc, char **argv)
{
int i;
int ret = 0;
for (i = 1; i < argc; i++)
{
FILE *f = fopen(argv[i], "rb");
long len;
uint8_t *buf;
size_t n_read;
if (!f)
{
fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
ret = 1;
continue;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0)
{
fprintf(stderr, "%s: could not determine size of %s\n",
argv[0], argv[i]);
fclose(f);
ret = 1;
continue;
}
buf = malloc(len);
if (!buf)
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
fclose(f);
return 1;
}
n_read = fread(buf, 1, len, f);
fclose(f);
LLVMFuzzerTestOneInput(buf, n_read);
free(buf);
}
return ret;
}
#endif /* STANDALONE_FUZZ_TARGET */
-203
View File
@@ -1,203 +0,0 @@
# Copyright (c) 2026, PostgreSQL Global Development Group
# Fuzzing harnesses for security testing.
#
# Build with:
# meson setup build -Dfuzzing=true
#
# For libFuzzer (recommended), also pass sanitizer flags:
# meson setup build -Dfuzzing=true \
# -Dc_args='-fsanitize=fuzzer-no-link' \
# -Dc_link_args='-fsanitize=fuzzer'
#
# Without a fuzzer engine the harnesses are built in standalone mode:
# each reads input from files named on the command line.
if not get_option('fuzzing')
subdir_done()
endif
# Detect whether a fuzzer engine (e.g. libFuzzer) is available.
# If so, link fuzzer executables with -fsanitize=fuzzer so that the
# engine provides main(). Otherwise compile with STANDALONE_FUZZ_TARGET
# so the harnesses supply their own main() that reads from files.
fuzz_c_args = []
fuzz_link_args = []
if cc.has_argument('-fsanitize=fuzzer-no-link')
fuzzer_has_engine = cc.links('''
#include <stdint.h>
#include <stddef.h>
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{ return 0; }
''',
args: ['-fsanitize=fuzzer'],
name: 'libFuzzer support')
else
fuzzer_has_engine = false
endif
if fuzzer_has_engine
fuzz_link_args += ['-fsanitize=fuzzer']
else
fuzz_c_args += ['-DSTANDALONE_FUZZ_TARGET']
endif
# --- Frontend targets (no backend dependencies) ---
fuzz_json = executable('fuzz_json',
'fuzz_json.c',
c_args: fuzz_c_args,
link_args: fuzz_link_args,
dependencies: [frontend_code],
kwargs: default_bin_args + {
'install': false,
},
)
fuzz_json_incremental = executable('fuzz_json_incremental',
'fuzz_json_incremental.c',
c_args: fuzz_c_args,
link_args: fuzz_link_args,
dependencies: [frontend_code],
kwargs: default_bin_args + {
'install': false,
},
)
fuzz_conninfo = executable('fuzz_conninfo',
'fuzz_conninfo.c',
c_args: fuzz_c_args,
link_args: fuzz_link_args,
dependencies: [frontend_code, libpq],
kwargs: default_bin_args + {
'install': false,
},
)
fuzz_pglz = executable('fuzz_pglz',
'fuzz_pglz.c',
c_args: fuzz_c_args,
link_args: fuzz_link_args,
dependencies: [frontend_code],
kwargs: default_bin_args + {
'install': false,
},
)
fuzz_unescapebytea = executable('fuzz_unescapebytea',
'fuzz_unescapebytea.c',
c_args: fuzz_c_args,
link_args: fuzz_link_args,
dependencies: [frontend_code, libpq],
kwargs: default_bin_args + {
'install': false,
},
)
fuzz_b64decode = executable('fuzz_b64decode',
'fuzz_b64decode.c',
c_args: fuzz_c_args,
link_args: fuzz_link_args,
dependencies: [frontend_code],
kwargs: default_bin_args + {
'install': false,
},
)
fuzz_saslprep = executable('fuzz_saslprep',
'fuzz_saslprep.c',
c_args: fuzz_c_args,
link_args: fuzz_link_args,
dependencies: [frontend_code],
kwargs: default_bin_args + {
'install': false,
},
)
fuzz_parsepgarray = executable('fuzz_parsepgarray',
'fuzz_parsepgarray.c',
c_args: fuzz_c_args,
link_args: fuzz_link_args,
dependencies: [frontend_code, libpq],
kwargs: default_bin_args + {
'install': false,
},
)
# The pgbench expression parser is built from generated Bison/Flex
# sources. We reference the same custom_target outputs used by the
# pgbench build, and provide our own syntax_error() / strtoint64() /
# strtodouble() so we don't pull in pgbench.c (which has its own
# main() and calls exit() on parse errors).
exprscan_fuzz = custom_target('exprscan_fuzz',
input: files('../../bin/pgbench/exprscan.l'),
output: 'exprscan.c',
command: flex_cmd,
)
exprparse_fuzz = custom_target('exprparse_fuzz',
input: files('../../bin/pgbench/exprparse.y'),
kwargs: bison_kw,
)
fuzz_pgbench_expr = executable('fuzz_pgbench_expr',
'fuzz_pgbench_expr.c',
exprscan_fuzz,
exprparse_fuzz,
c_args: fuzz_c_args,
link_args: fuzz_link_args,
include_directories: include_directories('../../bin/pgbench'),
dependencies: [frontend_code, libpq],
kwargs: default_bin_args + {
'install': false,
},
)
# --- Backend targets ---
#
# These link against postgres_lib using standard archive semantics
# (link_with), so only objects needed to resolve symbols are pulled in.
# The harness provides stub definitions for symbols exported by
# main/main.c, preventing the archive linker from pulling in main.o
# (which would conflict with the harness's own main()).
#
# Backend code uses function-pointer casts in hash tables (dynahash.c)
# that trigger UBSan's -fsanitize=function check. This is a known
# benign pattern; when using -fsanitize=undefined, also pass
# -fno-sanitize=function in the top-level c_args to suppress it.
fuzz_rawparser = executable('fuzz_rawparser',
'fuzz_rawparser.c',
c_args: fuzz_c_args,
link_args: fuzz_link_args,
link_with: [postgres_lib],
dependencies: backend_build_deps,
kwargs: default_bin_args + {
'install': false,
},
)
fuzz_regex = executable('fuzz_regex',
'fuzz_regex.c',
c_args: fuzz_c_args,
link_args: fuzz_link_args,
link_with: [postgres_lib],
dependencies: backend_build_deps,
kwargs: default_bin_args + {
'install': false,
},
)
fuzz_typeinput = executable('fuzz_typeinput',
'fuzz_typeinput.c',
c_args: fuzz_c_args,
link_args: fuzz_link_args,
link_with: [postgres_lib],
dependencies: backend_build_deps,
kwargs: default_bin_args + {
'install': false,
},
)
-2
View File
@@ -25,6 +25,4 @@ if icu.found()
subdir('icu')
endif
subdir('fuzzing')
subdir('perl')