@@ -860,13 +860,12 @@ defmodule Exqlite.Sqlite3Test do
860860 end
861861 end
862862
863- # Targets the missing NULL guard in exqlite_last_insert_rowid (line 923
864- # in the unfixed NIF). The function acquires the lock but never checks
865- # whether conn->db is NULL before calling sqlite3_last_insert_rowid(conn->db).
866- # After close() sets conn->db = NULL (inside its own lock and then releases),
867- # the very next last_insert_rowid call acquires the lock and dereferences the
868- # NULL pointer → segfault. No concurrency is required; the crash is
869- # deterministic.
863+ # Targets the missing NULL guard in exqlite_last_insert_rowid.
864+ # The function acquires the lock but never checks whether conn->db is NULL
865+ # before calling sqlite3_last_insert_rowid(conn->db).
866+ # After close() sets conn->db = NULL, the very next last_insert_rowid call
867+ # acquires the lock and dereferences the NULL pointer → segfault.
868+ # No concurrency is required; the crash is deterministic.
870869 test "last_insert_rowid after close does not segfault" do
871870 for _ <- 1 .. 100 do
872871 { :ok , conn } = Sqlite3 . open ( ":memory:" )
@@ -880,8 +879,8 @@ defmodule Exqlite.Sqlite3Test do
880879 end
881880 end
882881
883- # Targets the TOCTOU in exqlite_transaction_status (lines 950–955 in the
884- # unfixed NIF). The function checks !conn->db OUTSIDE the lock, then
882+ # Targets the TOCTOU in exqlite_transaction_status.
883+ # The function checks !conn->db OUTSIDE the lock, then
885884 # acquires the lock and calls sqlite3_get_autocommit(conn->db) inside it.
886885 # If close() completes (setting conn->db = NULL) between that NULL check
887886 # and the lock acquisition, transaction_status calls
@@ -899,7 +898,7 @@ defmodule Exqlite.Sqlite3Test do
899898 end
900899 end
901900
902- # Targets the TOCTOU in exqlite_changes (unfixed NIF line 463) :
901+ # Targets the TOCTOU in exqlite_changes:
903902 # conn->db is checked outside the lock; a concurrent close() can set
904903 # it to NULL between the check and the sqlite3_changes() call inside
905904 # the lock → sqlite3_changes(NULL) → segfault.
@@ -914,7 +913,7 @@ defmodule Exqlite.Sqlite3Test do
914913 end
915914 end
916915
917- # Targets the missing NULL guard in exqlite_serialize (unfixed NIF line 999) :
916+ # Targets the missing NULL guard in exqlite_serialize:
918917 # The function acquires the lock but calls sqlite3_serialize(conn->db, ...)
919918 # without checking for NULL → sqlite3_serialize(NULL, ...) → segfault.
920919 test "serialize after close does not segfault" do
@@ -925,7 +924,7 @@ defmodule Exqlite.Sqlite3Test do
925924 end
926925 end
927926
928- # Targets exqlite_enable_load_extension (unfixed NIF line 1258) :
927+ # Targets exqlite_enable_load_extension:
929928 # No lock is held and no NULL check is performed; after close sets
930929 # conn->db = NULL, sqlite3_enable_load_extension(NULL, ...) → segfault.
931930 test "enable_load_extension after close does not segfault" do
@@ -936,7 +935,7 @@ defmodule Exqlite.Sqlite3Test do
936935 end
937936 end
938937
939- # Targets the missing NULL guard in exqlite_deserialize (unfixed NIF) :
938+ # Targets the missing NULL guard in exqlite_deserialize:
940939 # Acquires the lock then calls sqlite3_deserialize(conn->db, ...) without
941940 # a NULL check → sqlite3_deserialize(NULL, ...) → segfault.
942941 test "deserialize after close does not segfault" do
@@ -966,7 +965,7 @@ defmodule Exqlite.Sqlite3Test do
966965 assert { :ok , :ok } = result , "close deadlocked after failed deserialize (lock not released)"
967966 end
968967
969- # Targets the missing NULL guard in exqlite_set_update_hook (unfixed NIF) :
968+ # Targets the missing NULL guard in exqlite_set_update_hook:
970969 # Acquires the lock then calls sqlite3_update_hook(conn->db, ...) without
971970 # a NULL check → sqlite3_update_hook(NULL, ...) → segfault.
972971 test "set_update_hook after close does not segfault" do
@@ -978,7 +977,7 @@ defmodule Exqlite.Sqlite3Test do
978977 end
979978 end
980979
981- describe ".step, .columns, .multi_step after release" do
980+ describe ".step, .columns, .multi_step, .reset, .bind_* after release" do
982981 # Targets statement use-after-release in exqlite_step.
983982 # After Sqlite3.release(conn, stmt), statement->statement is set to NULL
984983 # under the connection lock. exqlite_step acquires the connection lock
@@ -1013,7 +1012,7 @@ defmodule Exqlite.Sqlite3Test do
10131012
10141013 # Targets statement use-after-release in exqlite_multi_step.
10151014 # After Sqlite3.release(conn, stmt), statement->statement is NULL.
1016- # The pre-lock check at line 773 catches the sequential case, but there
1015+ # The pre-lock check catches the sequential case, but there
10171016 # is a TOCTOU window between that check and the sqlite3_step() call
10181017 # inside the connection lock. This test verifies the expected error
10191018 # return and documents the race condition.
@@ -1027,5 +1026,66 @@ defmodule Exqlite.Sqlite3Test do
10271026 :ok = Sqlite3 . close ( conn )
10281027 end
10291028 end
1029+
1030+ # Targets statement use-after-release in exqlite_reset.
1031+ # After Sqlite3.release(conn, stmt), statement->statement is NULL.
1032+ # exqlite_reset acquires the statement lock and calls sqlite3_reset(NULL)
1033+ # without checking for NULL → segfault.
1034+ test "reset after release does not segfault" do
1035+ for _ <- 1 .. 50 do
1036+ { :ok , conn } = Sqlite3 . open ( ":memory:" )
1037+ :ok = Sqlite3 . execute ( conn , "create table t (x integer)" )
1038+ { :ok , stmt } = Sqlite3 . prepare ( conn , "select * from t" )
1039+ :ok = Sqlite3 . release ( conn , stmt )
1040+ assert { :error , _ } = Sqlite3 . reset ( stmt )
1041+ :ok = Sqlite3 . close ( conn )
1042+ end
1043+ end
1044+
1045+ # Targets statement use-after-release in exqlite_bind_parameter_count.
1046+ # After Sqlite3.release(conn, stmt), statement->statement is NULL.
1047+ # exqlite_bind_parameter_count acquires the statement lock and calls
1048+ # sqlite3_bind_parameter_count(NULL) → segfault.
1049+ test "bind_parameter_count after release does not segfault" do
1050+ for _ <- 1 .. 50 do
1051+ { :ok , conn } = Sqlite3 . open ( ":memory:" )
1052+ { :ok , stmt } = Sqlite3 . prepare ( conn , "select ?" )
1053+ :ok = Sqlite3 . release ( conn , stmt )
1054+ assert { :error , _ } = Sqlite3 . bind_parameter_count ( stmt )
1055+ :ok = Sqlite3 . close ( conn )
1056+ end
1057+ end
1058+
1059+ # Targets statement use-after-release in the bind_* NIFs.
1060+ # After Sqlite3.release(conn, stmt), statement->statement is NULL.
1061+ # Each bind_* NIF acquires the statement lock and calls the SQLite bind
1062+ # function without checking for NULL → segfault.
1063+ test "bind_text after release does not segfault" do
1064+ for _ <- 1 .. 50 do
1065+ { :ok , conn } = Sqlite3 . open ( ":memory:" )
1066+ { :ok , stmt } = Sqlite3 . prepare ( conn , "select ?" )
1067+ :ok = Sqlite3 . release ( conn , stmt )
1068+ try do
1069+ Sqlite3 . bind_text ( stmt , 1 , "hello" )
1070+ rescue
1071+ _ -> :ok
1072+ end
1073+ :ok = Sqlite3 . close ( conn )
1074+ end
1075+ end
1076+
1077+ test "bind_integer after release does not segfault" do
1078+ for _ <- 1 .. 50 do
1079+ { :ok , conn } = Sqlite3 . open ( ":memory:" )
1080+ { :ok , stmt } = Sqlite3 . prepare ( conn , "select ?" )
1081+ :ok = Sqlite3 . release ( conn , stmt )
1082+ try do
1083+ Sqlite3 . bind_integer ( stmt , 1 , 42 )
1084+ rescue
1085+ _ -> :ok
1086+ end
1087+ :ok = Sqlite3 . close ( conn )
1088+ end
1089+ end
10301090 end
10311091end
0 commit comments