Skip to content

Commit 485b2ca

Browse files
committed
sqlite: enforce permissions through a VFS shim
Signed-off-by: Matteo Collina <matteo.collina@gmail.com>
1 parent 9b2b3fd commit 485b2ca

11 files changed

Lines changed: 1205 additions & 112 deletions

node.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,8 +481,10 @@
481481
],
482482
'node_sqlite_sources': [
483483
'src/node_sqlite.cc',
484+
'src/node_sqlite_vfs.cc',
484485
'src/node_webstorage.cc',
485486
'src/node_sqlite.h',
487+
'src/node_sqlite_vfs.h',
486488
'src/node_webstorage.h',
487489
],
488490
'node_ffi_sources': [

src/node_sqlite.cc

Lines changed: 96 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ class BackupJob : public ThreadPoolWork {
488488
std::string source_db,
489489
std::string destination_name,
490490
std::string dest_db,
491+
std::shared_ptr<SQLitePermissionVFS> permission_vfs,
491492
int pages,
492493
Local<Function> progressFunc)
493494
: ThreadPoolWork(env, "node_sqlite3.BackupJob"),
@@ -496,7 +497,8 @@ class BackupJob : public ThreadPoolWork {
496497
pages_(pages),
497498
source_db_(std::move(source_db)),
498499
destination_name_(std::move(destination_name)),
499-
dest_db_(std::move(dest_db)) {
500+
dest_db_(std::move(dest_db)),
501+
permission_vfs_(std::move(permission_vfs)) {
500502
resolver_.Reset(env->isolate(), resolver);
501503
progressFunc_.Reset(env->isolate(), progressFunc);
502504
}
@@ -508,7 +510,8 @@ class BackupJob : public ThreadPoolWork {
508510
destination_name_.c_str(),
509511
&dest_,
510512
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
511-
nullptr);
513+
permission_vfs_ == nullptr ? nullptr
514+
: permission_vfs_->name().c_str());
512515
Local<Promise::Resolver> resolver =
513516
Local<Promise::Resolver>::New(env()->isolate(), resolver_);
514517
if (backup_status_ != SQLITE_OK) {
@@ -609,6 +612,8 @@ class BackupJob : public ThreadPoolWork {
609612
sqlite3_close_v2(dest_);
610613
dest_ = nullptr;
611614
}
615+
616+
permission_vfs_.reset();
612617
}
613618

614619
private:
@@ -651,6 +656,7 @@ class BackupJob : public ThreadPoolWork {
651656
std::string source_db_;
652657
std::string destination_name_;
653658
std::string dest_db_;
659+
std::shared_ptr<SQLitePermissionVFS> permission_vfs_;
654660
};
655661

656662
UserDefinedFunction::UserDefinedFunction(Environment* env,
@@ -935,22 +941,58 @@ void DatabaseSync::MemoryInfo(MemoryTracker* tracker) const {
935941
"open_config", sizeof(open_config_), "DatabaseOpenConfiguration");
936942
}
937943

944+
namespace {
945+
946+
bool IsSQLiteMemoryLocation(std::string_view location) {
947+
return location == ":memory:" ||
948+
(location.starts_with("file:") &&
949+
(SQLitePathForPermission(location) == ":memory:" ||
950+
SQLiteUriParameterEquals(location, "mode", "memory")));
951+
}
952+
953+
bool IsSQLiteReadOnlyLocation(std::string_view location, bool read_only) {
954+
return read_only ||
955+
(location.starts_with("file:") &&
956+
SQLiteUriParameterEquals(location, "mode", "ro"));
957+
}
958+
959+
} // namespace
960+
938961
bool DatabaseSync::Open() {
939962
if (IsOpen()) {
940963
THROW_ERR_INVALID_STATE(env(), "database is already open");
941964
return false;
942965
}
943966

944-
// Permission checks: skip for in-memory databases, enforce FS permissions
945-
// for file-backed databases.
946-
std::string_view db_path = open_config_.location();
947-
if (db_path != ":memory:" && !db_path.empty()) {
948-
if (open_config_.get_read_only()) {
967+
// Permission checks: in-memory databases do not access the filesystem.
968+
// SQLite URI modes are handled before the VFS is created so that the
969+
// user-facing error remains Node's permission error.
970+
std::string_view db_location = open_config_.location();
971+
if (!IsSQLiteMemoryLocation(db_location)) {
972+
const std::string db_path = SQLitePathForPermission(db_location);
973+
const bool read_only =
974+
IsSQLiteReadOnlyLocation(db_location, open_config_.get_read_only());
975+
THROW_IF_INSUFFICIENT_PERMISSIONS(
976+
env(),
977+
permission::PermissionScope::kFileSystemRead,
978+
db_path,
979+
false);
980+
if (!read_only) {
949981
THROW_IF_INSUFFICIENT_PERMISSIONS(
950-
env(), permission::PermissionScope::kFileSystemRead, db_path, false);
951-
} else {
952-
THROW_IF_INSUFFICIENT_PERMISSIONS(
953-
env(), permission::PermissionScope::kFileSystemWrite, db_path, false);
982+
env(),
983+
permission::PermissionScope::kFileSystemWrite,
984+
db_path,
985+
false);
986+
}
987+
}
988+
989+
if (env()->permission()->enabled()) {
990+
permission_vfs_ = std::make_shared<SQLitePermissionVFS>(env());
991+
if (!permission_vfs_->Register()) {
992+
THROW_ERR_SQLITE_ERROR(env()->isolate(),
993+
"Unable to register the SQLite permission VFS");
994+
permission_vfs_.reset();
995+
return false;
954996
}
955997
}
956998

@@ -972,12 +1014,19 @@ bool DatabaseSync::Open() {
9721014
int flags = open_config_.get_read_only()
9731015
? SQLITE_OPEN_READONLY
9741016
: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
975-
int r = sqlite3_open_v2(open_config_.location().c_str(),
976-
&connection_,
977-
flags | default_flags,
978-
nullptr);
1017+
int r = sqlite3_open_v2(
1018+
open_config_.location().c_str(),
1019+
&connection_,
1020+
flags | default_flags,
1021+
permission_vfs_ == nullptr ? nullptr : permission_vfs_->name().c_str());
9791022
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
9801023

1024+
if (permission_vfs_ != nullptr) {
1025+
r = sqlite3_set_authorizer(
1026+
connection_, DatabaseSync::AuthorizerCallback, this);
1027+
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
1028+
}
1029+
9811030
r = sqlite3_db_config(connection_,
9821031
SQLITE_DBCONFIG_DQS_DML,
9831032
static_cast<int>(open_config_.get_enable_dqs()),
@@ -1101,10 +1150,21 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
11011150
constexpr auto has_null_bytes = [](std::string_view str) {
11021151
return str.find('\0') != std::string_view::npos;
11031152
};
1153+
const auto validate_uri_vfs = [&](std::string value)
1154+
-> std::optional<std::string> {
1155+
if (env->permission()->enabled() && HasSQLiteVfsUriParameter(value)) {
1156+
THROW_ERR_INVALID_ARG_VALUE(
1157+
env->isolate(),
1158+
"The \"%s\" argument must not specify a SQLite VFS.",
1159+
field_name);
1160+
return std::nullopt;
1161+
}
1162+
return value;
1163+
};
11041164
if (path->IsString()) {
11051165
Utf8Value location(env->isolate(), path.As<String>());
11061166
if (!has_null_bytes(location.ToStringView())) {
1107-
return location.ToString();
1167+
return validate_uri_vfs(location.ToString());
11081168
}
11091169
} else if (path->IsUint8Array()) {
11101170
Local<Uint8Array> buffer = path.As<Uint8Array>();
@@ -1113,7 +1173,8 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
11131173
auto data =
11141174
static_cast<const uint8_t*>(buffer->Buffer()->Data()) + byteOffset;
11151175
if (std::find(data, data + byteLength, 0) == data + byteLength) {
1116-
return std::string(reinterpret_cast<const char*>(data), byteLength);
1176+
return validate_uri_vfs(
1177+
std::string(reinterpret_cast<const char*>(data), byteLength));
11171178
}
11181179
} else if (path->IsObject()) { // When is URL
11191180
auto url = path.As<Object>();
@@ -1129,7 +1190,7 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
11291190
return std::nullopt;
11301191
}
11311192

1132-
return location_value.ToString();
1193+
return validate_uri_vfs(location_value.ToString());
11331194
}
11341195
}
11351196
}
@@ -1452,6 +1513,7 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
14521513
int r = sqlite3_close_v2(db->connection_);
14531514
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
14541515
db->connection_ = nullptr;
1516+
db->permission_vfs_.reset();
14551517
}
14561518

14571519
void DatabaseSync::Dispose(const v8::FunctionCallbackInfo<v8::Value>& args) {
@@ -2242,6 +2304,7 @@ void Backup(const FunctionCallbackInfo<Value>& args) {
22422304
std::move(source_db),
22432305
dest_path.value(),
22442306
std::move(dest_db),
2307+
db->PermissionVFS(),
22452308
rate,
22462309
progressFunc);
22472310
db->AddBackup(job);
@@ -2477,9 +2540,13 @@ void DatabaseSync::SetAuthorizer(const FunctionCallbackInfo<Value>& args) {
24772540
Isolate* isolate = env->isolate();
24782541

24792542
if (args[0]->IsNull()) {
2480-
// Clear the authorizer
2481-
sqlite3_set_authorizer(db->connection_, nullptr, nullptr);
24822543
db->object()->SetInternalField(kAuthorizerCallback, Null(isolate));
2544+
if (db->permission_vfs_ == nullptr) {
2545+
sqlite3_set_authorizer(db->connection_, nullptr, nullptr);
2546+
} else {
2547+
sqlite3_set_authorizer(
2548+
db->connection_, DatabaseSync::AuthorizerCallback, db);
2549+
}
24832550
return;
24842551
}
24852552

@@ -2508,16 +2575,19 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
25082575
const char* param3,
25092576
const char* param4) {
25102577
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
2511-
Environment* env = db->env();
2512-
Isolate* isolate = env->isolate();
2513-
HandleScope handle_scope(isolate);
2514-
Local<Context> context = env->context();
2578+
if (db->permission_vfs_ != nullptr && action_code == SQLITE_ATTACH &&
2579+
param1 != nullptr && HasSQLiteVfsUriParameter(param1)) {
2580+
return SQLITE_DENY;
2581+
}
25152582

25162583
Local<Value> cb =
25172584
db->object()->GetInternalField(kAuthorizerCallback).template As<Value>();
2585+
if (!cb->IsFunction()) return SQLITE_OK;
25182586

2519-
CHECK(cb->IsFunction());
2520-
2587+
Environment* env = db->env();
2588+
Isolate* isolate = env->isolate();
2589+
HandleScope handle_scope(isolate);
2590+
Local<Context> context = env->context();
25212591
Local<Function> callback = cb.As<Function>();
25222592

25232593
LocalVector<Value> js_argv(

src/node_sqlite.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
#include "base_object.h"
77
#include "lru_cache-inl.h"
88
#include "node_mem.h"
9+
#include "node_sqlite_vfs.h"
910
#include "sqlite3.h"
1011
#include "util.h"
1112

1213
#include <array>
1314
#include <list>
1415
#include <map>
16+
#include <memory>
1517
#include <optional>
1618
#include <string_view>
1719
#include <unordered_set>
@@ -220,6 +222,9 @@ class DatabaseSync : public BaseObject {
220222
return open_config_.get_allow_unknown_named_params();
221223
}
222224
sqlite3* Connection();
225+
std::shared_ptr<SQLitePermissionVFS> PermissionVFS() const {
226+
return permission_vfs_;
227+
}
223228

224229
// In some situations, such as when using custom functions, it is possible
225230
// that SQLite reports an error while JavaScript already has a pending
@@ -241,6 +246,7 @@ class DatabaseSync : public BaseObject {
241246
bool enable_load_extension_;
242247
sqlite3* connection_;
243248
bool ignore_next_sqlite_error_;
249+
std::shared_ptr<SQLitePermissionVFS> permission_vfs_;
244250

245251
std::set<BackupJob*> backups_;
246252
std::set<sqlite3_session*> sessions_;

0 commit comments

Comments
 (0)