Commit e20da720c8e for nodejs
commit e20da720c8e26f36e415dce22088888343d2cdfe
Author: Guilherme Araújo <arauujogui@gmail.com>
Date: Sun Oct 25 18:35:13 2026 -0300
sqlite: validate backup() source is a Database
backup() only checked that the source argument was a BaseObject before
casting it to Database, so passing a Statement or Session caused a
type confusion. Check against the Database constructor template
instead.
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66797
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
diff --git a/src/env_properties.h b/src/env_properties.h
index 087193faba9..c4e3bc5c72a 100644
--- a/src/env_properties.h
+++ b/src/env_properties.h
@@ -451,6 +451,7 @@
V(socketaddress_constructor_template, v8::FunctionTemplate) \
V(space_stats_template, v8::DictionaryTemplate) \
V(sqlite_column_template, v8::DictionaryTemplate) \
+ V(sqlite_database_constructor_template, v8::FunctionTemplate) \
V(sqlite_limits_template, v8::ObjectTemplate) \
V(sqlite_run_result_template, v8::DictionaryTemplate) \
V(sqlite_statement_sync_constructor_template, v8::FunctionTemplate) \
diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc
index d717745b2c3..b8b1988c50b 100644
--- a/src/node_sqlite.cc
+++ b/src/node_sqlite.cc
@@ -2909,9 +2909,11 @@ void Database::CreateSession(const FunctionCallbackInfo<Value>& args) {
void Backup(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
- if (args.Length() < 1 || !args[0]->IsObject()) {
- THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
- "The \"sourceDb\" argument must be an object.");
+ if (!args[0]->IsObject() ||
+ !env->sqlite_database_constructor_template()->HasInstance(args[0])) {
+ THROW_ERR_INVALID_ARG_TYPE(
+ env->isolate(),
+ "The \"sourceDb\" argument must be an instance of Database.");
return;
}
@@ -5085,51 +5087,60 @@ static void Initialize(Local<Object> target,
}
});
}
- Local<FunctionTemplate> db_tmpl = NewFunctionTemplate(isolate, Database::New);
- db_tmpl->InstanceTemplate()->SetInternalFieldCount(
- Database::kInternalFieldCount);
Local<Object> constants = Object::New(isolate);
DefineConstants(constants);
- SetProtoMethod(isolate, db_tmpl, "open", Database::Open);
- SetProtoMethod(isolate, db_tmpl, "close", Database::Close);
- SetProtoDispose(isolate, db_tmpl, Database::Dispose);
- SetProtoMethod(isolate, db_tmpl, "prepare", Database::Prepare);
- SetProtoMethod(isolate, db_tmpl, "exec", Database::Exec);
- SetProtoMethod(isolate, db_tmpl, "function", Database::CustomFunction);
- SetProtoMethod(isolate, db_tmpl, "createTagStore", Database::CreateTagStore);
- SetProtoMethodNoSideEffect(isolate, db_tmpl, "location", Database::Location);
- SetProtoMethod(isolate, db_tmpl, "aggregate", Database::AggregateFunction);
- SetProtoMethod(isolate, db_tmpl, "createSession", Database::CreateSession);
- SetProtoMethod(isolate, db_tmpl, "applyChangeset", Database::ApplyChangeset);
- SetProtoMethod(
- isolate, db_tmpl, "enableLoadExtension", Database::EnableLoadExtension);
- SetProtoMethod(
- isolate, db_tmpl, "enableDefensive", Database::EnableDefensive);
- SetProtoMethod(isolate, db_tmpl, "loadExtension", Database::LoadExtension);
- SetProtoMethod(isolate, db_tmpl, "serialize", Database::Serialize);
- SetProtoMethod(isolate, db_tmpl, "deserialize", Database::Deserialize);
- SetProtoMethod(isolate, db_tmpl, "setAuthorizer", Database::SetAuthorizer);
- SetProtoMethod(isolate, db_tmpl, "createModule", Database::CreateModule);
- SetSideEffectFreeGetter(isolate,
- db_tmpl,
- FIXED_ONE_BYTE_STRING(isolate, "isOpen"),
- Database::IsOpenGetter);
- SetSideEffectFreeGetter(isolate,
- db_tmpl,
- FIXED_ONE_BYTE_STRING(isolate, "isTransaction"),
- Database::IsTransactionGetter);
- SetSideEffectFreeGetter(
- isolate, db_tmpl, env->limits_string(), Database::LimitsGetter);
- Local<String> sqlite_type_key = FIXED_ONE_BYTE_STRING(isolate, "sqlite-type");
- Local<v8::Symbol> sqlite_type_symbol =
- v8::Symbol::For(isolate, sqlite_type_key);
- Local<String> database_sync_string =
- FIXED_ONE_BYTE_STRING(isolate, "node:sqlite");
- db_tmpl->InstanceTemplate()->Set(sqlite_type_symbol, database_sync_string);
-
- SetConstructorFunction(context, target, "Database", db_tmpl);
+ Local<FunctionTemplate> db_tmpl = env->sqlite_database_constructor_template();
+ if (db_tmpl.IsEmpty()) {
+ db_tmpl = NewFunctionTemplate(isolate, Database::New);
+ db_tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "Database"));
+ db_tmpl->InstanceTemplate()->SetInternalFieldCount(
+ Database::kInternalFieldCount);
+ SetProtoMethod(isolate, db_tmpl, "open", Database::Open);
+ SetProtoMethod(isolate, db_tmpl, "close", Database::Close);
+ SetProtoDispose(isolate, db_tmpl, Database::Dispose);
+ SetProtoMethod(isolate, db_tmpl, "prepare", Database::Prepare);
+ SetProtoMethod(isolate, db_tmpl, "exec", Database::Exec);
+ SetProtoMethod(isolate, db_tmpl, "function", Database::CustomFunction);
+ SetProtoMethod(
+ isolate, db_tmpl, "createTagStore", Database::CreateTagStore);
+ SetProtoMethodNoSideEffect(
+ isolate, db_tmpl, "location", Database::Location);
+ SetProtoMethod(isolate, db_tmpl, "aggregate", Database::AggregateFunction);
+ SetProtoMethod(isolate, db_tmpl, "createSession", Database::CreateSession);
+ SetProtoMethod(
+ isolate, db_tmpl, "applyChangeset", Database::ApplyChangeset);
+ SetProtoMethod(
+ isolate, db_tmpl, "enableLoadExtension", Database::EnableLoadExtension);
+ SetProtoMethod(
+ isolate, db_tmpl, "enableDefensive", Database::EnableDefensive);
+ SetProtoMethod(isolate, db_tmpl, "loadExtension", Database::LoadExtension);
+ SetProtoMethod(isolate, db_tmpl, "serialize", Database::Serialize);
+ SetProtoMethod(isolate, db_tmpl, "deserialize", Database::Deserialize);
+ SetProtoMethod(isolate, db_tmpl, "setAuthorizer", Database::SetAuthorizer);
+ SetProtoMethod(isolate, db_tmpl, "createModule", Database::CreateModule);
+ SetSideEffectFreeGetter(isolate,
+ db_tmpl,
+ FIXED_ONE_BYTE_STRING(isolate, "isOpen"),
+ Database::IsOpenGetter);
+ SetSideEffectFreeGetter(isolate,
+ db_tmpl,
+ FIXED_ONE_BYTE_STRING(isolate, "isTransaction"),
+ Database::IsTransactionGetter);
+ SetSideEffectFreeGetter(
+ isolate, db_tmpl, env->limits_string(), Database::LimitsGetter);
+ Local<String> sqlite_type_key =
+ FIXED_ONE_BYTE_STRING(isolate, "sqlite-type");
+ Local<v8::Symbol> sqlite_type_symbol =
+ v8::Symbol::For(isolate, sqlite_type_key);
+ Local<String> database_sync_string =
+ FIXED_ONE_BYTE_STRING(isolate, "node:sqlite");
+ db_tmpl->InstanceTemplate()->Set(sqlite_type_symbol, database_sync_string);
+ env->set_sqlite_database_constructor_template(db_tmpl);
+ }
+ SetConstructorFunction(
+ context, target, "Database", db_tmpl, SetConstructorFunctionFlag::NONE);
SetConstructorFunction(context,
target,
"Statement",
diff --git a/test/parallel/test-sqlite-backup.mjs b/test/parallel/test-sqlite-backup.mjs
index cae097aa92e..633c7da7735 100644
--- a/test/parallel/test-sqlite-backup.mjs
+++ b/test/parallel/test-sqlite-backup.mjs
@@ -48,10 +48,26 @@ describe('backup()', () => {
backup();
}, {
code: 'ERR_INVALID_ARG_TYPE',
- message: 'The "sourceDb" argument must be an object.'
+ message: 'The "sourceDb" argument must be an instance of Database.'
});
});
+ test('throws if the source is not an instance of Database', (t) => {
+ const database = makeSourceDb();
+ for (const source of [
+ {},
+ database.prepare('SELECT 1'),
+ database.createSession(),
+ ]) {
+ t.assert.throws(() => {
+ backup(source, 'out.db');
+ }, {
+ code: 'ERR_INVALID_ARG_TYPE',
+ message: 'The "sourceDb" argument must be an instance of Database.'
+ });
+ }
+ });
+
test('throws if path is not a string, URL, or Buffer', (t) => {
const database = makeSourceDb();