Skip to content

Commit 2a206d3

Browse files
committed
permission: ignore internalModuleStat on module loading
This improves Permission Model usage when allowing read access to specifi modules. To achieve that, the permission model check on internalModuleStat has been removed meaning that on module loading, uv_fs_stat is performed on files and folders even when the permission model is enabled. Although a uv_fs_stat is performed, reading/executing the module will still pass by the permission model check. Without this PR when an app tries to --allow-fs-read=./a.js --allow-fs-read=./b.js where `a` attempt to load b, it will fails as it reads $pwd and no permission has been given to this path. PR-URL: #55797 Backport-PR-URL: #58185 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Ulises Gascón <[email protected]>
1 parent 5357d0b commit 2a206d3

11 files changed

+556
-654
lines changed

doc/api/cli.md

+443-180
Large diffs are not rendered by default.

doc/api/permissions.md

+4-467
Large diffs are not rendered by default.

lib/internal/modules/cjs/loader.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ const policy = getLazy(
164164
);
165165
const shouldReportRequiredModules = getLazy(() => process.env.WATCH_REPORT_DEPENDENCIES);
166166

167-
const permission = require('internal/process/permission');
168167
const {
169168
vm_dynamic_import_default_internal,
170169
} = internalBinding('symbols');
@@ -704,11 +703,8 @@ Module._findPath = function(request, paths, isMain) {
704703
// For each path
705704
for (let i = 0; i < paths.length; i++) {
706705
// Don't search further if path doesn't exist
707-
// or doesn't have permission to it
708706
const curPath = paths[i];
709-
if (insidePath && curPath &&
710-
((permission.isEnabled() && !permission.has('fs.read', curPath)) || _stat(curPath) < 1)
711-
) {
707+
if (insidePath && curPath && _stat(curPath) < 1) {
712708
continue;
713709
}
714710

src/node_file.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -1045,15 +1045,15 @@ static void ExistsSync(const FunctionCallbackInfo<Value>& args) {
10451045
}
10461046

10471047
// Used to speed up module loading. Returns an array [string, boolean]
1048+
// Do not expose this function through public API as it doesn't hold
1049+
// Permission Model checks.
10481050
static void InternalModuleReadJSON(const FunctionCallbackInfo<Value>& args) {
10491051
Environment* env = Environment::GetCurrent(args);
10501052
Isolate* isolate = env->isolate();
10511053
uv_loop_t* loop = env->event_loop();
10521054

10531055
CHECK(args[0]->IsString());
10541056
node::Utf8Value path(isolate, args[0]);
1055-
THROW_IF_INSUFFICIENT_PERMISSIONS(
1056-
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
10571057

10581058
if (strlen(*path) != path.length()) {
10591059
args.GetReturnValue().Set(Array::New(isolate));

test/fixtures/permission/fs-read.js

+7
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,13 @@ const regularFile = __filename;
281281
permission: 'FileSystemRead',
282282
// resource: path.toNamespacedPath(blockedFolder),
283283
}));
284+
assert.throws(() => {
285+
fs.readdirSync(blockedFolder, { recursive: true });
286+
}, common.expectsError({
287+
code: 'ERR_ACCESS_DENIED',
288+
permission: 'FileSystemRead',
289+
resource: path.toNamespacedPath(blockedFolder),
290+
}));
284291
assert.throws(() => {
285292
fs.readdirSync(blockedFolder);
286293
}, common.expectsError({
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require('./required-module');
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './required-module.mjs';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('ok');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('ok');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Flags: --expose-internals --experimental-permission --allow-fs-read=test/common* --allow-fs-read=tools* --allow-fs-read=test/parallel* --allow-child-process
2+
'use strict';
3+
4+
const common = require('../common');
5+
common.skipIfWorker();
6+
7+
if (!common.hasCrypto) {
8+
common.skip('no crypto');
9+
}
10+
11+
const { internalBinding } = require('internal/test/binding');
12+
const fixtures = require('../common/fixtures');
13+
14+
const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md');
15+
const internalFsBinding = internalBinding('fs');
16+
17+
{
18+
internalFsBinding.internalModuleStat(internalFsBinding, blockedFile);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Flags: --experimental-permission --allow-fs-read=* --allow-child-process
2+
'use strict';
3+
4+
const common = require('../common');
5+
common.skipIfWorker();
6+
const fixtures = require('../common/fixtures');
7+
8+
const assert = require('node:assert');
9+
const { spawnSync } = require('node:child_process');
10+
11+
{
12+
const mainModule = fixtures.path('permission', 'main-module.js');
13+
const requiredModule = fixtures.path('permission', 'required-module.js');
14+
const { status, stdout, stderr } = spawnSync(
15+
process.execPath,
16+
[
17+
'--experimental-permission',
18+
'--allow-fs-read', mainModule,
19+
'--allow-fs-read', requiredModule,
20+
mainModule,
21+
]
22+
);
23+
24+
assert.strictEqual(status, 0, stderr.toString());
25+
assert.strictEqual(stdout.toString(), 'ok\n');
26+
}
27+
28+
{
29+
// When required module is not passed as allowed path
30+
const mainModule = fixtures.path('permission', 'main-module.js');
31+
const { status, stderr } = spawnSync(
32+
process.execPath,
33+
[
34+
'--experimental-permission',
35+
'--allow-fs-read', mainModule,
36+
mainModule,
37+
]
38+
);
39+
40+
assert.strictEqual(status, 1, stderr.toString());
41+
assert.match(stderr.toString(), /Error: Access to this API has been restricted/);
42+
}
43+
44+
{
45+
// ESM loader test
46+
const mainModule = fixtures.path('permission', 'main-module.mjs');
47+
const requiredModule = fixtures.path('permission', 'required-module.mjs');
48+
const { status, stdout, stderr } = spawnSync(
49+
process.execPath,
50+
[
51+
'--experimental-permission',
52+
'--allow-fs-read', mainModule,
53+
'--allow-fs-read', requiredModule,
54+
mainModule,
55+
]
56+
);
57+
58+
assert.strictEqual(status, 0, stderr.toString());
59+
assert.strictEqual(stdout.toString(), 'ok\n');
60+
}
61+
62+
{
63+
// When required module is not passed as allowed path (ESM)
64+
const mainModule = fixtures.path('permission', 'main-module.mjs');
65+
const { status, stderr } = spawnSync(
66+
process.execPath,
67+
[
68+
'--experimental-permission',
69+
'--allow-fs-read', mainModule,
70+
mainModule,
71+
]
72+
);
73+
74+
assert.strictEqual(status, 1, stderr.toString());
75+
assert.match(stderr.toString(), /Error: Access to this API has been restricted/);
76+
}

0 commit comments

Comments
 (0)