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
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -497,8 +497,10 @@
],
'node_sqlite_sources': [
'src/node_sqlite.cc',
'src/node_sqlite_vfs.cc',
'src/node_webstorage.cc',
'src/node_sqlite.h',
'src/node_sqlite_vfs.h',
'src/node_webstorage.h',
],
'node_ffi_sources': [
Expand Down
103 changes: 90 additions & 13 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ class BackupJob : public ThreadPoolWork {
std::string source_db,
std::string destination_name,
std::string dest_db,
std::shared_ptr<SQLitePermissionVFS> permission_vfs,
int pages,
Local<Function> progressFunc)
: ThreadPoolWork(env, "node_sqlite3.BackupJob"),
Expand All @@ -627,7 +628,8 @@ class BackupJob : public ThreadPoolWork {
pages_(pages),
source_db_(std::move(source_db)),
destination_name_(std::move(destination_name)),
dest_db_(std::move(dest_db)) {
dest_db_(std::move(dest_db)),
permission_vfs_(std::move(permission_vfs)) {
resolver_.Reset(env->isolate(), resolver);
progressFunc_.Reset(env->isolate(), progressFunc);
}
Expand All @@ -640,7 +642,7 @@ class BackupJob : public ThreadPoolWork {
destination_name_.c_str(),
&dest_raw,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
nullptr);
permission_vfs_ == nullptr ? nullptr : permission_vfs_->name().c_str());
dest_.reset(dest_raw);
Local<Promise::Resolver> resolver =
Local<Promise::Resolver>::New(env()->isolate(), resolver_);
Expand Down Expand Up @@ -746,6 +748,8 @@ class BackupJob : public ThreadPoolWork {
backup_status_ = sqlite3_errcode(dest_.get());
dest_.reset();
}

permission_vfs_.reset();
}

private:
Expand Down Expand Up @@ -794,6 +798,7 @@ class BackupJob : public ThreadPoolWork {
std::string source_db_;
std::string destination_name_;
std::string dest_db_;
std::shared_ptr<SQLitePermissionVFS> permission_vfs_;
};

UserDefinedFunction::UserDefinedFunction(Environment* env,
Expand Down Expand Up @@ -1625,12 +1630,55 @@ void Database::MemoryInfo(MemoryTracker* tracker) const {
"open_config", sizeof(open_config_), "DatabaseOpenConfiguration");
}

namespace {

bool IsSQLiteMemoryLocation(std::string_view location) {
return location == ":memory:" ||
(location.starts_with("file:") &&
(SQLitePathForPermission(location) == ":memory:" ||
SQLiteUriParameterEquals(location, "mode", "memory")));
}

bool IsSQLiteReadOnlyLocation(std::string_view location, bool read_only) {
return read_only || (location.starts_with("file:") &&
(SQLiteUriParameterEquals(location, "mode", "ro") ||
SQLiteUriBooleanParameter(location, "immutable")));
}

} // namespace

bool Database::Open() {
if (IsOpen()) {
THROW_ERR_INVALID_STATE(env(), "database is already open");
return false;
}

// Permission checks: in-memory databases do not access the filesystem.
// SQLite URI modes are handled before the VFS is created so that the
// user-facing error remains Node's permission error.
std::string_view db_location = open_config_.location();
if (!IsSQLiteMemoryLocation(db_location)) {
const std::string db_path = SQLitePathForPermission(db_location);
const bool read_only =
IsSQLiteReadOnlyLocation(db_location, open_config_.get_read_only());
THROW_IF_INSUFFICIENT_PERMISSIONS(
env(), permission::PermissionScope::kFileSystemRead, db_path, false);
if (!read_only) {
THROW_IF_INSUFFICIENT_PERMISSIONS(
env(), permission::PermissionScope::kFileSystemWrite, db_path, false);
}
}

if (env()->permission()->enabled()) {
permission_vfs_ = std::make_shared<SQLitePermissionVFS>(env());
if (!permission_vfs_->Register()) {
THROW_ERR_SQLITE_ERROR(env()->isolate(),
"Unable to register the SQLite permission VFS");
permission_vfs_.reset();
return false;
}
}

// sqlite3_open_v2() assigns a database handle even when it fails. Such a
// handle is in a "sick" state and may only be used to retrieve the error
// and must then be released with sqlite3_close(). Close and reset the
Expand All @@ -1649,13 +1697,20 @@ bool Database::Open() {
? SQLITE_OPEN_READONLY
: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
sqlite3* raw_conn = nullptr;
int r = sqlite3_open_v2(open_config_.location().c_str(),
&raw_conn,
flags | default_flags,
nullptr);
int r = sqlite3_open_v2(
open_config_.location().c_str(),
&raw_conn,
flags | default_flags,
permission_vfs_ == nullptr ? nullptr : permission_vfs_->name().c_str());
connection_.reset(raw_conn);
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);

if (permission_vfs_ != nullptr) {
r = sqlite3_set_authorizer(
connection_.get(), Database::AuthorizerCallback, this);
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
}

r = sqlite3_db_config(connection_.get(),
SQLITE_DBCONFIG_DQS_DML,
static_cast<int>(open_config_.get_enable_dqs()),
Expand Down Expand Up @@ -1824,10 +1879,21 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
constexpr auto has_null_bytes = [](std::string_view str) {
return str.find('\0') != std::string_view::npos;
};
const auto validate_uri_vfs =
[&](std::string value) -> std::optional<std::string> {
if (env->permission()->enabled() && HasSQLiteVfsUriParameter(value)) {
THROW_ERR_INVALID_ARG_VALUE(
env->isolate(),
"The \"%s\" argument must not specify a SQLite VFS.",
field_name);
return std::nullopt;
}
return value;
};
if (path->IsString()) {
Utf8Value location(env->isolate(), path.As<String>());
if (!has_null_bytes(location.ToStringView())) {
return location.ToString();
return validate_uri_vfs(location.ToString());
}
} else if (path->IsUint8Array()) {
Local<Uint8Array> buffer = path.As<Uint8Array>();
Expand All @@ -1836,7 +1902,8 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
auto data =
static_cast<const uint8_t*>(buffer->Buffer()->Data()) + byteOffset;
if (std::find(data, data + byteLength, 0) == data + byteLength) {
return std::string(reinterpret_cast<const char*>(data), byteLength);
return validate_uri_vfs(
std::string(reinterpret_cast<const char*>(data), byteLength));
}
} else if (path->IsObject()) { // When is URL
auto url = path.As<Object>();
Expand All @@ -1856,7 +1923,7 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
return std::nullopt;
}

return location_value.ToString();
return validate_uri_vfs(location_value.ToString());
}
}
}
Expand Down Expand Up @@ -2180,6 +2247,7 @@ void Database::Close(const FunctionCallbackInfo<Value>& args) {
int r = sqlite3_close_v2(db->connection_.get());
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
db->connection_.release();
db->permission_vfs_.reset();
// Backups can defer SQLite destruction until after the connection is closed.
db->user_defined_functions_.clear();
}
Expand Down Expand Up @@ -3325,6 +3393,7 @@ void Backup(const FunctionCallbackInfo<Value>& args) {
std::move(source_db),
dest_path.value(),
std::move(dest_db),
db->PermissionVFS(),
rate,
progressFunc);
db->AddBackup(job);
Expand Down Expand Up @@ -3611,9 +3680,13 @@ void Database::SetAuthorizer(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = env->isolate();

if (args[0]->IsNull()) {
// Clear the authorizer
sqlite3_set_authorizer(db->connection_.get(), nullptr, nullptr);
db->object()->SetInternalField(kAuthorizerCallback, Null(isolate));
if (db->permission_vfs_ == nullptr) {
sqlite3_set_authorizer(db->connection_.get(), nullptr, nullptr);
} else {
sqlite3_set_authorizer(
db->connection_.get(), Database::AuthorizerCallback, db);
}
return;
}

Expand Down Expand Up @@ -3642,6 +3715,11 @@ int Database::AuthorizerCallback(void* user_data,
const char* param3,
const char* param4) {
Database* db = static_cast<Database*>(user_data);
if (db->permission_vfs_ != nullptr && action_code == SQLITE_ATTACH &&
param1 != nullptr && HasSQLiteVfsUriParameter(param1)) {
return SQLITE_DENY;
}

CallbackDepthGuard guard(db);
AuthorizerDepthGuard authorizer_guard(db);
Environment* env = db->env();
Expand All @@ -3651,8 +3729,7 @@ int Database::AuthorizerCallback(void* user_data,

Local<Value> cb =
db->object()->GetInternalField(kAuthorizerCallback).template As<Value>();

CHECK(cb->IsFunction());
if (!cb->IsFunction()) return SQLITE_OK;

Local<Function> callback = cb.As<Function>();

Expand Down
6 changes: 6 additions & 0 deletions src/node_sqlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
#include "base_object.h"
#include "lru_cache-inl.h"
#include "node_mem.h"
#include "node_sqlite_vfs.h"
#include "sqlite3.h"
#include "util.h"

#include <algorithm>
#include <array>
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <string_view>
#include <unordered_set>
Expand Down Expand Up @@ -278,6 +280,9 @@ class Database : public BaseObject {
}
sqlite3* Connection();
void SetLimit(int sqlite_limit_id, int value);
std::shared_ptr<SQLitePermissionVFS> PermissionVFS() const {
return permission_vfs_;
}

// In some situations, such as when using custom functions, it is possible
// that SQLite reports an error while JavaScript already has a pending
Expand Down Expand Up @@ -347,6 +352,7 @@ class Database : public BaseObject {
int authorizer_depth_ = 0;
int trace_suppression_depth_ = 0;
std::vector<sqlite3_stmt*> stepping_statements_;
std::shared_ptr<SQLitePermissionVFS> permission_vfs_;

// SQLite owns these scalar and aggregate/window function registrations. Its
// destroy callbacks untrack replaced functions and failed registrations.
Expand Down
Loading
Loading