Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2e612fe

Browse filesBrowse files
panvaaduh95
authored andcommitted
lib: short-circuit WebIDL BufferSource SAB check
Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #62833 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Mattias Buelens <mattias@buelens.com>
1 parent b36e55a commit 2e612fe
Copy full SHA for 2e612fe

3 files changed

+283-7Lines changed: 283 additions & 7 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file
+54Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
5+
const bench = common.createBenchmark(main, {
6+
input: [
7+
'arraybuffer',
8+
'buffer',
9+
'dataview',
10+
'uint8array',
11+
'uint8clampedarray',
12+
'int8array',
13+
'uint16array',
14+
'int16array',
15+
'uint32array',
16+
'int32array',
17+
'float16array',
18+
'float32array',
19+
'float64array',
20+
'bigint64array',
21+
'biguint64array',
22+
],
23+
n: [1e6],
24+
}, { flags: ['--expose-internals'] });
25+
26+
function main({ n, input }) {
27+
const { converters } = require('internal/webidl');
28+
29+
let value;
30+
switch (input) {
31+
case 'arraybuffer': value = new ArrayBuffer(32); break;
32+
case 'buffer': value = Buffer.alloc(32); break;
33+
case 'dataview': value = new DataView(new ArrayBuffer(32)); break;
34+
case 'uint8array': value = new Uint8Array(32); break;
35+
case 'uint8clampedarray': value = new Uint8ClampedArray(32); break;
36+
case 'int8array': value = new Int8Array(32); break;
37+
case 'uint16array': value = new Uint16Array(16); break;
38+
case 'int16array': value = new Int16Array(16); break;
39+
case 'uint32array': value = new Uint32Array(8); break;
40+
case 'int32array': value = new Int32Array(8); break;
41+
case 'float16array': value = new Float16Array(16); break;
42+
case 'float32array': value = new Float32Array(8); break;
43+
case 'float64array': value = new Float64Array(4); break;
44+
case 'bigint64array': value = new BigInt64Array(4); break;
45+
case 'biguint64array': value = new BigUint64Array(4); break;
46+
}
47+
48+
const opts = { prefix: 'test', context: 'test' };
49+
50+
bench.start();
51+
for (let i = 0; i < n; i++)
52+
converters.BufferSource(value, opts);
53+
bench.end(n);
54+
}
Collapse file

‎lib/internal/webidl.js‎

Copy file name to clipboardExpand all lines: lib/internal/webidl.js
+21-7Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
ArrayBufferIsView,
5+
ArrayBufferPrototypeGetByteLength,
56
ArrayPrototypePush,
67
ArrayPrototypeToSorted,
78
DataViewPrototypeGetBuffer,
@@ -33,8 +34,7 @@ const {
3334
const { kEmptyObject } = require('internal/util');
3435
const {
3536
isArrayBuffer,
36-
isDataView,
37-
isSharedArrayBuffer,
37+
isTypedArray,
3838
} = require('internal/util/types');
3939

4040
const converters = { __proto__: null };
@@ -390,9 +390,23 @@ function createInterfaceConverter(name, I) {
390390
};
391391
}
392392

