Skip to content

Commit e33b0cd

Browse files
committed
sqlite: reject connection access from authorizer callbacks
SQLite requires that an authorizer callback not modify the connection that invoked it, and counts sqlite3_prepare_v2() and sqlite3_step() as modifications. node:sqlite let an authorizer callback call prepare(), exec(), the statement execution methods, and other connection-mutating APIs on the same DatabaseSync. Track authorizer depth on DatabaseSync with an RAII guard around the callback, and throw ERR_INVALID_STATE from the affected entry points while the callback is on the stack. The depth is per-connection, so other connections stay usable from the callback. The guard covers every authorizer invocation, not just those from an explicit prepare(), since SQLite may re-prepare a statement during sqlite3_step() after a schema change. serialize() and the session changeset() and patchset() methods prepare statements internally, so they re-enter the authorizer too. Reentry through changeset() does not terminate: it recurses until the process is killed, with no way to catch it from JavaScript. Reentering a statement that is currently being stepped is a separate hazard, and a memory-safety one rather than a contract violation. Finalizing it frees the virtual machine that sqlite3_step() is running, and re-running it through run(), get(), all(), iterate(), or the equivalent tag store methods resets that virtual machine mid-execution. Both crash. Any callback SQLite invokes during execution can reach them, not only an authorizer, so a user-defined function is enough. Track the statements currently being stepped and reject reentry into those, which leaves a user-defined function free to prepare, run, and finalize its own helper statements. Tracking is a stack so that nested execution is handled, and covers the paired sqlite3_reset() calls, which can run JavaScript through an aggregate's xFinal. Disposal stays idempotent, since throwing for an already-finalized statement would demote a `using` scope's exception to a SuppressedError. Signed-off-by: Trevor Burnham <trevorburnham@gmail.com> Fixes: #63207 Assisted-by: claude:opus-5
1 parent 5f1ef0a commit e33b0cd

5 files changed

Lines changed: 566 additions & 4 deletions

File tree

‎doc/api/sqlite.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,11 @@ wrapper around [`sqlite3_create_function_v2()`][].
439439

440440
<!-- YAML
441441
added: v24.10.0
442+
changes:
443+
- version: REPLACEME
444+
pr-url: https://github.com/nodejs/node/pull/65156
445+
description: Accessing the invoking database connection from the authorizer
446+
callback now throws.
442447
-->
443448

444449
* `callback` {Function|null} The authorizer function to set, or `null` to
@@ -464,6 +469,26 @@ The callback must return one of the following constants:
464469
* `SQLITE_DENY` - Deny the operation (causes an error).
465470
* `SQLITE_IGNORE` - Ignore the operation (silently skip).
466471

