Skip to content

Commit 53cc0cc

Browse files
himself65targos
authored andcommitted
sqlite: support db.loadExtension
PR-URL: #53900 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent 6f49c80 commit 53cc0cc

File tree

13 files changed

+409
-3
lines changed

13 files changed

+409
-3
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ coverage-report-js: ## Report JavaScript coverage results.
294294
cctest: all ## Run the C++ tests using the built `cctest` executable.
295295
@out/$(BUILDTYPE)/$@ --gtest_filter=$(GTEST_FILTER)
296296
$(NODE) ./test/embedding/test-embedding.js
297+
$(NODE) ./test/sqlite/test-sqlite-extensions.mjs
297298

298299
.PHONY: list-gtests
299300
list-gtests: ## List all available C++ gtests.
@@ -574,6 +575,7 @@ test-ci: | clear-stalled bench-addons-build build-addons build-js-native-api-tes
574575
--mode=$(BUILDTYPE_LOWER) --flaky-tests=$(FLAKY_TESTS) \
575576
$(TEST_CI_ARGS) $(CI_JS_SUITES) $(CI_NATIVE_SUITES) $(CI_DOC)
576577
$(NODE) ./test/embedding/test-embedding.js
578+
$(NODE) ./test/sqlite/test-sqlite-extensions.mjs
577579
$(info Clean up any leftover processes, error if found.)
578580
ps awwx | grep Release/node | grep -v grep | cat
579581
@PS_OUT=`ps awwx | grep Release/node | grep -v grep | awk '{print $$1}'`; \
@@ -1432,6 +1434,7 @@ LINT_CPP_FILES = $(filter-out $(LINT_CPP_EXCLUDE), $(wildcard \
14321434
test/cctest/*.h \
14331435
test/embedding/*.cc \
14341436
test/embedding/*.h \
1437+
test/sqlite/*.c \
14351438
test/fixtures/*.c \
14361439
test/js-native-api/*/*.cc \
14371440
test/node-api/*/*.cc \

doc/api/errors.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2172,6 +2172,16 @@ added:
21722172
An ESM loader hook returned without calling `next()` and without explicitly
21732173
signaling a short circuit.
21742174

2175+
<a id="ERR_LOAD_SQLITE_EXTENSION"></a>
2176+
2177+
### `ERR_LOAD_SQLITE_EXTENSION`
2178+
2179+
<!-- YAML
2180+
added: REPLACEME
2181+
-->
2182+
2183+
An error occurred while loading a SQLite extension.
2184+
21752185
<a id="ERR_MEMORY_ALLOCATION_FAILED"></a>
21762186

21772187
### `ERR_MEMORY_ALLOCATION_FAILED`

doc/api/permissions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ There are constraints you need to know before using this system:
147147
flags that can be set via runtime through `v8.setFlagsFromString`.
148148
* OpenSSL engines cannot be requested at runtime when the Permission
149149
Model is enabled, affecting the built-in crypto, https, and tls modules.
150+
* Run-Time Loadable Extensions cannot be loaded when the Permission Model is
151+
enabled, affecting the sqlite module.
150152
* Using existing file descriptors via the `node:fs` module bypasses the
151153
Permission Model.
152154

doc/api/sqlite.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ added: v22.5.0
108108
[double-quoted string literals][]. This is not recommended but can be
109109
enabled for compatibility with legacy database schemas.
110110
**Default:** `false`.
111+
* `allowExtension` {boolean} If `true`, the `loadExtension` SQL function
112+
and the `loadExtension()` method are enabled.
113+
You can call `enableLoadExtension(false)` later to disable this feature.
114+
**Default:** `false`.
111115

112116
Constructs a new `DatabaseSync` instance.
113117

@@ -120,6 +124,30 @@ added: v22.5.0
120124
Closes the database connection. An exception is thrown if the database is not
121125
open. This method is a wrapper around [`sqlite3_close_v2()`][].
122126

127+
### `database.loadExtension(path)`
128+
129+
<!-- YAML
130+
added: REPLACEME
131+
-->
132+
133+
* `path` {string} The path to the shared library to load.
134+
135+
Loads a shared library into the database connection. This method is a wrapper
136+
around [`sqlite3_load_extension()`][]. It is required to enable the
137+
`allowExtension` option when constructing the `DatabaseSync` instance.
138+
139+
### `database.enableLoadExtension(allow)`
140+
141+
<!-- YAML
142+
added: REPLACEME
143+
-->
144+
145+
* `allow` {boolean} Whether to allow loading extensions.
146+
147+
Enables or disables the `loadExtension` SQL function, and the `loadExtension()`
148+
method. When `allowExtension` is `false` when constructing, you cannot enable
149+
loading extensions for security reasons.
150+
123151
### `database.exec(sql)`
124152

125153
<!-- YAML
@@ -457,6 +485,7 @@ The following constants are meant for use with [`database.applyChangeset()`](#da
457485
[`sqlite3_exec()`]: https://www.sqlite.org/c3ref/exec.html
458486
[`sqlite3_expanded_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html
459487
[`sqlite3_last_insert_rowid()`]: https://www.sqlite.org/c3ref/last_insert_rowid.html
488+
[`sqlite3_load_extension()`]: https://www.sqlite.org/c3ref/load_extension.html
460489
[`sqlite3_prepare_v2()`]: https://www.sqlite.org/c3ref/prepare.html
461490
[`sqlite3_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html
462491
[`sqlite3changeset_apply()`]: https://www.sqlite.org/session/sqlite3changeset_apply.html

node.gyp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,26 @@
12961296
],
12971297
}, # embedtest
12981298

1299+
{
1300+
'target_name': 'sqlite_extension',
1301+
'type': 'shared_library',
1302+
'sources': [
1303+
'test/sqlite/extension.c'
1304+
],
1305+
1306+
'include_dirs': [
1307+
'test/sqlite',
1308+
'deps/sqlite',
1309+
],
1310+
1311+
'cflags': [
1312+
'-fPIC',
1313+
'-Wall',
1314+
'-Wextra',
1315+
'-O3',
1316+
],
1317+
}, # sqlitetest
1318+
12991319
{
13001320
'target_name': 'overlapped-checker',
13011321
'type': 'executable',

src/node_errors.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
9191
V(ERR_INVALID_THIS, TypeError) \
9292
V(ERR_INVALID_URL, TypeError) \
9393
V(ERR_INVALID_URL_SCHEME, TypeError) \
94+
V(ERR_LOAD_SQLITE_EXTENSION, Error) \
9495
V(ERR_MEMORY_ALLOCATION_FAILED, Error) \
9596
V(ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE, Error) \
9697
V(ERR_MISSING_ARGS, TypeError) \
@@ -191,6 +192,7 @@ ERRORS_WITH_CODE(V)
191192
V(ERR_INVALID_STATE, "Invalid state") \
192193
V(ERR_INVALID_THIS, "Value of \"this\" is the wrong type") \
193194
V(ERR_INVALID_URL_SCHEME, "The URL must be of scheme file:") \
195+
V(ERR_LOAD_SQLITE_EXTENSION, "Failed to load SQLite extension") \
194196
V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory") \
195197
V(ERR_OSSL_EVP_INVALID_DIGEST, "Invalid digest used") \
196198
V(ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE, \

src/node_sqlite.cc

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "node_sqlite.h"
2+
#include <path.h>
23
#include "base_object-inl.h"
34
#include "debug_utils-inl.h"
45
#include "env-inl.h"
@@ -114,10 +115,13 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, const char* message) {
114115
DatabaseSync::DatabaseSync(Environment* env,
115116
Local<Object> object,
116117
DatabaseOpenConfiguration&& open_config,
117-
bool open)
118+
bool open,
119+
bool allow_load_extension)
118120
: BaseObject(env, object), open_config_(std::move(open_config)) {
119121
MakeWeak();
120122
connection_ = nullptr;
123+
allow_load_extension_ = allow_load_extension;
124+
enable_load_extension_ = allow_load_extension;
121125

122126
if (open) {
123127
Open();
@@ -182,6 +186,19 @@ bool DatabaseSync::Open() {
182186
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
183187
CHECK_EQ(foreign_keys_enabled, open_config_.get_enable_foreign_keys());
184188

189+
if (allow_load_extension_) {
190+
if (env()->permission()->enabled()) [[unlikely]] {
191+
THROW_ERR_LOAD_SQLITE_EXTENSION(env(),
192+
"Cannot load SQLite extensions when the "
193+
"permission model is enabled.");
194+
return false;
195+
}
196+
const int load_extension_ret = sqlite3_db_config(
197+
connection_, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, nullptr);
198+
CHECK_ERROR_OR_THROW(
199+
env()->isolate(), connection_, load_extension_ret, SQLITE_OK, false);
200+
}
201+
185202
return true;
186203
}
187204

@@ -227,6 +244,7 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
227244
DatabaseOpenConfiguration open_config(std::move(location));
228245

229246
bool open = true;
247+
bool allow_load_extension = false;
230248

231249
if (args.Length() > 1) {
232250
if (!args[1]->IsObject()) {
@@ -302,9 +320,28 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
302320
}
303321
open_config.set_enable_dqs(enable_dqs_v.As<Boolean>()->Value());
304322
}
323+
324+
Local<String> allow_extension_string =
325+
FIXED_ONE_BYTE_STRING(env->isolate(), "allowExtension");
326+
Local<Value> allow_extension_v;
327+
if (!options->Get(env->context(), allow_extension_string)
328+
.ToLocal(&allow_extension_v)) {
329+
return;
330+
}
331+
332+
if (!allow_extension_v->IsUndefined()) {
333+
if (!allow_extension_v->IsBoolean()) {
334+
THROW_ERR_INVALID_ARG_TYPE(
335+
env->isolate(),
336+
"The \"options.allowExtension\" argument must be a boolean.");
337+
return;
338+
}
339+
allow_load_extension = allow_extension_v.As<Boolean>()->Value();
340+
}
305341
}
306342

307-
new DatabaseSync(env, args.This(), std::move(open_config), open);
343+
new DatabaseSync(
344+
env, args.This(), std::move(open_config), open, allow_load_extension);
308345
}
309346

310347
void DatabaseSync::Open(const FunctionCallbackInfo<Value>& args) {
@@ -526,6 +563,70 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
526563
args.GetReturnValue().Set(true);
527564
}
528565

566+
void DatabaseSync::EnableLoadExtension(
567+
const FunctionCallbackInfo<Value>& args) {
568+
DatabaseSync* db;
569+
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
570+
Environment* env = Environment::GetCurrent(args);
571+
if (!args[0]->IsBoolean()) {
572+
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
573+
"The \"allow\" argument must be a boolean.");
574+
return;
575+
}
576+
577+
const int enable = args[0].As<Boolean>()->Value();
578+
auto isolate = env->isolate();
579+
580+
if (db->allow_load_extension_ == false && enable == true) {
581+
THROW_ERR_INVALID_STATE(
582+
isolate,
583+
"Cannot enable extension loading because it was disabled at database "
584+
"creation.");
585+
return;
586+
}
587+
db->enable_load_extension_ = enable;
588+
const int load_extension_ret = sqlite3_db_config(
589+
db->connection_, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, enable, nullptr);
590+
CHECK_ERROR_OR_THROW(
591+
isolate, db->connection_, load_extension_ret, SQLITE_OK, void());
592+
}
593+
594+
void DatabaseSync::LoadExtension(const FunctionCallbackInfo<Value>& args) {
595+
DatabaseSync* db;
596+
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
597+
Environment* env = Environment::GetCurrent(args);
598+
THROW_AND_RETURN_ON_BAD_STATE(
599+
env, db->connection_ == nullptr, "database is not open");
600+
THROW_AND_RETURN_ON_BAD_STATE(
601+
env, !db->allow_load_extension_, "extension loading is not allowed");
602+
THROW_AND_RETURN_ON_BAD_STATE(
603+
env, !db->enable_load_extension_, "extension loading is not allowed");
604+
605+
if (!args[0]->IsString()) {
606+
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
607+
"The \"path\" argument must be a string.");
608+
return;
609+
}
610+
611+
auto isolate = env->isolate();
612+
613+
BufferValue path(isolate, args[0]);
614+
BufferValue entryPoint(isolate, args[1]);
615+
CHECK_NOT_NULL(*path);
616+
ToNamespacedPath(env, &path);
617+
if (*entryPoint == nullptr) {
618+
ToNamespacedPath(env, &entryPoint);
619+
}
620+
THROW_IF_INSUFFICIENT_PERMISSIONS(
621+
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
622+
char* errmsg = nullptr;
623+
const int r =
624+
sqlite3_load_extension(db->connection_, *path, *entryPoint, &errmsg);
625+
if (r != SQLITE_OK) {
626+
isolate->ThrowException(ERR_LOAD_SQLITE_EXTENSION(isolate, errmsg));
627+
}
628+
}
629+
529630
StatementSync::StatementSync(Environment* env,
530631
Local<Object> object,
531632
DatabaseSync* db,
@@ -1312,6 +1413,12 @@ static void Initialize(Local<Object> target,
13121413
isolate, db_tmpl, "createSession", DatabaseSync::CreateSession);
13131414
SetProtoMethod(
13141415
isolate, db_tmpl, "applyChangeset", DatabaseSync::ApplyChangeset);
1416+
SetProtoMethod(isolate,
1417+
db_tmpl,
1418+
"enableLoadExtension",
1419+
DatabaseSync::EnableLoadExtension);
1420+
SetProtoMethod(
1421+
isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension);
13151422
SetConstructorFunction(context, target, "DatabaseSync", db_tmpl);
13161423
SetConstructorFunction(context,
13171424
target,

src/node_sqlite.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ class DatabaseSync : public BaseObject {
4949
DatabaseSync(Environment* env,
5050
v8::Local<v8::Object> object,
5151
DatabaseOpenConfiguration&& open_config,
52-
bool open);
52+
bool open,
53+
bool allow_load_extension);
5354
void MemoryInfo(MemoryTracker* tracker) const override;
5455
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
5556
static void Open(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -58,6 +59,9 @@ class DatabaseSync : public BaseObject {
5859
static void Exec(const v8::FunctionCallbackInfo<v8::Value>& args);
5960
static void CreateSession(const v8::FunctionCallbackInfo<v8::Value>& args);
6061
static void ApplyChangeset(const v8::FunctionCallbackInfo<v8::Value>& args);
62+
static void EnableLoadExtension(
63+
const v8::FunctionCallbackInfo<v8::Value>& args);
64+
static void LoadExtension(const v8::FunctionCallbackInfo<v8::Value>& args);
6165
void FinalizeStatements();
6266
void UntrackStatement(StatementSync* statement);
6367
bool IsOpen();
@@ -72,6 +76,8 @@ class DatabaseSync : public BaseObject {
7276

7377
~DatabaseSync() override;
7478
DatabaseOpenConfiguration open_config_;
79+
bool allow_load_extension_;
80+
bool enable_load_extension_;
7581
sqlite3* connection_;
7682

7783
std::set<sqlite3_session*> sessions_;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('node:assert');
4+
const childProcess = require('child_process');
5+
6+
const code = `const sqlite = require('node:sqlite');
7+
const db = new sqlite.DatabaseSync(':memory:', { allowExtension: true });
8+
db.loadExtension('nonexistent');`.replace(/\n/g, ' ');
9+
10+
childProcess.exec(
11+
`${process.execPath} --experimental-permission -e "${code}"`,
12+
{},
13+
common.mustCall((err, _, stderr) => {
14+
assert.strictEqual(err.code, 1);
15+
assert.match(stderr, /Error: Cannot load SQLite extensions when the permission model is enabled/);
16+
assert.match(stderr, /code: 'ERR_LOAD_SQLITE_EXTENSION'/);
17+
})
18+
);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy