Skip to content

Commit 207aae4

Browse files
committed
src: register the --allow-fs-vfs permission flag
Mounting a VFS under the permission model is refused with a message telling the user to pass --allow-fs-vfs, but that option was never registered, so `node --allow-fs-vfs` failed with "bad option" and a virtual file system could not be mounted under --permission at all. Register it alongside the other permission flags and document it. Signed-off-by: Philipp Dunkel <pip@pipobscure.com>
1 parent 19b0f03 commit 207aae4

5 files changed

Lines changed: 81 additions & 0 deletions

File tree

doc/api/cli.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,26 @@ process.permission.has('fs.read', 'custom-require.js'); // true
268268
process.permission.has('fs.read', 'custom-require-2.js'); // true
269269
```
270270

271+
### `--allow-fs-vfs`
272+
273+
<!-- YAML
274+
added: REPLACEME
275+
-->
276+
277+
> Stability: 1.1 - Active development
278+
279+
When using the [Permission Model][], a [virtual file system][] cannot be
280+
mounted by default: [`vfs.mount()`][] throws `ERR_INVALID_STATE` unless the
281+
user explicitly passes the `--allow-fs-vfs` flag when starting Node.js.
282+
283+
A mounted VFS serves paths that the file system permissions do not describe,
284+
so mounting one is gated on its own flag rather than on `--allow-fs-read` or
285+
`--allow-fs-write`.
286+
287+
```console
288+
$ node --experimental-vfs --permission --allow-fs-vfs app.js
289+
```
290+
271291
### `--allow-fs-write`
272292

273293
<!-- YAML
@@ -3878,6 +3898,7 @@ one is included in the list below.
38783898
* `--allow-child-process`
38793899
* `--allow-ffi`
38803900
* `--allow-fs-read`
3901+
* `--allow-fs-vfs`
38813902
* `--allow-fs-write`
38823903
* `--allow-inspector`
38833904
* `--allow-net`
@@ -4589,6 +4610,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
45894610
[`v8.startupSnapshot.addDeserializeCallback()`]: v8.md#v8startupsnapshotadddeserializecallbackcallback-data
45904611
[`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data
45914612
[`v8.startupSnapshot` API]: v8.md#startup-snapshot-api
4613+
[`vfs.mount()`]: vfs.md#vfsmount
45924614
[asynchronous module customization hooks]: module.md#asynchronous-customization-hooks
45934615
[captured by the built-in snapshot of Node.js]: https://github.com/nodejs/node/blob/b19525a33cc84033af4addd0f80acd4dc33ce0cf/test/parallel/test-bootstrap-modules.js#L24
45944616
[collecting code coverage from tests]: test.md#collecting-code-coverage
@@ -4620,4 +4642,5 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
46204642
[test runner execution model]: test.md#test-runner-execution-model
46214643
[timezone IDs]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
46224644
[tracking issue for user-land snapshots]: https://github.com/nodejs/node/issues/44014
4645+
[virtual file system]: vfs.md
46234646
[ways that `TZ` is handled in other environments]: https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html

doc/node.1

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,17 @@ process.permission.has('fs.read', 'custom-require.js'); // true
166166
process.permission.has('fs.read', 'custom-require-2.js'); // true
167167
.Ed
168168
.
169+
.It Fl -allow-fs-vfs
170+
When using the Permission Model, a virtual file system cannot be
171+
mounted by default: \fBvfs.mount()\fR throws \fBERR_INVALID_STATE\fR unless the
172+
user explicitly passes the \fB--allow-fs-vfs\fR flag when starting Node.js.
173+
A mounted VFS serves paths that the file system permissions do not describe,
174+
so mounting one is gated on its own flag rather than on \fB--allow-fs-read\fR or
175+
\fB--allow-fs-write\fR.
176+
.Bd -literal
177+
$ node --experimental-vfs --permission --allow-fs-vfs app.js
178+
.Ed
179+
.
169180
.It Fl -allow-fs-write
170181
This flag configures file system write permissions using
171182
the Permission Model.
@@ -1944,6 +1955,8 @@ one is included in the list below.
19441955
.It
19451956
\fB--allow-fs-read\fR
19461957
.It
1958+
\fB--allow-fs-vfs\fR
1959+
.It
19471960
\fB--allow-fs-write\fR
19481961
.It
19491962
\fB--allow-inspector\fR

src/node_options.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,12 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
687687
&EnvironmentOptions::allow_fs_write,
688688
kAllowedInEnvvar,
689689
OptionNamespaces::kPermissionNamespace);
690+
AddOption("--allow-fs-vfs",
691+
"allow mounting a virtual file system when any permissions are set",
692+
BOOL_FIELD(allow_fs_vfs),
693+
kAllowedInEnvvar,
694+
false,
695+
OptionNamespaces::kPermissionNamespace);
690696
AddOption("--allow-addons",
691697
"allow use of addons when any permissions are set",
692698
BOOL_FIELD(allow_addons),

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ class EnvironmentOptions : public Options {
219219
DEFINE_BOOL_FIELD(permission) = false;
220220
DEFINE_BOOL_FIELD(permission_audit) = false;
221221
DEFINE_BOOL_FIELD(allow_addons) = false;
222+
DEFINE_BOOL_FIELD(allow_fs_vfs) = false;
222223
DEFINE_BOOL_FIELD(allow_inspector) = false;
223224
DEFINE_BOOL_FIELD(allow_child_process) = false;
224225
DEFINE_BOOL_FIELD(allow_net) = false;

test/parallel/test-permission-dlopen-binary.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,41 @@ function run(...permissions) {
7373
assert.strictEqual(res.status, 0, res.stderr);
7474
assert.match(res.stdout, /LOADED world/);
7575
}
76+
77+
// The same, reached the way a program actually gets here: an addon inside a
78+
// mounted VFS. Mounting one under the permission model needs --allow-fs-vfs.
79+
{
80+
const mounted = `
81+
const fs = require('fs');
82+
const vfs = require('node:vfs');
83+
const myVfs = vfs.create();
84+
myVfs.writeFileSync('/binding.node',
85+
fs.readFileSync(${JSON.stringify(addonPath)}));
86+
const mountPoint = myVfs.mount();
87+
try {
88+
console.log('LOADED', require(mountPoint + '/binding.node').hello());
89+
} catch (err) {
90+
console.log('DENIED', err.code, err.permission ?? '');
91+
}
92+
`;
93+
const vfsRun = (...permissions) => spawnSync(
94+
process.execPath,
95+
['--experimental-vfs', '--permission', '--allow-fs-read=*',
96+
...permissions, '-e', mounted],
97+
{ encoding: 'utf8' });
98+
99+
const ok = vfsRun('--allow-fs-vfs', '--allow-addons', '--allow-fs-write=*');
100+
assert.strictEqual(ok.status, 0, ok.stderr);
101+
assert.match(ok.stdout, /LOADED world/);
102+
103+
// Mounting itself is refused without --allow-fs-vfs.
104+
const noVfs = vfsRun('--allow-addons', '--allow-fs-write=*');
105+
assert.notStrictEqual(noVfs.status, 0);
106+
assert.match(noVfs.stderr, /ERR_INVALID_STATE/);
107+
assert.match(noVfs.stderr, /--allow-fs-vfs/);
108+
109+
// Mounted, addons allowed, but no write access: the addon is refused.
110+
const noWrite = vfsRun('--allow-fs-vfs', '--allow-addons');
111+
assert.strictEqual(noWrite.status, 0, noWrite.stderr);
112+
assert.match(noWrite.stdout, /DENIED ERR_ACCESS_DENIED FileSystemWrite/);
113+
}

0 commit comments

Comments
 (0)