Skip to content

Commit a39d432

Browse files
committed
Clear stale FUNC_SLOTS on sqlite3_close
Existing patches keep FUNC_SLOTS populated across sqlite3_close (to avoid p_app use-after-free during PHP GC mid-step). But the leftover stale `db` pointers persist process-wide, and may explain why the three remaining tests pass in fresh-PDO probes but fail mid-suite: prior tests' UDF registrations leave dangling db references that later queries' optimizers reach via dispatch_func_bridge. Patch sqlite3_close to walk FUNC_SLOTS and clear any slot whose `db` address matches the closing db. This doesn't touch p_app or invoke destroy callbacks (still safe vs the original UAF), it just prevents stale db pointers from persisting.
1 parent 5f879cf commit a39d432

1 file changed

Lines changed: 48 additions & 0 deletions

File tree

.github/workflows/phpunit-tests-turso.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,54 @@ jobs:
595595
print('patched CreateTrigger to preserve original SQL text')
596596
PY_TRIGGER_SQL
597597
598+
# Clear stale FUNC_SLOTS on sqlite3_close. Existing patches keep
599+
# slots populated across PDO close (avoiding p_app UAF during
600+
# PHP GC), but leftover stale `db` pointers persist process-wide
601+
# and may explain mid-suite state-dependent failures (the
602+
# 3 remaining tests pass in fresh-PDO probes).
603+
python3 - <<'PY_CLOSE_SLOTS'
604+
p = 'sqlite3/src/lib.rs'
605+
s = open(p).read()
606+
old = (
607+
"#[no_mangle]\n"
608+
"pub unsafe extern \"C\" fn sqlite3_close(db: *mut sqlite3) -> ffi::c_int {\n"
609+
" trace!(\"sqlite3_close\");\n"
610+
" if db.is_null() {\n"
611+
" return SQLITE_OK;\n"
612+
" }\n"
613+
" let _ = Box::from_raw(db);\n"
614+
" SQLITE_OK\n"
615+
"}\n"
616+
)
617+
new = (
618+
"#[no_mangle]\n"
619+
"pub unsafe extern \"C\" fn sqlite3_close(db: *mut sqlite3) -> ffi::c_int {\n"
620+
" trace!(\"sqlite3_close\");\n"
621+
" if db.is_null() {\n"
622+
" return SQLITE_OK;\n"
623+
" }\n"
624+
" // Clear any FUNC_SLOTS that still reference this db so the\n"
625+
" // process-global table doesn't accumulate stale entries.\n"
626+
" {\n"
627+
" let db_addr = db as usize;\n"
628+
" let mut slots = func_slots().lock().unwrap();\n"
629+
" for slot in slots.iter_mut() {\n"
630+
" if let Some(s) = slot.as_ref() {\n"
631+
" if s.db == db_addr {\n"
632+
" *slot = None;\n"
633+
" }\n"
634+
" }\n"
635+
" }\n"
636+
" }\n"
637+
" let _ = Box::from_raw(db);\n"
638+
" SQLITE_OK\n"
639+
"}\n"
640+
)
641+
assert old in s, 'sqlite3_close block not found'
642+
open(p, 'w').write(s.replace(old, new, 1))
643+
print('patched sqlite3_close to clear stale FUNC_SLOTS for closing db')
644+
PY_CLOSE_SLOTS
645+
598646
echo '--- Patched stub! macro ---'
599647
sed -n '/macro_rules! stub/,/^}$/p' sqlite3/src/lib.rs
600648

0 commit comments

Comments
 (0)