Uh oh!
There was an error while loading. Please reload this page.
sqlite: reject reentry into a running statement - #65106
Conversation
nodejs-github-bot
commented
Aug 7, 2026
Review requested:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@## main #65106 +/- ##
========================================
Coverage 90.31% 90.32% ========================================
Files 759 760 +1 Lines 248290 248551 +261 Branches 46859 46918 +59 ========================================
+ Hits 224241 224501 +260 + Misses 15472 15460 -12 - Partials 8577 8590 +13
🚀 New features to boost your workflow:
|
This comment was marked as outdated.
This comment was marked as outdated.
ee4e9b5 to
986eb0eCompareTrevorBurnham
commented
Aug 7, 2026
@trivikr I've addressed the issues you noted. The |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
986eb0e to
fde8f11CompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
fde8f11 to
02e92f5Comparenodejs-github-bot
commented
Aug 8, 2026
avivkeller
commented
Aug 9, 2026
Commit Queue failed- Loading data for nodejs/node/pull/65106 ✔ Done loading data for nodejs/node/pull/65106 ----------------------------------- PR info ------------------------------------ Title sqlite: reject reentry into a running statement (#65106) Author Trevor Burnham <trevorburnham@gmail.com> (@TrevorBurnham) Branch TrevorBurnham:sqlite/guard-statement-reentry -> nodejs:main Labels c++, author ready, needs-ci, commit-queue, sqlite Commits 1 - sqlite: reject reentry into a running statement Committers 1 - Trevor Burnham <trevorburnham@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65106 Fixes: https://github.com/nodejs/node/issues/65102 Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> ------------------------------ Generated metadata ------------------------------ PR-URL: https://github.com/nodejs/node/pull/65106 Fixes: https://github.com/nodejs/node/issues/65102 Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> -------------------------------------------------------------------------------- ℹ This PR was created on Fri, 07 Aug 2026 14:58:01 GMT ✔ Approvals: 1 ✔ - Trivikram Kamat (@trivikr): https://github.com/nodejs/node/pull/65106#pullrequestreview-4889248717 ✘ This PR needs to wait 113 more hours to land (or 0 minutes if there is one more approval) ✔ Last GitHub CI successful ℹ Last Full PR CI on 2026-08-08T17:08:25Z: https://ci.nodejs.org/job/node-test-pull-request/75659/ - Querying data for job/node-test-pull-request/75659/ ✔ Build data downloaded ✔ Last Jenkins CI successful -------------------------------------------------------------------------------- ✔ Aborted `git node land` session in /Users/avivkeller/Documents/projects/nodejs/node/.ncu/nodejs/node/actions/runs/ |
SQLite forbids stepping, resetting, or finalizing a statement while that statement's own user-defined function callback is on the stack. The callback depth added in 5cef767 is tracked per database, so it cannot tell reentry into the running statement apart from the common pattern of querying a different statement from a callback. Mark the statement being executed and reject step, reset, and finalize on that statement with ERR_INVALID_STATE. Statements other than the running one are unaffected. Reentry corrupts the running virtual machine rather than merely producing a wrong answer. Resetting from a callback halts a VM whose sqlite3_step() frame is still live, so a bounded reentrant get() or run(), and iterator.return() with its bare sqlite3_reset(), all segfault; from an aggregate's step or result callback the same reset tears down aggregate state mid-xValue and aborts on a CHECK in a release build. Where it does not crash it is still wrong: a reentrant iterator.next() silently consumed rows from the iteration in progress, and a recursive get() surfaced a V8 stack overflow instead of the constraint. close() and [Symbol.dispose]() are covered too, since finalizing mid-step frees the running VM. The mark is set before parameters are bound, so a getter or valueOf() that reenters while its own arguments are being evaluated is rejected as well. Without this, two iterators could share one virtual machine and interleave rows from a single result set. Signed-off-by: Trevor Burnham <trevorburnham@gmail.com> Assisted-by: claude:opus-5
02e92f5 to
abab573Compare
This comment was marked as resolved.
This comment was marked as resolved.
TrevorBurnham
commented
Aug 10, 2026
^ Updated the PR to cover |
Fixes: #65102
SQLite forbids stepping, resetting, or finalizing a statement while that statement's own user-defined function callback is on the stack. The callback depth added in 5cef767 (#64743) is tracked per database, so it cannot distinguish reentry into the running statement from the common pattern of querying a different statement from a callback.
This PR adds a per-statement flag, set for the duration of an execution, and rejects step/reset/finalize on that statement with
ERR_INVALID_STATE: statement is currently being executed.Severity
Reentry corrupts the running virtual machine. Resetting from a callback halts a VM whose
sqlite3_step()frame is still live:Verified on v24.15.0 and current
main. The crash needs the reentry to be bounded — the repros in #65102 recurse without limit, so V8's stack overflows before control returns into the corruptedstep(), which is why that issue concluded it was correctness-only.get()/run()/all()(each resets)iterator.return()(baresqlite3_reset)step/resultcallbackCHECK(agg->initialized)in Releaseiterator.next()(steps, no reset)get(), unboundedMaximum call stack size exceededcolumns()/sourceSQL/expandedSQLThe aggregate row is a distinct corruption path: the reentrant reset tears down aggregate state mid-
xValue, tripping theCHECKinCustomAggregate::DestroyAggregateData. ThatCHECKis compiled into release builds, so it aborts in production rather than only under a debug build.Covered entry points:
all(),get(),run(),iterate(),iterator.next(),iterator.return(),close(),[Symbol.dispose](), and the four SQL tag store methods.close()and[Symbol.dispose]()are included because finalizing mid-step frees the virtual machine thatsqlite3_step()is still executing.Prior art: #63183 took this approach alongside its own database-level guard. That PR was closed once #64743 landed, so this salvages the per-statement half and builds on the guard already in
main.