mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00
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: https://github.com/nodejs/node/pull/55797 Backport-PR-URL: https://github.com/nodejs/node/pull/58185 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com> Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>
This commit is contained in:
parent
c134103aaf
commit
7f8d1b2681
11 changed files with 109 additions and 23 deletions
|
@ -217,15 +217,8 @@ The initializer module also needs to be allowed. Consider the following example:
|
|||
|
||||
```console
|
||||
$ node --experimental-permission t.js
|
||||
node:internal/modules/cjs/loader:162
|
||||
const result = internalModuleStat(filename);
|
||||
^
|
||||
|
||||
Error: Access to this API has been restricted
|
||||
at stat (node:internal/modules/cjs/loader:162:18)
|
||||
at Module._findPath (node:internal/modules/cjs/loader:640:16)
|
||||
at resolveMainPath (node:internal/modules/run_main:15:25)
|
||||
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:53:24)
|
||||
at node:internal/main/run_main_module:23:47 {
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
|
|
|
@ -500,15 +500,8 @@ will be restricted.
|
|||
|
||||
```console
|
||||
$ node --experimental-permission index.js
|
||||
node:internal/modules/cjs/loader:171
|
||||
const result = internalModuleStat(filename);
|
||||
^
|
||||
|
||||
Error: Access to this API has been restricted
|
||||
at stat (node:internal/modules/cjs/loader:171:18)
|
||||
at Module._findPath (node:internal/modules/cjs/loader:627:16)
|
||||
at resolveMainPath (node:internal/modules/run_main:19:25)
|
||||
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:24)
|
||||
at node:internal/main/run_main_module:23:47 {
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
|
|
|
@ -164,7 +164,6 @@ const policy = getLazy(
|
|||
);
|
||||
const shouldReportRequiredModules = getLazy(() => process.env.WATCH_REPORT_DEPENDENCIES);
|
||||
|
||||
const permission = require('internal/process/permission');
|
||||
const {
|
||||
vm_dynamic_import_default_internal,
|
||||
} = internalBinding('symbols');
|
||||
|
@ -704,11 +703,8 @@ Module._findPath = function(request, paths, isMain) {
|
|||
// For each path
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
// Don't search further if path doesn't exist
|
||||
// or doesn't have permission to it
|
||||
const curPath = paths[i];
|
||||
if (insidePath && curPath &&
|
||||
((permission.isEnabled() && !permission.has('fs.read', curPath)) || _stat(curPath) < 1)
|
||||
) {
|
||||
if (insidePath && curPath && _stat(curPath) < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -1045,6 +1045,8 @@ static void ExistsSync(const FunctionCallbackInfo<Value>& args) {
|
|||
}
|
||||
|
||||
// Used to speed up module loading. Returns an array [string, boolean]
|
||||
// Do not expose this function through public API as it doesn't hold
|
||||
// Permission Model checks.
|
||||
static void InternalModuleReadJSON(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Isolate* isolate = env->isolate();
|
||||
|
@ -1052,8 +1054,6 @@ static void InternalModuleReadJSON(const FunctionCallbackInfo<Value>& args) {
|
|||
|
||||
CHECK(args[0]->IsString());
|
||||
node::Utf8Value path(isolate, args[0]);
|
||||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||||
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
|
||||
|
||||
if (strlen(*path) != path.length()) {
|
||||
args.GetReturnValue().Set(Array::New(isolate));
|
||||
|
@ -1149,8 +1149,6 @@ static void InternalModuleStat(const FunctionCallbackInfo<Value>& args) {
|
|||
|
||||
CHECK(args[0]->IsString());
|
||||
node::Utf8Value path(env->isolate(), args[0]);
|
||||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||||
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
|
||||
|
||||
uv_fs_t req;
|
||||
int rc = uv_fs_stat(env->event_loop(), &req, *path, nullptr);
|
||||
|
|
7
test/fixtures/permission/fs-read.js
vendored
7
test/fixtures/permission/fs-read.js
vendored
|
@ -281,6 +281,13 @@ const regularFile = __filename;
|
|||
permission: 'FileSystemRead',
|
||||
// resource: path.toNamespacedPath(blockedFolder),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.readdirSync(blockedFolder, { recursive: true });
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(blockedFolder),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.readdirSync(blockedFolder);
|
||||
}, common.expectsError({
|
||||
|
|
1
test/fixtures/permission/main-module.js
vendored
Normal file
1
test/fixtures/permission/main-module.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
require('./required-module');
|
1
test/fixtures/permission/main-module.mjs
vendored
Normal file
1
test/fixtures/permission/main-module.mjs
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import './required-module.mjs';
|
1
test/fixtures/permission/required-module.js
vendored
Normal file
1
test/fixtures/permission/required-module.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
console.log('ok');
|
1
test/fixtures/permission/required-module.mjs
vendored
Normal file
1
test/fixtures/permission/required-module.mjs
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
console.log('ok');
|
19
test/parallel/test-permission-fs-internal-module-stat.js
Normal file
19
test/parallel/test-permission-fs-internal-module-stat.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Flags: --expose-internals --experimental-permission --allow-fs-read=test/common* --allow-fs-read=tools* --allow-fs-read=test/parallel* --allow-child-process
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('no crypto');
|
||||
}
|
||||
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md');
|
||||
const internalFsBinding = internalBinding('fs');
|
||||
|
||||
{
|
||||
internalFsBinding.internalModuleStat(blockedFile);
|
||||
}
|
76
test/parallel/test-permission-fs-require.js
Normal file
76
test/parallel/test-permission-fs-require.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
// Flags: --experimental-permission --allow-fs-read=* --allow-child-process
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const assert = require('node:assert');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
|
||||
{
|
||||
const mainModule = fixtures.path('permission', 'main-module.js');
|
||||
const requiredModule = fixtures.path('permission', 'required-module.js');
|
||||
const { status, stdout, stderr } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--experimental-permission',
|
||||
'--allow-fs-read', mainModule,
|
||||
'--allow-fs-read', requiredModule,
|
||||
mainModule,
|
||||
]
|
||||
);
|
||||
|
||||
assert.strictEqual(status, 0, stderr.toString());
|
||||
assert.strictEqual(stdout.toString(), 'ok\n');
|
||||
}
|
||||
|
||||
{
|
||||
// When required module is not passed as allowed path
|
||||
const mainModule = fixtures.path('permission', 'main-module.js');
|
||||
const { status, stderr } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--experimental-permission',
|
||||
'--allow-fs-read', mainModule,
|
||||
mainModule,
|
||||
]
|
||||
);
|
||||
|
||||
assert.strictEqual(status, 1, stderr.toString());
|
||||
assert.match(stderr.toString(), /Error: Access to this API has been restricted/);
|
||||
}
|
||||
|
||||
{
|
||||
// ESM loader test
|
||||
const mainModule = fixtures.path('permission', 'main-module.mjs');
|
||||
const requiredModule = fixtures.path('permission', 'required-module.mjs');
|
||||
const { status, stdout, stderr } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--experimental-permission',
|
||||
'--allow-fs-read', mainModule,
|
||||
'--allow-fs-read', requiredModule,
|
||||
mainModule,
|
||||
]
|
||||
);
|
||||
|
||||
assert.strictEqual(status, 0, stderr.toString());
|
||||
assert.strictEqual(stdout.toString(), 'ok\n');
|
||||
}
|
||||
|
||||
{
|
||||
// When required module is not passed as allowed path (ESM)
|
||||
const mainModule = fixtures.path('permission', 'main-module.mjs');
|
||||
const { status, stderr } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--experimental-permission',
|
||||
'--allow-fs-read', mainModule,
|
||||
mainModule,
|
||||
]
|
||||
);
|
||||
|
||||
assert.strictEqual(status, 1, stderr.toString());
|
||||
assert.match(stderr.toString(), /Error: Access to this API has been restricted/);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue