From b2b7405d2635b9bc7638fb5d378d72c2d5f33c82 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Mon, 27 Jul 2026 19:45:54 -0700 Subject: [PATCH] sqlite: reject deserialize() while in a callback deserialize() could be called from a user-defined function invoked during statement execution, tearing down the database connection while sqlite3_step() was still using it. Reuse the existing callback depth check to throw ERR_INVALID_STATE instead, matching the guard already in place for close(). Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: codex:gpt-5.6-sol PR-URL: https://github.com/nodejs/node/pull/64796 Refs: https://github.com/nodejs/node/issues/64795 Reviewed-By: Stephen Belanger --- doc/api/sqlite.md | 7 +++++-- src/node_sqlite.cc | 4 ++++ test/parallel/test-sqlite-serialize.js | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/doc/api/sqlite.md b/doc/api/sqlite.md index 57a0007baabc..ae194ff2acaf 100644 --- a/doc/api/sqlite.md +++ b/doc/api/sqlite.md @@ -629,8 +629,10 @@ added: Loads a serialized database into this connection, replacing the current database. The deserialized database is writable. Existing prepared statements are finalized before deserialization is attempted, even if the operation -subsequently fails. This method is a wrapper around -[`sqlite3_deserialize()`][]. +subsequently fails. An [`ERR_INVALID_STATE`][] error is thrown if the method is +called while a database callback is on the stack, for example a user-defined +function, an aggregate function, an authorizer, or a changeset filter or conflict +handler. This method is a wrapper around [`sqlite3_deserialize()`][]. ```mjs import { DatabaseSync } from 'node:sqlite'; @@ -1796,6 +1798,7 @@ callback function to indicate what type of operation is being authorized. [SQL injection]: https://en.wikipedia.org/wiki/SQL_injection [Type conversion between JavaScript and SQLite]: #type-conversion-between-javascript-and-sqlite [`ATTACH DATABASE`]: https://www.sqlite.org/lang_attach.html +[`ERR_INVALID_STATE`]: errors.md#err_invalid_state [`PRAGMA foreign_keys`]: https://www.sqlite.org/pragma.html#pragma_foreign_keys [`SQLITE_DBCONFIG_DEFENSIVE`]: https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigdefensive [`SQLITE_DETERMINISTIC`]: https://www.sqlite.org/c3ref/c_deterministic.html diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 027372e42daa..80da5bc0d9bf 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -1858,6 +1858,10 @@ void DatabaseSync::Deserialize(const FunctionCallbackInfo& args) { ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); + THROW_AND_RETURN_ON_BAD_STATE( + env, + db->IsInCallback(), + "database cannot be deserialized while in a callback"); if (!args[0]->IsUint8Array()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), diff --git a/test/parallel/test-sqlite-serialize.js b/test/parallel/test-sqlite-serialize.js index 77b9d9c5f483..e54cfa3c6a75 100644 --- a/test/parallel/test-sqlite-serialize.js +++ b/test/parallel/test-sqlite-serialize.js @@ -190,6 +190,23 @@ suite('DatabaseSync.prototype.deserialize()', () => { }); }); + test('throws if called while in a callback', (t) => { + const source = new DatabaseSync(':memory:'); + const serialized = source.serialize(); + source.close(); + + const db = new DatabaseSync(':memory:'); + t.after(() => db.close()); + db.function('deserialize_database', () => db.deserialize(serialized)); + const stmt = db.prepare('SELECT deserialize_database()'); + + t.assert.throws(() => stmt.get(), { + code: 'ERR_INVALID_STATE', + message: 'database cannot be deserialized while in a callback', + }); + t.assert.strictEqual(db.isOpen, true); + }); + test('throws if buffer argument is not a Uint8Array', (t) => { const db = new DatabaseSync(':memory:'); t.assert.throws(() => {