Skip to content

Commit 2bcce32

Browse files
RafaelGSSmarco-ippolito
authored andcommitted
src,permission: --allow-wasi & prevent WASI exec
PR-URL: #53124 Reviewed-By: Marco Ippolito <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent f00ee1c commit 2bcce32

17 files changed

+183
-7
lines changed

doc/api/cli.md

+50
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,53 @@ Examples can be found in the [File System Permissions][] documentation.
267267

268268
Relative paths are NOT supported through the CLI flag.
269269

270+
### `--allow-wasi`
271+
272+
<!-- YAML
273+
added: REPLACEME
274+
-->
275+
276+
> Stability: 1.1 - Active development
277+
278+
When using the [Permission Model][], the process will not be capable of creating
279+
any WASI instances by default.
280+
For security reasons, the call will throw an `ERR_ACCESS_DENIED` unless the
281+
user explicitly passes the flag `--allow-wasi` in the main Node.js process.
282+
283+
Example:
284+
285+
```js
286+
const { WASI } = require('node:wasi');
287+
// Attempt to bypass the permission
288+
new WASI({
289+
version: 'preview1',
290+
// Attempt to mount the whole filesystem
291+
preopens: {
292+
'/': '/',
293+
},
294+
});
295+
```
296+
297+
```console
298+
$ node --experimental-permission --allow-fs-read=* index.js
299+
node:wasi:99
300+
const wrap = new _WASI(args, env, preopens, stdio);
301+
^
302+
303+
Error: Access to this API has been restricted
304+
at new WASI (node:wasi:99:18)
305+
at Object.<anonymous> (/home/index.js:3:1)
306+
at Module._compile (node:internal/modules/cjs/loader:1476:14)
307+
at Module._extensions..js (node:internal/modules/cjs/loader:1555:10)
308+
at Module.load (node:internal/modules/cjs/loader:1288:32)
309+
at Module._load (node:internal/modules/cjs/loader:1104:12)
310+
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:191:14)
311+
at node:internal/main/run_main_module:30:49 {
312+
code: 'ERR_ACCESS_DENIED',
313+
permission: 'WASI',
314+
}
315+
```
316+
270317
### `--allow-worker`
271318

272319
<!-- YAML
@@ -895,6 +942,7 @@ following permissions are restricted:
895942
[`--allow-fs-read`][], [`--allow-fs-write`][] flags
896943
* Child Process - manageable through [`--allow-child-process`][] flag
897944
* Worker Threads - manageable through [`--allow-worker`][] flag
945+
* WASI - manageable through [`--allow-wasi`][] flag
898946

899947
### `--experimental-policy`
900948

@@ -2533,6 +2581,7 @@ one is included in the list below.
25332581
* `--allow-child-process`
25342582
* `--allow-fs-read`
25352583
* `--allow-fs-write`
2584+
* `--allow-wasi`
25362585
* `--allow-worker`
25372586
* `--conditions`, `-C`
25382587
* `--diagnostic-dir`
@@ -3040,6 +3089,7 @@ done
30403089
[`--allow-child-process`]: #--allow-child-process
30413090
[`--allow-fs-read`]: #--allow-fs-read
30423091
[`--allow-fs-write`]: #--allow-fs-write
3092+
[`--allow-wasi`]: #--allow-wasi
30433093
[`--allow-worker`]: #--allow-worker
30443094
[`--build-snapshot`]: #--build-snapshot
30453095
[`--cpu-prof-dir`]: #--cpu-prof-dir

doc/api/permissions.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ flag.
482482

483483
When starting Node.js with `--experimental-permission`,
484484
the ability to access the file system through the `fs` module, spawn processes,
485-
use `node:worker_threads`, native addons, and enable the runtime inspector
485+
use `node:worker_threads`, use native addons, use WASI, and enable the runtime inspector
486486
will be restricted.
487487

