diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index 4f371955a73bfd..0c14bd22d6a9ad 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -31,6 +31,7 @@ const { } = primordials; const { + converters: sharedConverters, makeException, createEnumConverter, createSequenceConverter, @@ -43,11 +44,10 @@ const { } = require('internal/util'); const { CryptoKey } = require('internal/crypto/webcrypto'); const { - getDataViewOrTypedArrayBuffer, validateMaxBufferLength, kNamedCurveAliases, } = require('internal/crypto/util'); -const { isArrayBuffer, isSharedArrayBuffer } = require('internal/util/types'); +const { isSharedArrayBuffer } = require('internal/util/types'); // https://tc39.es/ecma262/#sec-tonumber function toNumber(value, opts = kEmptyObject) { @@ -193,8 +193,6 @@ converters.object = (V, opts) => { return V; }; -const isNonSharedArrayBuffer = isArrayBuffer; - /** * @param {string | object} V - The hash algorithm identifier (string or object). * @param {string} label - The dictionary name for the error message. @@ -223,25 +221,7 @@ converters.Uint8Array = (V, opts = kEmptyObject) => { return V; }; -converters.BufferSource = (V, opts = kEmptyObject) => { - if (ArrayBufferIsView(V)) { - if (isSharedArrayBuffer(getDataViewOrTypedArrayBuffer(V))) { - throw makeException( - 'is a view on a SharedArrayBuffer, which is not allowed.', - opts); - } - - return V; - } - - if (!isNonSharedArrayBuffer(V)) { - throw makeException( - 'is not instance of ArrayBuffer, Buffer, TypedArray, or DataView.', - opts); - } - - return V; -}; +converters.BufferSource = sharedConverters.BufferSource; converters['sequence'] = createSequenceConverter( converters.DOMString); diff --git a/lib/internal/webidl.js b/lib/internal/webidl.js index 4af564dad752e6..d6bfea8e8479fa 100644 --- a/lib/internal/webidl.js +++ b/lib/internal/webidl.js @@ -1,8 +1,10 @@ 'use strict'; const { + ArrayBufferIsView, ArrayPrototypePush, ArrayPrototypeToSorted, + DataViewPrototypeGetBuffer, MathAbs, MathMax, MathMin, @@ -19,6 +21,7 @@ const { Symbol, SymbolIterator, TypeError, + TypedArrayPrototypeGetBuffer, } = primordials; const { @@ -28,6 +31,11 @@ const { }, } = require('internal/errors'); const { kEmptyObject } = require('internal/util'); +const { + isArrayBuffer, + isDataView, + isSharedArrayBuffer, +} = require('internal/util/types'); const converters = { __proto__: null }; @@ -382,6 +390,47 @@ function createInterfaceConverter(name, I) { }; } +function getDataViewOrTypedArrayBuffer(V) { + return isDataView(V) ? + DataViewPrototypeGetBuffer(V) : TypedArrayPrototypeGetBuffer(V); +} + +// https://webidl.spec.whatwg.org/#ArrayBufferView +converters.ArrayBufferView = (V, opts = kEmptyObject) => { + if (!ArrayBufferIsView(V)) { + throw makeException( + 'is not an ArrayBufferView.', + opts); + } + if (isSharedArrayBuffer(getDataViewOrTypedArrayBuffer(V))) { + throw makeException( + 'is a view on a SharedArrayBuffer, which is not allowed.', + opts); + } + + return V; +}; + +// https://webidl.spec.whatwg.org/#BufferSource +converters.BufferSource = (V, opts = kEmptyObject) => { + if (ArrayBufferIsView(V)) { + if (isSharedArrayBuffer(getDataViewOrTypedArrayBuffer(V))) { + throw makeException( + 'is a view on a SharedArrayBuffer, which is not allowed.', + opts); + } + + return V; + } + + if (!isArrayBuffer(V)) { + throw makeException( + 'is not instance of ArrayBuffer, Buffer, TypedArray, or DataView.', + opts); + } + + return V; +}; module.exports = { type, diff --git a/lib/internal/webstreams/readablestream.js b/lib/internal/webstreams/readablestream.js index 94592cdf01258c..e72b4b2f11d6c6 100644 --- a/lib/internal/webstreams/readablestream.js +++ b/lib/internal/webstreams/readablestream.js @@ -48,6 +48,7 @@ const { const { isArrayBufferView, isDataView, + isSharedArrayBuffer, } = require('internal/util/types'); const { @@ -988,6 +989,15 @@ class ReadableStreamBYOBReader { const viewByteLength = ArrayBufferViewGetByteLength(view); const viewBuffer = ArrayBufferViewGetBuffer(view); + + if (isSharedArrayBuffer(viewBuffer)) { + throw new ERR_INVALID_ARG_VALUE( + 'view', + view, + 'must not be backed by a SharedArrayBuffer', + ); + } + const viewBufferByteLength = ArrayBufferPrototypeGetByteLength(viewBuffer); if (viewByteLength === 0 || viewBufferByteLength === 0) { @@ -1197,6 +1207,15 @@ class ReadableByteStreamController { validateBuffer(chunk); const chunkByteLength = ArrayBufferViewGetByteLength(chunk); const chunkBuffer = ArrayBufferViewGetBuffer(chunk); + + if (isSharedArrayBuffer(chunkBuffer)) { + throw new ERR_INVALID_ARG_VALUE( + 'chunk', + chunk, + 'must not be backed by a SharedArrayBuffer', + ); + } + const chunkBufferByteLength = ArrayBufferPrototypeGetByteLength(chunkBuffer); if (chunkByteLength === 0 || chunkBufferByteLength === 0) { throw new ERR_INVALID_STATE.TypeError( diff --git a/test/parallel/test-webapi-sharedarraybuffer-rejection.js b/test/parallel/test-webapi-sharedarraybuffer-rejection.js new file mode 100644 index 00000000000000..c5503dfc0a1b2d --- /dev/null +++ b/test/parallel/test-webapi-sharedarraybuffer-rejection.js @@ -0,0 +1,165 @@ +'use strict'; +// Flags: --expose-internals +const common = require('../common'); +const assert = require('assert'); +const test = require('node:test'); +const { ReadableStream } = require('stream/web'); + +const sab = new SharedArrayBuffer(8); +const sabView = new Uint8Array(sab); +const sabDataView = new DataView(sab); + +// -- ReadableStreamBYOBReader.read() -- + +test('ReadableStreamBYOBReader.read() rejects SAB-backed Uint8Array', async () => { + const rs = new ReadableStream({ + type: 'bytes', + pull(controller) { + controller.enqueue(new Uint8Array([1, 2, 3])); + }, + }); + const reader = rs.getReader({ mode: 'byob' }); + await assert.rejects( + reader.read(new Uint8Array(sab)), + { code: 'ERR_INVALID_ARG_VALUE' }, + ); + reader.releaseLock(); +}); + +test('ReadableStreamBYOBReader.read() rejects SAB-backed DataView', async () => { + const rs = new ReadableStream({ + type: 'bytes', + pull(controller) { + controller.enqueue(new Uint8Array([1, 2, 3])); + }, + }); + const reader = rs.getReader({ mode: 'byob' }); + await assert.rejects( + reader.read(sabDataView), + { code: 'ERR_INVALID_ARG_VALUE' }, + ); + reader.releaseLock(); +}); + +test('ReadableStreamBYOBReader.read() accepts regular view', async () => { + const rs = new ReadableStream({ + type: 'bytes', + pull(controller) { + controller.enqueue(new Uint8Array([1, 2, 3])); + }, + }); + const reader = rs.getReader({ mode: 'byob' }); + const { value, done } = await reader.read(new Uint8Array(3)); + assert.strictEqual(done, false); + assert.deepStrictEqual(value, new Uint8Array([1, 2, 3])); + reader.releaseLock(); +}); + +// -- ReadableByteStreamController.enqueue() -- + +test('ReadableByteStreamController.enqueue() rejects SAB-backed Uint8Array', async () => { + const sabForEnqueue = new SharedArrayBuffer(4); + const sabViewForEnqueue = new Uint8Array(sabForEnqueue); + sabViewForEnqueue[0] = 42; + + const rs = new ReadableStream({ + type: 'bytes', + pull: common.mustCall((controller) => { + assert.throws( + () => controller.enqueue(sabViewForEnqueue), + { code: 'ERR_INVALID_ARG_VALUE' }, + ); + controller.enqueue(new Uint8Array([1])); + }), + }); + const reader = rs.getReader(); + const { value } = await reader.read(); + assert.deepStrictEqual(value, new Uint8Array([1])); + reader.releaseLock(); +}); + +test('ReadableByteStreamController.enqueue() rejects SAB-backed DataView', async () => { + const sabForDv = new SharedArrayBuffer(4); + const dvForEnqueue = new DataView(sabForDv); + + const rs = new ReadableStream({ + type: 'bytes', + pull: common.mustCall((controller) => { + assert.throws( + () => controller.enqueue(dvForEnqueue), + { code: 'ERR_INVALID_ARG_VALUE' }, + ); + controller.enqueue(new Uint8Array([2])); + }), + }); + const reader = rs.getReader(); + const { value } = await reader.read(); + assert.deepStrictEqual(value, new Uint8Array([2])); + reader.releaseLock(); +}); + +// -- SharedWebIDL converters -- + +const { converters } = require('internal/webidl'); + +test('webidl converters.BufferSource rejects SharedArrayBuffer', () => { + assert.throws( + () => converters.BufferSource(sab), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +}); + +test('webidl converters.BufferSource rejects SAB-backed Uint8Array', () => { + assert.throws( + () => converters.BufferSource(sabView), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +}); + +test('webidl converters.BufferSource rejects SAB-backed DataView', () => { + assert.throws( + () => converters.BufferSource(sabDataView), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +}); + +test('webidl converters.BufferSource accepts ArrayBuffer', () => { + const ab = new ArrayBuffer(4); + assert.strictEqual(converters.BufferSource(ab), ab); +}); + +test('webidl converters.BufferSource accepts regular TypedArray', () => { + const ta = new Uint8Array(4); + assert.strictEqual(converters.BufferSource(ta), ta); +}); + +test('webidl converters.ArrayBufferView rejects SAB-backed Uint8Array', () => { + assert.throws( + () => converters.ArrayBufferView(sabView), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +}); + +test('webidl converters.ArrayBufferView rejects SAB-backed DataView', () => { + assert.throws( + () => converters.ArrayBufferView(sabDataView), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +}); + +test('webidl converters.ArrayBufferView rejects non-view', () => { + assert.throws( + () => converters.ArrayBufferView('not a view'), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +}); + +test('webidl converters.ArrayBufferView accepts regular Uint8Array', () => { + const ta = new Uint8Array(4); + assert.strictEqual(converters.ArrayBufferView(ta), ta); +}); + +test('webidl converters.ArrayBufferView accepts regular DataView', () => { + const dv = new DataView(new ArrayBuffer(4)); + assert.strictEqual(converters.ArrayBufferView(dv), dv); +});