Skip to content

Commit 6b20e84

Browse files
bnoordhuiscodebytere
authored andcommitted
wasi: relax WebAssembly.Instance type check
Instances coming from different VM contexts don't pass `instanceof` type checks because each context has its own copy of the built-in globals. After review of the relevant code it seems like it should be safe to relax the type check and that is what this commit does: `wasi.start()` now accepts any input that walks and quacks like a WebAssembly.Instance or WebAssembly.Memory instance. Fixes: #33415 PR-URL: #33431 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
1 parent 4c235b0 commit 6b20e84

File tree

2 files changed

+88
-9
lines changed

2 files changed

+88
-9
lines changed

lib/wasi.js

+14-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
'use strict';
2-
/* global WebAssembly */
32
const {
43
ArrayPrototypeMap,
54
ArrayPrototypePush,
@@ -13,6 +12,7 @@ const {
1312
ERR_WASI_ALREADY_STARTED
1413
} = require('internal/errors').codes;
1514
const { emitExperimentalWarning } = require('internal/util');
15+
const { isArrayBuffer } = require('internal/util/types');
1616
const {
1717
validateArray,
1818
validateBoolean,
@@ -71,10 +71,7 @@ class WASI {
7171
}
7272

7373
start(instance) {
74-
if (!(instance instanceof WebAssembly.Instance)) {
75-
throw new ERR_INVALID_ARG_TYPE(
76-
'instance', 'WebAssembly.Instance', instance);
77-
}
74+
validateObject(instance, 'instance');
7875

7976
const exports = instance.exports;
8077

@@ -92,9 +89,19 @@ class WASI {
9289
'instance.exports._initialize', 'undefined', _initialize);
9390
}
9491

95-
if (!(memory instanceof WebAssembly.Memory)) {
92+
// WASI::_SetMemory() in src/node_wasi.cc only expects that |memory| is
93+
// an object. It will try to look up the .buffer property when needed
94+
// and fail with UVWASI_EINVAL when the property is missing or is not
95+
// an ArrayBuffer. Long story short, we don't need much validation here
96+
// but we type-check anyway because it helps catch bugs in the user's
97+
// code early.
98+
validateObject(memory, 'instance.exports.memory');
99+
100+
if (!isArrayBuffer(memory.buffer)) {
96101
throw new ERR_INVALID_ARG_TYPE(
97-
'instance.exports.memory', 'WebAssembly.Memory', memory);
102+
'instance.exports.memory.buffer',
103+
['WebAssembly.Memory'],
104+
memory.buffer);
98105
}
99106

100107
if (this[kStarted]) {

test/wasi/test-wasi-start-validation.js

+74-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
const common = require('../common');
55
const assert = require('assert');
6+
const vm = require('vm');
67
const { WASI } = require('wasi');
78

89
const fixtures = require('../common/fixtures');
@@ -15,7 +16,10 @@ const bufferSource = fixtures.readSync('simple.wasm');
1516

1617
assert.throws(
1718
() => { wasi.start(); },
18-
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bWebAssembly\.Instance\b/ }
19+
{
20+
code: 'ERR_INVALID_ARG_TYPE',
21+
message: /"instance" argument must be of type object/
22+
}
1923
);
2024
}
2125

@@ -87,11 +91,79 @@ const bufferSource = fixtures.readSync('simple.wasm');
8791
() => { wasi.start(instance); },
8892
{
8993
code: 'ERR_INVALID_ARG_TYPE',
90-
message: /"instance\.exports\.memory" property .+ WebAssembly\.Memory/
94+
message: /"instance\.exports\.memory" property must be of type object/
95+
}
96+
);
97+
}
98+
99+
{
100+
// Verify that a non-ArrayBuffer memory.buffer is rejected.
101+
const wasi = new WASI({});
102+
const wasm = await WebAssembly.compile(bufferSource);
103+
const instance = await WebAssembly.instantiate(wasm);
104+
105+
Object.defineProperty(instance, 'exports', {
106+
get() {
107+
return {
108+
_start() {},
109+
memory: {},
110+
};
111+
}
112+
});
113+
// The error message is a little white lie because any object
114+
// with a .buffer property of type ArrayBuffer is accepted,
115+
// but 99% of the time a WebAssembly.Memory object is used.
116+
assert.throws(
117+
() => { wasi.start(instance); },
118+
{
119+
code: 'ERR_INVALID_ARG_TYPE',
120+
message: /"instance\.exports\.memory\.buffer" property must be an WebAssembly\.Memory/
91121
}
92122
);
93123
}
94124

125+
{
126+
// Verify that an argument that duck-types as a WebAssembly.Instance
127+
// is accepted.
128+
const wasi = new WASI({});
129+
const wasm = await WebAssembly.compile(bufferSource);
130+
const instance = await WebAssembly.instantiate(wasm);
131+
132+
Object.defineProperty(instance, 'exports', {
133+
get() {
134+
return {
135+
_start() {},
136+
memory: { buffer: new ArrayBuffer(0) },
137+
};
138+
}
139+
});
140+
wasi.start(instance);
141+
}
142+
143+
{
144+
// Verify that a WebAssembly.Instance from another VM context is accepted.
145+
const wasi = new WASI({});
146+
const instance = await vm.runInNewContext(`
147+
(async () => {
148+
const wasm = await WebAssembly.compile(bufferSource);
149+
const instance = await WebAssembly.instantiate(wasm);
150+
151+
Object.defineProperty(instance, 'exports', {
152+
get() {
153+
return {
154+
_start() {},
155+
memory: new WebAssembly.Memory({ initial: 1 })
156+
};
157+
}
158+
});
159+
160+
return instance;
161+
})()
162+
`, { bufferSource });
163+
164+
wasi.start(instance);
165+
}
166+
95167
{
96168
// Verify that start() can only be called once.
97169
const wasi = new WASI({});

0 commit comments

Comments
 (0)