Skip to content

Support building with address sanitizer#581

Open
staticlibs wants to merge 5 commits intoduckdb:mainfrom
staticlibs:asan_build
Open

Support building with address sanitizer#581
staticlibs wants to merge 5 commits intoduckdb:mainfrom
staticlibs:asan_build

Conversation

@staticlibs
Copy link
Collaborator

@staticlibs staticlibs commented Feb 26, 2026

This PR adds support for building JDBC driver with the address sanitizer enabled:

mkdir build
cd build
cmake .. -DENABLE_ADDRESS_SANITIZER=ON
make

To run the resulting binaries it is required to have the JVM built with address sanitizer enabled too. Example of such build of OpenJDK 17:

git clone https://github.com/openjdk/jdk17u.git
cd jdk17u
CC=/usr/bin/clang CXX=/usr/bin/clang++ bash ./configure \
    --enable-asan \
    --with-toolchain-type=clang \
    --disable-warnings-as-errors
make images

Example of running a Java app:

import java.sql.*;
class AsanTest {
    public static void main(String[] args) throws Exception {
        DriverManager.getConnection("jdbc:duckdb:").close();
    }
}
javac AsanTest.java
path/to/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java \
    -cp .:path/to/duckdb-java/build/duckdb_jdbc_nolib.jar \
    AsanTest

If we add the following deliberately erratic snippet somewhere inside the JDBC driver:

int *x = reinterpret_cast<int*>(malloc(10 * sizeof(int*)));
free(x);
std::cout << x[5] << std::endl;

Then the execution will abort with something like the following:

=================================================================
==114481==ERROR: AddressSanitizer: heap-use-after-free on address 0x7bf5410abf14 at pc 0x7b83372124ac bp 0x7f85413fe070 sp 0x7f85413fe068
READ of size 4 at 0x7bf5410abf14 thread T1
    #0 0x7b83372124ab in _duckdb_jdbc_startup(JNIEnv_*, _jclass*, _jbyteArray*, unsigned char, _jobject*) /home/alex/projects/duck/duckdb-java/src/jni/duckdb_java.cpp:79:18
    #1 0x7b8337239880 in Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1startup /home/alex/projects/duck/duckdb-java/src/jni/functions.cpp:9:30
    #2 0x7b852b3445d9  (<unknown module>)

0x7bf5410abf14 is located 20 bytes inside of 80-byte region [0x7bf5410abf00,0x7bf5410abf50)
freed by thread T1 here:
    #0 0x0000004a3fea in free (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x4a3fea) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)
    #1 0x7b8337212340 in _duckdb_jdbc_startup(JNIEnv_*, _jclass*, _jbyteArray*, unsigned char, _jobject*) /home/alex/projects/duck/duckdb-java/src/jni/duckdb_java.cpp:78:6
    #2 0x7b8337239880 in Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1startup /home/alex/projects/duck/duckdb-java/src/jni/functions.cpp:9:30
    #3 0x7b852b3445d9  (<unknown module>)
    #4 0x7b852b34025f  (<unknown module>)
    #5 0x7b852b34025f  (<unknown module>)
    #6 0x7b852b3406e5  (<unknown module>)
    #7 0x7b852b34025f  (<unknown module>)
    #8 0x7b852b34025f  (<unknown module>)
    #9 0x7b852b337cc8  (<unknown module>)
    #10 0x7b853c8ff879 in JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*) /home/alex/projects/external/jdk17u/src/hotspot/share/runtime/javaCalls.cpp:429:7
    #11 0x7b853cab0c0b in jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, JavaThread*) /home/alex/projects/external/jdk17u/src/hotspot/share/prims/jni.cpp:889:3
    #12 0x7b853cabb4ad in jni_CallStaticVoidMethod /home/alex/projects/external/jdk17u/src/hotspot/share/prims/jni.cpp:1718:3
    #13 0x7f854215457a in JavaMain /home/alex/projects/external/jdk17u/src/java.base/share/native/libjli/java.c:547:5
    #14 0x7f8542159368 in ThreadJavaMain /home/alex/projects/external/jdk17u/src/java.base/unix/native/libjli/java_md.c:651:29
    #15 0x0000004a1baa in asan_thread_start(void*) (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x4a1baa) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)

