Skip to content

Commit 89dd4fb

Browse files
lazergTrevorBurnham
andcommitted
sqlite: validate the backup() source database
backup() only checked that sourceDb was an object before unwrapping it, so passing a plain object, an array, a Statement or a Session crashed the process. Check it against the Database constructor template instead, and keep that template behind Database::GetConstructorTemplate() like Statement, Session and SQLTagStore already do. Also let an exception thrown by an href getter propagate from ValidateDatabasePath() instead of replacing it with ERR_INVALID_ARG_TYPE. Fixes: #65830 Co-authored-by: Trevor Burnham <trevorburnham@gmail.com> Signed-off-by: Lazizbek Ergashev <lazerg2@gmail.com> Assisted-by: Claude Code
1 parent 855e3f7 commit 89dd4fb

5 files changed

Lines changed: 97 additions & 47 deletions

File tree

‎src/env_properties.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@
451451
V(socketaddress_constructor_template, v8::FunctionTemplate) \
452452
V(space_stats_template, v8::DictionaryTemplate) \
453453
V(sqlite_column_template, v8::DictionaryTemplate) \
454+
V(sqlite_database_constructor_template, v8::FunctionTemplate) \
454455
V(sqlite_limits_template, v8::ObjectTemplate) \
455456
V(sqlite_run_result_template, v8::DictionaryTemplate) \
456457
V(sqlite_statement_sync_constructor_template, v8::FunctionTemplate) \

‎src/node_sqlite.cc‎

