Hi hackers,

On an AddressSanitizer build, every JIT-compiled query crashes or
hangs the backend while the JIT generates code for it, even "SELECT 1"
with jit = on and jit_above_cost = 0.  On Linux the backend dies of
SIGILL; on macOS it loops forever inside LLVM.

The JIT takes the LLVM types of PostgreSQL's structs and scalars from
the global variables in llvmjit_types.c, which is compiled to bitcode
with the same flags.  To add a redzone after each variable,
AddressSanitizer wraps it in a struct of type { T, [N x i8] }: the
declared type T at offset zero, followed by the redzone.  So
llvm_pg_var_type() hands that struct type to the JIT instead of T,
and, for example, a GEP for a field of ExprContext indexes a struct
with two elements, which makes the IR invalid.

v4, attached, leaves the build systems alone and fixes this where the
types are read:

  0001  The jit regression test.  It turns jit on and sets
        jit_above_cost to zero, so that "SELECT 1" is JIT-compiled.
  0002  llvm_pg_var_type() raises an error for a type that is not of
        the expected form (a scalar or a named struct) instead of
        handing it to the JIT.
  0003  When llvm_pg_var_type() finds a struct whose second element
        is a redzone, it returns the type of the first element, T.
  0004  An alternative to 0003: exclude the variables in
        llvmjit_types.c from AddressSanitizer instrumentation with a
        clang attribute pragma, so that they are never wrapped.

Only one of 0003 and 0004 is needed; whichever is better can be
picked.  The trade-off is:

  0003  Leaves llvmjit_types.c alone and changes only llvmjit.c, but
        relies on the shape of the struct that AddressSanitizer
        builds ({ T, [N x i8] }).  If that shape changes, it falls
        back to the error from 0002.
  0004  Keeps the wrapping from happening at all, so it does not
        depend on how AddressSanitizer works inside, but adds a
        clang-specific pragma to llvmjit_types.c.

0001 comes first only to show what each of the following patches
changes.  Committed alone, it breaks CI's AddressSanitizer build, so
it should go in together with 0002 and 0003 (or 0004), or after them.

Testing (the jit test, run by pg_regress against a running server):

                     macOS arm64           Rocky Linux 10 x86_64
                     Apple clang ASan      gcc 14 ASan
                     LLVM 21.1.7, meson    LLVM 21.1.8, meson
  0001               hangs inside LLVM     SIGILL
  0001+0002          ERROR                 ERROR
  0001+0002+0003     ok                    ok
  0001+0002+0004     ok                    ok

It of course works with both 0003 and 0004 applied as well: the
variables are then never wrapped, so the code in 0003 is just not
reached.

Best regards,
Henson

Attachment: v4-0001-Add-a-regression-test-that-runs-a-query-through-t.patch
Description: Binary data

Attachment: v4-0002-Raise-an-error-for-a-JIT-type-variable-of-an-unex.patch
Description: Binary data

Attachment: v4-0003-Make-the-LLVM-JIT-work-on-an-AddressSanitizer-bui.patch
Description: Binary data

Attachment: v4-0004-Exclude-the-variables-in-llvmjit_types.c-from-Add.patch
Description: Binary data

Reply via email to