Skip to content

Commit 1758ca1

Browse files
committed
fixup! src,lib: initial ffi implementation
1 parent a24ea22 commit 1758ca1

14 files changed

Lines changed: 150 additions & 7 deletions

File tree

doc/api/cli.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,48 @@ Error: Access to this API has been restricted
141141
}
142142
```
143143

144+
### `--allow-ffi`
145+
146+
<!-- YAML
147+
added: REPLACEME
148+
-->
149+
150+
> Stability: 1 - Experimental
151+
152+
When using the [Permission Model][], the process will not be able to get
153+
pointers from buffers, or load shared libraries through the `node:ffi` module by
154+
default. Attempts to do so will throw an `ERR_ACCESS_DENIED` unless the
155+
user explicitly passes the `--allow-ffi` flag when starting Node.js.
156+
157+
Example:
158+
159+
```js
160+
const ffi = require('node:ffi');
161+
// Attempt to bypass the permission
162+
ffi.getBufferPointer(Buffer.alloc(1));
163+
```
164+
165+
```console
166+
$ node --experimental-permission --allow-fs-read=* index.js
167+
node:ffi:226
168+
const ptr = getBufferPointerInternal(buf);
169+
^
170+
171+
Error: Access to this API has been restricted
172+
at Object.getBufferPointer (node:ffi:226:15)
173+
at Object.<anonymous> (/Users/bryan.english/node/script.js:3:5)
174+
at Module._compile (node:internal/modules/cjs/loader:1287:14)
175+
at Module._extensions..js (node:internal/modules/cjs/loader:1341:10)
176+
at Module.load (node:internal/modules/cjs/loader:1145:32)
177+
at Module._load (node:internal/modules/cjs/loader:984:12)
178+
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:83:12)
179+
at node:internal/main/run_main_module:23:47 {
180+
code: 'ERR_ACCESS_DENIED',
181+
permission: 'Ffi',
182+
resource: ''
183+
}
184+
```
185+
144186
### `--allow-fs-read`
145187

146188
<!-- YAML

doc/api/permissions.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -464,9 +464,9 @@ will restrict access to all available permissions.
464464
The available permissions are documented by the [`--experimental-permission`][]
465465
flag.
466466

467-
When starting Node.js with `--experimental-permission`,
468-
the ability to access the file system, spawn processes, and
469-
use `node:worker_threads` will be restricted.
467+
When starting Node.js with `--experimental-permission`, the ability to access
468+
the file system, spawn processes, use `node:worker_threads`, and load shared
469+
libraries with `node:ffi` will be restricted.
470470

471471
```console
472472
$ node --experimental-permission index.js
@@ -485,8 +485,9 @@ Error: Access to this API has been restricted
485485
}
486486
```
487487

488-
Allowing access to spawning a process and creating worker threads can be done
489-
using the [`--allow-child-process`][] and [`--allow-worker`][] respectively.
488+
Allowing access to spawning a process, creating worker threads, and loading
489+
shared libraries with FFI can be done using the [`--allow-child-process`][],
490+
[`--allow-worker`][], and [`--allow-ffi`][] flags respectively.
490491

491492
#### Runtime API
492493

@@ -588,6 +589,7 @@ const fd = fs.openSync('./README.md', 'r');
588589

589590
[Security Policy]: https://github.com/nodejs/node/blob/main/SECURITY.md
590591
[`--allow-child-process`]: cli.md#--allow-child-process
592+
[`--allow-ffi`]: cli.md#--allow-ffi
591593
[`--allow-fs-read`]: cli.md#--allow-fs-read
592594
[`--allow-fs-write`]: cli.md#--allow-fs-write
593595
[`--allow-worker`]: cli.md#--allow-worker

doc/node.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ Allow file system read access when using the permission model.
8282
.It Fl -allow-fs-write
8383
Allow file system write access when using the permission model.
8484
.
85+
.It Fl -allow-ffi
86+
Allow foreign function interface (FFI) when using the permission model.
87+
.
8588
.It Fl -allow-child-process
8689
Allow spawning process when using the permission model.
8790
.

lib/internal/process/pre_execution.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ function initializePermission() {
512512
const { has, deny } = require('internal/process/permission');
513513
const warnFlags = [
514514
'--allow-child-process',
515+
'--allow-ffi',
515516
'--allow-worker',
516517
];
517518
for (const flag of warnFlags) {
@@ -537,6 +538,7 @@ function initializePermission() {
537538
'--allow-fs-write',
538539
'--allow-child-process',
539540
'--allow-worker',
541+
'--allow-ffi',
540542
];
541543
ArrayPrototypeForEach(availablePermissionFlags, (flag) => {
542544
if (getOptionValue(flag)) {

node.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@
547547
'src/node_worker.cc',
548548
'src/node_zlib.cc',
549549
'src/permission/child_process_permission.cc',
550+
'src/permission/ffi_permission.cc',
550551
'src/permission/fs_permission.cc',
551552
'src/permission/permission.cc',
552553
'src/permission/worker_permission.cc',
@@ -663,6 +664,7 @@
663664
'src/node_watchdog.h',
664665
'src/node_worker.h',
665666
'src/permission/child_process_permission.h',
667+
'src/permission/ffi_permission.h',
666668
'src/permission/fs_permission.h',
667669
'src/permission/permission.h',
668670
'src/permission/permission_node.h',

src/env.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,9 @@ Environment::Environment(IsolateData* isolate_data,
769769
if (!options_->allow_worker_threads) {
770770
permission()->Deny(permission::PermissionScope::kWorkerThreads, {});
771771
}
772+
if (!options_->allow_ffi) {
773+
permission()->Deny(permission::PermissionScope::kFfi, {});
774+
}
772775
}
773776

774777
if (!options_->allow_fs_read.empty()) {

src/node_ffi.cc

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ static bool lossless = false;
3030

3131
void GetLibrary(const FunctionCallbackInfo<Value>& args) {
3232
Environment* env = Environment::GetCurrent(args);
33+
THROW_IF_INSUFFICIENT_PERMISSIONS(
34+
env, permission::PermissionScope::kFfi, "");
3335

3436
bool isNull = true;
3537
std::string fname = "";
@@ -59,6 +61,9 @@ void GetLibrary(const FunctionCallbackInfo<Value>& args) {
5961

6062
void GetSymbol(const FunctionCallbackInfo<Value>& args) {
6163
Environment* env = Environment::GetCurrent(args);
64+
THROW_IF_INSUFFICIENT_PERMISSIONS(
65+
env, permission::PermissionScope::kFfi, "");
66+
6267
CHECK(args[0]->IsBigInt());
6368
binding::DLib * lib = (binding::DLib *)args[0].As<BigInt>()
6469
->Uint64Value(&lossless);
@@ -69,6 +74,10 @@ void GetSymbol(const FunctionCallbackInfo<Value>& args) {
6974
}
7075

7176
void GetBufferPointer(const FunctionCallbackInfo<Value>& args) {
77+
Environment* env = Environment::GetCurrent(args);
78+
THROW_IF_INSUFFICIENT_PERMISSIONS(
79+
env, permission::PermissionScope::kFfi, "");
80+
7281
CHECK(args[0]->IsArrayBuffer());
7382
void* data = args[0].As<ArrayBuffer>()->Data();
7483

@@ -116,12 +125,16 @@ FfiSignature::~FfiSignature() {
116125
}
117126

118127
void FfiSignature::New(const FunctionCallbackInfo<Value>& args) {
128+
Isolate * isolate = args.GetIsolate();
129+
Environment * env = Environment::GetCurrent(isolate);
130+
131+
THROW_IF_INSUFFICIENT_PERMISSIONS(
132+
env, permission::PermissionScope::kFfi, "");
133+
119134
CHECK(args.IsConstructCall());
120135
CHECK(args[0]->IsBigInt());
121136
CHECK(args[1]->IsBigInt());
122137
CHECK(args[2]->IsArray());
123-
Isolate * isolate = args.GetIsolate();
124-
Environment * env = Environment::GetCurrent(isolate);
125138

126139
FfiSignature* sig = new FfiSignature(
127140
env,
@@ -137,6 +150,11 @@ void FfiSignature::New(const FunctionCallbackInfo<Value>& args) {
137150
}
138151

139152
void MakeCall(const FunctionCallbackInfo<Value> &args) {
153+
Isolate * isolate = args.GetIsolate();
154+
Environment * env = Environment::GetCurrent(isolate);
155+
THROW_IF_INSUFFICIENT_PERMISSIONS(
156+
env, permission::PermissionScope::kFfi, "");
157+
140158
char* callBuffer = reinterpret_cast<char*>(
141159
Realm::GetBindingData<FfiBindingData>(args)->callBuffer);
142160
FfiSignature * sig = *reinterpret_cast<FfiSignature **>(callBuffer);

src/node_options.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
436436
"allow worker threads when any permissions are set",
437437
&EnvironmentOptions::allow_worker_threads,
438438
kAllowedInEnvvar);
439+
AddOption("--allow-ffi",
440+
"allow FFI when any permissions are set",
441+
&EnvironmentOptions::allow_ffi,
442+
kAllowedInEnvvar);
439443
AddOption("--experimental-repl-await",
440444
"experimental await keyword support in REPL",
441445
&EnvironmentOptions::experimental_repl_await,

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class EnvironmentOptions : public Options {
123123
bool experimental_permission = false;
124124
std::string allow_fs_read;
125125
std::string allow_fs_write;
126+
bool allow_ffi = false;
126127
bool allow_child_process = false;
127128
bool allow_worker_threads = false;
128129
bool experimental_repl_await = true;

src/permission/ffi_permission.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include "permission/ffi_permission.h"
2+
3+
#include <string>
4+
#include <vector>
5+
6+
namespace node {
7+
8+
namespace permission {
9+
10+
// Currently, PolicyDenyFfi manage a single state
11+
// Once denied, it's always denied
12+
void FfiPermission::Apply(const std::string& deny, PermissionScope scope) {}
13+
14+
bool FfiPermission::Deny(PermissionScope perm,
15+
const std::vector<std::string>& params) {
16+
deny_all_ = true;
17+
return true;
18+
}
19+
20+
bool FfiPermission::is_granted(PermissionScope perm,
21+
const std::string_view& param) {
22+
return deny_all_ == false;
23+
}
24+
25+
} // namespace permission
26+
} // namespace node

0 commit comments

Comments
 (0)