488488
```console
@@ -507,7 +507,7 @@ Allowing access to spawning a process and creating worker threads can be done
507507
using the [`--allow-child-process`][] and [`--allow-worker`][] respectively.
508508

509509
To allow native addons when using permission model, use the [`--allow-addons`][]
510-
flag.
510+
flag. For WASI, use the [`--allow-wasi`][] flag.
511511

512512
#### Runtime API
513513

@@ -574,6 +574,7 @@ There are constraints you need to know before using this system:
574574
* Worker Threads
575575
* Inspector protocol
576576
* File system access
577+
* WASI
577578
* The Permission Model is initialized after the Node.js environment is set up.
578579
However, certain flags such as `--env-file` or `--openssl-config` are designed
579580
to read files before environment initialization. As a result, such flags are
@@ -597,6 +598,7 @@ There are constraints you need to know before using this system:
597598
[`--allow-child-process`]: cli.md#--allow-child-process
598599
[`--allow-fs-read`]: cli.md#--allow-fs-read
599600
[`--allow-fs-write`]: cli.md#--allow-fs-write
601+
[`--allow-wasi`]: cli.md#--allow-wasi
600602
[`--allow-worker`]: cli.md#--allow-worker
601603
[`--experimental-permission`]: cli.md#--experimental-permission
602604
[`permission.has()`]: process.md#processpermissionhasscope-reference

doc/node.1

+3
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ Allow using native addons when using the permission model.
8888
.It Fl -allow-child-process
8989
Allow spawning process when using the permission model.
9090
.
91+
.It Fl -allow-wasi
92+
Allow execution of WASI when using the permission model.
93+
.
9194
.It Fl -allow-worker
9295
Allow creating worker threads when using the permission model.
9396
.

lib/internal/process/pre_execution.js

+2
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,7 @@ function initializePermission() {
575575
const warnFlags = [
576576
'--allow-addons',
577577
'--allow-child-process',
578+
'--allow-wasi',
578579
'--allow-worker',
579580
];
580581
for (const flag of warnFlags) {
@@ -616,6 +617,7 @@ function initializePermission() {
616617
'--allow-fs-write',
617618
'--allow-addons',
618619
'--allow-child-process',
620+
'--allow-wasi',
619621
'--allow-worker',
620622
];
621623
ArrayPrototypeForEach(availablePermissionFlags, (flag) => {

node.gyp

+2
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
'src/permission/fs_permission.cc',
150150
'src/permission/inspector_permission.cc',
151151
'src/permission/permission.cc',
152+
'src/permission/wasi_permission.cc',
152153
'src/permission/worker_permission.cc',
153154
'src/pipe_wrap.cc',
154155
'src/process_wrap.cc',
@@ -271,6 +272,7 @@
271272
'src/permission/fs_permission.h',
272273
'src/permission/inspector_permission.h',
273274
'src/permission/permission.h',
275+
'src/permission/wasi_permission.h',
274276
'src/permission/worker_permission.h',
275277
'src/pipe_wrap.h',
276278
'src/req_wrap.h',

src/env.cc

+3
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,9 @@ Environment::Environment(IsolateData* isolate_data,
919919
permission()->Apply(
920920
this, {"*"}, permission::PermissionScope::kWorkerThreads);
921921
}
922+
if (!options_->allow_wasi) {
923+
permission()->Apply(this, {"*"}, permission::PermissionScope::kWASI);
924+
}
922925

923926
if (!options_->allow_fs_read.empty()) {
924927
permission()->Apply(this,

src/node_options.cc

+4
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
467467
"allow use of child process when any permissions are set",
468468
&EnvironmentOptions::allow_child_process,
469469
kAllowedInEnvvar);
470+
AddOption("--allow-wasi",
471+
"allow wasi when any permissions are set",
472+
&EnvironmentOptions::allow_wasi,
473+
kAllowedInEnvvar);
470474
AddOption("--allow-worker",
471475
"allow worker threads when any permissions are set",
472476
&EnvironmentOptions::allow_worker_threads,

src/node_options.h

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ class EnvironmentOptions : public Options {
130130
std::vector<std::string> allow_fs_write;
131131
bool allow_addons = false;
132132
bool allow_child_process = false;
133+
bool allow_wasi = false;
133134
bool allow_worker_threads = false;
134135
bool experimental_repl_await = true;
135136
bool experimental_vm_modules = false;

src/node_wasi.cc

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
#include "env-inl.h"
1+
#include "node_wasi.h"
22
#include "base_object-inl.h"
33
#include "debug_utils-inl.h"
4+
#include "env-inl.h"
45
#include "memory_tracker-inl.h"
5-
#include "node_mem-inl.h"
6-
#include "util-inl.h"
76
#include "node.h"
87
#include "node_errors.h"
8+
#include "node_mem-inl.h"
9+
#include "permission/permission.h"
10+
#include "util-inl.h"
911
#include "uv.h"
1012
#include "uvwasi.h"
11-
#include "node_wasi.h"
1213

1314
namespace node {
1415
namespace wasi {
@@ -120,8 +121,9 @@ void WASI::New(const FunctionCallbackInfo<Value>& args) {
120121
CHECK(args[1]->IsArray());
121122
CHECK(args[2]->IsArray());
122123
CHECK(args[3]->IsArray());
123-
124124
Environment* env = Environment::GetCurrent(args);
125+
THROW_IF_INSUFFICIENT_PERMISSIONS(
126+
env, permission::PermissionScope::kWASI, "");
125127
Local<Context> context = env->context();
126128
Local<Array> argv = args[0].As<Array>();
127129
const uint32_t argc = argv->Length();

src/permission/permission.cc

+5
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Permission::Permission() : enabled_(false) {
8282
std::make_shared<WorkerPermission>();
8383
std::shared_ptr<PermissionBase> inspector =
8484
std::make_shared<InspectorPermission>();
85+
std::shared_ptr<PermissionBase> wasi = std::make_shared<WASIPermission>();
8586
#define V(Name, _, __) \
8687
nodes_.insert(std::make_pair(PermissionScope::k##Name, fs));
8788
FILESYSTEM_PERMISSIONS(V)
@@ -98,6 +99,10 @@ Permission::Permission() : enabled_(false) {
9899
nodes_.insert(std::make_pair(PermissionScope::k##Name, inspector));
99100
INSPECTOR_PERMISSIONS(V)
100101
#undef V
102+
#define V(Name, _, __) \
103+
nodes_.insert(std::make_pair(PermissionScope::k##Name, wasi));
104+
WASI_PERMISSIONS(V)
105+
#undef V
101106
}
102107

103108
Local<Value> CreateAccessDeniedError(Environment* env,

src/permission/permission.h

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "permission/fs_permission.h"
1010
#include "permission/inspector_permission.h"
1111
#include "permission/permission_base.h"
12+
#include "permission/wasi_permission.h"
1213
#include "permission/worker_permission.h"
1314
#include "v8.h"
1415

src/permission/permission_base.h

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ namespace permission {
2121

2222
#define CHILD_PROCESS_PERMISSIONS(V) V(ChildProcess, "child", PermissionsRoot)
2323

24+
#define WASI_PERMISSIONS(V) V(WASI, "wasi", PermissionsRoot)
25+
2426
#define WORKER_THREADS_PERMISSIONS(V) \
2527
V(WorkerThreads, "worker", PermissionsRoot)
2628

@@ -29,6 +31,7 @@ namespace permission {
2931
#define PERMISSIONS(V) \
3032
FILESYSTEM_PERMISSIONS(V) \
3133
CHILD_PROCESS_PERMISSIONS(V) \
34+
WASI_PERMISSIONS(V) \
3235
WORKER_THREADS_PERMISSIONS(V) \
3336
INSPECTOR_PERMISSIONS(V)
3437

src/permission/wasi_permission.cc

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

src/permission/wasi_permission.h

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#ifndef SRC_PERMISSION_WASI_PERMISSION_H_
2+
#define SRC_PERMISSION_WASI_PERMISSION_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
#include <vector>
7+
#include "permission/permission_base.h"
8+
9+
namespace node {
10+
11+
namespace permission {
12+
13+
class WASIPermission final : public PermissionBase {
14+
public:
15+
void Apply(Environment* env,
16+
const std::vector<std::string>& allow,
17+
PermissionScope scope) override;
18+
bool is_granted(Environment* env,
19+
PermissionScope perm,
20+
const std::string_view& param = "") const override;
21+
22+
private:
23+
bool deny_all_;
24+
};
25+
26+
} // namespace permission
27+
28+
} // namespace node
29+
30+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
31+
#endif // SRC_PERMISSION_WASI_PERMISSION_H_
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Flags: --experimental-permission --allow-wasi --allow-fs-read=*
2+
'use strict';
3+
4+
const common = require('../common');
5+
common.skipIfWorker();
6+
7+
const assert = require('assert');
8+
const { WASI } = require('wasi');
9+
10+
// Guarantee the initial state
11+
{
12+
assert.ok(process.permission.has('wasi'));
13+
}
14+
15+
// When a permission is set by cli, the process shouldn't be able
16+
// to create WASI instance unless --allow-wasi is sent
17+
{
18+
// doesNotThrow
19+
new WASI({
20+
version: 'preview1',
21+
});
22+
}

test/parallel/test-permission-warning-flags.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const assert = require('assert');
77
const warnFlags = [
88
'--allow-addons',
99
'--allow-child-process',
10+
'--allow-wasi',
1011
'--allow-worker',
1112
];
1213

test/parallel/test-permission-wasi.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Flags: --experimental-permission --allow-fs-read=*
2+
'use strict';
3+
4+
const common = require('../common');
5+
const assert = require('node:assert');
6+
7+
const { WASI } = require('wasi');
8+
9+
{
10+
assert.throws(() => {
11+
new WASI({
12+
version: 'preview1',
13+
preopens: { '/': '/' },
14+
});
15+
}, common.expectsError({
16+
code: 'ERR_ACCESS_DENIED',
17+
permission: 'WASI',
18+
}));
19+
}

0 commit comments

Comments
 (0)