472+
SQLite requires that the authorizer callback not modify the database connection
473+
that invoked it, which includes preparing and stepping statements. Methods that
474+
would do so throw an error with code `ERR_INVALID_STATE` while the callback is
475+
on the stack, including `database.prepare()`, `database.exec()`, the execution
476+
methods of that connection's statements, iterators, and tag stores, and
477+
`database.setAuthorizer()` itself. Other connections remain usable.
478+
479+
The callback can also be invoked from within `statement.run()`,
480+
`statement.get()`, and similar methods, because SQLite may re-prepare a
481+
statement during execution after a schema change.
482+
483+
Separately, a statement that is currently being executed cannot be reentered.
484+
Calling `statement.close()` on it would free the virtual machine that is
485+
running, and re-running it through `statement.run()`, `statement.get()`,
486+
`statement.all()`, `statement.iterate()`, or the equivalent tag store methods
487+
would reset that virtual machine mid-execution. All of these throw an
488+
`ERR_INVALID_STATE` error instead. This applies to any callback SQLite invokes
489+
during execution, such as a user-defined function. Other statements on the
490+
connection remain usable.
491+
467492
```cjs
468493
const { DatabaseSync, constants } = require('node:sqlite');
469494
const db = new DatabaseSync(':memory:');

‎src/node_sqlite.cc‎

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,27 @@ inline MaybeLocal<String> Utf8StringMaybeOneByte(Isolate* isolate,
9696
} \
9797
} while (0)
9898

99+
// SQLite requires that an authorizer callback not modify the connection that
100+
// invoked it. Preparing and stepping statements both count as modifying it.
101+
// See https://www.sqlite.org/c3ref/set_authorizer.html.
102+
#define THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db) \
103+
THROW_AND_RETURN_ON_BAD_STATE( \
104+
(env), \
105+
(db)->IsInAuthorizerCallback(), \
106+
"database cannot be accessed from an authorizer callback")
107+
108+
// A statement's virtual machine cannot be reentered while sqlite3_step() is
109+
// running it. Finalizing it frees the VM outright, and re-running it resets the
110+
// VM mid-execution; both are use-after-free rather than merely a contract
111+
// violation. Callbacks that SQLite invokes during execution are therefore
112+
// barred from reaching the statement being stepped, though other statements on
113+
// the connection stay usable.
114+
#define THROW_AND_RETURN_IF_STEPPING(env, stmt) \
115+
THROW_AND_RETURN_ON_BAD_STATE( \
116+
(env), \
117+
(stmt)->db_->IsSteppingStatement((stmt)->statement_), \
118+
"statement is already being executed")
119+
99120
#define SQLITE_VALUE_TO_JS(from, isolate, use_big_int_args, result, ...) \
100121
do { \
101122
switch (sqlite3_##from##_type(__VA_ARGS__)) { \
@@ -825,6 +846,12 @@ Intercepted DatabaseSyncLimits::LimitsSetter(
825846
return Intercepted::kYes;
826847
}
827848

849+
if (limits->database_->IsInAuthorizerCallback()) {
850+
THROW_ERR_INVALID_STATE(
851+
env, "database cannot be accessed from an authorizer callback");
852+
return Intercepted::kYes;
853+
}
854+
828855
if (!value->IsNumber()) {
829856
THROW_ERR_INVALID_ARG_TYPE(
830857
isolate, "Limit value must be a non-negative integer or Infinity.");
@@ -1081,6 +1108,7 @@ void DatabaseSync::CreateTagStore(const FunctionCallbackInfo<Value>& args) {
10811108
THROW_ERR_INVALID_STATE(env, "database is not open");
10821109
return;
10831110
}
1111+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
10841112
int capacity = 1000;
10851113
if (args.Length() > 0 && !args[0]->IsUndefined()) {
10861114
if (!args[0]->IsNumber()) {
@@ -1483,6 +1511,7 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
14831511
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
14841512
Environment* env = Environment::GetCurrent(args);
14851513
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1514+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
14861515

14871516
if (!args[0]->IsString()) {
14881517
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
@@ -1606,6 +1635,7 @@ void DatabaseSync::Exec(const FunctionCallbackInfo<Value>& args) {
16061635
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
16071636
Environment* env = Environment::GetCurrent(args);
16081637
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1638+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
16091639

16101640
if (!args[0]->IsString()) {
16111641
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
@@ -1630,6 +1660,7 @@ void DatabaseSync::CustomFunction(const FunctionCallbackInfo<Value>& args) {
16301660
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
16311661
Environment* env = Environment::GetCurrent(args);
16321662
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1663+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
16331664

16341665
if (!args[0]->IsString()) {
16351666
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
@@ -1803,6 +1834,7 @@ void DatabaseSync::Serialize(const FunctionCallbackInfo<Value>& args) {
18031834
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
18041835
Environment* env = Environment::GetCurrent(args);
18051836
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1837+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
18061838

18071839
std::string db_name = "main";
18081840
if (!args[0]->IsUndefined()) {
@@ -1937,6 +1969,7 @@ void DatabaseSync::AggregateFunction(const FunctionCallbackInfo<Value>& args) {
19371969
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
19381970
Environment* env = Environment::GetCurrent(args);
19391971
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1972+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
19401973
Utf8Value name(env->isolate(), args[0].As<String>());
19411974
Local<Object> options = args[1].As<Object>();
19421975
Local<Value> start_v;
@@ -2148,6 +2181,7 @@ void DatabaseSync::CreateSession(const FunctionCallbackInfo<Value>& args) {
21482181
DatabaseSync* db;
21492182
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
21502183
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
2184+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
21512185

21522186
sqlite3_session* pSession;
21532187
int r = sqlite3session_create(db->connection_, db_name.c_str(), &pSession);
@@ -2317,6 +2351,7 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
23172351
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
23182352
Environment* env = Environment::GetCurrent(args);
23192353
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
2354+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
23202355

23212356
if (!args[0]->IsUint8Array()) {
23222357
THROW_ERR_INVALID_ARG_TYPE(
@@ -2452,6 +2487,7 @@ void DatabaseSync::EnableLoadExtension(
24522487
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
24532488
Environment* env = Environment::GetCurrent(args);
24542489
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
2490+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
24552491

24562492
Isolate* isolate = env->isolate();
24572493
if (!args[0]->IsBoolean()) {
@@ -2480,6 +2516,7 @@ void DatabaseSync::EnableDefensive(const FunctionCallbackInfo<Value>& args) {
24802516
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
24812517
Environment* env = Environment::GetCurrent(args);
24822518
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
2519+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
24832520

24842521
Isolate* isolate = env->isolate();
24852522
if (!args[0]->IsBoolean()) {
@@ -2505,6 +2542,7 @@ void DatabaseSync::LoadExtension(const FunctionCallbackInfo<Value>& args) {
25052542
env, !db->allow_load_extension_, "extension loading is not allowed");
25062543
THROW_AND_RETURN_ON_BAD_STATE(
25072544
env, !db->enable_load_extension_, "extension loading is not allowed");
2545+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
25082546

25092547
if (!args[0]->IsString()) {
25102548
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
@@ -2533,6 +2571,7 @@ void DatabaseSync::SetAuthorizer(const FunctionCallbackInfo<Value>& args) {
25332571
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
25342572
Environment* env = Environment::GetCurrent(args);
25352573
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
2574+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
25362575

25372576
Isolate* isolate = env->isolate();
25382577

@@ -2569,6 +2608,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
25692608
const char* param4) {
25702609
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
25712610
CallbackDepthGuard guard(db);
2611+
AuthorizerDepthGuard authorizer_guard(db);
25722612
Environment* env = db->env();
25732613
Isolate* isolate = env->isolate();
25742614
HandleScope handle_scope(isolate);
@@ -2682,12 +2722,20 @@ void StatementSync::Close(const FunctionCallbackInfo<Value>& args) {
26822722
Environment* env = Environment::GetCurrent(args);
26832723
THROW_AND_RETURN_ON_BAD_STATE(
26842724
env, stmt->IsFinalized(), "statement has been finalized");
2725+
THROW_AND_RETURN_IF_STEPPING(env, stmt);
26852726
stmt->Close();
26862727
}
26872728

26882729
void StatementSync::Dispose(const FunctionCallbackInfo<Value>& args) {
26892730
StatementSync* stmt;
26902731
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
2732+
Environment* env = Environment::GetCurrent(args);
2733+
// Disposal is idempotent, so an already-finalized statement is a no-op even
2734+
// inside a callback.
2735+
if (stmt->IsFinalized()) {
2736+
return;
2737+
}
2738+
THROW_AND_RETURN_IF_STEPPING(env, stmt);
26912739
stmt->Close();
26922740
}
26932741

@@ -2968,6 +3016,7 @@ MaybeLocal<Value> StatementExecutionHelper::All(Environment* env,
29683016
LocalVector<Value> row_values(isolate);
29693017
LocalVector<Name> row_keys(isolate);
29703018

3019+
SteppingStatementGuard stepping(db, stmt);
29713020
while ((r = sqlite3_step(stmt)) == SQLITE_ROW) {
29723021
if (num_cols == 0) {
29733022
num_cols = sqlite3_column_count(stmt);
@@ -3010,8 +3059,14 @@ MaybeLocal<Object> StatementExecutionHelper::Run(Environment* env,
30103059
bool use_big_ints) {
30113060
Isolate* isolate = env->isolate();
30123061
EscapableHandleScope scope(isolate);
3013-
sqlite3_step(stmt);
3014-
int r = sqlite3_reset(stmt);
3062+
int r;
3063+
{
3064+
// sqlite3_reset() can run JavaScript through an aggregate's xFinal, so it
3065+
// stays inside the guard.
3066+
SteppingStatementGuard stepping(db, stmt);
3067+
sqlite3_step(stmt);
3068+
r = sqlite3_reset(stmt);
3069+
}
30153070
CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_OK, MaybeLocal<Object>());
30163071

30173072
sqlite3_int64 last_insert_rowid = sqlite3_last_insert_rowid(db->Connection());
@@ -3086,6 +3141,9 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env,
30863141
bool use_big_ints) {
30873142
Isolate* isolate = env->isolate();
30883143
EscapableHandleScope scope(isolate);
3144+
// Declared before the reset below so that it outlives it: sqlite3_reset()
3145+
// can run JavaScript through an aggregate's xFinal.
3146+
SteppingStatementGuard stepping(db, stmt);
30893147
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt); });
30903148

30913149
int r = sqlite3_step(stmt);
@@ -3132,6 +3190,8 @@ void StatementSync::All(const FunctionCallbackInfo<Value>& args) {
31323190
Environment* env = Environment::GetCurrent(args);
31333191
THROW_AND_RETURN_ON_BAD_STATE(
31343192
env, stmt->IsFinalized(), "statement has been finalized");
3193+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
3194+
THROW_AND_RETURN_IF_STEPPING(env, stmt);
31353195
Isolate* isolate = env->isolate();
31363196
int r = stmt->ResetStatement();
31373197
CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void());
@@ -3159,6 +3219,8 @@ void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
31593219
Environment* env = Environment::GetCurrent(args);
31603220
THROW_AND_RETURN_ON_BAD_STATE(
31613221
env, stmt->IsFinalized(), "statement has been finalized");
3222+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
3223+
THROW_AND_RETURN_IF_STEPPING(env, stmt);
31623224
int r = stmt->ResetStatement();
31633225
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
31643226

@@ -3182,6 +3244,8 @@ void StatementSync::Get(const FunctionCallbackInfo<Value>& args) {
31823244
Environment* env = Environment::GetCurrent(args);
31833245
THROW_AND_RETURN_ON_BAD_STATE(
31843246
env, stmt->IsFinalized(), "statement has been finalized");
3247+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
3248+
THROW_AND_RETURN_IF_STEPPING(env, stmt);
31853249
int r = stmt->ResetStatement();
31863250
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
31873251

@@ -3206,6 +3270,8 @@ void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
32063270
Environment* env = Environment::GetCurrent(args);
32073271
THROW_AND_RETURN_ON_BAD_STATE(
32083272
env, stmt->IsFinalized(), "statement has been finalized");
3273+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
3274+
THROW_AND_RETURN_IF_STEPPING(env, stmt);
32093275
int r = stmt->ResetStatement();
32103276
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
32113277

@@ -3488,13 +3554,16 @@ void SQLTagStore::Run(const FunctionCallbackInfo<Value>& args) {
34883554

34893555
THROW_AND_RETURN_ON_BAD_STATE(
34903556
env, !session->database_->IsOpen(), "database is not open");
3557+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get());
34913558

34923559
BaseObjectPtr<StatementSync> stmt = PrepareStatement(args);
34933560

34943561
if (!stmt) {
34953562
return;
34963563
}
34973564

3565+
THROW_AND_RETURN_IF_STEPPING(env, stmt.get());
3566+
34983567
if (!ResetAndBindStatement(env, stmt.get(), args)) {
34993568
return;
35003569
}
@@ -3514,13 +3583,16 @@ void SQLTagStore::Iterate(const FunctionCallbackInfo<Value>& args) {
35143583

35153584
THROW_AND_RETURN_ON_BAD_STATE(
35163585
env, !session->database_->IsOpen(), "database is not open");
3586+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get());
35173587

35183588
BaseObjectPtr<StatementSync> stmt = PrepareStatement(args);
35193589

35203590
if (!stmt) {
35213591
return;
35223592
}
35233593

3594+
THROW_AND_RETURN_IF_STEPPING(env, stmt.get());
3595+
35243596
if (!ResetAndBindStatement(env, stmt.get(), args)) {
35253597
return;
35263598
}
@@ -3542,13 +3614,16 @@ void SQLTagStore::Get(const FunctionCallbackInfo<Value>& args) {
35423614

35433615
THROW_AND_RETURN_ON_BAD_STATE(
35443616
env, !session->database_->IsOpen(), "database is not open");
3617+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get());
35453618

35463619
BaseObjectPtr<StatementSync> stmt = PrepareStatement(args);
35473620

35483621
if (!stmt) {
35493622
return;
35503623
}
35513624

3625+
THROW_AND_RETURN_IF_STEPPING(env, stmt.get());
3626+
35523627
if (!ResetAndBindStatement(env, stmt.get(), args)) {
35533628
return;
35543629
}
@@ -3571,13 +3646,16 @@ void SQLTagStore::All(const FunctionCallbackInfo<Value>& args) {
35713646

35723647
THROW_AND_RETURN_ON_BAD_STATE(
35733648
env, !session->database_->IsOpen(), "database is not open");
3649+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get());
35743650

35753651
BaseObjectPtr<StatementSync> stmt = PrepareStatement(args);
35763652

35773653
if (!stmt) {
35783654
return;
35793655
}
35803656

3657+
THROW_AND_RETURN_IF_STEPPING(env, stmt.get());
3658+
35813659
if (!ResetAndBindStatement(env, stmt.get(), args)) {
35823660
return;
35833661
}
@@ -3597,6 +3675,10 @@ void SQLTagStore::All(const FunctionCallbackInfo<Value>& args) {
35973675
void SQLTagStore::Clear(const FunctionCallbackInfo<Value>& args) {
35983676
SQLTagStore* store;
35993677
ASSIGN_OR_RETURN_UNWRAP(&store, args.This());
3678+
Environment* env = Environment::GetCurrent(args);
3679+
if (store->database_) {
3680+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, store->database_.get());
3681+
}
36003682
store->sql_tags_.Clear();
36013683
}
36023684

@@ -3790,6 +3872,7 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) {
37903872
Environment* env = Environment::GetCurrent(args);
37913873
THROW_AND_RETURN_ON_BAD_STATE(
37923874
env, iter->stmt_->IsFinalized(), "statement has been finalized");
3875+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, iter->stmt_->db_.get());
37933876
Isolate* isolate = env->isolate();
37943877

37953878
auto iter_template = getLazyIterTemplate(env);
@@ -3812,6 +3895,10 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) {
38123895
iter->statement_reset_generation_ != iter->stmt_->reset_generation_,
38133896
"iterator was invalidated");
38143897

3898+
// sqlite3_reset() can run JavaScript through an aggregate's xFinal, so it
3899+
// stays inside the guard.
3900+
SteppingStatementGuard stepping(iter->stmt_->db_.get(),
3901+
iter->stmt_->statement_);
38153902
int r = sqlite3_step(iter->stmt_->statement_);
38163903
if (r != SQLITE_ROW) {
38173904
CHECK_ERROR_OR_THROW(
@@ -3867,6 +3954,7 @@ void StatementSyncIterator::Return(const FunctionCallbackInfo<Value>& args) {
38673954
Environment* env = Environment::GetCurrent(args);
38683955
THROW_AND_RETURN_ON_BAD_STATE(
38693956
env, iter->stmt_->IsFinalized(), "statement has been finalized");
3957+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, iter->stmt_->db_.get());
38703958
Isolate* isolate = env->isolate();
38713959

38723960
sqlite3_reset(iter->stmt_->statement_);
@@ -3945,6 +4033,7 @@ void Session::Changeset(const FunctionCallbackInfo<Value>& args) {
39454033
env, !session->database_->IsOpen(), "database is not open");
39464034
THROW_AND_RETURN_ON_BAD_STATE(
39474035
env, session->session_ == nullptr, "session is not open");
4036+
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get());
39484037

39494038
int nChangeset;
39504039
void* pChangeset;

0 commit comments

Comments
 (0)