Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions doc/api/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@ Registers a new aggregate function with the SQLite database. This method is a wr
JavaScript numbers. **Default:** `false`.
* `varargs` {boolean} If `true`, `options.step` and `options.inverse` may be invoked with any number of
arguments (between zero and [`SQLITE_MAX_FUNCTION_ARG`][]). If `false`,
`inverse` and `step` must be invoked with exactly `length` arguments.
`inverse` and `step` must be invoked with exactly `length` arguments, and
their `length` properties must be integers.
**Default:** `false`.
* `start` {number | string | null | Array | Object | Function} The identity
value for the aggregation function. This value is used when the aggregation
Expand Down Expand Up @@ -430,7 +431,8 @@ added:
JavaScript numbers. **Default:** `false`.
* `varargs` {boolean} If `true`, `function` may be invoked with any number of
arguments (between zero and [`SQLITE_MAX_FUNCTION_ARG`][]). If `false`,
`function` must be invoked with exactly `function.length` arguments.
`function` must be invoked with exactly `function.length` arguments, which
must be an integer.
**Default:** `false`.
* `fn` {Function} The JavaScript function to call when the SQLite function is
invoked. The return value of this function should be a valid SQLite data type:
Expand Down
81 changes: 72 additions & 9 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1718,6 +1718,10 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
}
}

// Reading the options bag above can run user JavaScript through a property
// getter, which may have closed the database since it was checked.
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

Utf8Value sql(env->isolate(), args[0].As<String>());
sqlite3_stmt* s = nullptr;

Expand Down Expand Up @@ -1905,9 +1909,22 @@ void DatabaseSync::CustomFunction(const FunctionCallbackInfo<Value>& args) {
if (!fn->Get(env->context(), env->length_string()).ToLocal(&js_len)) {
return;
}

if (!js_len->IsInt32()) {
THROW_ERR_INVALID_ARG_TYPE(
env->isolate(),
"The \"function.length\" property must be an integer.");
return;
}

argc = js_len.As<Int32>()->Value();
}

// Reading the options bag and "function.length" above can run user
// JavaScript through a property getter, which may have closed the database
// since it was checked.
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

UserDefinedFunction* user_data = new UserDefinedFunction(
env, fn, BaseObjectWeakPtr<DatabaseSync>(db), use_bigint_args);
int text_rep = SQLITE_UTF8;
Expand Down Expand Up @@ -2070,6 +2087,10 @@ void DatabaseSync::Deserialize(const FunctionCallbackInfo<Value>& args) {
}
}

// Reading the options bag above can run user JavaScript through a property
// getter, which may have closed the database since it was checked.
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

// sqlite3_malloc64 is required because SQLITE_DESERIALIZE_FREEONCLOSE
// transfers ownership to SQLite, which calls sqlite3_free() on close.
// See: https://www.sqlite.org/c3ref/deserialize.html
Expand All @@ -2080,7 +2101,16 @@ void DatabaseSync::Deserialize(const FunctionCallbackInfo<Value>& args) {
return;
}

input->CopyContents(buf, byte_length);
// The same user JavaScript may also have shrunk or detached the backing
// store, in which case byte_length is stale and CopyContents() leaves the
// remainder of buf uninitialized. Handing that to SQLite would disclose it
// through serialize().
if (input->CopyContents(buf, byte_length) != byte_length) {
sqlite3_free(buf);
THROW_ERR_INVALID_STATE(
env, "The \"buffer\" argument was resized while reading \"options\"");
return;
}

db->FinalizeStatements();

Expand Down Expand Up @@ -2218,17 +2248,37 @@ void DatabaseSync::AggregateFunction(const FunctionCallbackInfo<Value>& args) {
return;
}

if (!js_len->IsInt32()) {
THROW_ERR_INVALID_ARG_TYPE(
env->isolate(),
"The \"options.step.length\" property must be an integer.");
return;
}

// Subtract 1 because the first argument is the aggregate value.
argc = js_len.As<Int32>()->Value() - 1;
if (!inverseFunc.IsEmpty() &&
!inverseFunc->Get(env->context(), env->length_string())
.ToLocal(&js_len)) {
return;
if (!inverseFunc.IsEmpty()) {
if (!inverseFunc->Get(env->context(), env->length_string())
.ToLocal(&js_len)) {
return;
}

if (!js_len->IsInt32()) {
THROW_ERR_INVALID_ARG_TYPE(
env->isolate(),
"The \"options.inverse.length\" property must be an integer.");
return;
}
}

argc = std::max({argc, js_len.As<Int32>()->Value() - 1, 0});
}

// Reading the options bag and the step/inverse "length" properties above can
// run user JavaScript through a property getter, which may have closed the
// database since it was checked.
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

int text_rep = SQLITE_UTF8;
if (direct_only) {
text_rep |= SQLITE_DIRECTONLY;
Expand Down Expand Up @@ -2257,10 +2307,15 @@ void DatabaseSync::AggregateFunction(const FunctionCallbackInfo<Value>& args) {
}

void DatabaseSync::CreateSession(const FunctionCallbackInfo<Value>& args) {
DatabaseSync* db;
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
Environment* env = Environment::GetCurrent(args);
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

std::string table;
std::string db_name = "main";

Environment* env = Environment::GetCurrent(args);
if (args.Length() > 0) {
if (!args[0]->IsObject()) {
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
Expand Down Expand Up @@ -2311,10 +2366,9 @@ void DatabaseSync::CreateSession(const FunctionCallbackInfo<Value>& args) {
}
}

DatabaseSync* db;
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
// Reading the options bag above can run user JavaScript through a property
// getter, which may have closed the database since it was checked.
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

sqlite3_session* pSession;
int r =
Expand Down Expand Up @@ -2441,6 +2495,11 @@ void Backup(const FunctionCallbackInfo<Value>& args) {
}
}

// Reading the destination path and the options bag above can run user
// JavaScript through a property getter, which may have closed the database
// since it was checked.
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

Local<Promise::Resolver> resolver;
if (!Promise::Resolver::New(env->context()).ToLocal(&resolver)) {
return;
Expand Down Expand Up @@ -2584,6 +2643,10 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
}
}

// Reading the options bag above can run user JavaScript through a property
// getter, which may have closed the database since it was checked.
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

// Keep the database alive during sqlite3changeset_apply(), which may
// call conflict or filter callbacks that trigger JavaScript execution.
// If the JavaScript callback drops all references to the database,
Expand Down
Loading
Loading