previously allocated by thread T1 here:
    #0 0x0000004a4288 in malloc (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x4a4288) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)
    #1 0x7b8337212335 in _duckdb_jdbc_startup(JNIEnv_*, _jclass*, _jbyteArray*, unsigned char, _jobject*) /home/alex/projects/duck/duckdb-java/src/jni/duckdb_java.cpp:77:40
    #2 0x7b8337239880 in Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1startup /home/alex/projects/duck/duckdb-java/src/jni/functions.cpp:9:30
    #3 0x7b852b3445d9  (<unknown module>)
    #4 0x7b852b34025f  (<unknown module>)
    #5 0x7b852b34025f  (<unknown module>)
    #6 0x7b852b3406e5  (<unknown module>)
    #7 0x7b852b34025f  (<unknown module>)
    #8 0x7b852b34025f  (<unknown module>)
    #9 0x7b852b337cc8  (<unknown module>)
    #10 0x7b853c8ff879 in JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*) /home/alex/projects/external/jdk17u/src/hotspot/share/runtime/javaCalls.cpp:429:7
    #11 0x7b853cab0c0b in jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, JavaThread*) /home/alex/projects/external/jdk17u/src/hotspot/share/prims/jni.cpp:889:3
    #12 0x7b853cabb4ad in jni_CallStaticVoidMethod /home/alex/projects/external/jdk17u/src/hotspot/share/prims/jni.cpp:1718:3
    #13 0x7f854215457a in JavaMain /home/alex/projects/external/jdk17u/src/java.base/share/native/libjli/java.c:547:5
    #14 0x7f8542159368 in ThreadJavaMain /home/alex/projects/external/jdk17u/src/java.base/unix/native/libjli/java_md.c:651:29
    #15 0x0000004a1baa in asan_thread_start(void*) (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x4a1baa) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)

Thread T1 created by T0 here:
    #0 0x0000004883a5 in pthread_create (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x4883a5) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)
    #1 0x7f8542159275 in CallJavaMainInNewThread /home/alex/projects/external/jdk17u/src/java.base/unix/native/libjli/java_md.c:670:9
    #2 0x7f85421576f5 in ContinueInNewThread /home/alex/projects/external/jdk17u/src/java.base/share/native/libjli/java.c:2287:16
    #3 0x7f854215262c in JLI_Launch /home/alex/projects/external/jdk17u/src/java.base/share/native/libjli/java.c:340:12
    #4 0x0000004e8e68 in main /home/alex/projects/external/jdk17u/src/java.base/share/native/launcher/main.c:224:12
    #5 0x7f8541e2f574 in __libc_start_call_main (/lib64/libc.so.6+0x3574) (BuildId: 92b5376d35bb29c098175948cf3e7cbcae3aeae1)
    #6 0x7f8541e2f627 in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x3627) (BuildId: 92b5376d35bb29c098175948cf3e7cbcae3aeae1)
    #7 0x000000400774 in _start (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x400774) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)

SUMMARY: AddressSanitizer: heap-use-after-free /home/alex/projects/duck/duckdb-java/src/jni/duckdb_java.cpp:79:18 in _duckdb_jdbc_startup(JNIEnv_*, _jclass*, _jbyteArray*, unsigned char, _jobject*)
Shadow bytes around the buggy address:
  0x7bf5410abc80: 00 00 00 00 00 00 fa fa fa fa 00 00 00 00 00 00
  0x7bf5410abd00: 00 00 00 00 fa fa fa fa 00 00 00 00 00 00 00 00
  0x7bf5410abd80: 00 fa fa fa fa fa 00 00 00 00 00 00 00 00 00 00
  0x7bf5410abe00: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 fa fa
  0x7bf5410abe80: fa fa 00 00 00 00 00 00 00 00 00 fa fa fa fa fa
=>0x7bf5410abf00: fd fd[fd]fd fd fd fd fd fd fd fa fa fa fa fa fa
  0x7bf5410abf80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bf5410ac000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bf5410ac080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bf5410ac100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bf5410ac180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==114481==ABORTING

Note that on successful run the JVM may print a lot of internal "leak" warnings, it may be necessary to prepare a suppression file to prevent this.

