Skip to content

Commit 78e997b

Browse files
committed
Skip UAF in Turso's slot-reuse destroy path
The create_function_v2 reuse-by-name branch invokes the previous slot's destroy callback with its stored p_app. In PHPUnit usage (setUp opens a fresh PDO per test), by the time a second setUp re-registers a same-named UDF, the previous PDO is already gone and its destroy callback UAFs, which hangs pdo_sqlite indefinitely (this is what was hanging testFromBase64Function, testAlterTableAdd*, etc.). Drop the destroy invocation — callbacks still fire from the PHP side at real PDO-destruction time.
1 parent 7c38d50 commit 78e997b

1 file changed

Lines changed: 40 additions & 3 deletions

File tree

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

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,42 @@ jobs:
105105
print(f'patched {n} sqlite3_column_* functions')
106106
PY
107107
108+
# Turso's create_function_v2 invokes the previous FuncSlot's destroy
109+
# callback when re-registering a UDF with the same name. In practice
110+
# (PHPUnit) this means:
111+
# - setUp #1 opens PDO A, registers 44 UDFs, each with a destroy
112+
# callback + p_app pointing to A.
113+
# - tearDown closes PDO A — Turso's sqlite3_close doesn't clear
114+
# those FuncSlots.
115+
# - setUp #2 opens PDO B, re-registers the same 44 names. Turso
116+
# invokes the OLD destroy callback with the now-dangling A p_app,
117+
# which trips pdo_sqlite and hangs the process.
118+
# Comment the destroy invocation out; the callbacks still fire at
119+
# real PDO-destruction time from the PHP side.
120+
python3 - <<'PY_FIX_DESTROY'
121+
p = 'sqlite3/src/lib.rs'
122+
s = open(p).read()
123+
old = (
124+
" // Reuse existing slot — invoke old destroy callback on old user data.\n"
125+
" if let Some(old) = slots[id].take() {\n"
126+
" if old.destroy != 0 {\n"
127+
" let old_destroy: unsafe extern \"C\" fn(*mut ffi::c_void) =\n"
128+
" std::mem::transmute(old.destroy);\n"
129+
" old_destroy(old.p_app as *mut ffi::c_void);\n"
130+
" }\n"
131+
" }\n"
132+
)
133+
new = (
134+
" // Don't invoke the old destroy callback here — in PDO\n"
135+
" // usage the previous slot's p_app often belongs to a db\n"
136+
" // that has already been closed, so the callback UAFs.\n"
137+
" let _ = slots[id].take();\n"
138+
)
139+
assert old in s, 'slot destroy block not found'
140+
open(p, 'w').write(s.replace(old, new, 1))
141+
print('patched slot-reuse destroy invocation')
142+
PY_FIX_DESTROY
143+
108144
# Turso's custom-function registry is capped at 32 pre-generated
109145
# bridge trampolines; the driver registers 44 UDFs, so the last 12
110146
# silently fail. Bump to 64 by adding 32 more func_bridge!/FUNC_BRIDGES
@@ -801,9 +837,10 @@ jobs:
801837
# a single PHP loop; hangs past our 10-minute step budget on
802838
# this runner. Pure PHP (doesn't touch Turso), skipping until
803839
# it can run in its own process.
804-
# Temporarily unskipping everything to see what still hangs with the
805-
# current set of patches.
806-
skip_regex='.+'
840+
# Skipping the two CSV-driven MySQL server-suite tests — they
841+
# tokenize/parse a 5.7 MB fixture in a single loop and run for well
842+
# over 10 min under LD_PRELOAD (pure-PHP, not a Turso issue).
843+
skip_regex='^(?!WP_MySQL_Server_Suite_).+'
807844
timeout --kill-after=10 600 \
808845
php ./vendor/bin/phpunit -c ./phpunit.xml.dist \
809846
--debug \

0 commit comments

Comments
 (0)