From dd5c37f3219eaf9bb2758b995da42e04550c8c10 Mon Sep 17 00:00:00 2001 From: Chidiebere Ekennia <54110155+Davien21@users.noreply.github.com> Date: Mon, 26 Oct 2026 02:19:09 +0100 Subject: [PATCH] util: reject immutable buffers in encodeInto encodeInto() wrote into a Uint8Array backed by an immutable ArrayBuffer created with transferToImmutable(). Check the backing store before either write path and throw ERR_INVALID_ARG_VALUE, matching websocketMask(). Fixes: https://github.com/nodejs/node/issues/66346 Signed-off-by: Chidiebere Ekennia <54110155+Davien21@users.noreply.github.com> --- lib/internal/encoding.js | 7 ++- src/encoding_binding.cc | 6 +++ ...g-encoding-custom-textencoder-immutable.js | 54 +++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-whatwg-encoding-custom-textencoder-immutable.js diff --git a/lib/internal/encoding.js b/lib/internal/encoding.js index c6eafa2ba34a..1828b8b9a54b 100644 --- a/lib/internal/encoding.js +++ b/lib/internal/encoding.js @@ -21,6 +21,7 @@ const { FastBuffer } = require('internal/buffer'); const { ERR_ENCODING_NOT_SUPPORTED, ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, ERR_INVALID_THIS, ERR_NO_ICU, } = require('internal/errors').codes; @@ -363,7 +364,11 @@ class TextEncoder { } #encodeInto(input, dest) { - encodeInto(input, dest); + const accepted = encodeInto(input, dest) !== false; + if (!accepted) { + const reason = 'must not be backed by an immutable ArrayBuffer'; + throw new ERR_INVALID_ARG_VALUE('dest', dest, reason); + } // We need to read from the binding here since the buffer gets refreshed // from the snapshot. const { 0: read, 1: written } = encodeIntoResults; diff --git a/src/encoding_binding.cc b/src/encoding_binding.cc index 8a445088b54a..88705c1dae72 100644 --- a/src/encoding_binding.cc +++ b/src/encoding_binding.cc @@ -208,6 +208,12 @@ void BindingData::EncodeInto(const FunctionCallbackInfo& args) { Local dest = args[1].As(); Local buf = dest->Buffer(); + // Read-only backing store: return false so the JS caller throws instead. + if (buf->IsImmutable()) { + args.GetReturnValue().Set(false); + return; + } + // Handle detached buffers - return {read: 0, written: 0} if (buf->Data() == nullptr) { binding_data->encode_into_results_buffer_[0] = 0; diff --git a/test/parallel/test-whatwg-encoding-custom-textencoder-immutable.js b/test/parallel/test-whatwg-encoding-custom-textencoder-immutable.js new file mode 100644 index 000000000000..b1577078d5f9 --- /dev/null +++ b/test/parallel/test-whatwg-encoding-custom-textencoder-immutable.js @@ -0,0 +1,54 @@ +// Flags: --js-immutable-arraybuffer +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +if (typeof ArrayBuffer.prototype.transferToImmutable !== 'function') + common.skip('ArrayBuffer.prototype.transferToImmutable is not available'); + +const encoder = new TextEncoder(); +const original = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + +function immutable() { + const ab = new ArrayBuffer(original.length); + new Uint8Array(ab).set(original); + return ab.transferToImmutable(); +} + +// Strings longer than 32 code units take the fast path in the binding, so +// both write paths are exercised. +const inputs = [ + '', + 'hi', + 'héllo wörld', + 'a'.repeat(64), + '日本語のテキスト'.repeat(8), + '\uD800 lone surrogate', +]; + +const views = [ + (ab) => new Uint8Array(ab), + (ab) => new Uint8Array(ab, 4, 8), + (ab) => new Uint8Array(ab, 0, 0), + (ab) => new Uint8Array(ab, ab.byteLength, 0), +]; + +for (const input of inputs) { + for (const makeView of views) { + const ab = immutable(); + const dest = makeView(ab); + assert.throws(() => encoder.encodeInto(input, dest), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: /'dest' must not be backed by an immutable ArrayBuffer/, + }); + assert.deepStrictEqual([...new Uint8Array(ab)], original); + } +} + +// Mutable buffers are unaffected. +{ + const dest = new Uint8Array(4); + assert.deepStrictEqual(encoder.encodeInto('hi', dest), { read: 2, written: 2 }); + assert.deepStrictEqual([...dest], [104, 105, 0, 0]); +}