vm: harden module type checks
Check if the value returned from user linker function is a null-ish value. `validateInternalField` should be preferred when checking `this` argument to guard against null-ish `this`. Co-authored-by: Mike Ralphson <mike.ralphson@gmail.com> PR-URL: https://github.com/nodejs/node/pull/52162 Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
This commit is contained in:
parent
0b676736a0
commit
d1d5da22e4
@ -9,6 +9,7 @@ const {
|
||||
ObjectDefineProperty,
|
||||
ObjectFreeze,
|
||||
ObjectGetPrototypeOf,
|
||||
ObjectPrototypeHasOwnProperty,
|
||||
ObjectSetPrototypeOf,
|
||||
ReflectApply,
|
||||
SafePromiseAllReturnVoid,
|
||||
@ -44,6 +45,7 @@ const {
|
||||
validateObject,
|
||||
validateUint32,
|
||||
validateString,
|
||||
validateInternalField,
|
||||
} = require('internal/validators');
|
||||
|
||||
const binding = internalBinding('module_wrap');
|
||||
@ -76,6 +78,13 @@ const kLink = Symbol('kLink');
|
||||
|
||||
const { isContext } = require('internal/vm');
|
||||
|
||||
function isModule(object) {
|
||||
if (typeof object !== 'object' || object === null || !ObjectPrototypeHasOwnProperty(object, kWrap)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class Module {
|
||||
constructor(options) {
|
||||
emitExperimentalWarning('VM Modules');
|
||||
@ -149,23 +158,17 @@ class Module {
|
||||
}
|
||||
|
||||
get identifier() {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
validateInternalField(this, kWrap, 'Module');
|
||||
return this[kWrap].url;
|
||||
}
|
||||
|
||||
get context() {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
validateInternalField(this, kWrap, 'Module');
|
||||
return this[kContext];
|
||||
}
|
||||
|
||||
get namespace() {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
validateInternalField(this, kWrap, 'Module');
|
||||
if (this[kWrap].getStatus() < kInstantiated) {
|
||||
throw new ERR_VM_MODULE_STATUS('must not be unlinked or linking');
|
||||
}
|
||||
@ -173,16 +176,12 @@ class Module {
|
||||
}
|
||||
|
||||
get status() {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
validateInternalField(this, kWrap, 'Module');
|
||||
return STATUS_MAP[this[kWrap].getStatus()];
|
||||
}
|
||||
|
||||
get error() {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
validateInternalField(this, kWrap, 'Module');
|
||||
if (this[kWrap].getStatus() !== kErrored) {
|
||||
throw new ERR_VM_MODULE_STATUS('must be errored');
|
||||
}
|
||||
@ -190,9 +189,7 @@ class Module {
|
||||
}
|
||||
|
||||
async link(linker) {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
validateInternalField(this, kWrap, 'Module');
|
||||
validateFunction(linker, 'linker');
|
||||
if (this.status === 'linked') {
|
||||
throw new ERR_VM_MODULE_ALREADY_LINKED();
|
||||
@ -205,10 +202,7 @@ class Module {
|
||||
}
|
||||
|
||||
async evaluate(options = kEmptyObject) {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
|
||||
validateInternalField(this, kWrap, 'Module');
|
||||
validateObject(options, 'options');
|
||||
|
||||
let timeout = options.timeout;
|
||||
@ -231,9 +225,7 @@ class Module {
|
||||
}
|
||||
|
||||
[customInspectSymbol](depth, options) {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
validateInternalField(this, kWrap, 'Module');
|
||||
if (typeof depth === 'number' && depth < 0)
|
||||
return this;
|
||||
|
||||
@ -308,7 +300,7 @@ class SourceTextModule extends Module {
|
||||
|
||||
const promises = this[kWrap].link(async (identifier, attributes) => {
|
||||
const module = await linker(identifier, this, { attributes, assert: attributes });
|
||||
if (module[kWrap] === undefined) {
|
||||
if (!isModule(module)) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
if (module.context !== this.context) {
|
||||
@ -339,17 +331,13 @@ class SourceTextModule extends Module {
|
||||
}
|
||||
|
||||
get dependencySpecifiers() {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
|
||||
this[kDependencySpecifiers] ??= ObjectFreeze(this[kWrap].getStaticDependencySpecifiers());
|
||||
return this[kDependencySpecifiers];
|
||||
}
|
||||
|
||||
get status() {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
|
||||
if (this.#error !== kNoError) {
|
||||
return 'errored';
|
||||
}
|
||||
@ -360,9 +348,7 @@ class SourceTextModule extends Module {
|
||||
}
|
||||
|
||||
get error() {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
|
||||
if (this.#error !== kNoError) {
|
||||
return this.#error;
|
||||
}
|
||||
@ -415,9 +401,7 @@ class SyntheticModule extends Module {
|
||||
}
|
||||
|
||||
setExport(name, value) {
|
||||
if (this[kWrap] === undefined) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
validateInternalField(this, kWrap, 'SyntheticModule');
|
||||
validateString(name, 'name');
|
||||
if (this[kWrap].getStatus() < kInstantiated) {
|
||||
throw new ERR_VM_MODULE_STATUS('must be linked');
|
||||
@ -432,7 +416,7 @@ function importModuleDynamicallyWrap(importModuleDynamically) {
|
||||
if (isModuleNamespaceObject(m)) {
|
||||
return m;
|
||||
}
|
||||
if (!m || m[kWrap] === undefined) {
|
||||
if (!isModule(m)) {
|
||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
if (m.status === 'errored') {
|
||||
|
@ -84,13 +84,15 @@ const util = require('util');
|
||||
|
||||
assert.strictEqual(util.inspect(m, { depth: -1 }), '[SourceTextModule]');
|
||||
|
||||
assert.throws(
|
||||
() => m[util.inspect.custom].call({ __proto__: null }),
|
||||
{
|
||||
code: 'ERR_VM_MODULE_NOT_MODULE',
|
||||
message: 'Provided module is not an instance of Module'
|
||||
},
|
||||
);
|
||||
for (const value of [null, { __proto__: null }, SourceTextModule.prototype]) {
|
||||
assert.throws(
|
||||
() => m[util.inspect.custom].call(value),
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /The "this" argument must be an instance of Module/,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -216,8 +216,8 @@ async function checkInvalidOptionForEvaluate() {
|
||||
await assert.rejects(async () => {
|
||||
await Module.prototype[method]();
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_NOT_MODULE',
|
||||
message: /Provided module is not an instance of Module/
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /The "this" argument must be an instance of Module/
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -241,8 +241,8 @@ function checkInvalidCachedData() {
|
||||
|
||||
function checkGettersErrors() {
|
||||
const expectedError = {
|
||||
code: 'ERR_VM_MODULE_NOT_MODULE',
|
||||
message: /Provided module is not an instance of Module/
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /The "this" argument must be an instance of (?:Module|SourceTextModule)/,
|
||||
};
|
||||
const getters = ['identifier', 'context', 'namespace', 'status', 'error'];
|
||||
getters.forEach((getter) => {
|
||||
|
@ -28,6 +28,22 @@ async function simple() {
|
||||
delete globalThis.fiveResult;
|
||||
}
|
||||
|
||||
async function invalidLinkValue() {
|
||||
const invalidValues = [
|
||||
undefined,
|
||||
null,
|
||||
{},
|
||||
SourceTextModule.prototype,
|
||||
];
|
||||
|
||||
for (const value of invalidValues) {
|
||||
const module = new SourceTextModule('import "foo"');
|
||||
await assert.rejects(module.link(() => value), {
|
||||
code: 'ERR_VM_MODULE_NOT_MODULE',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function depth() {
|
||||
const foo = new SourceTextModule('export default 5');
|
||||
await foo.link(common.mustNotCall());
|
||||
@ -143,6 +159,7 @@ const finished = common.mustCall();
|
||||
|
||||
(async function main() {
|
||||
await simple();
|
||||
await invalidLinkValue();
|
||||
await depth();
|
||||
await circular();
|
||||
await circular2();
|
||||
|
@ -66,12 +66,12 @@ const assert = require('assert');
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
for (const value of [null, {}, SyntheticModule.prototype]) {
|
||||
assert.throws(() => {
|
||||
SyntheticModule.prototype.setExport.call({}, 'foo');
|
||||
SyntheticModule.prototype.setExport.call(value, 'foo');
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_NOT_MODULE',
|
||||
message: /Provided module is not an instance of Module/
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /The "this" argument must be an instance of SyntheticModule/
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user