Lines changed: 63 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,8 +1840,12 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
18401840
} else if (path->IsObject()) { // When is URL
18411841
auto url = path.As<Object>();
18421842
Local<Value> href;
1843-
if (url->Get(env->context(), env->href_string()).ToLocal(&href) &&
1844-
href->IsString()) {
1843+
// Let an exception thrown by the href getter propagate instead of
1844+
// replacing it with ERR_INVALID_ARG_TYPE.
1845+
if (!url->Get(env->context(), env->href_string()).ToLocal(&href)) {
1846+
return std::nullopt;
1847+
}
1848+
if (href->IsString()) {
18451849
Utf8Value location_value(env->isolate(), href.As<String>());
18461850
auto location = location_value.ToStringView();
18471851
if (!has_null_bytes(location)) {
@@ -3194,9 +3198,13 @@ void Database::CreateSession(const FunctionCallbackInfo<Value>& args) {
31943198

31953199
void Backup(const FunctionCallbackInfo<Value>& args) {
31963200
Environment* env = Environment::GetCurrent(args);
3197-
if (args.Length() < 1 || !args[0]->IsObject()) {
3198-
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
3199-
"The \"sourceDb\" argument must be an object.");
3201+
// Unlike the other unwrap sites in this file, which rely on V8's signature
3202+
// check for args.This(), this one takes a value out of args[] and so has to
3203+
// check the type itself before unwrapping it.
3204+
if (!Database::GetConstructorTemplate(env)->HasInstance(args[0])) {
3205+
THROW_ERR_INVALID_ARG_TYPE(
3206+
env->isolate(),
3207+
"The \"sourceDb\" argument must be an instance of Database.");
32003208
return;
32013209
}
32023210

@@ -4606,6 +4614,54 @@ static inline void SetSideEffectFreeGetter(
46064614
name, getter, Local<FunctionTemplate>(), DontDelete);
46074615
}
46084616

4617+
Local<FunctionTemplate> Database::GetConstructorTemplate(Environment* env) {
4618+
Local<FunctionTemplate> tmpl = env->sqlite_database_constructor_template();
4619+
if (tmpl.IsEmpty()) {
4620+
Isolate* isolate = env->isolate();
4621+
tmpl = NewFunctionTemplate(isolate, Database::New);
4622+
tmpl->InstanceTemplate()->SetInternalFieldCount(
4623+
Database::kInternalFieldCount);
4624+
SetProtoMethod(isolate, tmpl, "open", Database::Open);
4625+
SetProtoMethod(isolate, tmpl, "close", Database::Close);
4626+
SetProtoDispose(isolate, tmpl, Database::Dispose);
4627+
SetProtoMethod(isolate, tmpl, "prepare", Database::Prepare);
4628+
SetProtoMethod(isolate, tmpl, "exec", Database::Exec);
4629+
SetProtoMethod(isolate, tmpl, "function", Database::CustomFunction);
4630+
SetProtoMethod(isolate, tmpl, "createTagStore", Database::CreateTagStore);
4631+
SetProtoMethodNoSideEffect(isolate, tmpl, "location", Database::Location);
4632+
SetProtoMethod(isolate, tmpl, "aggregate", Database::AggregateFunction);
4633+
SetProtoMethod(isolate, tmpl, "createSession", Database::CreateSession);
4634+
SetProtoMethod(isolate, tmpl, "applyChangeset", Database::ApplyChangeset);
4635+
SetProtoMethod(
4636+
isolate, tmpl, "enableLoadExtension", Database::EnableLoadExtension);
4637+
SetProtoMethod(isolate, tmpl, "enableDefensive", Database::EnableDefensive);
4638+
SetProtoMethod(isolate, tmpl, "loadExtension", Database::LoadExtension);
4639+
SetProtoMethod(isolate, tmpl, "serialize", Database::Serialize);
4640+
SetProtoMethod(isolate, tmpl, "deserialize", Database::Deserialize);
4641+
SetProtoMethod(isolate, tmpl, "setAuthorizer", Database::SetAuthorizer);
4642+
SetProtoMethod(isolate, tmpl, "createModule", Database::CreateModule);
4643+
SetSideEffectFreeGetter(isolate,
4644+
tmpl,
4645+
FIXED_ONE_BYTE_STRING(isolate, "isOpen"),
4646+
Database::IsOpenGetter);
4647+
SetSideEffectFreeGetter(isolate,
4648+
tmpl,
4649+
FIXED_ONE_BYTE_STRING(isolate, "isTransaction"),
4650+
Database::IsTransactionGetter);
4651+
SetSideEffectFreeGetter(
4652+
isolate, tmpl, env->limits_string(), Database::LimitsGetter);
4653+
Local<String> sqlite_type_key =
4654+
FIXED_ONE_BYTE_STRING(isolate, "sqlite-type");
4655+
Local<v8::Symbol> sqlite_type_symbol =
4656+
v8::Symbol::For(isolate, sqlite_type_key);
4657+
Local<String> database_sync_string =
4658+
FIXED_ONE_BYTE_STRING(isolate, "node:sqlite");
4659+
tmpl->InstanceTemplate()->Set(sqlite_type_symbol, database_sync_string);
4660+
env->set_sqlite_database_constructor_template(tmpl);
4661+
}
4662+
return tmpl;
4663+
}
4664+
46094665
SQLTagStore::~SQLTagStore() {}
46104666

46114667
Local<FunctionTemplate> SQLTagStore::GetConstructorTemplate(Environment* env) {
@@ -5342,51 +5398,12 @@ static void Initialize(Local<Object> target,
53425398
}
53435399
});
53445400
}
5345-
Local<FunctionTemplate> db_tmpl = NewFunctionTemplate(isolate, Database::New);
5346-
db_tmpl->InstanceTemplate()->SetInternalFieldCount(
5347-
Database::kInternalFieldCount);
53485401
Local<Object> constants = Object::New(isolate);
53495402

53505403
DefineConstants(constants);
53515404

5352-
SetProtoMethod(isolate, db_tmpl, "open", Database::Open);
5353-
SetProtoMethod(isolate, db_tmpl, "close", Database::Close);
5354-
SetProtoDispose(isolate, db_tmpl, Database::Dispose);
5355-
SetProtoMethod(isolate, db_tmpl, "prepare", Database::Prepare);
5356-
SetProtoMethod(isolate, db_tmpl, "exec", Database::Exec);
5357-
SetProtoMethod(isolate, db_tmpl, "function", Database::CustomFunction);
5358-
SetProtoMethod(isolate, db_tmpl, "createTagStore", Database::CreateTagStore);
5359-
SetProtoMethodNoSideEffect(isolate, db_tmpl, "location", Database::Location);
5360-
SetProtoMethod(isolate, db_tmpl, "aggregate", Database::AggregateFunction);
5361-
SetProtoMethod(isolate, db_tmpl, "createSession", Database::CreateSession);
5362-
SetProtoMethod(isolate, db_tmpl, "applyChangeset", Database::ApplyChangeset);
5363-
SetProtoMethod(
5364-
isolate, db_tmpl, "enableLoadExtension", Database::EnableLoadExtension);
5365-
SetProtoMethod(
5366-
isolate, db_tmpl, "enableDefensive", Database::EnableDefensive);
5367-
SetProtoMethod(isolate, db_tmpl, "loadExtension", Database::LoadExtension);
5368-
SetProtoMethod(isolate, db_tmpl, "serialize", Database::Serialize);
5369-
SetProtoMethod(isolate, db_tmpl, "deserialize", Database::Deserialize);
5370-
SetProtoMethod(isolate, db_tmpl, "setAuthorizer", Database::SetAuthorizer);
5371-
SetProtoMethod(isolate, db_tmpl, "createModule", Database::CreateModule);
5372-
SetSideEffectFreeGetter(isolate,
5373-
db_tmpl,
5374-
FIXED_ONE_BYTE_STRING(isolate, "isOpen"),
5375-
Database::IsOpenGetter);
5376-
SetSideEffectFreeGetter(isolate,
5377-
db_tmpl,
5378-
FIXED_ONE_BYTE_STRING(isolate, "isTransaction"),
5379-
Database::IsTransactionGetter);
5380-
SetSideEffectFreeGetter(
5381-
isolate, db_tmpl, env->limits_string(), Database::LimitsGetter);
5382-
Local<String> sqlite_type_key = FIXED_ONE_BYTE_STRING(isolate, "sqlite-type");
5383-
Local<v8::Symbol> sqlite_type_symbol =
5384-
v8::Symbol::For(isolate, sqlite_type_key);
5385-
Local<String> database_sync_string =
5386-
FIXED_ONE_BYTE_STRING(isolate, "node:sqlite");
5387-
db_tmpl->InstanceTemplate()->Set(sqlite_type_symbol, database_sync_string);
5388-
5389-
SetConstructorFunction(context, target, "Database", db_tmpl);
5405+
SetConstructorFunction(
5406+
context, target, "Database", Database::GetConstructorTemplate(env));
53905407
SetConstructorFunction(context,
53915408
target,
53925409
"Statement",

‎src/node_sqlite.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ class Database : public BaseObject {
223223
bool open,
224224
bool allow_load_extension);
225225
void MemoryInfo(MemoryTracker* tracker) const override;
226+
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
227+
Environment* env);
226228
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
227229
static void Open(const v8::FunctionCallbackInfo<v8::Value>& args);
228230
static void IsOpenGetter(const v8::FunctionCallbackInfo<v8::Value>& args);

‎test/parallel/test-sqlite-backup.mjs‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,31 @@ describe('backup()', () => {
4848
backup();
4949
}, {
5050
code: 'ERR_INVALID_ARG_TYPE',
51-
message: 'The "sourceDb" argument must be an object.'
51+
message: 'The "sourceDb" argument must be an instance of Database.'
5252
});
5353
});
5454

55+
test('throws if the source database is not a Database', (t) => {
56+
const database = makeSourceDb();
57+
const values = [
58+
{},
59+
[],
60+
{ p0: 1, p1: 2, p2: 3, p3: 4 },
61+
{ __proto__: Database.prototype },
62+
database.prepare('SELECT 1'),
63+
database.createSession(),
64+
];
65+
66+
for (const value of values) {
67+
t.assert.throws(() => {
68+
backup(value, nextDb());
69+
}, {
70+
code: 'ERR_INVALID_ARG_TYPE',
71+
message: 'The "sourceDb" argument must be an instance of Database.'
72+
});
73+
}
74+
});
75+
5576
test('throws if path is not a string, URL, or Buffer', (t) => {
5677
const database = makeSourceDb();
5778

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ suite('Database() constructor', () => {
5757
}, { code: 'ERR_INVALID_URL' });
5858
});
5959

60+
test('propagates an exception thrown by the href getter', (t) => {
61+
t.assert.throws(() => {
62+
new Database({ get href() { throw new RangeError('boom'); } });
63+
}, {
64+
name: 'RangeError',
65+
message: 'boom',
66+
});
67+
});
68+
6069
test('throws if options is provided but is not an object', (t) => {
6170
t.assert.throws(() => {
6271
new Database('foo', null);

0 commit comments

Comments
 (0)