module: fix code injection through export names

createDynamicModule() properly escapes import names, but not export
names. In WebAssembly, any string is a valid export name. Importing a
WebAssembly module that uses a non-identifier export name leads to
either a syntax error in createDynamicModule() or to code injection,
that is, to the evaluation of almost arbitrary JavaScript code outside
of the WebAssembly module.

To address this issue, adopt the same mechanism in createExport() that
createImport() already uses. Add tests for both exports and imports.

PR-URL: https://github.com/nodejs-private/node-private/pull/461
Backport-PR-URL: https://github.com/nodejs-private/node-private/pull/489
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
CVE-ID: CVE-2023-39333
This commit is contained in:
Tobias Nießen 2023-08-06 10:41:33 +00:00 committed by RafaelGSS
parent e673c03629
commit 3b23b2ee53
8 changed files with 82 additions and 7 deletions

View File

@ -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,
};`;
}

View File

@ -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:o<b>a[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',

Binary file not shown.

View File

@ -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)))

Binary file not shown.

View File

@ -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:o<b>a[r]" (global $0)))

Binary file not shown.

View File

@ -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:o<b>a[r]") i32)
(func $xor (result i32)
(i32.xor (i32.xor (global.get $0) (global.get $1)) (global.get $2)))
(export "xor" (func $xor)))