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,
|
ObjectDefineProperty,
|
||||||
ObjectFreeze,
|
ObjectFreeze,
|
||||||
ObjectGetPrototypeOf,
|
ObjectGetPrototypeOf,
|
||||||
|
ObjectPrototypeHasOwnProperty,
|
||||||
ObjectSetPrototypeOf,
|
ObjectSetPrototypeOf,
|
||||||
ReflectApply,
|
ReflectApply,
|
||||||
SafePromiseAllReturnVoid,
|
SafePromiseAllReturnVoid,
|
||||||
@ -44,6 +45,7 @@ const {
|
|||||||
validateObject,
|
validateObject,
|
||||||
validateUint32,
|
validateUint32,
|
||||||
validateString,
|
validateString,
|
||||||
|
validateInternalField,
|
||||||
} = require('internal/validators');
|
} = require('internal/validators');
|
||||||
|
|
||||||
const binding = internalBinding('module_wrap');
|
const binding = internalBinding('module_wrap');
|
||||||
@ -76,6 +78,13 @@ const kLink = Symbol('kLink');
|
|||||||
|
|
||||||
const { isContext } = require('internal/vm');
|
const { isContext } = require('internal/vm');
|
||||||
|
|
||||||
|
function isModule(object) {
|
||||||
|
if (typeof object !== 'object' || object === null || !ObjectPrototypeHasOwnProperty(object, kWrap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
class Module {
|
class Module {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
emitExperimentalWarning('VM Modules');
|
emitExperimentalWarning('VM Modules');
|
||||||
@ -149,23 +158,17 @@ class Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get identifier() {
|
get identifier() {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kWrap, 'Module');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
return this[kWrap].url;
|
return this[kWrap].url;
|
||||||
}
|
}
|
||||||
|
|
||||||
get context() {
|
get context() {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kWrap, 'Module');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
return this[kContext];
|
return this[kContext];
|
||||||
}
|
}
|
||||||
|
|
||||||
get namespace() {
|
get namespace() {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kWrap, 'Module');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
if (this[kWrap].getStatus() < kInstantiated) {
|
if (this[kWrap].getStatus() < kInstantiated) {
|
||||||
throw new ERR_VM_MODULE_STATUS('must not be unlinked or linking');
|
throw new ERR_VM_MODULE_STATUS('must not be unlinked or linking');
|
||||||
}
|
}
|
||||||
@ -173,16 +176,12 @@ class Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get status() {
|
get status() {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kWrap, 'Module');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
return STATUS_MAP[this[kWrap].getStatus()];
|
return STATUS_MAP[this[kWrap].getStatus()];
|
||||||
}
|
}
|
||||||
|
|
||||||
get error() {
|
get error() {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kWrap, 'Module');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
if (this[kWrap].getStatus() !== kErrored) {
|
if (this[kWrap].getStatus() !== kErrored) {
|
||||||
throw new ERR_VM_MODULE_STATUS('must be errored');
|
throw new ERR_VM_MODULE_STATUS('must be errored');
|
||||||
}
|
}
|
||||||
@ -190,9 +189,7 @@ class Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async link(linker) {
|
async link(linker) {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kWrap, 'Module');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
validateFunction(linker, 'linker');
|
validateFunction(linker, 'linker');
|
||||||
if (this.status === 'linked') {
|
if (this.status === 'linked') {
|
||||||
throw new ERR_VM_MODULE_ALREADY_LINKED();
|
throw new ERR_VM_MODULE_ALREADY_LINKED();
|
||||||
@ -205,10 +202,7 @@ class Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async evaluate(options = kEmptyObject) {
|
async evaluate(options = kEmptyObject) {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kWrap, 'Module');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
|
|
||||||
validateObject(options, 'options');
|
validateObject(options, 'options');
|
||||||
|
|
||||||
let timeout = options.timeout;
|
let timeout = options.timeout;
|
||||||
@ -231,9 +225,7 @@ class Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[customInspectSymbol](depth, options) {
|
[customInspectSymbol](depth, options) {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kWrap, 'Module');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
if (typeof depth === 'number' && depth < 0)
|
if (typeof depth === 'number' && depth < 0)
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
@ -308,7 +300,7 @@ class SourceTextModule extends Module {
|
|||||||
|
|
||||||
const promises = this[kWrap].link(async (identifier, attributes) => {
|
const promises = this[kWrap].link(async (identifier, attributes) => {
|
||||||
const module = await linker(identifier, this, { attributes, assert: attributes });
|
const module = await linker(identifier, this, { attributes, assert: attributes });
|
||||||
if (module[kWrap] === undefined) {
|
if (!isModule(module)) {
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||||
}
|
}
|
||||||
if (module.context !== this.context) {
|
if (module.context !== this.context) {
|
||||||
@ -339,17 +331,13 @@ class SourceTextModule extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get dependencySpecifiers() {
|
get dependencySpecifiers() {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
this[kDependencySpecifiers] ??= ObjectFreeze(this[kWrap].getStaticDependencySpecifiers());
|
this[kDependencySpecifiers] ??= ObjectFreeze(this[kWrap].getStaticDependencySpecifiers());
|
||||||
return this[kDependencySpecifiers];
|
return this[kDependencySpecifiers];
|
||||||
}
|
}
|
||||||
|
|
||||||
get status() {
|
get status() {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
if (this.#error !== kNoError) {
|
if (this.#error !== kNoError) {
|
||||||
return 'errored';
|
return 'errored';
|
||||||
}
|
}
|
||||||
@ -360,9 +348,7 @@ class SourceTextModule extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get error() {
|
get error() {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
if (this.#error !== kNoError) {
|
if (this.#error !== kNoError) {
|
||||||
return this.#error;
|
return this.#error;
|
||||||
}
|
}
|
||||||
@ -415,9 +401,7 @@ class SyntheticModule extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setExport(name, value) {
|
setExport(name, value) {
|
||||||
if (this[kWrap] === undefined) {
|
validateInternalField(this, kWrap, 'SyntheticModule');
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
||||||
}
|
|
||||||
validateString(name, 'name');
|
validateString(name, 'name');
|
||||||
if (this[kWrap].getStatus() < kInstantiated) {
|
if (this[kWrap].getStatus() < kInstantiated) {
|
||||||
throw new ERR_VM_MODULE_STATUS('must be linked');
|
throw new ERR_VM_MODULE_STATUS('must be linked');
|
||||||
@ -432,7 +416,7 @@ function importModuleDynamicallyWrap(importModuleDynamically) {
|
|||||||
if (isModuleNamespaceObject(m)) {
|
if (isModuleNamespaceObject(m)) {
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
if (!m || m[kWrap] === undefined) {
|
if (!isModule(m)) {
|
||||||
throw new ERR_VM_MODULE_NOT_MODULE();
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||||
}
|
}
|
||||||
if (m.status === 'errored') {
|
if (m.status === 'errored') {
|
||||||
|
@ -84,13 +84,15 @@ const util = require('util');
|
|||||||
|
|
||||||
assert.strictEqual(util.inspect(m, { depth: -1 }), '[SourceTextModule]');
|
assert.strictEqual(util.inspect(m, { depth: -1 }), '[SourceTextModule]');
|
||||||
|
|
||||||
assert.throws(
|
for (const value of [null, { __proto__: null }, SourceTextModule.prototype]) {
|
||||||
() => m[util.inspect.custom].call({ __proto__: null }),
|
assert.throws(
|
||||||
{
|
() => m[util.inspect.custom].call(value),
|
||||||
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/,
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -216,8 +216,8 @@ async function checkInvalidOptionForEvaluate() {
|
|||||||
await assert.rejects(async () => {
|
await assert.rejects(async () => {
|
||||||
await Module.prototype[method]();
|
await Module.prototype[method]();
|
||||||
}, {
|
}, {
|
||||||
code: 'ERR_VM_MODULE_NOT_MODULE',
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
message: /Provided module is not an instance of Module/
|
message: /The "this" argument must be an instance of Module/
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -241,8 +241,8 @@ function checkInvalidCachedData() {
|
|||||||
|
|
||||||
function checkGettersErrors() {
|
function checkGettersErrors() {
|
||||||
const expectedError = {
|
const expectedError = {
|
||||||
code: 'ERR_VM_MODULE_NOT_MODULE',
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
message: /Provided module is not an instance of Module/
|
message: /The "this" argument must be an instance of (?:Module|SourceTextModule)/,
|
||||||
};
|
};
|
||||||
const getters = ['identifier', 'context', 'namespace', 'status', 'error'];
|
const getters = ['identifier', 'context', 'namespace', 'status', 'error'];
|
||||||
getters.forEach((getter) => {
|
getters.forEach((getter) => {
|
||||||
|
@ -28,6 +28,22 @@ async function simple() {
|
|||||||
delete globalThis.fiveResult;
|
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() {
|
async function depth() {
|
||||||
const foo = new SourceTextModule('export default 5');
|
const foo = new SourceTextModule('export default 5');
|
||||||
await foo.link(common.mustNotCall());
|
await foo.link(common.mustNotCall());
|
||||||
@ -143,6 +159,7 @@ const finished = common.mustCall();
|
|||||||
|
|
||||||
(async function main() {
|
(async function main() {
|
||||||
await simple();
|
await simple();
|
||||||
|
await invalidLinkValue();
|
||||||
await depth();
|
await depth();
|
||||||
await circular();
|
await circular();
|
||||||
await circular2();
|
await circular2();
|
||||||
|
@ -66,12 +66,12 @@ const assert = require('assert');
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
for (const value of [null, {}, SyntheticModule.prototype]) {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
SyntheticModule.prototype.setExport.call({}, 'foo');
|
SyntheticModule.prototype.setExport.call(value, 'foo');
|
||||||
}, {
|
}, {
|
||||||
code: 'ERR_VM_MODULE_NOT_MODULE',
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
message: /Provided module is not an instance of Module/
|
message: /The "this" argument must be an instance of SyntheticModule/
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user