Skip to content

Commit 5a01df2

Browse files
trivikraduh95
authored andcommitted
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: #64796 Refs: #64795 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
1 parent c9b6e75 commit 5a01df2

3 files changed

Lines changed: 26 additions & 2 deletions

File tree

‎doc/api/sqlite.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -607,8 +607,10 @@ added: v24.16.0
607607
Loads a serialized database into this connection, replacing the current
608608
database. The deserialized database is writable. Existing prepared statements
609609
are finalized before deserialization is attempted, even if the operation
610-
subsequently fails. This method is a wrapper around
611-
[`sqlite3_deserialize()`][].
610+
subsequently fails. An [`ERR_INVALID_STATE`][] error is thrown if the method is
611+
called while a database callback is on the stack, for example a user-defined
612+
function, an aggregate function, an authorizer, or a changeset filter or conflict
613+
handler. This method is a wrapper around [`sqlite3_deserialize()`][].
612614

613615
```mjs
614616
import { DatabaseSync } from 'node:sqlite';
@@ -1753,6 +1755,7 @@ callback function to indicate what type of operation is being authorized.
17531755
[SQL injection]: https://en.wikipedia.org/wiki/SQL_injection
17541756
[Type conversion between JavaScript and SQLite]: #type-conversion-between-javascript-and-sqlite
17551757
[`ATTACH DATABASE`]: https://www.sqlite.org/lang_attach.html
1758+
[`ERR_INVALID_STATE`]: errors.md#err_invalid_state
17561759
[`PRAGMA foreign_keys`]: https://www.sqlite.org/pragma.html#pragma_foreign_keys
17571760
[`SQLITE_DBCONFIG_DEFENSIVE`]: https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigdefensive
17581761
[`SQLITE_DETERMINISTIC`]: https://www.sqlite.org/c3ref/c_deterministic.html

‎src/node_sqlite.cc‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1871,6 +1871,10 @@ void DatabaseSync::Deserialize(const FunctionCallbackInfo<Value>& args) {
18711871
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
18721872
Environment* env = Environment::GetCurrent(args);
18731873
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1874+
THROW_AND_RETURN_ON_BAD_STATE(
1875+
env,
1876+
db->IsInCallback(),
1877+
"database cannot be deserialized while in a callback");
18741878

18751879
if (!args[0]->IsUint8Array()) {
18761880
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),

‎test/parallel/test-sqlite-serialize.js‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,23 @@ suite('DatabaseSync.prototype.deserialize()', () => {
190190
});
191191
});
192192

193+
test('throws if called while in a callback', (t) => {
194+
const source = new DatabaseSync(':memory:');
195+
const serialized = source.serialize();
196+
source.close();
197+
198+
const db = new DatabaseSync(':memory:');
199+
t.after(() => db.close());
200+
db.function('deserialize_database', () => db.deserialize(serialized));
201+
const stmt = db.prepare('SELECT deserialize_database()');
202+
203+
t.assert.throws(() => stmt.get(), {
204+
code: 'ERR_INVALID_STATE',
205+
message: 'database cannot be deserialized while in a callback',
206+
});
207+
t.assert.strictEqual(db.isOpen, true);
208+
});
209+
193210
test('throws if buffer argument is not a Uint8Array', (t) => {
194211
const db = new DatabaseSync(':memory:');
195212
t.assert.throws(() => {

0 commit comments

Comments
 (0)