mirror of
https://github.com/postgres/postgres.git
synced 2026-05-06 08:56:39 -04:00
Revert "Add built-in fuzzing harnesses for security testing."
This reverts commit 4a18907b41.
inadvertenly pushed, mea culpa
This commit is contained in:
@@ -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')
|
||||
|
||||
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
@@ -25,6 +25,4 @@ if icu.found()
|
||||
subdir('icu')
|
||||
endif
|
||||
|
||||
subdir('fuzzing')
|
||||
|
||||
subdir('perl')
|
||||
|
||||
Reference in New Issue
Block a user