Skip to content

Commit 5ca4272

Browse files
committed
Add regression test for concurrent interrupt and close TOCTOU
exqlite_interrupt() reads conn->db and calls sqlite3_interrupt() without any lock. A concurrent close() can sqlite3_close_v2() and NULL conn->db between the check and the call → sqlite3_interrupt on a freed pointer → use-after-free segfault.
1 parent 1e9e419 commit 5ca4272

1 file changed

Lines changed: 14 additions & 0 deletions

File tree

test/exqlite/sqlite3_test.exs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,20 @@ defmodule Exqlite.Sqlite3Test do
845845
:ok = Sqlite3.close(conn)
846846
end
847847

848+
# Targets the TOCTOU in exqlite_interrupt:
849+
# The function checks conn->db == NULL outside the lock, then calls
850+
# sqlite3_interrupt(conn->db) without holding the lock. A concurrent
851+
# close() can sqlite3_close_v2() and NULL conn->db between the check and
852+
# the call → sqlite3_interrupt on a freed pointer → use-after-free segfault.
853+
test "concurrent interrupt and close does not segfault" do
854+
for _ <- 1..500 do
855+
{:ok, conn} = Sqlite3.open(":memory:")
856+
task = Task.async(fn -> Sqlite3.interrupt(conn) end)
857+
Sqlite3.close(conn)
858+
Task.await(task, 1000)
859+
end
860+
end
861+
848862
# Targets the two-concurrent-close TOCTOU:
849863
# Both threads pass the `conn->db == NULL` check outside the lock.
850864
# One closes and sets conn->db = NULL; the other then calls

0 commit comments

Comments
 (0)