You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found while closing the first #214 finding. The fix landed; the thing that
catches the next one did not exist and still does not.
What happened
The #214 fuzzer was run against an ASAN and UBSAN build of PostgreSQL 18.4 and
reported, at src/columnar_encoding.c:1135:
runtime error: load of misaligned address 0x75064ac5813e for type 'uint32',
which requires 4 byte alignment
Values in a chunk's raw buffer are packed end to end with no alignment padding,
so a four-byte varlena header begins wherever the previous value ended. VARSIZE_ANY reads it by casting to varattrib_4b and loading a uint32.
Undefined on an unaligned address; works on x86_64; SIGBUS on a
strict-alignment target, which docs/limitations.md says is supported.
The part that matters for this issue: it was not a crafted-input defect. A
plain INSERT of low-cardinality text reports it six times. It had been on the
ordinary write path the whole time. Three call sites were affected
(encode_dict, and two buffer walks in columnar_reader.c).
Every one of the 92 suites passed on all five majors, before and after, on both
sides of the fix. The full matrix is green either way. Nothing in the gate can
see this class of defect, because nothing in the gate is instrumented.
Why the fuzzer is not the answer on its own
test/fuzz_parquet.sh classifies a sanitizer report as a finding and is in the
matrix, so this looks covered. It is not. The check is only live when the suite
runs against a sanitizer build, and the matrix's five majors are all ordinary
assert builds. On those, the sanitizer check passes vacuously, every time. That
is the same shape as a suite that greps a file for a guard that is not there:
green because nothing was looked at.
What to build
A sixth configuration in the matrix, or a second gate that runs beside it: one
PostgreSQL major built with -fsanitize=address,undefined, running a subset of
the suites. It does not need all 92 and it does not need five majors. It needs
the write path, the read path, the encoders, and the import and decode paths,
which is where hand-rolled buffer walks live.
Everything needed to build it is already worked out
The environment fights back in four specific ways, all now solved. They are
recorded here so nobody rediscovers them:
Build in-tree, not VPATH./root/postgresql-18.4 has a config.status
and a built postgres in it, so it was configured in place. A VPATH build
against such a tree fails with subdirectories that "enter" and "leave" without
building, then a link that cannot find the objects. Extract a fresh tarball.
libclang-rt-<N>-dev must match the clang major, or -fsanitize=address
cannot link at all. gcc works too, via libasan.
ASAN_OPTIONS=detect_leaks=0.pg_config is itself instrumented and leaks
by design at exit, so LeakSanitizer fires on every invocation and poisons the
values PGXS reads. The extension build fails before it starts.
ASAN_OPTIONS=detect_stack_use_after_return=0. Without it, initdb fails
with FATAL: stack depth limit exceeded during bootstrap, and raising max_stack_depth to 32MB does not help, because it is not really running out
of stack. Clang now defaults that option on, which relocates locals to a heap
fake stack, and PostgreSQL's check_stack_depth() is pointer arithmetic
against stack_base_ptr. The arithmetic becomes meaningless and the check
misfires.
UBSAN needs a suppression for function. Every backend start reports dynahash.c:1022: call to function string_compare through pointer to incorrect function type. That is a PostgreSQL-wide idiom, not a defect, and unsuppressed
it buries real reports. A suppressions file works; -fno-sanitize=function
would too.
The working build is /usr/local/pg18_san in the dev container, and /root/build-san.sh is the script that produced it.
Todo
Decide where it runs: a sixth entry in run_all_versions.sh, or a
separate test/run_sanitizer.sh invoked on its own cadence. A sanitizer
build is several times slower, and the matrix is currently six minutes,
which is worth protecting.
Choose the suite subset, favouring paths that walk buffers by hand: native_encoding, native_roundtrip, native_writer, differential, parquet_import, native_read_parquet, fuzz_parquet, hardening.
Script the build so it is reproducible rather than a container artifact,
with the five gotchas above encoded rather than remembered.
Bake the required ASAN_OPTIONS and UBSAN_OPTIONS into the runner, so a
correct run is the default and not something the operator has to know.
Prove the gate by removal: revert the ColumnarVarSizeAnyUnaligned change
on a branch and confirm the sanitizer gate goes red and the ordinary matrix
stays green. Until that is shown, the gate is not known to work.
Decide whether the sanitizer build blocks a merge or reports. Given it
found a real defect on its first run, blocking is defensible.
Related
Fix and full account: #214 and the PR that closes it. test/mutate_guard.py
already records that ASAN cannot see overruns inside palloc'd chunks, because
palloc sub-allocates from larger blocks, and that only a USE_VALGRIND build
shows those. So a sanitizer gate is a floor, not a ceiling, and a Valgrind
configuration is the natural follow-on.
Found while closing the first #214 finding. The fix landed; the thing that
catches the next one did not exist and still does not.
What happened
The #214 fuzzer was run against an ASAN and UBSAN build of PostgreSQL 18.4 and
reported, at
src/columnar_encoding.c:1135:Values in a chunk's raw buffer are packed end to end with no alignment padding,
so a four-byte varlena header begins wherever the previous value ended.
VARSIZE_ANYreads it by casting tovarattrib_4band loading auint32.Undefined on an unaligned address; works on x86_64; SIGBUS on a
strict-alignment target, which
docs/limitations.mdsays is supported.The part that matters for this issue: it was not a crafted-input defect. A
plain
INSERTof low-cardinality text reports it six times. It had been on theordinary write path the whole time. Three call sites were affected
(
encode_dict, and two buffer walks incolumnar_reader.c).Every one of the 92 suites passed on all five majors, before and after, on both
sides of the fix. The full matrix is green either way. Nothing in the gate can
see this class of defect, because nothing in the gate is instrumented.
Why the fuzzer is not the answer on its own
test/fuzz_parquet.shclassifies a sanitizer report as a finding and is in thematrix, so this looks covered. It is not. The check is only live when the suite
runs against a sanitizer build, and the matrix's five majors are all ordinary
assert builds. On those, the sanitizer check passes vacuously, every time. That
is the same shape as a suite that greps a file for a guard that is not there:
green because nothing was looked at.
What to build
A sixth configuration in the matrix, or a second gate that runs beside it: one
PostgreSQL major built with
-fsanitize=address,undefined, running a subset ofthe suites. It does not need all 92 and it does not need five majors. It needs
the write path, the read path, the encoders, and the import and decode paths,
which is where hand-rolled buffer walks live.
Everything needed to build it is already worked out
The environment fights back in four specific ways, all now solved. They are
recorded here so nobody rediscovers them:
/root/postgresql-18.4has aconfig.statusand a built
postgresin it, so it was configured in place. A VPATH buildagainst such a tree fails with subdirectories that "enter" and "leave" without
building, then a link that cannot find the objects. Extract a fresh tarball.
libclang-rt-<N>-devmust match the clang major, or-fsanitize=addresscannot link at all. gcc works too, via
libasan.ASAN_OPTIONS=detect_leaks=0.pg_configis itself instrumented and leaksby design at exit, so LeakSanitizer fires on every invocation and poisons the
values PGXS reads. The extension build fails before it starts.
ASAN_OPTIONS=detect_stack_use_after_return=0. Without it,initdbfailswith
FATAL: stack depth limit exceededduring bootstrap, and raisingmax_stack_depthto 32MB does not help, because it is not really running outof stack. Clang now defaults that option on, which relocates locals to a heap
fake stack, and PostgreSQL's
check_stack_depth()is pointer arithmeticagainst
stack_base_ptr. The arithmetic becomes meaningless and the checkmisfires.
function. Every backend start reportsdynahash.c:1022: call to function string_compare through pointer to incorrect function type. That is a PostgreSQL-wide idiom, not a defect, and unsuppressedit buries real reports. A suppressions file works;
-fno-sanitize=functionwould too.
The working build is
/usr/local/pg18_sanin the dev container, and/root/build-san.shis the script that produced it.Todo
run_all_versions.sh, or aseparate
test/run_sanitizer.shinvoked on its own cadence. A sanitizerbuild is several times slower, and the matrix is currently six minutes,
which is worth protecting.
native_encoding,native_roundtrip,native_writer,differential,parquet_import,native_read_parquet,fuzz_parquet,hardening.with the five gotchas above encoded rather than remembered.
ASAN_OPTIONSandUBSAN_OPTIONSinto the runner, so acorrect run is the default and not something the operator has to know.
ColumnarVarSizeAnyUnalignedchangeon a branch and confirm the sanitizer gate goes red and the ordinary matrix
stays green. Until that is shown, the gate is not known to work.
found a real defect on its first run, blocking is defensible.
Related
Fix and full account: #214 and the PR that closes it.
test/mutate_guard.pyalready records that ASAN cannot see overruns inside
palloc'd chunks, becausepalloc sub-allocates from larger blocks, and that only a
USE_VALGRINDbuildshows those. So a sanitizer gate is a floor, not a ceiling, and a Valgrind
configuration is the natural follow-on.