Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vm: harden module type checks #52162

Merged
merged 1 commit into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 23 additions & 39 deletions lib/internal/vm/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
ObjectDefineProperty,
ObjectFreeze,
ObjectGetPrototypeOf,
ObjectPrototypeHasOwnProperty,
ObjectSetPrototypeOf,
ReflectApply,
SafePromiseAllReturnVoid,
Expand Down Expand Up @@ -44,6 +45,7 @@ const {
validateObject,
validateUint32,
validateString,
validateInternalField,
} = require('internal/validators');

const binding = internalBinding('module_wrap');
Expand Down Expand Up @@ -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)) {
anonrig marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
return true;
}

class Module {
constructor(options) {
emitExperimentalWarning('VM Modules');
Expand Down Expand Up @@ -149,50 +158,38 @@ 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');
}
return this[kWrap].getNamespace();
}

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');
}
return this[kWrap].getError();
}

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();
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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';
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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');
Expand All @@ -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') {
Expand Down
16 changes: 9 additions & 7 deletions test/parallel/test-vm-module-basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/,
},
);
}
}

{
Expand Down
8 changes: 4 additions & 4 deletions test/parallel/test-vm-module-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/
});
});
}
Expand All @@ -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) => {
Expand Down
17 changes: 17 additions & 0 deletions test/parallel/test-vm-module-link.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -143,6 +159,7 @@ const finished = common.mustCall();

(async function main() {
await simple();
await invalidLinkValue();
await depth();
await circular();
await circular2();
Expand Down
8 changes: 4 additions & 4 deletions test/parallel/test-vm-module-synthetic.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/
});
}

Expand Down
Loading