This PR adds support for building JDBC driver with the [address
sanitizer](https://github.com/google/sanitizers/wiki/addresssanitizer)
enabled:

```
mkdir build
cd build
cmake .. -DENABLE_ADDRESS_SANITIZER=ON
make
```

To run the resulting binaries it is required to have the JVM built with
address sanitizer enabled too. Example of such build of [OpenJDK 17](https://wiki.openjdk.org/spaces/JDKUpdates/pages/70320316/JDK+17u):

```
git clone https://github.com/openjdk/jdk17u.git
cd jdk17u
CC=/usr/bin/clang CXX=/usr/bin/clang++ bash ./configure \
    --enable-asan \
    --with-toolchain-type=clang \
    --disable-warnings-as-errors
```

Example of running a Java app:

```java
import java.sql.*;
class AsanTest {
    public static void main(String[] args) throws Exception {
        DriverManager.getConnection("jdbc:duckdb:").close();
    }
}
```
```
javac AsanTest.java
path/to/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java \
    -cp .:path/to/duckdb-java/build/duckdb_jdbc_nolib.jar \
    AsanTest
```

If we add the following deliberately erratic snippet somewhere inside the
JDBC driver:

```c++
int *x = reinterpret_cast<int*>(malloc(10 * sizeof(int*)));
free(x);
std::cout << x[5] << std::endl;
```

Then the execution will abort with something like the following:

```
=================================================================
==114481==ERROR: AddressSanitizer: heap-use-after-free on address 0x7bf5410abf14 at pc 0x7b83372124ac bp 0x7f85413fe070 sp 0x7f85413fe068
READ of size 4 at 0x7bf5410abf14 thread T1
    #0 0x7b83372124ab in _duckdb_jdbc_startup(JNIEnv_*, _jclass*, _jbyteArray*, unsigned char, _jobject*) /home/alex/projects/duck/duckdb-java/src/jni/duckdb_java.cpp:79:18
    #1 0x7b8337239880 in Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1startup /home/alex/projects/duck/duckdb-java/src/jni/functions.cpp:9:30
    duckdb#2 0x7b852b3445d9  (<unknown module>)

0x7bf5410abf14 is located 20 bytes inside of 80-byte region [0x7bf5410abf00,0x7bf5410abf50)
freed by thread T1 here:
    #0 0x0000004a3fea in free (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x4a3fea) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)
    #1 0x7b8337212340 in _duckdb_jdbc_startup(JNIEnv_*, _jclass*, _jbyteArray*, unsigned char, _jobject*) /home/alex/projects/duck/duckdb-java/src/jni/duckdb_java.cpp:78:6
    duckdb#2 0x7b8337239880 in Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1startup /home/alex/projects/duck/duckdb-java/src/jni/functions.cpp:9:30
    duckdb#3 0x7b852b3445d9  (<unknown module>)
    duckdb#4 0x7b852b34025f  (<unknown module>)
    duckdb#5 0x7b852b34025f  (<unknown module>)
    duckdb#6 0x7b852b3406e5  (<unknown module>)
    duckdb#7 0x7b852b34025f  (<unknown module>)
    duckdb#8 0x7b852b34025f  (<unknown module>)
    duckdb#9 0x7b852b337cc8  (<unknown module>)
    duckdb#10 0x7b853c8ff879 in JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*) /home/alex/projects/external/jdk17u/src/hotspot/share/runtime/javaCalls.cpp:429:7
    duckdb#11 0x7b853cab0c0b in jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, JavaThread*) /home/alex/projects/external/jdk17u/src/hotspot/share/prims/jni.cpp:889:3
    duckdb#12 0x7b853cabb4ad in jni_CallStaticVoidMethod /home/alex/projects/external/jdk17u/src/hotspot/share/prims/jni.cpp:1718:3
    duckdb#13 0x7f854215457a in JavaMain /home/alex/projects/external/jdk17u/src/java.base/share/native/libjli/java.c:547:5
    duckdb#14 0x7f8542159368 in ThreadJavaMain /home/alex/projects/external/jdk17u/src/java.base/unix/native/libjli/java_md.c:651:29
    duckdb#15 0x0000004a1baa in asan_thread_start(void*) (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x4a1baa) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)

previously allocated by thread T1 here:
    #0 0x0000004a4288 in malloc (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x4a4288) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)
    #1 0x7b8337212335 in _duckdb_jdbc_startup(JNIEnv_*, _jclass*, _jbyteArray*, unsigned char, _jobject*) /home/alex/projects/duck/duckdb-java/src/jni/duckdb_java.cpp:77:40
    duckdb#2 0x7b8337239880 in Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1startup /home/alex/projects/duck/duckdb-java/src/jni/functions.cpp:9:30
    duckdb#3 0x7b852b3445d9  (<unknown module>)
    duckdb#4 0x7b852b34025f  (<unknown module>)
    duckdb#5 0x7b852b34025f  (<unknown module>)
    duckdb#6 0x7b852b3406e5  (<unknown module>)
    duckdb#7 0x7b852b34025f  (<unknown module>)
    duckdb#8 0x7b852b34025f  (<unknown module>)
    duckdb#9 0x7b852b337cc8  (<unknown module>)
    duckdb#10 0x7b853c8ff879 in JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*) /home/alex/projects/external/jdk17u/src/hotspot/share/runtime/javaCalls.cpp:429:7
    duckdb#11 0x7b853cab0c0b in jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, JavaThread*) /home/alex/projects/external/jdk17u/src/hotspot/share/prims/jni.cpp:889:3
    duckdb#12 0x7b853cabb4ad in jni_CallStaticVoidMethod /home/alex/projects/external/jdk17u/src/hotspot/share/prims/jni.cpp:1718:3
    duckdb#13 0x7f854215457a in JavaMain /home/alex/projects/external/jdk17u/src/java.base/share/native/libjli/java.c:547:5
    duckdb#14 0x7f8542159368 in ThreadJavaMain /home/alex/projects/external/jdk17u/src/java.base/unix/native/libjli/java_md.c:651:29
    duckdb#15 0x0000004a1baa in asan_thread_start(void*) (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x4a1baa) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)

Thread T1 created by T0 here:
    #0 0x0000004883a5 in pthread_create (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x4883a5) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)
    #1 0x7f8542159275 in CallJavaMainInNewThread /home/alex/projects/external/jdk17u/src/java.base/unix/native/libjli/java_md.c:670:9
    duckdb#2 0x7f85421576f5 in ContinueInNewThread /home/alex/projects/external/jdk17u/src/java.base/share/native/libjli/java.c:2287:16
    duckdb#3 0x7f854215262c in JLI_Launch /home/alex/projects/external/jdk17u/src/java.base/share/native/libjli/java.c:340:12
    duckdb#4 0x0000004e8e68 in main /home/alex/projects/external/jdk17u/src/java.base/share/native/launcher/main.c:224:12
    duckdb#5 0x7f8541e2f574 in __libc_start_call_main (/lib64/libc.so.6+0x3574) (BuildId: 92b5376d35bb29c098175948cf3e7cbcae3aeae1)
    duckdb#6 0x7f8541e2f627 in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x3627) (BuildId: 92b5376d35bb29c098175948cf3e7cbcae3aeae1)
    duckdb#7 0x000000400774 in _start (/home/alex/projects/external/jdk17u/build/linux-x86_64-server-release/images/jdk/bin/java+0x400774) (BuildId: b4e8ae1f23ee7414ebabfce60a8c985b06eaf2d8)

SUMMARY: AddressSanitizer: heap-use-after-free /home/alex/projects/duck/duckdb-java/src/jni/duckdb_java.cpp:79:18 in _duckdb_jdbc_startup(JNIEnv_*, _jclass*, _jbyteArray*, unsigned char, _jobject*)
Shadow bytes around the buggy address:
  0x7bf5410abc80: 00 00 00 00 00 00 fa fa fa fa 00 00 00 00 00 00
  0x7bf5410abd00: 00 00 00 00 fa fa fa fa 00 00 00 00 00 00 00 00
  0x7bf5410abd80: 00 fa fa fa fa fa 00 00 00 00 00 00 00 00 00 00
  0x7bf5410abe00: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 fa fa
  0x7bf5410abe80: fa fa 00 00 00 00 00 00 00 00 00 fa fa fa fa fa
=>0x7bf5410abf00: fd fd[fd]fd fd fd fd fd fd fd fa fa fa fa fa fa
  0x7bf5410abf80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bf5410ac000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bf5410ac080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bf5410ac100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bf5410ac180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==114481==ABORTING
```

Note that on successful run the JVM may print a lot of internal "leak"
warnings, it may be necessary to prepare a [suppression file](https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#suppressions)
to prevent this.
staticlibs added a commit to staticlibs/duckdb-java that referenced this pull request Feb 28, 2026
This is a backport of the PR duckdb#581 to `v1.5-variegata` stable branch.

Appender instances are not thread-safe. Only the `close()` method can
be safely called from other threads concurrently with other operations.

`append()` and `flush()` operations operate on the same native buffer
and cannot be called concurrently.

This PR implements a variant of a thread confinement for the Appender
class intances. Only the thread that has created the Appender can call
its methods (`close()` method still can be called from any thread).
Method calls with throw `SQLExeption`s when called from other threads.

When it is necessary to use Appender from multiple threads, it is
required to call `unsafeBreakThreadConfinement()` method first and use a
`Lock` instance returned from it to synchronize the access to this
Appender instance.

Example:

```java
try (DuckDBAppender appender = connection.createAppender("tab1")) {
    Thread th = new Thread(() -> {
        // appender.flush(); // throws SQLException
        Lock appenderLock = appender.unsafeBreakThreadConfinement();
        appenderLock.lock();
        try {
            appender.flush();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            appenderLock.unlock();
        }
    });
    th.start();
    th.join();
}
```

Testing: a concurrent test added that, without the patch, was crashing
the JVM in about 1 of 10 runs.

Fixes: duckdb#582
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant