镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Commit d4eb6c6

Browse files
mcollinaasroyxCySec
authored andcommitted
sqlite: prevent database close during callbacks
Co-authored-by: Asroy Cristian Sitorus <asroycristiansitorus@gmail.com> Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64743 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent cef9085 commit d4eb6c6

3 files changed

Lines changed: 92 additions & 7 deletions

File tree

‎src/node_sqlite.cc‎

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ class CustomAggregate {
356356
Global<Function> CustomAggregate::*mptr) {
357357
CustomAggregate* self =
358358
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
359+
CallbackDepthGuard guard(self->db_);
359360
Environment* env = self->env_;
360361
Isolate* isolate = env->isolate();
361362
auto agg = self->GetAggregate(ctx);
@@ -402,12 +403,18 @@ class CustomAggregate {
402403
return;
403404
}
404405

406+
if (!self->db_->IsOpen()) {
407+
THROW_ERR_INVALID_STATE(env, "database is not open");
408+
return;
409+
}
410+
405411
agg->value.Reset(isolate, ret);
406412
}
407413

408414
static inline void xValueBase(sqlite3_context* ctx, bool is_final) {
409415
CustomAggregate* self =
410416
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
417+
CallbackDepthGuard guard(self->db_);
411418
Environment* env = self->env_;
412419
Isolate* isolate = env->isolate();
413420
auto agg = self->GetAggregate(ctx);
@@ -433,6 +440,9 @@ class CustomAggregate {
433440
.ToLocal(&result)) {
434441
self->db_->SetIgnoreNextSQLiteError(true);
435442
sqlite3_result_error(ctx, "", 0);
443+
} else if (!self->db_->IsOpen()) {
444+
THROW_ERR_INVALID_STATE(env, "database is not open");
445+
return;
436446
}
437447
} else {
438448
result = Local<Value>::New(isolate, agg->value);
@@ -464,6 +474,10 @@ class CustomAggregate {
464474
auto fn = start_v.As<Function>();
465475
MaybeLocal<Value> retval =
466476
fn->Call(env_->context(), Null(isolate), 0, nullptr);
477+
if (!db_->IsOpen()) {
478+
THROW_ERR_INVALID_STATE(env_, "database is not open");
479+
return nullptr;
480+
}
467481
if (!retval.ToLocal(&start_v)) {
468482
db_->SetIgnoreNextSQLiteError(true);
469483
sqlite3_result_error(ctx, "", 0);
@@ -676,6 +690,7 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
676690
sqlite3_value** argv) {
677691
UserDefinedFunction* self =
678692
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
693+
CallbackDepthGuard guard(self->db_);
679694
Environment* env = self->env_;
680695
Isolate* isolate = env->isolate();
681696
auto recv = Undefined(isolate);
@@ -707,6 +722,12 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
707722

708723
MaybeLocal<Value> retval =
709724
fn->Call(env->context(), recv, argc, js_argv.data());
725+
726+
if (!self->db_->IsOpen()) {
727+
THROW_ERR_INVALID_STATE(env, "database is not open");
728+
return;
729+
}
730+
710731
Local<Value> result;
711732
if (!retval.ToLocal(&result)) {
712733
// Ignore the SQLite error because a JavaScript exception is pending.
@@ -1453,6 +1474,8 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
14531474
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
14541475
Environment* env = Environment::GetCurrent(args);
14551476
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1477+
THROW_AND_RETURN_ON_BAD_STATE(
1478+
env, db->IsInCallback(), "database cannot be closed while in a callback");
14561479
db->FinalizeStatements();
14571480
db->DeleteSessions();
14581481
int r = sqlite3_close_v2(db->connection_);
@@ -2417,13 +2440,17 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
24172440
BaseObjectPtr<DatabaseSync> guard(db);
24182441

24192442
ArrayBufferViewContents<uint8_t> buf(args[0]);
2420-
int r = sqlite3changeset_apply(
2421-
db->connection_,
2422-
buf.length(),
2423-
const_cast<void*>(static_cast<const void*>(buf.data())),
2424-
context.filterCallback ? xFilter : nullptr,
2425-
xConflict,
2426-
static_cast<void*>(&context));
2443+
int r;
2444+
{
2445+
CallbackDepthGuard guard(db);
2446+
r = sqlite3changeset_apply(
2447+
db->connection_,
2448+
buf.length(),
2449+
const_cast<void*>(static_cast<const void*>(buf.data())),
2450+
context.filterCallback ? xFilter : nullptr,
2451+
xConflict,
2452+
static_cast<void*>(&context));
2453+
}
24272454
if (r == SQLITE_OK) {
24282455
args.GetReturnValue().Set(true);
24292456
return;
@@ -2558,6 +2585,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
25582585
const char* param3,
25592586
const char* param4) {
25602587
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
2588+
CallbackDepthGuard guard(db);
25612589
Environment* env = db->env();
25622590
Isolate* isolate = env->isolate();
25632591
HandleScope handle_scope(isolate);

‎src/node_sqlite.h‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ class DatabaseSync : public BaseObject {
229229
void SetIgnoreNextSQLiteError(bool ignore);
230230
bool ShouldIgnoreSQLiteError();
231231

232+
void IncrementCallbackDepth() { ++callback_depth_; }
233+
void DecrementCallbackDepth() { --callback_depth_; }
234+
bool IsInCallback() const { return callback_depth_ > 0; }
235+
232236
SET_MEMORY_INFO_NAME(DatabaseSync)
233237
SET_SELF_SIZE(DatabaseSync)
234238

@@ -242,6 +246,7 @@ class DatabaseSync : public BaseObject {
242246
bool enable_load_extension_;
243247
sqlite3* connection_;
244248
bool ignore_next_sqlite_error_;
249+
int callback_depth_ = 0;
245250

246251
std::set<BackupJob*> backups_;
247252
std::unordered_set<Session*> sessions_;
@@ -406,6 +411,19 @@ class SQLTagStore : public BaseObject {
406411
friend class StatementExecutionHelper;
407412
};
408413

414+
class CallbackDepthGuard {
415+
public:
416+
explicit CallbackDepthGuard(DatabaseSync* db) : db_(db) {
417+
db_->IncrementCallbackDepth();
418+
}
419+
~CallbackDepthGuard() { db_->DecrementCallbackDepth(); }
420+
CallbackDepthGuard(const CallbackDepthGuard&) = delete;
421+
CallbackDepthGuard& operator=(const CallbackDepthGuard&) = delete;
422+
423+
private:
424+
DatabaseSync* db_;
425+
};
426+
409427
class UserDefinedFunction {
410428
public:
411429
UserDefinedFunction(Environment* env,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use strict';
2+
3+
const { skipIfSQLiteMissing } = require('../common');
4+
skipIfSQLiteMissing();
5+
const assert = require('node:assert');
6+
const { test } = require('node:test');
7+
const { DatabaseSync } = require('node:sqlite');
8+
9+
for (const method of ['all', 'get', 'run', 'iterate']) {
10+
test(`database.close() from a UDF during statement.${method}()`, () => {
11+
const db = new DatabaseSync(':memory:');
12+
db.exec(`
13+
CREATE TABLE data (value INTEGER);
14+
INSERT INTO data VALUES (1), (2), (3);
15+
`);
16+
17+
db.function('close_db', (value) => {
18+
db.close();
19+
return value;
20+
});
21+
22+
const statement = db.prepare('SELECT close_db(value) FROM data');
23+
assert.throws(() => {
24+
if (method === 'iterate') {
25+
for (const row of statement.iterate()) {
26+
assert.ok(row);
27+
}
28+
} else {
29+
statement[method]();
30+
}
31+
}, {
32+
code: 'ERR_INVALID_STATE',
33+
message: 'database cannot be closed while in a callback',
34+
});
35+
36+
assert.strictEqual(db.isOpen, true);
37+
db.close();
38+
});
39+
}

0 commit comments

Comments
 (0)