Skip to content

sqlite: reject statement-less SQL in SQLTagStore - #65157

Draft
TrevorBurnham wants to merge 2 commits into
nodejs:mainfrom
TrevorBurnham:sqlite/fix-tagstore-null-stmt
Draft

sqlite: reject statement-less SQL in SQLTagStore#65157
TrevorBurnham wants to merge 2 commits into
nodejs:mainfrom
TrevorBurnham:sqlite/fix-tagstore-null-stmt

Conversation

@TrevorBurnham

@TrevorBurnham TrevorBurnham commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

sqlite3_prepare_v2() returns SQLITE_OK without producing a statement when its input holds no SQL, such as a comment or an empty string. Neither caller checked for this.

SQLTagStore cached a StatementSync wrapping the null sqlite3_stmt. Executing it reached sqlite3_clear_bindings(), which only guards against a null statement under SQLITE_ENABLE_API_ARMOR, and segfaulted:

const db = new DatabaseSync(':memory:');
const store = db.createTagStore();
store.run`-- comment`;  // segfault

All four query methods (run, get, all, iterate) were affected.

db.prepare() returned a StatementSync whose statement_ was null. Every method on it threw statement has been finalized, which was misleading because nothing had been finalized. It was also still inserted into statements_, and because IsFinalized() is true for a null statement, its destructor skipped UntrackStatement() and left a dangling pointer that a later close() would finalize.

Both now throw ERR_INVALID_ARG_VALUE at preparation. SQL that merely contains comments is unaffected.

Fixes: #65149

@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 9, 2026
Comment thread src/node_sqlite.cc Outdated
Comment on lines +3651 to +3657
// sqlite3_prepare_v2() reports success without producing a statement when
// the input holds no SQL, such as a comment. Such a statement cannot be
// bound or executed, so reject it instead of caching it.
if (s == nullptr) {
THROW_ERR_INVALID_ARG_VALUE(env, "The SQL query contains no statement.");
return BaseObjectPtr<StatementSync>();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the right approach, but if we do this for the tag store then we should also perform this check for db.prepare() (ie. throw at the point of preparation rather than at the point of attempting to step the null query).

const stmt = db.prepare('-- :-/') // currently succeeds
stmt.get() // currently fails at this point with ERR_INVALID_STATE

TrevorBurnham added a commit to TrevorBurnham/node that referenced this pull request Aug 9, 2026
Apply the same check to DatabaseSync::Prepare() so that statement-less
SQL is rejected at preparation instead of on first use.

Previously db.prepare('-- comment') returned a StatementSync whose
statement_ was null. Every method on it threw "statement has been
finalized", which was misleading because nothing had been finalized, and
the object was still inserted into statements_. Since IsFinalized() is
true for a null statement, its destructor skipped UntrackStatement() and
left a dangling pointer in the set that a later close() would finalize.

Refs: nodejs#65157 (comment)

Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
sqlite3_prepare_v2() returns SQLITE_OK without producing a statement
when its input holds no SQL, such as a comment. PrepareStatement() only
checked the return code, so it cached a StatementSync wrapping a null
sqlite3_stmt. Executing it reached sqlite3_clear_bindings(), which only
guards against a null statement under SQLITE_ENABLE_API_ARMOR, and
segfaulted.

Reject such input instead of caching it. The StatementSync methods
already avoid the crash because their IsFinalized() guard treats a null
statement as finalized.

Fixes: nodejs#65149

Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
Apply the same check to DatabaseSync::Prepare() so that statement-less
SQL is rejected at preparation instead of on first use. This matches
SQLite's own oo1 JavaScript API, which throws when the SQL contains no
statements rather than exposing the C API's null statement pointer.

Previously db.prepare('-- comment') returned a StatementSync whose
statement_ was null. Every method on it threw "statement has been
finalized", which was misleading because nothing had been finalized, and
the object was still inserted into statements_. Since IsFinalized() is
true for a null statement, its destructor skipped UntrackStatement() and
left a dangling pointer in the set that a later close() would finalize.

Refs: nodejs#65157 (comment)
Refs: https://sqlite.org/wasm/doc/trunk/api-oo1.md

Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
@TrevorBurnham
TrevorBurnham force-pushed the sqlite/fix-tagstore-null-stmt branch from af2725f to 3d2d495 Compare August 9, 2026 15:55

@Renegade334 Renegade334 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ‘πŸ‘

Comment thread doc/api/sqlite.md
Comment on lines +689 to +691
Throws an `ERR_INVALID_ARG_VALUE` error if `sql` contains no statements, such as
an empty string or a lone comment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This is somewhat unnecessary, if a user is passing an empty SQL query then that's on them

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

Labels

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: segfault when a SQLTagStore query contains only a comment

3 participants