Skip to content

Commit b1cfecf

Browse files
committed
sqlite: fix permission VFS edge cases
Signed-off-by: Matteo Collina <matteo.collina@gmail.com>
1 parent 485b2ca commit b1cfecf

4 files changed

Lines changed: 251 additions & 33 deletions

File tree

src/node_sqlite.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,8 @@ bool IsSQLiteMemoryLocation(std::string_view location) {
953953
bool IsSQLiteReadOnlyLocation(std::string_view location, bool read_only) {
954954
return read_only ||
955955
(location.starts_with("file:") &&
956-
SQLiteUriParameterEquals(location, "mode", "ro"));
956+
(SQLiteUriParameterEquals(location, "mode", "ro") ||
957+
SQLiteUriBooleanParameter(location, "immutable")));
957958
}
958959

959960
} // namespace

src/node_sqlite_vfs.cc

Lines changed: 126 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
#include <algorithm>
77
#include <atomic>
88
#include <cctype>
9+
#include <cstdlib>
910
#include <cstring>
1011
#include <string>
1112
#include <string_view>
13+
#include <vector>
1214

1315
namespace node {
1416
namespace sqlite {
@@ -45,6 +47,7 @@ void DecodeUriComponent(std::string_view component, std::string* decoded) {
4547

4648
bool GetSQLiteUriParameter(std::string_view path,
4749
std::string_view parameter,
50+
bool use_last,
4851
std::string* value) {
4952
if (!path.starts_with("file:")) return false;
5053

@@ -62,6 +65,7 @@ bool GetSQLiteUriParameter(std::string_view path,
6265
size_t parameter_start = query_start + 1;
6366
std::string decoded_key;
6467
std::string decoded_value;
68+
bool found = false;
6569
while (parameter_start <= query_end) {
6670
size_t parameter_end = path.find('&', parameter_start);
6771
if (parameter_end == std::string_view::npos || parameter_end > query_end) {
@@ -83,13 +87,25 @@ bool GetSQLiteUriParameter(std::string_view path,
8387
&decoded_value);
8488
*value = decoded_value;
8589
}
86-
return true;
90+
found = true;
91+
if (!use_last) return true;
8792
}
8893

8994
if (parameter_end == query_end) break;
9095
parameter_start = parameter_end + 1;
9196
}
92-
return false;
97+
return found;
98+
}
99+
100+
bool EqualsIgnoreCase(std::string_view left, std::string_view right) {
101+
if (left.size() != right.size()) return false;
102+
for (size_t i = 0; i < left.size(); ++i) {
103+
if (std::tolower(static_cast<unsigned char>(left[i])) !=
104+
std::tolower(static_cast<unsigned char>(right[i]))) {
105+
return false;
106+
}
107+
}
108+
return true;
93109
}
94110

95111
bool IsSidecarPath(std::string_view path, std::string* database_path) {
@@ -113,6 +129,25 @@ bool IsSidecarPath(std::string_view path, std::string* database_path) {
113129
return false;
114130
}
115131

132+
sqlite3_filename CreateReadOnlyShmFilename(sqlite3_filename name) {
133+
std::vector<const char*> parameters = {"readonly_shm", "1"};
134+
for (int index = 0;; ++index) {
135+
const char* key = sqlite3_uri_key(name, index);
136+
if (key == nullptr) break;
137+
if (strcmp(key, "readonly_shm") == 0) continue;
138+
139+
const char* value = sqlite3_uri_parameter(name, key);
140+
parameters.push_back(key);
141+
parameters.push_back(value == nullptr ? "" : value);
142+
}
143+
144+
return sqlite3_create_filename(sqlite3_filename_database(name),
145+
sqlite3_filename_journal(name),
146+
sqlite3_filename_wal(name),
147+
static_cast<int>(parameters.size() / 2),
148+
parameters.data());
149+
}
150+
116151
std::string CanonicalPath(SQLitePermissionVFS* vfs, const char* path) {
117152
if (path == nullptr) return {};
118153

@@ -133,9 +168,11 @@ std::string CanonicalPath(SQLitePermissionVFS* vfs, const char* path) {
133168
struct PermissionFile {
134169
sqlite3_file base;
135170
SQLitePermissionVFS* vfs;
171+
sqlite3_filename parent_name;
136172
bool can_read;
137173
bool can_write;
138174
bool can_write_sidecar;
175+
bool read_only_shm;
139176
sqlite3_io_methods methods;
140177
};
141178

@@ -203,6 +240,8 @@ int PermissionClose(sqlite3_file* file) {
203240
const int result = real->pMethods == nullptr
204241
? SQLITE_OK
205242
: real->pMethods->xClose(real);
243+
sqlite3_free_filename(permission_file->parent_name);
244+
permission_file->parent_name = nullptr;
206245
file->pMethods = nullptr;
207246
return result;
208247
}
@@ -321,8 +360,12 @@ int PermissionShmMap(sqlite3_file* file,
321360
if (!HasReadPermission(permission_file)) return SQLITE_PERM;
322361

323362
// OS VFS implementations can open or create the SHM file read-write even
324-
// when extend is false.
325-
if (!permission_file->can_write_sidecar) return SQLITE_PERM;
363+
// when extend is false. A read-only clone of the SQLite filename is used
364+
// when the caller has no permission to write sidecars.
365+
if (!permission_file->can_write_sidecar &&
366+
!permission_file->read_only_shm) {
367+
return SQLITE_PERM;
368+
}
326369

327370
sqlite3_file* real = RealFile(permission_file);
328371
if (real->pMethods->xShmMap == nullptr) return SQLITE_NOTFOUND;
@@ -468,14 +511,27 @@ const char* PermissionVfsNextSystemCall(sqlite3_vfs* vfs, const char* name) {
468511

469512
bool HasSQLiteVfsUriParameter(std::string_view path) {
470513
std::string value;
471-
return GetSQLiteUriParameter(path, "vfs", &value);
514+
return GetSQLiteUriParameter(path, "vfs", false, &value);
472515
}
473516

474517
bool SQLiteUriParameterEquals(std::string_view path,
475518
std::string_view parameter,
476519
std::string_view value) {
477520
std::string actual;
478-
return GetSQLiteUriParameter(path, parameter, &actual) && actual == value;
521+
return GetSQLiteUriParameter(path, parameter, true, &actual) &&
522+
actual == value;
523+
}
524+
525+
bool SQLiteUriBooleanParameter(std::string_view path,
526+
std::string_view parameter) {
527+
std::string value;
528+
if (!GetSQLiteUriParameter(path, parameter, false, &value)) return false;
529+
530+
if (!value.empty() && std::isdigit(static_cast<unsigned char>(value[0]))) {
531+
return std::strtoll(value.c_str(), nullptr, 10) != 0;
532+
}
533+
return EqualsIgnoreCase(value, "on") || EqualsIgnoreCase(value, "yes") ||
534+
EqualsIgnoreCase(value, "true");
479535
}
480536

481537
std::string SQLitePathForPermission(std::string_view path) {
@@ -516,12 +572,15 @@ std::string SQLitePathForPermission(std::string_view path) {
516572

517573
SQLitePermissionVFS::SQLitePermissionVFS(Environment* env)
518574
: parent_(sqlite3_vfs_find(nullptr)),
575+
memory_vfs_(sqlite3_vfs_find("memdb")),
519576
permission_(env->permission()),
520577
name_("node-permission-" + std::to_string(++next_vfs_id)) {
521578
CHECK_NOT_NULL(parent_);
579+
CHECK_NOT_NULL(memory_vfs_);
522580

523581
vfs_.iVersion = parent_->iVersion;
524-
vfs_.szOsFile = sizeof(PermissionFile) + parent_->szOsFile;
582+
vfs_.szOsFile = sizeof(PermissionFile) +
583+
std::max(parent_->szOsFile, memory_vfs_->szOsFile);
525584
vfs_.mxPathname = parent_->mxPathname;
526585
vfs_.zName = name_.c_str();
527586
vfs_.pAppData = this;
@@ -585,18 +644,29 @@ bool SQLitePermissionVFS::AllowsPath(std::string_view path,
585644
candidate));
586645
};
587646

588-
if (allowed(canonical)) return true;
647+
return allowed(canonical);
648+
}
649+
650+
bool SQLitePermissionVFS::AllowsSidecarPath(std::string_view path,
651+
bool read,
652+
bool write) const {
653+
if (AllowsPath(path, read, write)) return true;
654+
655+
const std::string canonical = CanonicalPath(
656+
const_cast<SQLitePermissionVFS*>(this), std::string(path).c_str());
657+
if (canonical.empty()) return false;
589658

590659
std::string database_path;
591-
return IsSidecarPath(canonical, &database_path) && allowed(database_path);
660+
return IsSidecarPath(canonical, &database_path) &&
661+
AllowsPath(database_path, read, write);
592662
}
593663

594664
bool SQLitePermissionVFS::AllowsTemporaryFile(bool read, bool write) const {
595665
if (permission_->warning_only()) return true;
596666

597-
// SQLite does not provide a pathname for anonymous temporary files. Only
598-
// allow them when the corresponding global permission is granted; a
599-
// path-specific grant cannot safely authorize an unknown temporary path.
667+
// SQLite does not provide a pathname for anonymous temporary files. Disk
668+
// storage is allowed only with global permissions. Otherwise xOpen uses
669+
// SQLite's memory VFS.
600670
return (!read || permission_->is_granted_no_side_effects(
601671
permission::PermissionScope::kFileSystemRead, "")) &&
602672
(!write || permission_->is_granted_no_side_effects(
@@ -613,32 +683,66 @@ int SQLitePermissionVFS::Open(sqlite3_filename name,
613683

614684
const bool read =
615685
(flags & (SQLITE_OPEN_READONLY | SQLITE_OPEN_READWRITE)) != 0;
616-
const bool write = (flags & SQLITE_OPEN_READWRITE) != 0;
686+
const bool immutable = name != nullptr && (flags & SQLITE_OPEN_MAIN_DB) &&
687+
sqlite3_uri_boolean(name, "immutable", 0) != 0;
688+
bool write = (flags & SQLITE_OPEN_READWRITE) != 0 && !immutable;
689+
int parent_flags = flags;
690+
sqlite3_vfs* selected_vfs = parent_;
617691
bool can_write_sidecar = write;
692+
bool read_only_shm = false;
618693
bool allowed;
619694

620695
if (flags & SQLITE_OPEN_MEMORY) {
696+
selected_vfs = memory_vfs_;
621697
allowed = true;
622698
} else if (name == nullptr) {
623699
allowed = AllowsTemporaryFile(read, write);
700+
if (!allowed) {
701+
selected_vfs = memory_vfs_;
702+
allowed = true;
703+
}
624704
} else {
625705
const char* permission_name = name;
626-
if (flags & (SQLITE_OPEN_MAIN_JOURNAL | SQLITE_OPEN_WAL)) {
706+
const bool is_journal =
707+
flags & (SQLITE_OPEN_MAIN_JOURNAL | SQLITE_OPEN_WAL);
708+
if (is_journal) {
627709
permission_name = sqlite3_filename_database(name);
710+
sqlite3_file* database_file = sqlite3_database_file_object(name);
711+
if (database_file != nullptr &&
712+
!PermissionFileFromBase(database_file)->can_write_sidecar) {
713+
write = false;
714+
parent_flags &= ~(SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
715+
parent_flags |= SQLITE_OPEN_READONLY;
716+
}
628717
}
718+
629719
allowed = permission_name != nullptr &&
630720
!HasSQLiteVfsUriParameter(permission_name) &&
631-
AllowsPath(permission_name, read, write);
632-
if (permission_name != nullptr && (flags & SQLITE_OPEN_MAIN_DB)) {
721+
((flags & SQLITE_OPEN_SUPER_JOURNAL)
722+
? AllowsSidecarPath(permission_name, read, write)
723+
: AllowsPath(permission_name, read, write));
724+
if (allowed && permission_name != nullptr &&
725+
(flags & SQLITE_OPEN_MAIN_DB)) {
633726
can_write_sidecar = AllowsPath(permission_name, false, true);
727+
if (!can_write_sidecar) {
728+
permission_file->parent_name = CreateReadOnlyShmFilename(name);
729+
if (permission_file->parent_name == nullptr) return SQLITE_NOMEM;
730+
read_only_shm = true;
731+
}
634732
}
635733
}
636734

637735
if (!allowed) return SQLITE_PERM;
638736

737+
sqlite3_filename parent_name = permission_file->parent_name == nullptr
738+
? name
739+
: permission_file->parent_name;
639740
sqlite3_file* real = RealFile(permission_file);
640-
const int result = parent_->xOpen(parent_, name, real, flags, out_flags);
741+
const int result = selected_vfs->xOpen(
742+
selected_vfs, parent_name, real, parent_flags, out_flags);
641743
if (real->pMethods == nullptr) {
744+
sqlite3_free_filename(permission_file->parent_name);
745+
permission_file->parent_name = nullptr;
642746
file->pMethods = nullptr;
643747
return result;
644748
}
@@ -648,6 +752,7 @@ int SQLitePermissionVFS::Open(sqlite3_filename name,
648752
(out_flags == nullptr ||
649753
(*out_flags & SQLITE_OPEN_READONLY) == 0);
650754
permission_file->can_write_sidecar = can_write_sidecar;
755+
permission_file->read_only_shm = read_only_shm;
651756
permission_file->methods = kPermissionIoMethods;
652757
permission_file->methods.iVersion = std::min(
653758
permission_file->methods.iVersion, real->pMethods->iVersion);
@@ -666,7 +771,9 @@ int SQLitePermissionVFS::Open(sqlite3_filename name,
666771
}
667772

668773
int SQLitePermissionVFS::Delete(const char* name, int sync_dir) {
669-
if (name == nullptr || !AllowsPath(name, false, true)) return SQLITE_PERM;
774+
if (name == nullptr || !AllowsSidecarPath(name, false, true)) {
775+
return SQLITE_PERM;
776+
}
670777
return parent_->xDelete(parent_, name, sync_dir);
671778
}
672779

@@ -678,7 +785,7 @@ int SQLitePermissionVFS::Access(const char* name, int flags, int* result) {
678785

679786
const bool read = true;
680787
const bool write = flags == SQLITE_ACCESS_READWRITE;
681-
if (!AllowsPath(name, read, write)) {
788+
if (!AllowsSidecarPath(name, read, write)) {
682789
*result = 0;
683790
return SQLITE_OK;
684791
}

src/node_sqlite_vfs.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ bool HasSQLiteVfsUriParameter(std::string_view path);
2727
bool SQLiteUriParameterEquals(std::string_view path,
2828
std::string_view parameter,
2929
std::string_view value);
30+
// Returns true if the first matching SQLite URI parameter is a true boolean.
31+
bool SQLiteUriBooleanParameter(std::string_view path,
32+
std::string_view parameter);
3033
// Returns the decoded filesystem path portion of a SQLite URI filename. Plain
3134
// filenames are returned unchanged.
3235
std::string SQLitePathForPermission(std::string_view path);
@@ -67,11 +70,13 @@ class SQLitePermissionVFS {
6770
const char* NextSystemCall(const char* name);
6871

6972
bool AllowsPath(std::string_view path, bool read, bool write) const;
73+
bool AllowsSidecarPath(std::string_view path, bool read, bool write) const;
7074
bool AllowsTemporaryFile(bool read, bool write) const;
7175

7276
private:
7377
sqlite3_vfs vfs_{};
7478
sqlite3_vfs* parent_ = nullptr;
79+
sqlite3_vfs* memory_vfs_ = nullptr;
7580
permission::Permission* permission_ = nullptr;
7681
std::string name_;
7782
bool registered_ = false;

0 commit comments

Comments
 (0)