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>
This commit is contained in:
Guilherme Araújo authored and GitHub committed 2026-10-25 21:35:13 +00:00
1 parent c7ae7ab5b9
commit e20da720c8
3 files changed
+73 -45

No files matched your search

+1
View File
@@ -451,6 +451,7 @@
V(socketaddress_constructor_template, v8::FunctionTemplate) \ V(socketaddress_constructor_template, v8::FunctionTemplate) \
V(space_stats_template, v8::DictionaryTemplate) \ V(space_stats_template, v8::DictionaryTemplate) \
V(sqlite_column_template, v8::DictionaryTemplate) \ V(sqlite_column_template, v8::DictionaryTemplate) \
V(sqlite_database_constructor_template, v8::FunctionTemplate) \
V(sqlite_limits_template, v8::ObjectTemplate) \ V(sqlite_limits_template, v8::ObjectTemplate) \
V(sqlite_run_result_template, v8::DictionaryTemplate) \ V(sqlite_run_result_template, v8::DictionaryTemplate) \
V(sqlite_statement_sync_constructor_template, v8::FunctionTemplate) \ V(sqlite_statement_sync_constructor_template, v8::FunctionTemplate) \
+55 -44
View File
@@ -2909,9 +2909,11 @@ void Database::CreateSession(const FunctionCallbackInfo<Value>& args) {
void Backup(const FunctionCallbackInfo<Value>& args) { void Backup(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
if (args.Length() < 1 || !args[0]->IsObject()) { if (!args[0]->IsObject() ||
THROW_ERR_INVALID_ARG_TYPE(env->isolate(), !env->sqlite_database_constructor_template()->HasInstance(args[0])) {
"The \"sourceDb\" argument must be an object."); THROW_ERR_INVALID_ARG_TYPE(
env->isolate(),
"The \"sourceDb\" argument must be an instance of Database.");
return; 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); Local<Object> constants = Object::New(isolate);
DefineConstants(constants); DefineConstants(constants);
SetProtoMethod(isolate, db_tmpl, "open", Database::Open); Local<FunctionTemplate> db_tmpl = env->sqlite_database_constructor_template();
SetProtoMethod(isolate, db_tmpl, "close", Database::Close); if (db_tmpl.IsEmpty()) {
SetProtoDispose(isolate, db_tmpl, Database::Dispose); db_tmpl = NewFunctionTemplate(isolate, Database::New);
SetProtoMethod(isolate, db_tmpl, "prepare", Database::Prepare); db_tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "Database"));
SetProtoMethod(isolate, db_tmpl, "exec", Database::Exec); db_tmpl->InstanceTemplate()->SetInternalFieldCount(
SetProtoMethod(isolate, db_tmpl, "function", Database::CustomFunction); Database::kInternalFieldCount);
SetProtoMethod(isolate, db_tmpl, "createTagStore", Database::CreateTagStore); SetProtoMethod(isolate, db_tmpl, "open", Database::Open);
SetProtoMethodNoSideEffect(isolate, db_tmpl, "location", Database::Location); SetProtoMethod(isolate, db_tmpl, "close", Database::Close);
SetProtoMethod(isolate, db_tmpl, "aggregate", Database::AggregateFunction); SetProtoDispose(isolate, db_tmpl, Database::Dispose);
SetProtoMethod(isolate, db_tmpl, "createSession", Database::CreateSession); SetProtoMethod(isolate, db_tmpl, "prepare", Database::Prepare);
SetProtoMethod(isolate, db_tmpl, "applyChangeset", Database::ApplyChangeset); SetProtoMethod(isolate, db_tmpl, "exec", Database::Exec);
SetProtoMethod( SetProtoMethod(isolate, db_tmpl, "function", Database::CustomFunction);
isolate, db_tmpl, "enableLoadExtension", Database::EnableLoadExtension); SetProtoMethod(
SetProtoMethod( isolate, db_tmpl, "createTagStore", Database::CreateTagStore);
isolate, db_tmpl, "enableDefensive", Database::EnableDefensive); SetProtoMethodNoSideEffect(
SetProtoMethod(isolate, db_tmpl, "loadExtension", Database::LoadExtension); isolate, db_tmpl, "location", Database::Location);
SetProtoMethod(isolate, db_tmpl, "serialize", Database::Serialize); SetProtoMethod(isolate, db_tmpl, "aggregate", Database::AggregateFunction);
SetProtoMethod(isolate, db_tmpl, "deserialize", Database::Deserialize); SetProtoMethod(isolate, db_tmpl, "createSession", Database::CreateSession);
SetProtoMethod(isolate, db_tmpl, "setAuthorizer", Database::SetAuthorizer); SetProtoMethod(
SetProtoMethod(isolate, db_tmpl, "createModule", Database::CreateModule); isolate, db_tmpl, "applyChangeset", Database::ApplyChangeset);
SetSideEffectFreeGetter(isolate, SetProtoMethod(
db_tmpl, isolate, db_tmpl, "enableLoadExtension", Database::EnableLoadExtension);
FIXED_ONE_BYTE_STRING(isolate, "isOpen"), SetProtoMethod(
Database::IsOpenGetter); isolate, db_tmpl, "enableDefensive", Database::EnableDefensive);
SetSideEffectFreeGetter(isolate, SetProtoMethod(isolate, db_tmpl, "loadExtension", Database::LoadExtension);
db_tmpl, SetProtoMethod(isolate, db_tmpl, "serialize", Database::Serialize);
FIXED_ONE_BYTE_STRING(isolate, "isTransaction"), SetProtoMethod(isolate, db_tmpl, "deserialize", Database::Deserialize);
Database::IsTransactionGetter); SetProtoMethod(isolate, db_tmpl, "setAuthorizer", Database::SetAuthorizer);
SetSideEffectFreeGetter( SetProtoMethod(isolate, db_tmpl, "createModule", Database::CreateModule);
isolate, db_tmpl, env->limits_string(), Database::LimitsGetter); SetSideEffectFreeGetter(isolate,
Local<String> sqlite_type_key = FIXED_ONE_BYTE_STRING(isolate, "sqlite-type"); db_tmpl,
Local<v8::Symbol> sqlite_type_symbol = FIXED_ONE_BYTE_STRING(isolate, "isOpen"),
v8::Symbol::For(isolate, sqlite_type_key); Database::IsOpenGetter);
Local<String> database_sync_string = SetSideEffectFreeGetter(isolate,
FIXED_ONE_BYTE_STRING(isolate, "node:sqlite"); db_tmpl,
db_tmpl->InstanceTemplate()->Set(sqlite_type_symbol, database_sync_string); FIXED_ONE_BYTE_STRING(isolate, "isTransaction"),
Database::IsTransactionGetter);
SetConstructorFunction(context, target, "Database", db_tmpl); 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, SetConstructorFunction(context,
target, target,
"Statement", "Statement",
+17 -1
View File
@@ -48,10 +48,26 @@ describe('backup()', () => {
backup(); backup();
}, { }, {
code: 'ERR_INVALID_ARG_TYPE', 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) => { test('throws if path is not a string, URL, or Buffer', (t) => {
const database = makeSourceDb(); const database = makeSourceDb();