Skip to content

Commit 5081f84

Browse files
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 <[email protected]> PR-URL: #52162 Reviewed-By: Vinícius Lourenço Claro Cardoso <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]>
1 parent cd1415c commit 5081f84

File tree

5 files changed

+58
-57
lines changed

5 files changed

+58
-57
lines changed

lib/internal/vm/module.js

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
ArrayPrototypeSome,
99
ObjectDefineProperty,
1010
ObjectGetPrototypeOf,
11+
ObjectPrototypeHasOwnProperty,
1112
ObjectSetPrototypeOf,
1213
ReflectApply,
1314
SafePromiseAllReturnVoid,
@@ -43,6 +44,7 @@ const {
4344
validateObject,
4445
validateUint32,
4546
validateString,
47+
validateInternalField,
4648
} = require('internal/validators');
4749

4850
const binding = internalBinding('module_wrap');
@@ -75,6 +77,13 @@ const kLink = Symbol('kLink');
7577

7678
const { isContext } = require('internal/vm');
7779

80+
function isModule(object) {
81+
if (typeof object !== 'object' || object === null || !ObjectPrototypeHasOwnProperty(object, kWrap)) {
82+
return false;
83+
}
84+
return true;
85+
}
86+
7887
class Module {
7988
constructor(options) {
8089
emitExperimentalWarning('VM Modules');
@@ -148,50 +157,38 @@ class Module {
148157
}
149158

150159
get identifier() {
151-
if (this[kWrap] === undefined) {
152-
throw new ERR_VM_MODULE_NOT_MODULE();
153-
}
160+
validateInternalField(this, kWrap, 'Module');
154161
return this[kWrap].url;
155162
}
156163

157164
get context() {
158-
if (this[kWrap] === undefined) {
159-
throw new ERR_VM_MODULE_NOT_MODULE();
160-
}
165+
validateInternalField(this, kWrap, 'Module');
161166
return this[kContext];
162167
}
163168

164169
get namespace() {
165-
if (this[kWrap] === undefined) {
166-
throw new ERR_VM_MODULE_NOT_MODULE();
167-
}
170+
validateInternalField(this, kWrap, 'Module');
168171
if (this[kWrap].getStatus() < kInstantiated) {
169172
throw new ERR_VM_MODULE_STATUS('must not be unlinked or linking');
170173
}
171174
return this[kWrap].getNamespace();
172175
}
173176

174177
get status() {
175-
if (this[kWrap] === undefined) {
176-
throw new ERR_VM_MODULE_NOT_MODULE();
177-
}
178+
validateInternalField(this, kWrap, 'Module');
178179
return STATUS_MAP[this[kWrap].getStatus()];
179180
}
180181

181182
get error() {
182-
if (this[kWrap] === undefined) {
183-
throw new ERR_VM_MODULE_NOT_MODULE();
184-
}
183+
validateInternalField(this, kWrap, 'Module');
185184
if (this[kWrap].getStatus() !== kErrored) {
186185
throw new ERR_VM_MODULE_STATUS('must be errored');
187186
}
188187
return this[kWrap].getError();
189188
}
190189

191190
async link(linker) {
192-
if (this[kWrap] === undefined) {
193-
throw new ERR_VM_MODULE_NOT_MODULE();
194-
}
191+
validateInternalField(this, kWrap, 'Module');
195192
validateFunction(linker, 'linker');
196193
if (this.status === 'linked') {
197194
throw new ERR_VM_MODULE_ALREADY_LINKED();
@@ -204,10 +201,7 @@ class Module {
204201
}
205202

206203
async evaluate(options = kEmptyObject) {
207-
if (this[kWrap] === undefined) {
208-
throw new ERR_VM_MODULE_NOT_MODULE();
209-
}
210-
204+
validateInternalField(this, kWrap, 'Module');
211205
validateObject(options, 'options');
212206

213207
let timeout = options.timeout;
@@ -230,9 +224,7 @@ class Module {
230224
}
231225

232226
[customInspectSymbol](depth, options) {
233-
if (this[kWrap] === undefined) {
234-
throw new ERR_VM_MODULE_NOT_MODULE();
235-
}
227+
validateInternalField(this, kWrap, 'Module');
236228
if (typeof depth === 'number' && depth < 0)
237229
return this;
238230

@@ -307,7 +299,7 @@ class SourceTextModule extends Module {
307299

308300
const promises = this[kWrap].link(async (identifier, attributes) => {
309301
const module = await linker(identifier, this, { attributes, assert: attributes });
310-
if (module[kWrap] === undefined) {
302+
if (!isModule(module)) {
311303
throw new ERR_VM_MODULE_NOT_MODULE();
312304
}
313305
if (module.context !== this.context) {
@@ -338,19 +330,13 @@ class SourceTextModule extends Module {
338330
}
339331

340332
get dependencySpecifiers() {
341-
if (this[kWrap] === undefined) {
342-
throw new ERR_VM_MODULE_NOT_MODULE();
343-
}
344-
if (this[kDependencySpecifiers] === undefined) {
345-
this[kDependencySpecifiers] = this[kWrap].getStaticDependencySpecifiers();
346-
}
333+
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
334+
this[kDependencySpecifiers] ??= this[kWrap].getStaticDependencySpecifiers();
347335
return this[kDependencySpecifiers];
348336
}
349337

350338
get status() {
351-
if (this[kWrap] === undefined) {
352-
throw new ERR_VM_MODULE_NOT_MODULE();
353-
}
339+
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
354340
if (this.#error !== kNoError) {
355341
return 'errored';
356342
}
@@ -361,9 +347,7 @@ class SourceTextModule extends Module {
361347
}
362348

363349
get error() {
364-
if (this[kWrap] === undefined) {
365-
throw new ERR_VM_MODULE_NOT_MODULE();
366-
}
350+
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
367351
if (this.#error !== kNoError) {
368352
return this.#error;
369353
}
@@ -416,9 +400,7 @@ class SyntheticModule extends Module {
416400
}
417401

418402
setExport(name, value) {
419-
if (this[kWrap] === undefined) {
420-
throw new ERR_VM_MODULE_NOT_MODULE();
421-
}
403+
validateInternalField(this, kWrap, 'SyntheticModule');
422404
validateString(name, 'name');
423405
if (this[kWrap].getStatus() < kInstantiated) {
424406
throw new ERR_VM_MODULE_STATUS('must be linked');
@@ -433,7 +415,7 @@ function importModuleDynamicallyWrap(importModuleDynamically) {
433415
if (isModuleNamespaceObject(m)) {
434416
return m;
435417
}
436-
if (!m || m[kWrap] === undefined) {
418+
if (!isModule(m)) {
437419
throw new ERR_VM_MODULE_NOT_MODULE();
438420
}
439421
if (m.status === 'errored') {

test/parallel/test-vm-module-basic.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,15 @@ const util = require('util');
8484

8585
assert.strictEqual(util.inspect(m, { depth: -1 }), '[SourceTextModule]');
8686

87-
assert.throws(
88-
() => m[util.inspect.custom].call({ __proto__: null }),
89-
{
90-
code: 'ERR_VM_MODULE_NOT_MODULE',
91-
message: 'Provided module is not an instance of Module'
92-
},
93-
);
87+
for (const value of [null, { __proto__: null }, SourceTextModule.prototype]) {
88+
assert.throws(
89+
() => m[util.inspect.custom].call(value),
90+
{
91+
code: 'ERR_INVALID_ARG_TYPE',
92+
message: /The "this" argument must be an instance of Module/,
93+
},
94+
);
95+
}
9496
}
9597

9698
{

test/parallel/test-vm-module-errors.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ async function checkInvalidOptionForEvaluate() {
216216
await assert.rejects(async () => {
217217
await Module.prototype[method]();
218218
}, {
219-
code: 'ERR_VM_MODULE_NOT_MODULE',
220-
message: /Provided module is not an instance of Module/
219+
code: 'ERR_INVALID_ARG_TYPE',
220+
message: /The "this" argument must be an instance of Module/
221221
});
222222
});
223223
}
@@ -241,8 +241,8 @@ function checkInvalidCachedData() {
241241

242242
function checkGettersErrors() {
243243
const expectedError = {
244-
code: 'ERR_VM_MODULE_NOT_MODULE',
245-
message: /Provided module is not an instance of Module/
244+
code: 'ERR_INVALID_ARG_TYPE',
245+
message: /The "this" argument must be an instance of (?:Module|SourceTextModule)/,
246246
};
247247
const getters = ['identifier', 'context', 'namespace', 'status', 'error'];
248248
getters.forEach((getter) => {

test/parallel/test-vm-module-link.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ async function simple() {
2828
delete globalThis.fiveResult;
2929
}
3030

31+
async function invalidLinkValue() {
32+
const invalidValues = [
33+
undefined,
34+
null,
35+
{},
36+
SourceTextModule.prototype,
37+
];
38+
39+
for (const value of invalidValues) {
40+
const module = new SourceTextModule('import "foo"');
41+
await assert.rejects(module.link(() => value), {
42+
code: 'ERR_VM_MODULE_NOT_MODULE',
43+
});
44+
}
45+
}
46+
3147
async function depth() {
3248
const foo = new SourceTextModule('export default 5');
3349
await foo.link(common.mustNotCall());
@@ -143,6 +159,7 @@ const finished = common.mustCall();
143159

144160
(async function main() {
145161
await simple();
162+
await invalidLinkValue();
146163
await depth();
147164
await circular();
148165
await circular2();

test/parallel/test-vm-module-synthetic.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,12 @@ const assert = require('assert');
6666
});
6767
}
6868

69-
{
69+
for (const value of [null, {}, SyntheticModule.prototype]) {
7070
assert.throws(() => {
71-
SyntheticModule.prototype.setExport.call({}, 'foo');
71+
SyntheticModule.prototype.setExport.call(value, 'foo');
7272
}, {
73-
code: 'ERR_VM_MODULE_NOT_MODULE',
74-
message: /Provided module is not an instance of Module/
73+
code: 'ERR_INVALID_ARG_TYPE',
74+
message: /The "this" argument must be an instance of SyntheticModule/
7575
});
7676
}
7777

0 commit comments

Comments
 (0)