Skip to content

Commit

Permalink
policy: handle Module.constructor and main.extensions bypass
Browse files Browse the repository at this point in the history
  • Loading branch information
RafaelGSS committed Aug 8, 2023
1 parent d4570fa commit 15bced0
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 2 deletions.
13 changes: 12 additions & 1 deletion lib/internal/modules/cjs/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {
StringPrototypeStartsWith,
} = primordials;
const {
ERR_INVALID_ARG_TYPE,
ERR_MANIFEST_DEPENDENCY_MISSING,
ERR_UNKNOWN_BUILTIN_MODULE,
} = require('internal/errors').codes;
Expand Down Expand Up @@ -58,12 +59,22 @@ function loadBuiltinModule(filename, request) {
}
}

let $Module = null;
function lazyModule() {
$Module = $Module || require('internal/modules/cjs/loader').Module;
return $Module;
}

// Invoke with makeRequireFunction(module) where |module| is the Module object
// to use as the context for the require() function.
// Use redirects to set up a mapping from a policy and restrict dependencies
const urlToFileCache = new SafeMap();
function makeRequireFunction(mod, redirects) {
const Module = mod.constructor;
// lazy due to cycle
const Module = lazyModule();
if (mod instanceof Module !== true) {
throw new ERR_INVALID_ARG_TYPE('mod', 'Module', mod);
}

let require;
if (redirects) {
Expand Down
11 changes: 10 additions & 1 deletion lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ const isWindows = process.platform === 'win32';
const relativeResolveCache = ObjectCreate(null);

let requireDepth = 0;
let statCache = null;
let isPreloading = false;
let statCache = null;

function internalRequire(module, id) {
validateString(id, 'id');
Expand Down Expand Up @@ -1447,5 +1447,14 @@ Module.isBuiltin = function isBuiltin(moduleName) {
return allBuiltins.has(moduleName);
};

ObjectDefineProperty(Module.prototype, 'constructor', {
__proto__: null,
get: function() {
return policy ? undefined : Module;
},
configurable: false,
enumerable: false,
});

// Backwards compatibility
Module.Module = Module;
2 changes: 2 additions & 0 deletions test/fixtures/policy-manifest/createRequire-bypass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const os = module.constructor.createRequire('file:///os-access-module.js')('os')
os.cpus()
Empty file.
2 changes: 2 additions & 0 deletions test/fixtures/policy-manifest/main-constructor-bypass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const m = new require.main.constructor();
m.require('./invalid-module')
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const m = new require.main.constructor();
require.extensions['.js'](m, './invalid-module')
13 changes: 13 additions & 0 deletions test/fixtures/policy-manifest/manifest-impersonate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"resources": {
"./createRequire-bypass.js": {
"integrity": true
},
"/os-access-module.js": {
"integrity": true,
"dependencies": {
"os": true
}
}
}
}
1 change: 1 addition & 0 deletions test/fixtures/policy-manifest/module-constructor-bypass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.constructor._load('node:child_process');
55 changes: 55 additions & 0 deletions test/parallel/test-policy-manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,58 @@ const fixtures = require('../common/fixtures.js');
assert.match(stderr, /ERR_MANIFEST_DEPENDENCY_MISSING/);
assert.match(stderr, /does not list os as a dependency specifier for conditions: require, node, node-addons/);
}

{
const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
const mainModuleBypass = fixtures.path('policy-manifest', 'module-constructor-bypass.js');
const result = spawnSync(process.execPath, [
'--experimental-policy',
policyFilepath,
mainModuleBypass,
]);
assert.notStrictEqual(result.status, 0);
const stderr = result.stderr.toString();
assert.match(stderr, /TypeError/);
}

{
const policyFilepath = fixtures.path('policy-manifest', 'manifest-impersonate.json');
const createRequireBypass = fixtures.path('policy-manifest', 'createRequire-bypass.js');
const result = spawnSync(process.execPath, [
'--experimental-policy',
policyFilepath,
createRequireBypass,
]);

assert.notStrictEqual(result.status, 0);
const stderr = result.stderr.toString();
assert.match(stderr, /TypeError/);
}

{
const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
const mainModuleBypass = fixtures.path('policy-manifest', 'main-constructor-bypass.js');
const result = spawnSync(process.execPath, [
'--experimental-policy',
policyFilepath,
mainModuleBypass,
]);

assert.notStrictEqual(result.status, 0);
const stderr = result.stderr.toString();
assert.match(stderr, /TypeError/);
}

{
const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
const mainModuleBypass = fixtures.path('policy-manifest', 'main-constructor-extensions-bypass.js');
const result = spawnSync(process.execPath, [
'--experimental-policy',
policyFilepath,
mainModuleBypass,
]);

assert.notStrictEqual(result.status, 0);
const stderr = result.stderr.toString();
assert.match(stderr, /TypeError/);
}

0 comments on commit 15bced0

Please sign in to comment.