Title: sqlite: reentrancy into a running statement from a user-defined function is unguarded · Issue #65102 · nodejs/node · GitHub
Open Graph Title: sqlite: reentrancy into a running statement from a user-defined function is unguarded · Issue #65102 · nodejs/node
X Title: sqlite: reentrancy into a running statement from a user-defined function is unguarded · Issue #65102 · nodejs/node
Description: Version main (verified on 2b350bb8e42) Platform Darwin 25.6.0 arm64 (macOS); not platform-specific Subsystem sqlite What steps will reproduce the bug? #64743 added a database-level callback-depth guard, so db.close() from inside a user-d...
Open Graph Description: Version main (verified on 2b350bb8e42) Platform Darwin 25.6.0 arm64 (macOS); not platform-specific Subsystem sqlite What steps will reproduce the bug? #64743 added a database-level callback-depth g...
X Description: Version main (verified on 2b350bb8e42) Platform Darwin 25.6.0 arm64 (macOS); not platform-specific Subsystem sqlite What steps will reproduce the bug? #64743 added a database-level callback-depth g...
Opengraph URL: https://github.com/nodejs/node/issues/65102
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"sqlite: reentrancy into a running statement from a user-defined function is unguarded","articleBody":"### Version\n\n`main` (verified on `2b350bb8e42`)\n\n### Platform\n\n```text\nDarwin 25.6.0 arm64 (macOS); not platform-specific\n```\n\n### Subsystem\n\nsqlite\n\n### What steps will reproduce the bug?\n\n#64743 added a database-level callback-depth guard, so `db.close()` from inside a user-defined function now correctly throws `ERR_INVALID_STATE` instead of finalizing a statement mid-`sqlite3_step()` (that was #63180, now fixed). The guard is per-*database*, so it does not cover a callback reentering the specific statement that invoked it. SQLite forbids calling `sqlite3_step()` / `sqlite3_reset()` / `sqlite3_finalize()` on a statement while that statement's own callback is on the stack.\n\nTwo cases:\n\n**1. Reentrant `iter.next()` silently advances the iterator being consumed.**\n\n```js\nconst { DatabaseSync } = require('node:sqlite');\nconst db = new DatabaseSync(':memory:');\ndb.exec('CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER)');\ndb.prepare('INSERT INTO t VALUES (1, 10)').run();\n\nlet iter;\ndb.function('reenter', () => {\n iter.next(); // reenters the statement currently being stepped\n return 0;\n});\n\niter = db.prepare('SELECT reenter() FROM t').iterate();\nconsole.log(iter.next());\n```\n\n**2. Recursive `stmt.get()` on the running statement.**\n\n```js\nconst { DatabaseSync } = require('node:sqlite');\nconst db = new DatabaseSync(':memory:');\nlet stmt;\ndb.function('x', () => stmt.get());\nstmt = db.prepare('SELECT x()');\nstmt.get();\n```\n\n### How often does it reproduce? Is there a required condition?\n\nAlways. The callback must reenter the same statement object that is currently executing.\n\n### What is the expected behavior? Why is that the expected behavior?\n\nBoth should throw `ERR_INVALID_STATE`, consistent with how #64743 handles `db.close()` from a callback. Cross-statement use from a callback (the common \"lookup\" pattern — preparing or stepping a *different* statement) should keep working; only reentry into the running statement is forbidden by the C API.\n\n### What do you see instead?\n\nCase 1: no error. The reentrant `next()` consumes rows from the same VM, so the outer iteration silently skips them:\n\n```\nreentrant next() SUCCEEDED\nreentrant next() SUCCEEDED\nfirst: {\"done\":false,\"value\":{\"reenter()\":0}}\n```\n\nCase 2: `Maximum call stack size exceeded` — a V8 stack overflow rather than a SQLite-level error, so the actual constraint is never reported.\n\nNeither crashes, so this is a correctness issue rather than memory safety.\n\n### Additional information\n\nThe closed #63183 contained a per-statement `IsStepping()` RAII flag (`MarkStepping()` around every `sqlite3_step` caller, with JS-callable step/reset/finalize checking it) that addresses exactly this, plus regression tests for both cases. That PR was closed in favor of #64743, which covers only the database-level `close()` path, so the per-statement piece is currently unowned. It may be worth salvaging from that branch.\n","author":{"url":"https://github.com/TrevorBurnham","@type":"Person","name":"TrevorBurnham"},"datePublished":"2026-08-07T12:23:41Z","url":"https://github.com/nodejs/node/issues/65102"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:fb7fbc11-20ff-4f5e-6d1b-954ff53ad62b |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 996C:2D3467:7DF161:A6F155:6AED269B |
| html-safe-nonce | 0176a6537d30f4b9ebf049dfdf8135200d9ecb28bdfa27c6eb72d9e4fc256de8 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5OTZDOjJEMzQ2Nzo3REYxNjE6QTZGMTU1OjZBRUQyNjlCIiwidmlzaXRvcl9pZCI6IjU4NzA3OTQ3NjcyNDYxMDgzMTUiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 40a3ba9c719ad57a828cbfe715781642f551ce8082d4f5cc6fd2a2f914251ccd |
| hovercard-subject-tag | issue:5090108486 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/65102/issue_layout |
| twitter:image | https://opengraph.githubassets.com/b6c7b8a78dd324f82d941052b87deeccb1ea7639918b9702b0954ebf81a1fd29/nodejs/node/issues/65102 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/b6c7b8a78dd324f82d941052b87deeccb1ea7639918b9702b0954ebf81a1fd29/nodejs/node/issues/65102 |
| og:image:alt | Version main (verified on 2b350bb8e42) Platform Darwin 25.6.0 arm64 (macOS); not platform-specific Subsystem sqlite What steps will reproduce the bug? #64743 added a database-level callback-depth g... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | TrevorBurnham |
| hostname | github.com |
| expected-hostname | github.com |
| None | 9d95365dda0576efe9b71f45807595e396bd0ca9286fba5367a05048e6ddf8f2 |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 98b41fa4b5635ceb45a4c54320fced73817ff290 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width