diff --git a/lib/internal/modules/esm/create_dynamic_module.js b/lib/internal/modules/esm/create_dynamic_module.js index 2eac81a82211ee..d4f5a85db95f77 100644 --- a/lib/internal/modules/esm/create_dynamic_module.js +++ b/lib/internal/modules/esm/create_dynamic_module.js @@ -25,14 +25,15 @@ import.meta.imports[${imptPath}] = $import_${index};`; /** * Creates an export for a given module. * @param {string} expt - The name of the export. + * @param {number} index - The index of the export statement. */ -function createExport(expt) { - const name = `${expt}`; - return `let $${name}; -export { $${name} as ${name} }; -import.meta.exports.${name} = { - get: () => $${name}, - set: (v) => $${name} = v, +function createExport(expt, index) { + const nameStringLit = JSONStringify(expt); + return `let $export_${index}; +export { $export_${index} as ${nameStringLit} }; +import.meta.exports[${nameStringLit}] = { + get: () => $export_${index}, + set: (v) => $export_${index} = v, };`; } diff --git a/test/es-module/test-esm-wasm.mjs b/test/es-module/test-esm-wasm.mjs index fac1d4b2837df0..be33e3d437df3a 100644 --- a/test/es-module/test-esm-wasm.mjs +++ b/test/es-module/test-esm-wasm.mjs @@ -29,6 +29,56 @@ describe('ESM: WASM modules', { concurrency: true }, () => { strictEqual(code, 0); }); + it('should not allow code injection through export names', async () => { + const { code, stderr, stdout } = await spawnPromisified(execPath, [ + '--no-warnings', + '--experimental-wasm-modules', + '--input-type=module', + '--eval', + `import * as wasmExports from ${JSON.stringify(fixtures.fileURL('es-modules/export-name-code-injection.wasm'))};`, + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + }); + + it('should allow non-identifier export names', async () => { + const { code, stderr, stdout } = await spawnPromisified(execPath, [ + '--no-warnings', + '--experimental-wasm-modules', + '--input-type=module', + '--eval', + [ + 'import { strictEqual } from "node:assert";', + `import * as wasmExports from ${JSON.stringify(fixtures.fileURL('es-modules/export-name-syntax-error.wasm'))};`, + 'assert.strictEqual(wasmExports["?f!o:oa[r]"]?.value, 12682);', + ].join('\n'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + }); + + it('should properly escape import names as well', async () => { + const { code, stderr, stdout } = await spawnPromisified(execPath, [ + '--no-warnings', + '--experimental-wasm-modules', + '--input-type=module', + '--eval', + [ + 'import { strictEqual } from "node:assert";', + `import * as wasmExports from ${JSON.stringify(fixtures.fileURL('es-modules/import-name.wasm'))};`, + 'assert.strictEqual(wasmExports.xor(), 12345);', + ].join('\n'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + }); + it('should emit experimental warning', async () => { const { code, signal, stderr } = await spawnPromisified(execPath, [ '--experimental-wasm-modules', diff --git a/test/fixtures/es-modules/export-name-code-injection.wasm b/test/fixtures/es-modules/export-name-code-injection.wasm new file mode 100644 index 00000000000000..c88b6b81e3f852 Binary files /dev/null and b/test/fixtures/es-modules/export-name-code-injection.wasm differ diff --git a/test/fixtures/es-modules/export-name-code-injection.wat b/test/fixtures/es-modules/export-name-code-injection.wat new file mode 100644 index 00000000000000..3cca749ea3a4ab --- /dev/null +++ b/test/fixtures/es-modules/export-name-code-injection.wat @@ -0,0 +1,8 @@ +;; Compiled using the WebAssembly Binary Toolkit (https://github.com/WebAssembly/wabt) +;; $ wat2wasm export-name-code-injection.wat + +(module + (global $0 i32 (i32.const 123)) + (global $1 i32 (i32.const 456)) + (export ";import.meta.done=()=>{};console.log('code injection');{/*" (global $0)) + (export "/*/$;`//" (global $1))) diff --git a/test/fixtures/es-modules/export-name-syntax-error.wasm b/test/fixtures/es-modules/export-name-syntax-error.wasm new file mode 100644 index 00000000000000..30787c5ab54ed2 Binary files /dev/null and b/test/fixtures/es-modules/export-name-syntax-error.wasm differ diff --git a/test/fixtures/es-modules/export-name-syntax-error.wat b/test/fixtures/es-modules/export-name-syntax-error.wat new file mode 100644 index 00000000000000..fe8728e8207628 --- /dev/null +++ b/test/fixtures/es-modules/export-name-syntax-error.wat @@ -0,0 +1,6 @@ +;; Compiled using the WebAssembly Binary Toolkit (https://github.com/WebAssembly/wabt) +;; $ wat2wasm export-name-syntax-error.wat + +(module + (global $0 i32 (i32.const 12682)) + (export "?f!o:oa[r]" (global $0))) diff --git a/test/fixtures/es-modules/import-name.wasm b/test/fixtures/es-modules/import-name.wasm new file mode 100644 index 00000000000000..1c261b7a45af0f Binary files /dev/null and b/test/fixtures/es-modules/import-name.wasm differ diff --git a/test/fixtures/es-modules/import-name.wat b/test/fixtures/es-modules/import-name.wat new file mode 100644 index 00000000000000..3501aa5ead9eda --- /dev/null +++ b/test/fixtures/es-modules/import-name.wat @@ -0,0 +1,10 @@ +;; Compiled using the WebAssembly Binary Toolkit (https://github.com/WebAssembly/wabt) +;; $ wat2wasm import-name.wat + +(module + (global $0 (import "./export-name-code-injection.wasm" ";import.meta.done=()=>{};console.log('code injection');{/*") i32) + (global $1 (import "./export-name-code-injection.wasm" "/*/$;`//") i32) + (global $2 (import "./export-name-syntax-error.wasm" "?f!o:oa[r]") i32) + (func $xor (result i32) + (i32.xor (i32.xor (global.get $0) (global.get $1)) (global.get $2))) + (export "xor" (func $xor)))