Commit 5da6eaa

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 079339a commit 5da6eaa

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
@@ -349,6 +349,7 @@ class CustomAggregate {
349349
Global<Function> CustomAggregate::*mptr) {
350350
CustomAggregate* self =
351351
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
352+
CallbackDepthGuard guard(self->db_);
352353
Environment* env = self->env_;
353354
Isolate* isolate = env->isolate();
354355
auto agg = self->GetAggregate(ctx);
@@ -395,12 +396,18 @@ class CustomAggregate {
395396
return;
396397
}
397398

399+
if (!self->db_->IsOpen()) {
400+
THROW_ERR_INVALID_STATE(env, "database is not open");
401+
return;
402+
}
403+
398404
agg->value.Reset(isolate, ret);
399405
}
400406

401407
staticinlinevoidxValueBase(sqlite3_context* ctx, bool is_final) {
402408
CustomAggregate* self =
403409
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
410+
CallbackDepthGuard guard(self->db_);
404411
Environment* env = self->env_;
405412
Isolate* isolate = env->isolate();
406413
auto agg = self->GetAggregate(ctx);
@@ -426,6 +433,9 @@ class CustomAggregate {
426433
.ToLocal(&result)) {
427434
self->db_->SetIgnoreNextSQLiteError(true);
428435
sqlite3_result_error(ctx, "", 0);
436+
} elseif (!self->db_->IsOpen()) {
437+
THROW_ERR_INVALID_STATE(env, "database is not open");
438+
return;
429439
}
430440
} else {
431441
result = Local<Value>::New(isolate, agg->value);
@@ -457,6 +467,10 @@ class CustomAggregate {
457467
auto fn = start_v.As<Function>();
458468
MaybeLocal<Value> retval =
459469
fn->Call(env_->context(), Null(isolate), 0, nullptr);
470+
if (!db_->IsOpen()) {
471+
THROW_ERR_INVALID_STATE(env_, "database is not open");
472+
returnnullptr;
473+
}
460474
if (!retval.ToLocal(&start_v)) {
461475
db_->SetIgnoreNextSQLiteError(true);
462476
sqlite3_result_error(ctx, "", 0);
@@ -669,6 +683,7 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
669683
sqlite3_value** argv) {
670684
UserDefinedFunction* self =
671685
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
686+
CallbackDepthGuard guard(self->db_);
672687
Environment* env = self->env_;
673688
Isolate* isolate = env->isolate();
674689
auto recv = Undefined(isolate);
@@ -700,6 +715,12 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
700715

701716
MaybeLocal<Value> retval =
702717
fn->Call(env->context(), recv, argc, js_argv.data());
718+
719+
if (!self->db_->IsOpen()) {
720+
THROW_ERR_INVALID_STATE(env, "database is not open");
721+
return;
722+
}
723+
703724
Local<Value> result;
704725
if (!retval.ToLocal(&result)) {
705726
// Ignore the SQLite error because a JavaScript exception is pending.
@@ -1433,6 +1454,8 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
14331454
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
14341455
Environment* env = Environment::GetCurrent(args);
14351456
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1457+
THROW_AND_RETURN_ON_BAD_STATE(
1458+
env, db->IsInCallback(), "database cannot be closed while in a callback");
14361459
db->FinalizeStatements();
14371460
db->DeleteSessions();
14381461
int r = sqlite3_close_v2(db->connection_);
@@ -2381,13 +2404,17 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
23812404
BaseObjectPtr<DatabaseSync> guard(db);
23822405

23832406
ArrayBufferViewContents<uint8_t> buf(args[0]);
2384-
int r = sqlite3changeset_apply(
2385-
db->connection_,
2386-
buf.length(),
2387-
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2388-
context.filterCallback ? xFilter : nullptr,
2389-
xConflict,
2390-
static_cast<void*>(&context));
2407+
int r;
2408+
{
2409+
CallbackDepthGuard guard(db);
2410+
r = sqlite3changeset_apply(
2411+
db->connection_,
2412+
buf.length(),
2413+
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2414+
context.filterCallback ? xFilter : nullptr,
2415+
xConflict,
2416+
static_cast<void*>(&context));
2417+
}
23912418
if (r == SQLITE_OK) {
23922419
args.GetReturnValue().Set(true);
23932420
return;
@@ -2522,6 +2549,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
25222549
constchar* param3,
25232550
constchar* param4) {
25242551
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
2552+
CallbackDepthGuard guard(db);
25252553
Environment* env = db->env();
25262554
Isolate* isolate = env->isolate();
25272555
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
voidSetIgnoreNextSQLiteError(bool ignore);
230230
boolShouldIgnoreSQLiteError();
231231

232+
voidIncrementCallbackDepth() { ++callback_depth_; }
233+
voidDecrementCallbackDepth() { --callback_depth_; }
234+
boolIsInCallback() 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_;
@@ -401,6 +406,19 @@ class SQLTagStore : public BaseObject {
401406
friendclassStatementExecutionHelper;
402407
};
403408

409+
classCallbackDepthGuard {
410+
public:
411+
explicitCallbackDepthGuard(DatabaseSync* db) : db_(db) {
412+
db_->IncrementCallbackDepth();
413+
}
414+
~CallbackDepthGuard() { db_->DecrementCallbackDepth(); }
415+
CallbackDepthGuard(const CallbackDepthGuard&) = delete;
416+
CallbackDepthGuard& operator=(const CallbackDepthGuard&) = delete;
417+
418+
private:
419+
DatabaseSync* db_;
420+
};
421+
404422
classUserDefinedFunction {
405423
public:
406424
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+
constassert=require('node:assert');
6+
const{ test }=require('node:test');
7+
const{ DatabaseSync }=require('node:sqlite');
8+
9+
for(constmethodof['all','get','run','iterate']){
10+
test(`database.close() from a UDF during statement.${method}()`,()=>{
11+
constdb=newDatabaseSync(':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+
returnvalue;
20+
});
21+
22+
conststatement=db.prepare('SELECT close_db(value) FROM data');
23+
assert.throws(()=>{
24+
if(method==='iterate'){
25+
for(constrowofstatement.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)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit 5da6eaa

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 079339a commit 5da6eaa

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
@@ -349,6 +349,7 @@ class CustomAggregate {
349349
Global<Function> CustomAggregate::*mptr) {
350350
CustomAggregate* self =
351351
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
352+
CallbackDepthGuard guard(self->db_);
352353
Environment* env = self->env_;
353354
Isolate* isolate = env->isolate();
354355
auto agg = self->GetAggregate(ctx);
@@ -395,12 +396,18 @@ class CustomAggregate {
395396
return;
396397
}
397398

399+
if (!self->db_->IsOpen()) {
400+
THROW_ERR_INVALID_STATE(env, "database is not open");
401+
return;
402+
}
403+
398404
agg->value.Reset(isolate, ret);
399405
}
400406

401407
staticinlinevoidxValueBase(sqlite3_context* ctx, bool is_final) {
402408
CustomAggregate* self =
403409
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
410+
CallbackDepthGuard guard(self->db_);
404411
Environment* env = self->env_;
405412
Isolate* isolate = env->isolate();
406413
auto agg = self->GetAggregate(ctx);
@@ -426,6 +433,9 @@ class CustomAggregate {
426433
.ToLocal(&result)) {
427434
self->db_->SetIgnoreNextSQLiteError(true);
428435
sqlite3_result_error(ctx, "", 0);
436+
} elseif (!self->db_->IsOpen()) {
437+
THROW_ERR_INVALID_STATE(env, "database is not open");
438+
return;
429439
}
430440
} else {
431441
result = Local<Value>::New(isolate, agg->value);
@@ -457,6 +467,10 @@ class CustomAggregate {
457467
auto fn = start_v.As<Function>();
458468
MaybeLocal<Value> retval =
459469
fn->Call(env_->context(), Null(isolate), 0, nullptr);
470+
if (!db_->IsOpen()) {
471+
THROW_ERR_INVALID_STATE(env_, "database is not open");
472+
returnnullptr;
473+
}
460474
if (!retval.ToLocal(&start_v)) {
461475
db_->SetIgnoreNextSQLiteError(true);
462476
sqlite3_result_error(ctx, "", 0);
@@ -669,6 +683,7 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
669683
sqlite3_value** argv) {
670684
UserDefinedFunction* self =
671685
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
686+
CallbackDepthGuard guard(self->db_);
672687
Environment* env = self->env_;
673688
Isolate* isolate = env->isolate();
674689
auto recv = Undefined(isolate);
@@ -700,6 +715,12 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
700715

701716
MaybeLocal<Value> retval =
702717
fn->Call(env->context(), recv, argc, js_argv.data());
718+
719+
if (!self->db_->IsOpen()) {
720+
THROW_ERR_INVALID_STATE(env, "database is not open");
721+
return;
722+
}
723+
703724
Local<Value> result;
704725
if (!retval.ToLocal(&result)) {
705726
// Ignore the SQLite error because a JavaScript exception is pending.
@@ -1433,6 +1454,8 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
14331454
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
14341455
Environment* env = Environment::GetCurrent(args);
14351456
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1457+
THROW_AND_RETURN_ON_BAD_STATE(
1458+
env, db->IsInCallback(), "database cannot be closed while in a callback");
14361459
db->FinalizeStatements();
14371460
db->DeleteSessions();
14381461
int r = sqlite3_close_v2(db->connection_);
@@ -2381,13 +2404,17 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
23812404
BaseObjectPtr<DatabaseSync> guard(db);
23822405

23832406
ArrayBufferViewContents<uint8_t> buf(args[0]);
2384-
int r = sqlite3changeset_apply(
2385-
db->connection_,
2386-
buf.length(),
2387-
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2388-
context.filterCallback ? xFilter : nullptr,
2389-
xConflict,
2390-
static_cast<void*>(&context));
2407+
int r;
2408+
{
2409+
CallbackDepthGuard guard(db);
2410+
r = sqlite3changeset_apply(
2411+
db->connection_,
2412+
buf.length(),
2413+
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2414+
context.filterCallback ? xFilter : nullptr,
2415+
xConflict,
2416+
static_cast<void*>(&context));
2417+
}
23912418
if (r == SQLITE_OK) {
23922419
args.GetReturnValue().Set(true);
23932420
return;
@@ -2522,6 +2549,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
25222549
constchar* param3,
25232550
constchar* param4) {
25242551
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
2552+
CallbackDepthGuard guard(db);
25252553
Environment* env = db->env();
25262554
Isolate* isolate = env->isolate();
25272555
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
voidSetIgnoreNextSQLiteError(bool ignore);
230230
boolShouldIgnoreSQLiteError();
231231

232+
voidIncrementCallbackDepth() { ++callback_depth_; }
233+
voidDecrementCallbackDepth() { --callback_depth_; }
234+
boolIsInCallback() 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_;
@@ -401,6 +406,19 @@ class SQLTagStore : public BaseObject {
401406
friendclassStatementExecutionHelper;
402407
};
403408

409+
classCallbackDepthGuard {
410+
public:
411+
explicitCallbackDepthGuard(DatabaseSync* db) : db_(db) {
412+
db_->IncrementCallbackDepth();
413+
}
414+
~CallbackDepthGuard() { db_->DecrementCallbackDepth(); }
415+
CallbackDepthGuard(const CallbackDepthGuard&) = delete;
416+
CallbackDepthGuard& operator=(const CallbackDepthGuard&) = delete;
417+
418+
private:
419+
DatabaseSync* db_;
420+
};
421+
404422
classUserDefinedFunction {
405423
public:
406424
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+
constassert=require('node:assert');
6+
const{ test }=require('node:test');
7+
const{ DatabaseSync }=require('node:sqlite');
8+
9+
for(constmethodof['all','get','run','iterate']){
10+
test(`database.close() from a UDF during statement.${method}()`,()=>{
11+
constdb=newDatabaseSync(':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+
returnvalue;
20+
});
21+
22+
conststatement=db.prepare('SELECT close_db(value) FROM data');
23+
assert.throws(()=>{
24+
if(method==='iterate'){
25+
for(constrowofstatement.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)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 5da6eaa

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 079339a commit 5da6eaa

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
@@ -349,6 +349,7 @@ class CustomAggregate {
349349
Global<Function> CustomAggregate::*mptr) {
350350
CustomAggregate* self =
351351
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
352+
CallbackDepthGuard guard(self->db_);
352353
Environment* env = self->env_;
353354
Isolate* isolate = env->isolate();
354355
auto agg = self->GetAggregate(ctx);
@@ -395,12 +396,18 @@ class CustomAggregate {
395396
return;
396397
}
397398

399+
if (!self->db_->IsOpen()) {
400+
THROW_ERR_INVALID_STATE(env, "database is not open");
401+
return;
402+
}
403+
398404
agg->value.Reset(isolate, ret);
399405
}
400406

401407
staticinlinevoidxValueBase(sqlite3_context* ctx, bool is_final) {
402408
CustomAggregate* self =
403409
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
410+
CallbackDepthGuard guard(self->db_);
404411
Environment* env = self->env_;
405412
Isolate* isolate = env->isolate();
406413
auto agg = self->GetAggregate(ctx);
@@ -426,6 +433,9 @@ class CustomAggregate {
426433
.ToLocal(&result)) {
427434
self->db_->SetIgnoreNextSQLiteError(true);
428435
sqlite3_result_error(ctx, "", 0);
436+
} elseif (!self->db_->IsOpen()) {
437+
THROW_ERR_INVALID_STATE(env, "database is not open");
438+
return;
429439
}
430440
} else {
431441
result = Local<Value>::New(isolate, agg->value);
@@ -457,6 +467,10 @@ class CustomAggregate {
457467
auto fn = start_v.As<Function>();
458468
MaybeLocal<Value> retval =
459469
fn->Call(env_->context(), Null(isolate), 0, nullptr);
470+
if (!db_->IsOpen()) {
471+
THROW_ERR_INVALID_STATE(env_, "database is not open");
472+
returnnullptr;
473+
}
460474
if (!retval.ToLocal(&start_v)) {
461475
db_->SetIgnoreNextSQLiteError(true);
462476
sqlite3_result_error(ctx, "", 0);
@@ -669,6 +683,7 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
669683
sqlite3_value** argv) {
670684
UserDefinedFunction* self =
671685
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
686+
CallbackDepthGuard guard(self->db_);
672687
Environment* env = self->env_;
673688
Isolate* isolate = env->isolate();
674689
auto recv = Undefined(isolate);
@@ -700,6 +715,12 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
700715

701716
MaybeLocal<Value> retval =
702717
fn->Call(env->context(), recv, argc, js_argv.data());
718+
719+
if (!self->db_->IsOpen()) {
720+
THROW_ERR_INVALID_STATE(env, "database is not open");
721+
return;
722+
}
723+
703724
Local<Value> result;
704725
if (!retval.ToLocal(&result)) {
705726
// Ignore the SQLite error because a JavaScript exception is pending.
@@ -1433,6 +1454,8 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
14331454
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
14341455
Environment* env = Environment::GetCurrent(args);
14351456
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1457+
THROW_AND_RETURN_ON_BAD_STATE(
1458+
env, db->IsInCallback(), "database cannot be closed while in a callback");
14361459
db->FinalizeStatements();
14371460
db->DeleteSessions();
14381461
int r = sqlite3_close_v2(db->connection_);
@@ -2381,13 +2404,17 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
23812404
BaseObjectPtr<DatabaseSync> guard(db);
23822405

23832406
ArrayBufferViewContents<uint8_t> buf(args[0]);
2384-
int r = sqlite3changeset_apply(
2385-
db->connection_,
2386-
buf.length(),
2387-
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2388-
context.filterCallback ? xFilter : nullptr,
2389-
xConflict,
2390-
static_cast<void*>(&context));
2407+
int r;
2408+
{
2409+
CallbackDepthGuard guard(db);
2410+
r = sqlite3changeset_apply(
2411+
db->connection_,
2412+
buf.length(),
2413+
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2414+
context.filterCallback ? xFilter : nullptr,
2415+
xConflict,
2416+
static_cast<void*>(&context));
2417+
}
23912418
if (r == SQLITE_OK) {
23922419
args.GetReturnValue().Set(true);
23932420
return;
@@ -2522,6 +2549,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
25222549
constchar* param3,
25232550
constchar* param4) {
25242551
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
2552+
CallbackDepthGuard guard(db);
25252553
Environment* env = db->env();
25262554
Isolate* isolate = env->isolate();
25272555
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
voidSetIgnoreNextSQLiteError(bool ignore);
230230
boolShouldIgnoreSQLiteError();
231231

232+
voidIncrementCallbackDepth() { ++callback_depth_; }
233+
voidDecrementCallbackDepth() { --callback_depth_; }
234+
boolIsInCallback() 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_;
@@ -401,6 +406,19 @@ class SQLTagStore : public BaseObject {
401406
friendclassStatementExecutionHelper;
402407
};
403408

409+
classCallbackDepthGuard {
410+
public:
411+
explicitCallbackDepthGuard(DatabaseSync* db) : db_(db) {
412+
db_->IncrementCallbackDepth();
413+
}
414+
~CallbackDepthGuard() { db_->DecrementCallbackDepth(); }
415+
CallbackDepthGuard(const CallbackDepthGuard&) = delete;
416+
CallbackDepthGuard& operator=(const CallbackDepthGuard&) = delete;
417+
418+
private:
419+
DatabaseSync* db_;
420+
};
421+
404422
classUserDefinedFunction {
405423
public:
406424
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+
constassert=require('node:assert');
6+
const{ test }=require('node:test');
7+
const{ DatabaseSync }=require('node:sqlite');
8+
9+
for(constmethodof['all','get','run','iterate']){
10+
test(`database.close() from a UDF during statement.${method}()`,()=>{
11+
constdb=newDatabaseSync(':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+
returnvalue;
20+
});
21+
22+
conststatement=db.prepare('SELECT close_db(value) FROM data');
23+
assert.throws(()=>{
24+
if(method==='iterate'){
25+
for(constrowofstatement.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)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 5da6eaa

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 079339a commit 5da6eaa

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
@@ -349,6 +349,7 @@ class CustomAggregate {
349349
Global<Function> CustomAggregate::*mptr) {
350350
CustomAggregate* self =
351351
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
352+
CallbackDepthGuard guard(self->db_);
352353
Environment* env = self->env_;
353354
Isolate* isolate = env->isolate();
354355
auto agg = self->GetAggregate(ctx);
@@ -395,12 +396,18 @@ class CustomAggregate {
395396
return;
396397
}
397398

399+
if (!self->db_->IsOpen()) {
400+
THROW_ERR_INVALID_STATE(env, "database is not open");
401+
return;
402+
}
403+
398404
agg->value.Reset(isolate, ret);
399405
}
400406

401407
staticinlinevoidxValueBase(sqlite3_context* ctx, bool is_final) {
402408
CustomAggregate* self =
403409
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
410+
CallbackDepthGuard guard(self->db_);
404411
Environment* env = self->env_;
405412
Isolate* isolate = env->isolate();
406413
auto agg = self->GetAggregate(ctx);
@@ -426,6 +433,9 @@ class CustomAggregate {
426433
.ToLocal(&result)) {
427434
self->db_->SetIgnoreNextSQLiteError(true);
428435
sqlite3_result_error(ctx, "", 0);
436+
} elseif (!self->db_->IsOpen()) {
437+
THROW_ERR_INVALID_STATE(env, "database is not open");
438+
return;
429439
}
430440
} else {
431441
result = Local<Value>::New(isolate, agg->value);
@@ -457,6 +467,10 @@ class CustomAggregate {
457467
auto fn = start_v.As<Function>();
458468
MaybeLocal<Value> retval =
459469
fn->Call(env_->context(), Null(isolate), 0, nullptr);
470+
if (!db_->IsOpen()) {
471+
THROW_ERR_INVALID_STATE(env_, "database is not open");
472+
returnnullptr;
473+
}
460474
if (!retval.ToLocal(&start_v)) {
461475
db_->SetIgnoreNextSQLiteError(true);
462476
sqlite3_result_error(ctx, "", 0);
@@ -669,6 +683,7 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
669683
sqlite3_value** argv) {
670684
UserDefinedFunction* self =
671685
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
686+
CallbackDepthGuard guard(self->db_);
672687
Environment* env = self->env_;
673688
Isolate* isolate = env->isolate();
674689
auto recv = Undefined(isolate);
@@ -700,6 +715,12 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
700715

701716
MaybeLocal<Value> retval =
702717
fn->Call(env->context(), recv, argc, js_argv.data());
718+
719+
if (!self->db_->IsOpen()) {
720+
THROW_ERR_INVALID_STATE(env, "database is not open");
721+
return;
722+
}
723+
703724
Local<Value> result;
704725
if (!retval.ToLocal(&result)) {
705726
// Ignore the SQLite error because a JavaScript exception is pending.
@@ -1433,6 +1454,8 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
14331454
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
14341455
Environment* env = Environment::GetCurrent(args);
14351456
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1457+
THROW_AND_RETURN_ON_BAD_STATE(
1458+
env, db->IsInCallback(), "database cannot be closed while in a callback");
14361459
db->FinalizeStatements();
14371460
db->DeleteSessions();
14381461
int r = sqlite3_close_v2(db->connection_);
@@ -2381,13 +2404,17 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
23812404
BaseObjectPtr<DatabaseSync> guard(db);
23822405

23832406
ArrayBufferViewContents<uint8_t> buf(args[0]);
2384-
int r = sqlite3changeset_apply(
2385-
db->connection_,
2386-
buf.length(),
2387-
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2388-
context.filterCallback ? xFilter : nullptr,
2389-
xConflict,
2390-
static_cast<void*>(&context));
2407+
int r;
2408+
{
2409+
CallbackDepthGuard guard(db);
2410+
r = sqlite3changeset_apply(
2411+
db->connection_,
2412+
buf.length(),
2413+
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2414+
context.filterCallback ? xFilter : nullptr,
2415+
xConflict,
2416+
static_cast<void*>(&context));
2417+
}
23912418
if (r == SQLITE_OK) {
23922419
args.GetReturnValue().Set(true);
23932420
return;
@@ -2522,6 +2549,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
25222549
constchar* param3,
25232550
constchar* param4) {
25242551
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
2552+
CallbackDepthGuard guard(db);
25252553
Environment* env = db->env();
25262554
Isolate* isolate = env->isolate();
25272555
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
voidSetIgnoreNextSQLiteError(bool ignore);
230230
boolShouldIgnoreSQLiteError();
231231

232+
voidIncrementCallbackDepth() { ++callback_depth_; }
233+
voidDecrementCallbackDepth() { --callback_depth_; }
234+
boolIsInCallback() 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_;
@@ -401,6 +406,19 @@ class SQLTagStore : public BaseObject {
401406
friendclassStatementExecutionHelper;
402407
};
403408

409+
classCallbackDepthGuard {
410+
public:
411+
explicitCallbackDepthGuard(DatabaseSync* db) : db_(db) {
412+
db_->IncrementCallbackDepth();
413+
}
414+
~CallbackDepthGuard() { db_->DecrementCallbackDepth(); }
415+
CallbackDepthGuard(const CallbackDepthGuard&) = delete;
416+
CallbackDepthGuard& operator=(const CallbackDepthGuard&) = delete;
417+
418+
private:
419+
DatabaseSync* db_;
420+
};
421+
404422
classUserDefinedFunction {
405423
public:
406424
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+
constassert=require('node:assert');
6+
const{ test }=require('node:test');
7+
const{ DatabaseSync }=require('node:sqlite');
8+
9+
for(constmethodof['all','get','run','iterate']){
10+
test(`database.close() from a UDF during statement.${method}()`,()=>{
11+
constdb=newDatabaseSync(':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+
returnvalue;
20+
});
21+
22+
conststatement=db.prepare('SELECT close_db(value) FROM data');
23+
assert.throws(()=>{
24+
if(method==='iterate'){
25+
for(constrowofstatement.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)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit 5da6eaa

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 079339a commit 5da6eaa

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
@@ -349,6 +349,7 @@ class CustomAggregate {
349349
Global<Function> CustomAggregate::*mptr) {
350350
CustomAggregate* self =
351351
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
352+
CallbackDepthGuard guard(self->db_);
352353
Environment* env = self->env_;
353354
Isolate* isolate = env->isolate();
354355
auto agg = self->GetAggregate(ctx);
@@ -395,12 +396,18 @@ class CustomAggregate {
395396
return;
396397
}
397398

399+
if (!self->db_->IsOpen()) {
400+
THROW_ERR_INVALID_STATE(env, "database is not open");
401+
return;
402+
}
403+
398404
agg->value.Reset(isolate, ret);
399405
}
400406

401407
staticinlinevoidxValueBase(sqlite3_context* ctx, bool is_final) {
402408
CustomAggregate* self =
403409
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
410+
CallbackDepthGuard guard(self->db_);
404411
Environment* env = self->env_;
405412
Isolate* isolate = env->isolate();
406413
auto agg = self->GetAggregate(ctx);
@@ -426,6 +433,9 @@ class CustomAggregate {
426433
.ToLocal(&result)) {
427434
self->db_->SetIgnoreNextSQLiteError(true);
428435
sqlite3_result_error(ctx, "", 0);
436+
} elseif (!self->db_->IsOpen()) {
437+
THROW_ERR_INVALID_STATE(env, "database is not open");
438+
return;
429439
}
430440
} else {
431441
result = Local<Value>::New(isolate, agg->value);
@@ -457,6 +467,10 @@ class CustomAggregate {
457467
auto fn = start_v.As<Function>();
458468
MaybeLocal<Value> retval =
459469
fn->Call(env_->context(), Null(isolate), 0, nullptr);
470+
if (!db_->IsOpen()) {
471+
THROW_ERR_INVALID_STATE(env_, "database is not open");
472+
returnnullptr;
473+
}
460474
if (!retval.ToLocal(&start_v)) {
461475
db_->SetIgnoreNextSQLiteError(true);
462476
sqlite3_result_error(ctx, "", 0);
@@ -669,6 +683,7 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
669683
sqlite3_value** argv) {
670684
UserDefinedFunction* self =
671685
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
686+
CallbackDepthGuard guard(self->db_);
672687
Environment* env = self->env_;
673688
Isolate* isolate = env->isolate();
674689
auto recv = Undefined(isolate);
@@ -700,6 +715,12 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
700715

701716
MaybeLocal<Value> retval =
702717
fn->Call(env->context(), recv, argc, js_argv.data());
718+
719+
if (!self->db_->IsOpen()) {
720+
THROW_ERR_INVALID_STATE(env, "database is not open");
721+
return;
722+
}
723+
703724
Local<Value> result;
704725
if (!retval.ToLocal(&result)) {
705726
// Ignore the SQLite error because a JavaScript exception is pending.
@@ -1433,6 +1454,8 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
14331454
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
14341455
Environment* env = Environment::GetCurrent(args);
14351456
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1457+
THROW_AND_RETURN_ON_BAD_STATE(
1458+
env, db->IsInCallback(), "database cannot be closed while in a callback");
14361459
db->FinalizeStatements();
14371460
db->DeleteSessions();
14381461
int r = sqlite3_close_v2(db->connection_);
@@ -2381,13 +2404,17 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
23812404
BaseObjectPtr<DatabaseSync> guard(db);
23822405

23832406
ArrayBufferViewContents<uint8_t> buf(args[0]);
2384-
int r = sqlite3changeset_apply(
2385-
db->connection_,
2386-
buf.length(),
2387-
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2388-
context.filterCallback ? xFilter : nullptr,
2389-
xConflict,
2390-
static_cast<void*>(&context));
2407+
int r;
2408+
{
2409+
CallbackDepthGuard guard(db);
2410+
r = sqlite3changeset_apply(
2411+
db->connection_,
2412+
buf.length(),
2413+
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2414+
context.filterCallback ? xFilter : nullptr,
2415+
xConflict,
2416+
static_cast<void*>(&context));
2417+
}
23912418
if (r == SQLITE_OK) {
23922419
args.GetReturnValue().Set(true);
23932420
return;
@@ -2522,6 +2549,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
25222549
constchar* param3,
25232550
constchar* param4) {
25242551
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
2552+
CallbackDepthGuard guard(db);
25252553
Environment* env = db->env();
25262554
Isolate* isolate = env->isolate();
25272555
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
voidSetIgnoreNextSQLiteError(bool ignore);
230230
boolShouldIgnoreSQLiteError();
231231

232+
voidIncrementCallbackDepth() { ++callback_depth_; }
233+
voidDecrementCallbackDepth() { --callback_depth_; }
234+
boolIsInCallback() 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_;
@@ -401,6 +406,19 @@ class SQLTagStore : public BaseObject {
401406
friendclassStatementExecutionHelper;
402407
};
403408

409+
classCallbackDepthGuard {
410+
public:
411+
explicitCallbackDepthGuard(DatabaseSync* db) : db_(db) {
412+
db_->IncrementCallbackDepth();
413+
}
414+
~CallbackDepthGuard() { db_->DecrementCallbackDepth(); }
415+
CallbackDepthGuard(const CallbackDepthGuard&) = delete;
416+
CallbackDepthGuard& operator=(const CallbackDepthGuard&) = delete;
417+
418+
private:
419+
DatabaseSync* db_;
420+
};
421+
404422
classUserDefinedFunction {
405423
public:
406424
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+
constassert=require('node:assert');
6+
const{ test }=require('node:test');
7+
const{ DatabaseSync }=require('node:sqlite');
8+
9+
for(constmethodof['all','get','run','iterate']){
10+
test(`database.close() from a UDF during statement.${method}()`,()=>{
11+
constdb=newDatabaseSync(':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+
returnvalue;
20+
});
21+
22+
conststatement=db.prepare('SELECT close_db(value) FROM data');
23+
assert.throws(()=>{
24+
if(method==='iterate'){
25+
for(constrowofstatement.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)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 5da6eaa

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 079339a commit 5da6eaa

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
@@ -349,6 +349,7 @@ class CustomAggregate {
349349
Global<Function> CustomAggregate::*mptr) {
350350
CustomAggregate* self =
351351
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
352+
CallbackDepthGuard guard(self->db_);
352353
Environment* env = self->env_;
353354
Isolate* isolate = env->isolate();
354355
auto agg = self->GetAggregate(ctx);
@@ -395,12 +396,18 @@ class CustomAggregate {
395396
return;
396397
}
397398

399+
if (!self->db_->IsOpen()) {
400+
THROW_ERR_INVALID_STATE(env, "database is not open");
401+
return;
402+
}
403+
398404
agg->value.Reset(isolate, ret);
399405
}
400406

401407
staticinlinevoidxValueBase(sqlite3_context* ctx, bool is_final) {
402408
CustomAggregate* self =
403409
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
410+
CallbackDepthGuard guard(self->db_);
404411
Environment* env = self->env_;
405412
Isolate* isolate = env->isolate();
406413
auto agg = self->GetAggregate(ctx);
@@ -426,6 +433,9 @@ class CustomAggregate {
426433
.ToLocal(&result)) {
427434
self->db_->SetIgnoreNextSQLiteError(true);
428435
sqlite3_result_error(ctx, "", 0);
436+
} elseif (!self->db_->IsOpen()) {
437+
THROW_ERR_INVALID_STATE(env, "database is not open");
438+
return;
429439
}
430440
} else {
431441
result = Local<Value>::New(isolate, agg->value);
@@ -457,6 +467,10 @@ class CustomAggregate {
457467
auto fn = start_v.As<Function>();
458468
MaybeLocal<Value> retval =
459469
fn->Call(env_->context(), Null(isolate), 0, nullptr);
470+
if (!db_->IsOpen()) {
471+
THROW_ERR_INVALID_STATE(env_, "database is not open");
472+
returnnullptr;
473+
}
460474
if (!retval.ToLocal(&start_v)) {
461475
db_->SetIgnoreNextSQLiteError(true);
462476
sqlite3_result_error(ctx, "", 0);
@@ -669,6 +683,7 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
669683
sqlite3_value** argv) {
670684
UserDefinedFunction* self =
671685
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
686+
CallbackDepthGuard guard(self->db_);
672687
Environment* env = self->env_;
673688
Isolate* isolate = env->isolate();
674689
auto recv = Undefined(isolate);
@@ -700,6 +715,12 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
700715

701716
MaybeLocal<Value> retval =
702717
fn->Call(env->context(), recv, argc, js_argv.data());
718+
719+
if (!self->db_->IsOpen()) {
720+
THROW_ERR_INVALID_STATE(env, "database is not open");
721+
return;
722+
}
723+
703724
Local<Value> result;
704725
if (!retval.ToLocal(&result)) {
705726
// Ignore the SQLite error because a JavaScript exception is pending.
@@ -1433,6 +1454,8 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
14331454
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
14341455
Environment* env = Environment::GetCurrent(args);
14351456
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1457+
THROW_AND_RETURN_ON_BAD_STATE(
1458+
env, db->IsInCallback(), "database cannot be closed while in a callback");
14361459
db->FinalizeStatements();
14371460
db->DeleteSessions();
14381461
int r = sqlite3_close_v2(db->connection_);
@@ -2381,13 +2404,17 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
23812404
BaseObjectPtr<DatabaseSync> guard(db);
23822405

23832406
ArrayBufferViewContents<uint8_t> buf(args[0]);
2384-
int r = sqlite3changeset_apply(
2385-
db->connection_,
2386-
buf.length(),
2387-
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2388-
context.filterCallback ? xFilter : nullptr,
2389-
xConflict,
2390-
static_cast<void*>(&context));
2407+
int r;
2408+
{
2409+
CallbackDepthGuard guard(db);
2410+
r = sqlite3changeset_apply(
2411+
db->connection_,
2412+
buf.length(),
2413+
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2414+
context.filterCallback ? xFilter : nullptr,
2415+
xConflict,
2416+
static_cast<void*>(&context));
2417+
}
23912418
if (r == SQLITE_OK) {
23922419
args.GetReturnValue().Set(true);
23932420
return;
@@ -2522,6 +2549,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
25222549
constchar* param3,
25232550
constchar* param4) {
25242551
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
2552+
CallbackDepthGuard guard(db);
25252553
Environment* env = db->env();
25262554
Isolate* isolate = env->isolate();
25272555
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
voidSetIgnoreNextSQLiteError(bool ignore);
230230
boolShouldIgnoreSQLiteError();
231231

232+
voidIncrementCallbackDepth() { ++callback_depth_; }
233+
voidDecrementCallbackDepth() { --callback_depth_; }
234+
boolIsInCallback() 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_;
@@ -401,6 +406,19 @@ class SQLTagStore : public BaseObject {
401406
friendclassStatementExecutionHelper;
402407
};
403408

409+
classCallbackDepthGuard {
410+
public:
411+
explicitCallbackDepthGuard(DatabaseSync* db) : db_(db) {
412+
db_->IncrementCallbackDepth();
413+
}
414+
~CallbackDepthGuard() { db_->DecrementCallbackDepth(); }
415+
CallbackDepthGuard(const CallbackDepthGuard&) = delete;
416+
CallbackDepthGuard& operator=(const CallbackDepthGuard&) = delete;
417+
418+
private:
419+
DatabaseSync* db_;
420+
};
421+
404422
classUserDefinedFunction {
405423
public:
406424
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+
constassert=require('node:assert');
6+
const{ test }=require('node:test');
7+
const{ DatabaseSync }=require('node:sqlite');
8+
9+
for(constmethodof['all','get','run','iterate']){
10+
test(`database.close() from a UDF during statement.${method}()`,()=>{
11+
constdb=newDatabaseSync(':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+
returnvalue;
20+
});
21+
22+
conststatement=db.prepare('SELECT close_db(value) FROM data');
23+
assert.throws(()=>{
24+
if(method==='iterate'){
25+
for(constrowofstatement.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)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 5da6eaa

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 079339a commit 5da6eaa

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
@@ -349,6 +349,7 @@ class CustomAggregate {
349349
Global<Function> CustomAggregate::*mptr) {
350350
CustomAggregate* self =
351351
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
352+
CallbackDepthGuard guard(self->db_);
352353
Environment* env = self->env_;
353354
Isolate* isolate = env->isolate();
354355
auto agg = self->GetAggregate(ctx);
@@ -395,12 +396,18 @@ class CustomAggregate {
395396
return;
396397
}
397398

399+
if (!self->db_->IsOpen()) {
400+
THROW_ERR_INVALID_STATE(env, "database is not open");
401+
return;
402+
}
403+
398404
agg->value.Reset(isolate, ret);
399405
}
400406

401407
staticinlinevoidxValueBase(sqlite3_context* ctx, bool is_final) {
402408
CustomAggregate* self =
403409
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
410+
CallbackDepthGuard guard(self->db_);
404411
Environment* env = self->env_;
405412
Isolate* isolate = env->isolate();
406413
auto agg = self->GetAggregate(ctx);
@@ -426,6 +433,9 @@ class CustomAggregate {
426433
.ToLocal(&result)) {
427434
self->db_->SetIgnoreNextSQLiteError(true);
428435
sqlite3_result_error(ctx, "", 0);
436+
} elseif (!self->db_->IsOpen()) {
437+
THROW_ERR_INVALID_STATE(env, "database is not open");
438+
return;
429439
}
430440
} else {
431441
result = Local<Value>::New(isolate, agg->value);
@@ -457,6 +467,10 @@ class CustomAggregate {
457467
auto fn = start_v.As<Function>();
458468
MaybeLocal<Value> retval =
459469
fn->Call(env_->context(), Null(isolate), 0, nullptr);
470+
if (!db_->IsOpen()) {
471+
THROW_ERR_INVALID_STATE(env_, "database is not open");
472+
returnnullptr;
473+
}
460474
if (!retval.ToLocal(&start_v)) {
461475
db_->SetIgnoreNextSQLiteError(true);
462476
sqlite3_result_error(ctx, "", 0);
@@ -669,6 +683,7 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
669683
sqlite3_value** argv) {
670684
UserDefinedFunction* self =
671685
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
686+
CallbackDepthGuard guard(self->db_);
672687
Environment* env = self->env_;
673688
Isolate* isolate = env->isolate();
674689
auto recv = Undefined(isolate);
@@ -700,6 +715,12 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
700715

701716
MaybeLocal<Value> retval =
702717
fn->Call(env->context(), recv, argc, js_argv.data());
718+
719+
if (!self->db_->IsOpen()) {
720+
THROW_ERR_INVALID_STATE(env, "database is not open");
721+
return;
722+
}
723+
703724
Local<Value> result;
704725
if (!retval.ToLocal(&result)) {
705726
// Ignore the SQLite error because a JavaScript exception is pending.
@@ -1433,6 +1454,8 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
14331454
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
14341455
Environment* env = Environment::GetCurrent(args);
14351456
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1457+
THROW_AND_RETURN_ON_BAD_STATE(
1458+
env, db->IsInCallback(), "database cannot be closed while in a callback");
14361459
db->FinalizeStatements();
14371460
db->DeleteSessions();
14381461
int r = sqlite3_close_v2(db->connection_);
@@ -2381,13 +2404,17 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
23812404
BaseObjectPtr<DatabaseSync> guard(db);
23822405

23832406
ArrayBufferViewContents<uint8_t> buf(args[0]);
2384-
int r = sqlite3changeset_apply(
2385-
db->connection_,
2386-
buf.length(),
2387-
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2388-
context.filterCallback ? xFilter : nullptr,
2389-
xConflict,
2390-
static_cast<void*>(&context));
2407+
int r;
2408+
{
2409+
CallbackDepthGuard guard(db);
2410+
r = sqlite3changeset_apply(
2411+
db->connection_,
2412+
buf.length(),
2413+
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2414+
context.filterCallback ? xFilter : nullptr,
2415+
xConflict,
2416+
static_cast<void*>(&context));
2417+
}
23912418
if (r == SQLITE_OK) {
23922419
args.GetReturnValue().Set(true);
23932420
return;
@@ -2522,6 +2549,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
25222549
constchar* param3,
25232550
constchar* param4) {
25242551
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
2552+
CallbackDepthGuard guard(db);
25252553
Environment* env = db->env();
25262554
Isolate* isolate = env->isolate();
25272555
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
voidSetIgnoreNextSQLiteError(bool ignore);
230230
boolShouldIgnoreSQLiteError();
231231

232+
voidIncrementCallbackDepth() { ++callback_depth_; }
233+
voidDecrementCallbackDepth() { --callback_depth_; }
234+
boolIsInCallback() 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_;
@@ -401,6 +406,19 @@ class SQLTagStore : public BaseObject {
401406
friendclassStatementExecutionHelper;
402407
};
403408

409+
classCallbackDepthGuard {
410+
public:
411+
explicitCallbackDepthGuard(DatabaseSync* db) : db_(db) {
412+
db_->IncrementCallbackDepth();
413+
}
414+
~CallbackDepthGuard() { db_->DecrementCallbackDepth(); }
415+
CallbackDepthGuard(const CallbackDepthGuard&) = delete;
416+
CallbackDepthGuard& operator=(const CallbackDepthGuard&) = delete;
417+
418+
private:
419+
DatabaseSync* db_;
420+
};
421+
404422
classUserDefinedFunction {
405423
public:
406424
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+
constassert=require('node:assert');
6+
const{ test }=require('node:test');
7+
const{ DatabaseSync }=require('node:sqlite');
8+
9+
for(constmethodof['all','get','run','iterate']){
10+
test(`database.close() from a UDF during statement.${method}()`,()=>{
11+
constdb=newDatabaseSync(':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+
returnvalue;
20+
});
21+
22+
conststatement=db.prepare('SELECT close_db(value) FROM data');
23+
assert.throws(()=>{
24+
if(method==='iterate'){
25+
for(constrowofstatement.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)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit 5da6eaa

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 079339a commit 5da6eaa

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
@@ -349,6 +349,7 @@ class CustomAggregate {
349349
Global<Function> CustomAggregate::*mptr) {
350350
CustomAggregate* self =
351351
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
352+
CallbackDepthGuard guard(self->db_);
352353
Environment* env = self->env_;
353354
Isolate* isolate = env->isolate();
354355
auto agg = self->GetAggregate(ctx);
@@ -395,12 +396,18 @@ class CustomAggregate {
395396
return;
396397
}
397398

399+
if (!self->db_->IsOpen()) {
400+
THROW_ERR_INVALID_STATE(env, "database is not open");
401+
return;
402+
}
403+
398404
agg->value.Reset(isolate, ret);
399405
}
400406

401407
staticinlinevoidxValueBase(sqlite3_context* ctx, bool is_final) {
402408
CustomAggregate* self =
403409
static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
410+
CallbackDepthGuard guard(self->db_);
404411
Environment* env = self->env_;
405412
Isolate* isolate = env->isolate();
406413
auto agg = self->GetAggregate(ctx);
@@ -426,6 +433,9 @@ class CustomAggregate {
426433
.ToLocal(&result)) {
427434
self->db_->SetIgnoreNextSQLiteError(true);
428435
sqlite3_result_error(ctx, "", 0);
436+
} elseif (!self->db_->IsOpen()) {
437+
THROW_ERR_INVALID_STATE(env, "database is not open");
438+
return;
429439
}
430440
} else {
431441
result = Local<Value>::New(isolate, agg->value);
@@ -457,6 +467,10 @@ class CustomAggregate {
457467
auto fn = start_v.As<Function>();
458468
MaybeLocal<Value> retval =
459469
fn->Call(env_->context(), Null(isolate), 0, nullptr);
470+
if (!db_->IsOpen()) {
471+
THROW_ERR_INVALID_STATE(env_, "database is not open");
472+
returnnullptr;
473+
}
460474
if (!retval.ToLocal(&start_v)) {
461475
db_->SetIgnoreNextSQLiteError(true);
462476
sqlite3_result_error(ctx, "", 0);
@@ -669,6 +683,7 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
669683
sqlite3_value** argv) {
670684
UserDefinedFunction* self =
671685
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
686+
CallbackDepthGuard guard(self->db_);
672687
Environment* env = self->env_;
673688
Isolate* isolate = env->isolate();
674689
auto recv = Undefined(isolate);
@@ -700,6 +715,12 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
700715

701716
MaybeLocal<Value> retval =
702717
fn->Call(env->context(), recv, argc, js_argv.data());
718+
719+
if (!self->db_->IsOpen()) {
720+
THROW_ERR_INVALID_STATE(env, "database is not open");
721+
return;
722+
}
723+
703724
Local<Value> result;
704725
if (!retval.ToLocal(&result)) {
705726
// Ignore the SQLite error because a JavaScript exception is pending.
@@ -1433,6 +1454,8 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
14331454
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
14341455
Environment* env = Environment::GetCurrent(args);
14351456
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1457+
THROW_AND_RETURN_ON_BAD_STATE(
1458+
env, db->IsInCallback(), "database cannot be closed while in a callback");
14361459
db->FinalizeStatements();
14371460
db->DeleteSessions();
14381461
int r = sqlite3_close_v2(db->connection_);
@@ -2381,13 +2404,17 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
23812404
BaseObjectPtr<DatabaseSync> guard(db);
23822405

23832406
ArrayBufferViewContents<uint8_t> buf(args[0]);
2384-
int r = sqlite3changeset_apply(
2385-
db->connection_,
2386-
buf.length(),
2387-
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2388-
context.filterCallback ? xFilter : nullptr,
2389-
xConflict,
2390-
static_cast<void*>(&context));
2407+
int r;
2408+
{
2409+
CallbackDepthGuard guard(db);
2410+
r = sqlite3changeset_apply(
2411+
db->connection_,
2412+
buf.length(),
2413+
const_cast<void*>(static_cast<constvoid*>(buf.data())),
2414+
context.filterCallback ? xFilter : nullptr,
2415+
xConflict,
2416+
static_cast<void*>(&context));
2417+
}
23912418
if (r == SQLITE_OK) {
23922419
args.GetReturnValue().Set(true);
23932420
return;
@@ -2522,6 +2549,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
25222549
constchar* param3,
25232550
constchar* param4) {
25242551
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
2552+
CallbackDepthGuard guard(db);
25252553
Environment* env = db->env();
25262554
Isolate* isolate = env->isolate();
25272555
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
voidSetIgnoreNextSQLiteError(bool ignore);
230230
boolShouldIgnoreSQLiteError();
231231

232+
voidIncrementCallbackDepth() { ++callback_depth_; }
233+
voidDecrementCallbackDepth() { --callback_depth_; }
234+
boolIsInCallback() 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_;
@@ -401,6 +406,19 @@ class SQLTagStore : public BaseObject {
401406
friendclassStatementExecutionHelper;
402407
};
403408

409+
classCallbackDepthGuard {
410+
public:
411+
explicitCallbackDepthGuard(DatabaseSync* db) : db_(db) {
412+
db_->IncrementCallbackDepth();
413+
}
414+
~CallbackDepthGuard() { db_->DecrementCallbackDepth(); }
415+
CallbackDepthGuard(const CallbackDepthGuard&) = delete;
416+
CallbackDepthGuard& operator=(const CallbackDepthGuard&) = delete;
417+
418+
private:
419+
DatabaseSync* db_;
420+
};
421+
404422
classUserDefinedFunction {
405423
public:
406424
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+
constassert=require('node:assert');
6+
const{ test }=require('node:test');
7+
const{ DatabaseSync }=require('node:sqlite');
8+
9+
for(constmethodof['all','get','run','iterate']){
10+
test(`database.close() from a UDF during statement.${method}()`,()=>{
11+
constdb=newDatabaseSync(':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+
returnvalue;
20+
});
21+
22+
conststatement=db.prepare('SELECT close_db(value) FROM data');
23+
assert.throws(()=>{
24+
if(method==='iterate'){
25+
for(constrowofstatement.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)