+ fixes policies can be bypassed via Module._load + fixes policies can be bypassed by module.constructor.createRequire (CVE-2023-32002, CVE-2023-32006, bsc#1214150, bsc#1214156) * CVE-2023-32559.patch: Policies can be bypassed via process.binding (CVE-2023-32559, bsc#1214154) OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs14?expand=0&rev=108
188 lines
6.5 KiB
Diff
188 lines
6.5 KiB
Diff
commit d8ccfe9ad4dce9da900cff9dd2b934dfa3600b8b
|
|
Author: RafaelGSS <rafael.nunu@hotmail.com>
|
|
Date: Mon May 29 19:45:33 2023 -0300
|
|
|
|
policy: handle Module.constructor and main.extensions bypass
|
|
|
|
Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>
|
|
PR-URL: https://github.com/nodejs-private/node-private/pull/445
|
|
Refs: https://hackerone.com/bugs?subject=nodejs&report_id=1960870
|
|
Refs: https://hackerone.com/bugs?subject=nodejs&report_id=2043807
|
|
CVE-ID: CVE-2023-32002,CVE-2023-32006
|
|
|
|
Index: node-v14.21.3/lib/internal/modules/cjs/helpers.js
|
|
===================================================================
|
|
--- node-v14.21.3.orig/lib/internal/modules/cjs/helpers.js
|
|
+++ node-v14.21.3/lib/internal/modules/cjs/helpers.js
|
|
@@ -13,6 +13,7 @@ const {
|
|
StringPrototypeStartsWith,
|
|
} = primordials;
|
|
const {
|
|
+ ERR_INVALID_ARG_TYPE,
|
|
ERR_MANIFEST_DEPENDENCY_MISSING,
|
|
ERR_UNKNOWN_BUILTIN_MODULE
|
|
} = require('internal/errors').codes;
|
|
@@ -55,12 +56,22 @@ function loadNativeModule(filename, requ
|
|
}
|
|
}
|
|
|
|
+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) {
|
|
Index: node-v14.21.3/lib/internal/modules/cjs/loader.js
|
|
===================================================================
|
|
--- node-v14.21.3.orig/lib/internal/modules/cjs/loader.js
|
|
+++ node-v14.21.3/lib/internal/modules/cjs/loader.js
|
|
@@ -147,8 +147,8 @@ const isWindows = process.platform === '
|
|
const relativeResolveCache = ObjectCreate(null);
|
|
|
|
let requireDepth = 0;
|
|
-let statCache = null;
|
|
let isPreloading = false;
|
|
+let statCache = null;
|
|
|
|
function internalRequire(module, id) {
|
|
validateString(id, 'id');
|
|
@@ -1284,5 +1284,14 @@ Module.syncBuiltinESMExports = function
|
|
}
|
|
};
|
|
|
|
+ObjectDefineProperty(Module.prototype, 'constructor', {
|
|
+ __proto__: null,
|
|
+ get: function() {
|
|
+ return policy ? undefined : Module;
|
|
+ },
|
|
+ configurable: false,
|
|
+ enumerable: false,
|
|
+});
|
|
+
|
|
// Backwards compatibility
|
|
Module.Module = Module;
|
|
Index: node-v14.21.3/test/fixtures/policy-manifest/createRequire-bypass.js
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ node-v14.21.3/test/fixtures/policy-manifest/createRequire-bypass.js
|
|
@@ -0,0 +1,2 @@
|
|
+const os = module.constructor.createRequire('file:///os-access-module.js')('os')
|
|
+os.cpus()
|
|
\ No newline at end of file
|
|
Index: node-v14.21.3/test/fixtures/policy-manifest/main-constructor-bypass.js
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ node-v14.21.3/test/fixtures/policy-manifest/main-constructor-bypass.js
|
|
@@ -0,0 +1,2 @@
|
|
+const m = new require.main.constructor();
|
|
+m.require('./invalid-module')
|
|
Index: node-v14.21.3/test/fixtures/policy-manifest/main-constructor-extensions-bypass.js
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ node-v14.21.3/test/fixtures/policy-manifest/main-constructor-extensions-bypass.js
|
|
@@ -0,0 +1,2 @@
|
|
+const m = new require.main.constructor();
|
|
+require.extensions['.js'](m, './invalid-module')
|
|
Index: node-v14.21.3/test/fixtures/policy-manifest/manifest-impersonate.json
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ node-v14.21.3/test/fixtures/policy-manifest/manifest-impersonate.json
|
|
@@ -0,0 +1,13 @@
|
|
+{
|
|
+ "resources": {
|
|
+ "./createRequire-bypass.js": {
|
|
+ "integrity": true
|
|
+ },
|
|
+ "/os-access-module.js": {
|
|
+ "integrity": true,
|
|
+ "dependencies": {
|
|
+ "os": true
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
\ No newline at end of file
|
|
Index: node-v14.21.3/test/fixtures/policy-manifest/module-constructor-bypass.js
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ node-v14.21.3/test/fixtures/policy-manifest/module-constructor-bypass.js
|
|
@@ -0,0 +1 @@
|
|
+module.constructor._load('node:child_process');
|
|
Index: node-v14.21.3/test/parallel/test-policy-manifest.js
|
|
===================================================================
|
|
--- node-v14.21.3.orig/test/parallel/test-policy-manifest.js
|
|
+++ node-v14.21.3/test/parallel/test-policy-manifest.js
|
|
@@ -76,3 +76,58 @@ const fixtures = require('../common/fixt
|
|
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/);
|
|
+}
|