393-
function getDataViewOrTypedArrayBuffer(V) {
394-
return isDataView(V) ?
395-
DataViewPrototypeGetBuffer(V) : TypedArrayPrototypeGetBuffer(V);
393+
// Returns the [[ViewedArrayBuffer]] of an ArrayBufferView without leaving JS.
394+
function getViewedArrayBuffer(V) {
395+
return isTypedArray(V) ?
396+
TypedArrayPrototypeGetBuffer(V) : DataViewPrototypeGetBuffer(V);
397+
}
398+
399+
// Returns `true` if `buffer` is a `SharedArrayBuffer`. Uses a brand check via
400+
// the `ArrayBuffer.prototype.byteLength` getter, which succeeds only on real
401+
// (non-shared) ArrayBuffers and throws on SharedArrayBuffers — independent
402+
// of the receiver's prototype chain.
403+
function isSharedArrayBufferBacking(buffer) {
404+
try {
405+
ArrayBufferPrototypeGetByteLength(buffer);
406+
return false;
407+
} catch {
408+
return true;
409+
}
396410
}
397411

398412
// https://webidl.spec.whatwg.org/#ArrayBufferView
@@ -402,7 +416,7 @@ converters.ArrayBufferView = (V, opts = kEmptyObject) => {
402416
'is not an ArrayBufferView.',
403417
opts);
404418
}
405-
if (isSharedArrayBuffer(getDataViewOrTypedArrayBuffer(V))) {
419+
if (isSharedArrayBufferBacking(getViewedArrayBuffer(V))) {
406420
throw makeException(
407421
'is a view on a SharedArrayBuffer, which is not allowed.',
408422
opts);
@@ -414,7 +428,7 @@ converters.ArrayBufferView = (V, opts = kEmptyObject) => {
414428
// https://webidl.spec.whatwg.org/#BufferSource
415429
converters.BufferSource = (V, opts = kEmptyObject) => {
416430
if (ArrayBufferIsView(V)) {
417-
if (isSharedArrayBuffer(getDataViewOrTypedArrayBuffer(V))) {
431+
if (isSharedArrayBufferBacking(getViewedArrayBuffer(V))) {
418432
throw makeException(
419433
'is a view on a SharedArrayBuffer, which is not allowed.',
420434
opts);
Collapse file
+208Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
4+
require('../common');
5+
const assert = require('assert');
6+
const { test } = require('node:test');
7+
8+
const { converters } = require('internal/webidl');
9+
10+
const TYPED_ARRAY_CTORS = [
11+
Uint8Array, Int8Array, Uint8ClampedArray,
12+
Uint16Array, Int16Array,
13+
Uint32Array, Int32Array,
14+
Float16Array, Float32Array, Float64Array,
15+
BigInt64Array, BigUint64Array,
16+
];
17+
18+
test('BufferSource accepts ArrayBuffer', () => {
19+
const ab = new ArrayBuffer(8);
20+
assert.strictEqual(converters.BufferSource(ab), ab);
21+
});
22+
23+
test('BufferSource accepts all TypedArray kinds', () => {
24+
for (const Ctor of TYPED_ARRAY_CTORS) {
25+
const ta = new Ctor(4);
26+
assert.strictEqual(converters.BufferSource(ta), ta);
27+
}
28+
});
29+
30+
test('BufferSource accepts Buffer', () => {
31+
const buf = Buffer.alloc(8);
32+
assert.strictEqual(converters.BufferSource(buf), buf);
33+
});
34+
35+
test('BufferSource accepts DataView', () => {
36+
const dv = new DataView(new ArrayBuffer(8));
37+
assert.strictEqual(converters.BufferSource(dv), dv);
38+
});
39+
40+
test('BufferSource accepts ArrayBuffer subclass instance', () => {
41+
class MyAB extends ArrayBuffer {}
42+
const sub = new MyAB(8);
43+
assert.strictEqual(converters.BufferSource(sub), sub);
44+
});
45+
46+
test('BufferSource accepts TypedArray with null prototype', () => {
47+
const ta = new Uint8Array(4);
48+
Object.setPrototypeOf(ta, null);
49+
assert.strictEqual(converters.BufferSource(ta), ta);
50+
});
51+
52+
test('BufferSource accepts DataView with null prototype', () => {
53+
const dv = new DataView(new ArrayBuffer(4));
54+
Object.setPrototypeOf(dv, null);
55+
assert.strictEqual(converters.BufferSource(dv), dv);
56+
});
57+
58+
test('BufferSource accepts ArrayBuffer with null prototype', () => {
59+
const ab = new ArrayBuffer(4);
60+
Object.setPrototypeOf(ab, null);
61+
assert.strictEqual(converters.BufferSource(ab), ab);
62+
});
63+
64+
test('BufferSource rejects SharedArrayBuffer', () => {
65+
assert.throws(
66+
() => converters.BufferSource(new SharedArrayBuffer(4)),
67+
{ code: 'ERR_INVALID_ARG_TYPE' },
68+
);
69+
});
70+
71+
test('BufferSource rejects SAB-backed TypedArray', () => {
72+
const view = new Uint8Array(new SharedArrayBuffer(4));
73+
assert.throws(
74+
() => converters.BufferSource(view),
75+
{ code: 'ERR_INVALID_ARG_TYPE' },
76+
);
77+
});
78+
79+
test('BufferSource rejects SAB-backed DataView', () => {
80+
const dv = new DataView(new SharedArrayBuffer(4));
81+
assert.throws(
82+
() => converters.BufferSource(dv),
83+
{ code: 'ERR_INVALID_ARG_TYPE' },
84+
);
85+
});
86+
87+
test('BufferSource rejects SAB view whose buffer prototype was reassigned', () => {
88+
const sab = new SharedArrayBuffer(4);
89+
Object.setPrototypeOf(sab, ArrayBuffer.prototype);
90+
const view = new Uint8Array(sab);
91+
assert.throws(
92+
() => converters.BufferSource(view),
93+
{ code: 'ERR_INVALID_ARG_TYPE' },
94+
);
95+
});
96+
97+
test('BufferSource accepts a detached ArrayBuffer', () => {
98+
const ab = new ArrayBuffer(8);
99+
structuredClone(ab, { transfer: [ab] });
100+
assert.strictEqual(ab.byteLength, 0);
101+
assert.strictEqual(converters.BufferSource(ab), ab);
102+
});
103+
104+
test('BufferSource rejects objects with a forged @@toStringTag', () => {
105+
const fake = { [Symbol.toStringTag]: 'Uint8Array' };
106+
assert.throws(
107+
() => converters.BufferSource(fake),
108+
{ code: 'ERR_INVALID_ARG_TYPE' },
109+
);
110+
});
111+
112+
for (const value of [null, undefined, 0, 1, 1n, '', 'x', true, Symbol('s'), [],
113+
{}, () => {}]) {
114+
test(`BufferSource rejects ${typeof value} ${String(value)}`, () => {
115+
assert.throws(
116+
() => converters.BufferSource(value),
117+
{ code: 'ERR_INVALID_ARG_TYPE' },
118+
);
119+
});
120+
}
121+
122+
test('ArrayBufferView accepts all TypedArray kinds', () => {
123+
for (const Ctor of TYPED_ARRAY_CTORS) {
124+
const ta = new Ctor(4);
125+
assert.strictEqual(converters.ArrayBufferView(ta), ta);
126+
}
127+
});
128+
129+
test('ArrayBufferView accepts DataView', () => {
130+
const dv = new DataView(new ArrayBuffer(8));
131+
assert.strictEqual(converters.ArrayBufferView(dv), dv);
132+
});
133+
134+
test('ArrayBufferView accepts TypedArray subclass instance', () => {
135+
class MyU8 extends Uint8Array {}
136+
const sub = new MyU8(4);
137+
assert.strictEqual(converters.ArrayBufferView(sub), sub);
138+
});
139+
140+
test('ArrayBufferView accepts TypedArray with null prototype', () => {
141+
const ta = new Uint8Array(4);
142+
Object.setPrototypeOf(ta, null);
143+
assert.strictEqual(converters.ArrayBufferView(ta), ta);
144+
});
145+
146+
test('ArrayBufferView accepts DataView with null prototype', () => {
147+
const dv = new DataView(new ArrayBuffer(4));
148+
Object.setPrototypeOf(dv, null);
149+
assert.strictEqual(converters.ArrayBufferView(dv), dv);
150+
});
151+
152+
test('ArrayBufferView rejects raw ArrayBuffer', () => {
153+
assert.throws(
154+
() => converters.ArrayBufferView(new ArrayBuffer(4)),
155+
{ code: 'ERR_INVALID_ARG_TYPE' },
156+
);
157+
});
158+
159+
test('ArrayBufferView rejects raw SharedArrayBuffer', () => {
160+
assert.throws(
161+
() => converters.ArrayBufferView(new SharedArrayBuffer(4)),
162+
{ code: 'ERR_INVALID_ARG_TYPE' },
163+
);
164+
});
165+
166+
test('ArrayBufferView rejects SAB-backed TypedArray', () => {
167+
const view = new Uint8Array(new SharedArrayBuffer(4));
168+
assert.throws(
169+
() => converters.ArrayBufferView(view),
170+
{ code: 'ERR_INVALID_ARG_TYPE' },
171+
);
172+
});
173+
174+
test('ArrayBufferView rejects SAB-backed DataView', () => {
175+
const dv = new DataView(new SharedArrayBuffer(4));
176+
assert.throws(
177+
() => converters.ArrayBufferView(dv),
178+
{ code: 'ERR_INVALID_ARG_TYPE' },
179+
);
180+
});
181+
182+
test('ArrayBufferView rejects SAB view whose buffer prototype was reassigned', () => {
183+
const sab = new SharedArrayBuffer(4);
184+
Object.setPrototypeOf(sab, ArrayBuffer.prototype);
185+
const view = new Uint8Array(sab);
186+
assert.throws(
187+
() => converters.ArrayBufferView(view),
188+
{ code: 'ERR_INVALID_ARG_TYPE' },
189+
);
190+
});
191+
192+
test('ArrayBufferView rejects objects with a forged @@toStringTag', () => {
193+
const fake = { [Symbol.toStringTag]: 'Uint8Array' };
194+
assert.throws(
195+
() => converters.ArrayBufferView(fake),
196+
{ code: 'ERR_INVALID_ARG_TYPE' },
197+
);
198+
});
199+
200+
for (const value of [null, undefined, 0, 1, 1n, '', 'x', true, Symbol('s'), [],
201+
{}, () => {}]) {
202+
test(`ArrayBufferView rejects ${typeof value} ${String(value)}`, () => {
203+
assert.throws(
204+
() => converters.ArrayBufferView(value),
205+
{ code: 'ERR_INVALID_ARG_TYPE' },
206+
);
207+
});
208+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.