Skip to content

sqlite: reject closing a session from a callback - #65454

Open
TrevorBurnham wants to merge 3 commits into
nodejs:mainfrom
TrevorBurnham:sqlite-session-close-in-callback
Open

sqlite: reject closing a session from a callback#65454
TrevorBurnham wants to merge 3 commits into
nodejs:mainfrom
TrevorBurnham:sqlite-session-close-in-callback

Conversation

@TrevorBurnham

@TrevorBurnham TrevorBurnham commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

session.close() from a SQLite callback frees the session while SQLite is still using it:

const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE t(a PRIMARY KEY)');
const session = db.createSession();
db.setAuthorizer(() => (session.close(), constants.SQLITE_OK));
db.exec('INSERT INTO t VALUES (1)'); // segfault

SQLite's pre-update hook walks the connection's session list and calls sessionTableInfo(), which prepares and steps PRAGMA table_xinfo. That reaches JavaScript, so a callback runs while the walk still holds pointers into the session it is visiting; sqlite3session_delete() there frees them.

Two callbacks reach that window: an authorizer callback (the reported case) and a 'sqlite.db.query' subscriber, which fires on SQLITE_TRACE_PROFILE when the internal PRAGMA finishes. Both segfault on main, so an authorizer-only guard would be incomplete.

The guard rejects close() and Symbol.dispose whenever the connection is in any callback, matching the existing db.close() rule. Node cannot tell whether SQLite is currently inside xPreUpdate, so a narrower guard would rest on no other callback ever running there. It does newly reject closing a session from a user-defined function, which is safe today. Disposal stays a no-op for an already-closed session; only a live one throws.

Rebased onto #65449, which made session[Symbol.dispose]() throw for a session that is generating a changeset. This guard follows that precedent: the callback check sits below the in-use check, so the more specific message still wins, and #65449's test covers both orders. One consequence is that a using declaration inside a callback demotes the block's own error to SuppressedError; test-sqlite-session.js pins that.

Fixes: #65428

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/sqlite

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem. labels Aug 21, 2026
@TrevorBurnham
TrevorBurnham marked this pull request as ready for review August 21, 2026 17:01
@codecov

codecov Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.06%. Comparing base (0544741) to head (2e65c4c).
⚠️ Report is 16 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #65454      +/-   ##
==========================================
- Coverage   90.07%   90.06%   -0.02%     
==========================================
  Files         751      751              
  Lines      254921   254923       +2     
  Branches    48129    48124       -5     
==========================================
- Hits       229627   229602      -25     
- Misses      16479    16504      +25     
- Partials     8815     8817       +2     
Files with missing lines Coverage Δ
src/node_sqlite.cc 82.12% <100.00%> (+0.01%) ⬆️

... and 33 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trivikr trivikr added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 28, 2026
@trivikr
trivikr requested a review from Renegade334 August 28, 2026 04:37
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 28, 2026
@nodejs-github-bot

This comment was marked as outdated.

@trivikr trivikr added the author ready PRs with CI started, the required approvals, and no outstanding review comments. label Aug 28, 2026
SQLite runs "PRAGMA table_xinfo" from inside its pre-update hook, while
it is still walking the connection's session list. Deleting a session
from a callback that the PRAGMA triggers frees memory that walk is still
using. Both an authorizer callback and a 'sqlite.db.query' subscriber
reach that window, and either one segfaults.

Reject session.close() and Symbol.dispose when the connection is inside
any callback. An already-closed session stays a no-op so that disposal
remains idempotent.

Fixes: nodejs#65428
Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
Pin the deliberate over-rejection with a test: closing from a
user-defined function is safe today but rejected anyway, because Node
cannot tell whether SQLite is inside its pre-update hook.

Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
Cover the `using` form of the rejected disposal, whose block error is
demoted to SuppressedError, and note why the callback check has to stay
below the changeset check in Session::Close().

Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
@TrevorBurnham
TrevorBurnham force-pushed the sqlite-session-close-in-callback branch from 5037bc8 to 2e65c4c Compare August 28, 2026 14:48
@TrevorBurnham
TrevorBurnham requested a review from trivikr August 28, 2026 14:49
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author ready PRs with CI started, the required approvals, and no outstanding review comments. c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sqlite: two reentrancy guard gaps let a callback free the object SQLite is still using

3 participants