From 81bda366b3073bd46e80052d03c081e390a04094 Mon Sep 17 00:00:00 2001 From: Andrew Jackson Date: Sat, 24 Oct 2026 23:14:25 -0500 Subject: [PATCH 1/2] feat: add UBSAN/ASAN --- .github/workflows/ci.yml | 29 ++++++++++++++++++++++++++++- examples/hello/src/hello.c | 13 +++++++++++++ nix/postgresql/generic.nix | 17 ++++++++++++----- nix/xpg.nix | 20 +++++++++++++++----- 4 files changed, 68 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43bf43e..971292c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,9 +76,18 @@ jobs: - name: Install GCC run: | - nix profile install nixpkgs#gcc14 + nix profile install nixpkgs#gcc15 echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH" + # TODO: Currently builds are not fully hermetic. We will care about the state of the machine + # until we can invest in making this repo fully hermetic + - name: Print state + run: | + gcc --version + ulimit -s + cat /etc/os-release + which gcc + - name: Build postgres extension run: | cd examples/hello @@ -96,13 +105,31 @@ jobs: nix run .#xpg${{ matrix.pg-version }} -- --options "-c work_mem=8MB" psql -c "DO \$\$ BEGIN ASSERT current_setting('work_mem') = '8MB'; END \$\$;" # test for cassert enabled build + rm -rf build-${{ matrix.pg-version }} nix run .#xpg${{ matrix.pg-version }} -- --cassert psql -c "DO \$\$ BEGIN ASSERT current_setting('debug_assertions') = 'on'; END \$\$;" # test for pg core test + rm -rf ./build-{{ matrix.pg-version }} if [ "${{ matrix.pg-version }}" -ge 15 ]; then nix run .#xpg${{ matrix.pg-version }} -- test-core fi + - name: Test UBSAN reporting + run: | + cd examples/hello + rm -rf ./build-{{ matrix.pg-version }} + + # run test with cassert and injected DIV_BY_ZERO error, print all stdout but exit non zero if + # expected error code not found + (HELLO_NULL_PTR_DEREF=1 nix run .#xpg${{ matrix.pg-version }} -- --cassert -v ${{ matrix.pg-version }} test 2>&1 || true) | awk '/runtime error: load of null pointer of type/ { found=1 } { print } END { if (!found) exit 1 }' + + - name: Test ASAN reporting + run: | + cd examples/hello + # run test with cassert and injected OOB_WRITE error, print all stdout but exit non zero if + # expected error code not found + (HELLO_OOB_WRITE=1 nix run .#xpg${{ matrix.pg-version }} -- --cassert -v ${{ matrix.pg-version }} test 2>&1 || true) | awk '/SUMMARY: AddressSanitizer: heap-buffer-overflow/ { found=1 } { print } END { if (!found) exit 1 }' + - if: ${{ failure() }} run: | cd examples/hello diff --git a/examples/hello/src/hello.c b/examples/hello/src/hello.c index df29c7e..a7b7714 100644 --- a/examples/hello/src/hello.c +++ b/examples/hello/src/hello.c @@ -9,6 +9,19 @@ Datum hello(PG_FUNCTION_ARGS){ StringInfoData buf; initStringInfo(&buf); + // ASAN should flag this + if (getenv("HELLO_OOB_WRITE")) { + char *buf2 = (char *)malloc(4); + buf2[5] = 'A'; // Writing past the 4-byte heap buffer + free(buf2); + } + + // UBSAN should flag this + if (getenv("HELLO_NULL_PTR_DEREF")) { + int *i = NULL; + printf("%d\n", *i); + } + appendStringInfoString(&buf, "hello "); appendBinaryStringInfo(&buf, VARDATA_ANY(input), VARSIZE_ANY_EXHDR(input)); diff --git a/nix/postgresql/generic.nix b/nix/postgresql/generic.nix index 7065bd8..09adcc8 100644 --- a/nix/postgresql/generic.nix +++ b/nix/postgresql/generic.nix @@ -142,6 +142,12 @@ let buildFlags = [ "world" ]; + env.CFLAGS = + "" + + lib.optionalString cassertSupport "-fsanitize=undefined -fno-sanitize-recover=all -fno-omit-frame-pointer " + + lib.optionalString (cassertSupport && stdenv.isDarwin) "-fno-sanitize=function" + + lib.optionalString (cassertSupport && stdenv'.isLinux) "-fsanitize=address"; + # Makes cross-compiling work when xml2-config can't be executed on the host. # Fixed upstream in https://github.com/postgres/postgres/commit/0bc8cebdb889368abdf224aeac8bc197fe4c9ae6 env.NIX_CFLAGS_COMPILE = @@ -300,11 +306,12 @@ let # autodetection doesn't seem to able to find this, but it's there. checkTarget = "check"; - # PG 19+ embeds additional tool paths in installed artifacts, so keep the - # stricter gcc-wrapper reference check only for older versions for now. - disallowedReferences = lib.optionals (lib.versionOlder (lib.versions.major version) "19") [ - stdenv'.cc - ]; + # Prevents hardcoded file paths referencing nix store in pg19 + disallowedReferences = + lib.optionals (lib.versionOlder (lib.versions.major version) "19" && !cassertSupport) + [ + stdenv'.cc + ]; passthru = let diff --git a/nix/xpg.nix b/nix/xpg.nix index fd0b131..629585d 100644 --- a/nix/xpg.nix +++ b/nix/xpg.nix @@ -52,11 +52,21 @@ let ${ if cassert then '' - if [ "$_arg_cassert" = on ]; then - export PATH=${ourPg."postgresql_${v}_cassert"}/bin:"$PATH" - else - export PATH=${ourPg."postgresql_${v}"}/bin:"$PATH" - fi + if [ "$_arg_cassert" = on ]; then + export PATH=${ourPg."postgresql_${v}_cassert"}/bin:"$PATH" + + # See below links + # TODO: detect_stack_use_after_return in ASAN_OPTIONS can be removed after + # below patch lands and is backported, thought it will still need to + # remain for EOL'd versions + # https://www.postgresql.org/message-id/flat/2kk4z4odvuyrg7qlwjd7ft4eron4cle4btb33v4qatgsdkayir%40gj6e62rgsel4 + # https://github.com/postgres/postgres/blob/51ae21aa313bb6b79ad914920279cd0d89e92220/.github/workflows/pg-ci.yml#L464 + + export UBSAN_OPTIONS="print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2" + export ASAN_OPTIONS="print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0" + else + export PATH=${ourPg."postgresql_${v}"}/bin:"$PATH" + fi '' else '' From 8d0158151edf83b560de1e694209eec589ea0f4b Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 30 Oct 2026 19:02:00 +0000 Subject: [PATCH 2/2] docs: add notes --- nix/postgresql/generic.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nix/postgresql/generic.nix b/nix/postgresql/generic.nix index 09adcc8..ed64e16 100644 --- a/nix/postgresql/generic.nix +++ b/nix/postgresql/generic.nix @@ -146,6 +146,9 @@ let "" + lib.optionalString cassertSupport "-fsanitize=undefined -fno-sanitize-recover=all -fno-omit-frame-pointer " + lib.optionalString (cassertSupport && stdenv.isDarwin) "-fno-sanitize=function" + # TODO: compiling with clang on darwin with -fsanitize=address results in a binary + # that never returns and pins at 100% CPU saturation of a single core. Possibly + # revisit the below if this ever gets fixed + lib.optionalString (cassertSupport && stdenv'.isLinux) "-fsanitize=address"; # Makes cross-compiling work when xml2-config can't be executed on the host.