Repository navigation
Double free in mariadb #220
Copy link
Copy link
Closed
Description
I ran into a double free. My program is using execute(db, ...) on multiple threads but it's guarded by a lock so no two executes can run concurrently. I've also experienced a few crashes which seem related (just segfaults with no printout).
MySQL v1.4.6
MariaDB_Connector_C_jll v3.1.12+0
double free or corruption (fasttop)
[2167724] signal (6.-6): Aborted
in expression starting at REPL[48]:1
pthread_kill at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
raise at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
abort at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
unknown function (ip: 0x7f0132879675)
unknown function (ip: 0x7f0132890cfb)
unknown function (ip: 0x7f01328929c9)
free at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
SSL_free at /home/salmon/.julia/artifacts/69903d1a57355b6e78750aac3a8b3fddab87180f/lib/libssl.so (unknown line)
ma_tls_close at /home/salmon/.julia/artifacts/c688df815e86a92f5f36c990983dd26d8a80f17a/lib/mariadb/libmariadb.so (unknown line)
ma_pvio_close at /home/salmon/.julia/artifacts/c688df815e86a92f5f36c990983dd26d8a80f17a/lib/mariadb/libmariadb.so (unknown line)
mthd_my_send_cmd at /home/salmon/.julia/artifacts/c688df815e86a92f5f36c990983dd26d8a80f17a/lib/mariadb/libmariadb.so (unknown line)
mysql_stmt_execute at /home/salmon/.julia/artifacts/c688df815e86a92f5f36c990983dd26d8a80f17a/lib/mariadb/libmariadb.so (unknown line)
mysql_stmt_execute at /home/salmon/.julia/packages/MySQL/NMNbH/src/api/ccalls.jl:8 [inlined]
execute at /home/salmon/.julia/packages/MySQL/NMNbH/src/api/papi.jl:3 [inlined]
#execute#35 at /home/salmon/.julia/packages/MySQL/NMNbH/src/prepare.jl:162
execute at /home/salmon/.julia/packages/MySQL/NMNbH/src/prepare.jl:152
unknown function (ip: 0x7eff73f23349)
_jl_invoke at /cache/build/builder-amdci4-4/julialang/julia-release-1-dot-10/src/gf.c:2895 [inlined]
ijl_apply_generic at /cache/build/builder-amdci4-4/julialang/julia-release-1-dot-10/src/gf.c:3077
#82 at /home/salmon/git/project/main.jl:70
lock at ./lock.jl:229
unknown function (ip: 0x7eff73fed249)
_jl_invoke at /cache/build/builder-amdci4-4/julialang/julia-release-1-dot-10/src/gf.c:2895 [inlined]
ijl_apply_generic at /cache/build/builder-amdci4-4/julialang/julia-release-1-dot-10/src/gf.c:3077
#81 at /home/salmon/git/project/main.jl:69
unknown function (ip: 0x7eff73fea5b2)
_jl_invoke at /cache/build/builder-amdci4-4/julialang/julia-release-1-dot-10/src/gf.c:2895 [inlined]
ijl_apply_generic at /cache/build/builder-amdci4-4/julialang/julia-release-1-dot-10/src/gf.c:3077
jl_apply at /cache/build/builder-amdci4-4/julialang/julia-release-1-dot-10/src/julia.h:1982 [inlined]
start_task at /cache/build/builder-amdci4-4/julialang/julia-release-1-dot-10/src/task.c:1238
Allocations: 121462440 (Pool: 121313001; Big: 149439); GC: 60
[1] 2167724 IOT instruction (core dumped) julia -t auto --project
julia> versioninfo()
Julia Version 1.10.5
Commit 6f3fdf7b362 (2024-08-27 14:19 UTC)
Build Info:
Official https://julialang.org/ release
Platform Info:
OS: Linux (x86_64-linux-gnu)
CPU: 32 × 13th Gen Intel(R) Core(TM) i9-13900K
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-15.0.7 (ORCJIT, goldmont)
Threads: 32 default, 0 interactive, 16 GC (on 32 virtual cores)
kernel 5.15.153.1-microsoft-standard-WSL2
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
quinnj commentedon Aug 1, 2026
I hit this same crash and dug into it — I believe I have the root cause, a fast reproducer, and controls that isolate the trigger.
TL;DR: the
API.MYSQL_STMTfinalizer callsmysql_stmt_close, which is not a client-side free — it sendsCOM_STMT_CLOSEover the connection's socket. Finalizers run on whichever thread triggers GC, and GC (plus finalizers) runs while other threads are insidemysql_*ccalls. So an abandoned prepared statement's finalizer does socket I/O on aMYSQL*concurrently with a legitimate call on another thread — no user-level lock can prevent it, because the user's lock isn't (and can't be) held by GC. libmariadb connections are not thread-safe, so this is memory-unsafe.The finalizer in question (
src/api/apitypes.jl#L56-L61):API.MYSQL(mysql_closesendsCOM_QUIT) andAPI.MYSQL_RES(mysql_free_resultreads any un-fetched rows off the wire) have the same problem in their finalizers.Caught in the act
macOS crash report from the abort, showing two threads simultaneously inside libmariadb on the same connection — thread 3 is a GC finalizer closing a statement, thread 5 is the application thread (which held a
ReentrantLockserializing every DB call):The interleaved use of one
SSL*from two threads corrupts OpenSSL state; then both threads' error paths callma_pvio_close→ma_tls_close→SSL_freeon the same SSL — the double free in this issue's title. Across runs I've seen the abort land inSSL_write(undermysql_stmt_execute), inSSL_free/SSL_CTX_free(as above and as in the OP's trace:mysql_stmt_execute → mthd_my_send_cmd → ma_pvio_close → ma_tls_close → SSL_free), and once as a straight SIGSEGV jumping through a freed pointer.Why it looks TLS-correlated
COM_STMT_CLOSEis fire-and-forget (no server response). On a plaintext socket, the racing finalizer write usually just interleaves at packet granularity and nothing visibly breaks — no shared client-side state is being mutated concurrently except the net buffer. Under TLS, both threads mutate the same SSL record layer (cipher sequence, buffers), so you get(2026): TLS/SSL error: ssl/tls alert bad record mac, wedged connections, or heap corruption → SIGABRT/SIGSEGV. The race is present without TLS too — one plaintext run here producedArgumentError: unsafe_wrap: pointer 0x9 is not properly aligned to 8 bytesfrom a result fetch, i.e. silent client-side corruption instead of a crash.Reproducer
Crashes within seconds-to-a-minute on every run here (3/3 runs ended in SIGABRT or SIGSEGV; interspersed rounds die with
bad record mac). All DB access is behind a singleReentrantLock, as in the OP.Controls
bad record macrounds)DBInterface.close!(stmt)inside the locktls_version=) + abandoned statementsunsafe_wrap: pointer 0x9 is not properly alignedThe explicit-close control is the smoking gun: the only difference is whether
mysql_stmt_closeruns under the user's lock or in a GC finalizer.Single-threaded Julia (
-t 1) is safe — finalizers can't run concurrently with a ccall on the same thread. Any-t ≥ 2workload that ever lets aStatement, un-drainedMYSQL_RES, orConnectionbe GC'd while the connection is in use is exposed, which is why this shows up as rare, unexplainable crashes in long test suites.Environment
main, 1.5.2) / MariaDB_Connector_C_jll 3.4.9+1 / OpenSSL_jll 3.5.4Two aggravating factors worth noting:
ssl_mode=SSL_MODE_DISABLEDcannot turn it off (that option maps onto a different, unrelated option in libmariadb — filing separately). So essentially all users of current MySQL.jl + mysql:8 are in the crashy configuration.@rpath/libssl.3.dylibreference resolves to Julia's bundledlib/julia/libssl.3.dylibrather than OpenSSL_jll's — didn't affect this bug, but it means the OpenSSL actually in use isn't the one the jll pins.Fix sketch
The invariant the fix needs: finalizers must never touch the socket. One workable shape:
Connectiona small reap queue (spinlock-guardedVector{Ptr{Cvoid}}). TheMYSQL_STMTfinalizer pushes the raw ptr and returns — no libmariadb call.mysql_stmt_closeon each ptr) at the start of every user-facing operation on that connection (prepare,execute,transaction,close!…). Those sites are already serialized by whatever discipline the user applies, so the close packets go out under the same protection as everything else.DBInterface.close!(conn)drains the queue beforemysql_close. If the connection is already closed/dead, drop the ptrs via plainmysql_stmt_closeaftermysql_closehas invalidated them (libmariadb turns that into a local free), or leak them — either is better than a crash.MYSQL_RES(an un-drained result'smysql_free_resultreads from the socket) and theMYSQLfinalizer'smysql_close.Happy to turn this into a PR if the approach sounds right.
Never do socket I/O in finalizers; map ssl_mode onto real Connector/C…