LibWeb: Prevent crash when encoding into detached buffer

This handles the case where data is encoded into a detached buffer.
This commit is contained in:
Glenn Skrzypczak 2025-04-18 18:35:44 +02:00
parent b88fddbf47
commit fa1f6d654a
4 changed files with 301 additions and 0 deletions

View file

@ -53,6 +53,12 @@ GC::Ref<JS::Uint8Array> TextEncoder::encode(String const& input) const
// https://encoding.spec.whatwg.org/#dom-textencoder-encodeinto
TextEncoderEncodeIntoResult TextEncoder::encode_into(String const& source, GC::Root<WebIDL::BufferSource> const& destination) const
{
// AD-HOC: Return early if destination is detached. This is not explicitly handled in the spec,
// however no bytes are copied as destinations size is always zero in this case.
// See: https://github.com/whatwg/encoding/issues/324
if (destination->viewed_array_buffer()->is_detached())
return { 0, 0 };
auto data = destination->viewed_array_buffer()->buffer().bytes().slice(destination->byte_offset(), destination->byte_length());
// 1. Let read be 0.
@ -91,6 +97,7 @@ TextEncoderEncodeIntoResult TextEncoder::encode_into(String const& source, GC::R
// 6.4.1.3. Write the bytes in result into destination, with startingOffset set to written.
// 6.4.1.4. Increment written by the number of bytes in result.
// WARNING: See the warning for SharedArrayBuffer objects at https://encoding.spec.whatwg.org/#sharedarraybuffer-warning.
for (auto byte : result)
data[written++] = byte;
}

View file

@ -0,0 +1,117 @@
Harness status: OK
Found 111 tests
85 Pass
26 Fail
Pass encodeInto() into ArrayBuffer with Hi and destination length 0, offset 0, filler 0
Pass encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 0, filler 0
Pass encodeInto() into ArrayBuffer with Hi and destination length 0, offset 4, filler 0
Pass encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 4, filler 0
Pass encodeInto() into ArrayBuffer with Hi and destination length 0, offset 0, filler 128
Pass encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 0, filler 128
Pass encodeInto() into ArrayBuffer with Hi and destination length 0, offset 4, filler 128
Pass encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 4, filler 128
Pass encodeInto() into ArrayBuffer with Hi and destination length 0, offset 0, filler random
Pass encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 0, filler random
Pass encodeInto() into ArrayBuffer with Hi and destination length 0, offset 4, filler random
Pass encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 4, filler random
Pass encodeInto() into ArrayBuffer with A and destination length 10, offset 0, filler 0
Pass encodeInto() into SharedArrayBuffer with A and destination length 10, offset 0, filler 0
Pass encodeInto() into ArrayBuffer with A and destination length 10, offset 4, filler 0
Pass encodeInto() into SharedArrayBuffer with A and destination length 10, offset 4, filler 0
Pass encodeInto() into ArrayBuffer with A and destination length 10, offset 0, filler 128
Pass encodeInto() into SharedArrayBuffer with A and destination length 10, offset 0, filler 128
Pass encodeInto() into ArrayBuffer with A and destination length 10, offset 4, filler 128
Pass encodeInto() into SharedArrayBuffer with A and destination length 10, offset 4, filler 128
Pass encodeInto() into ArrayBuffer with A and destination length 10, offset 0, filler random
Pass encodeInto() into SharedArrayBuffer with A and destination length 10, offset 0, filler random
Pass encodeInto() into ArrayBuffer with A and destination length 10, offset 4, filler random
Pass encodeInto() into SharedArrayBuffer with A and destination length 10, offset 4, filler random
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 0, filler 0
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 0, filler 0
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 4, filler 0
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 4, filler 0
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 0, filler 128
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 0, filler 128
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 4, filler 128
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 4, filler 128
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 0, filler random
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 0, filler random
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 4, filler random
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06 and destination length 4, offset 4, filler random
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 0, filler 0
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 0, filler 0
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 4, filler 0
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 4, filler 0
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 0, filler 128
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 0, filler 128
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 4, filler 128
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 4, filler 128
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 0, filler random
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 0, filler random
Pass encodeInto() into ArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 4, filler random
Pass encodeInto() into SharedArrayBuffer with <20><><EFBFBD>U+df06A and destination length 3, offset 4, filler random
Pass encodeInto() into ArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 0, filler 0
Pass encodeInto() into SharedArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 0, filler 0
Pass encodeInto() into ArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 4, filler 0
Pass encodeInto() into SharedArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 4, filler 0
Pass encodeInto() into ArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 0, filler 128
Pass encodeInto() into SharedArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 0, filler 128
Pass encodeInto() into ArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 4, filler 128
Pass encodeInto() into SharedArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 4, filler 128
Pass encodeInto() into ArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 0, filler random
Pass encodeInto() into SharedArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 0, filler random
Pass encodeInto() into ArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 4, filler random
Pass encodeInto() into SharedArrayBuffer with U+d834AU+df06A¥Hi and destination length 10, offset 4, filler random
Pass encodeInto() into ArrayBuffer with AU+df06 and destination length 4, offset 0, filler 0
Pass encodeInto() into SharedArrayBuffer with AU+df06 and destination length 4, offset 0, filler 0
Pass encodeInto() into ArrayBuffer with AU+df06 and destination length 4, offset 4, filler 0
Pass encodeInto() into SharedArrayBuffer with AU+df06 and destination length 4, offset 4, filler 0
Pass encodeInto() into ArrayBuffer with AU+df06 and destination length 4, offset 0, filler 128
Pass encodeInto() into SharedArrayBuffer with AU+df06 and destination length 4, offset 0, filler 128
Pass encodeInto() into ArrayBuffer with AU+df06 and destination length 4, offset 4, filler 128
Pass encodeInto() into SharedArrayBuffer with AU+df06 and destination length 4, offset 4, filler 128
Pass encodeInto() into ArrayBuffer with AU+df06 and destination length 4, offset 0, filler random
Pass encodeInto() into SharedArrayBuffer with AU+df06 and destination length 4, offset 0, filler random
Pass encodeInto() into ArrayBuffer with AU+df06 and destination length 4, offset 4, filler random
Pass encodeInto() into SharedArrayBuffer with AU+df06 and destination length 4, offset 4, filler random
Pass encodeInto() into ArrayBuffer with ¥¥ and destination length 4, offset 0, filler 0
Pass encodeInto() into SharedArrayBuffer with ¥¥ and destination length 4, offset 0, filler 0
Pass encodeInto() into ArrayBuffer with ¥¥ and destination length 4, offset 4, filler 0
Pass encodeInto() into SharedArrayBuffer with ¥¥ and destination length 4, offset 4, filler 0
Pass encodeInto() into ArrayBuffer with ¥¥ and destination length 4, offset 0, filler 128
Pass encodeInto() into SharedArrayBuffer with ¥¥ and destination length 4, offset 0, filler 128
Pass encodeInto() into ArrayBuffer with ¥¥ and destination length 4, offset 4, filler 128
Pass encodeInto() into SharedArrayBuffer with ¥¥ and destination length 4, offset 4, filler 128
Pass encodeInto() into ArrayBuffer with ¥¥ and destination length 4, offset 0, filler random
Pass encodeInto() into SharedArrayBuffer with ¥¥ and destination length 4, offset 0, filler random
Pass encodeInto() into ArrayBuffer with ¥¥ and destination length 4, offset 4, filler random
Pass encodeInto() into SharedArrayBuffer with ¥¥ and destination length 4, offset 4, filler random
Fail Invalid encodeInto() destination: DataView, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: DataView, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: Int8Array, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: Int8Array, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: Int16Array, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: Int16Array, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: Int32Array, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: Int32Array, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: Uint16Array, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: Uint16Array, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: Uint32Array, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: Uint32Array, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: Uint8ClampedArray, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: Uint8ClampedArray, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: BigInt64Array, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: BigInt64Array, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: BigUint64Array, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: BigUint64Array, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: Float16Array, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: Float16Array, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: Float32Array, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: Float32Array, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: Float64Array, backed by: ArrayBuffer
Fail Invalid encodeInto() destination: Float64Array, backed by: SharedArrayBuffer
Fail Invalid encodeInto() destination: ArrayBuffer
Fail Invalid encodeInto() destination: SharedArrayBuffer
Pass encodeInto() and a detached output buffer

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="../common/sab.js"></script>
<div id=log></div>
<script src="../encoding/encodeInto.any.js"></script>

View file

@ -0,0 +1,162 @@
// META: global=window,worker
// META: script=/common/sab.js
[
{
"input": "Hi",
"read": 0,
"destinationLength": 0,
"written": []
},
{
"input": "A",
"read": 1,
"destinationLength": 10,
"written": [0x41]
},
{
"input": "\u{1D306}", // "\uD834\uDF06"
"read": 2,
"destinationLength": 4,
"written": [0xF0, 0x9D, 0x8C, 0x86]
},
{
"input": "\u{1D306}A",
"read": 0,
"destinationLength": 3,
"written": []
},
{
"input": "\uD834A\uDF06A¥Hi",
"read": 5,
"destinationLength": 10,
"written": [0xEF, 0xBF, 0xBD, 0x41, 0xEF, 0xBF, 0xBD, 0x41, 0xC2, 0xA5]
},
{
"input": "A\uDF06",
"read": 2,
"destinationLength": 4,
"written": [0x41, 0xEF, 0xBF, 0xBD]
},
{
"input": "¥¥",
"read": 2,
"destinationLength": 4,
"written": [0xC2, 0xA5, 0xC2, 0xA5]
}
].forEach(testData => {
[
{
"bufferIncrease": 0,
"destinationOffset": 0,
"filler": 0
},
{
"bufferIncrease": 10,
"destinationOffset": 4,
"filler": 0
},
{
"bufferIncrease": 0,
"destinationOffset": 0,
"filler": 0x80
},
{
"bufferIncrease": 10,
"destinationOffset": 4,
"filler": 0x80
},
{
"bufferIncrease": 0,
"destinationOffset": 0,
"filler": "random"
},
{
"bufferIncrease": 10,
"destinationOffset": 4,
"filler": "random"
}
].forEach(destinationData => {
["ArrayBuffer", "SharedArrayBuffer"].forEach(arrayBufferOrSharedArrayBuffer => {
test(() => {
// Setup
const bufferLength = testData.destinationLength + destinationData.bufferIncrease;
const destinationOffset = destinationData.destinationOffset;
const destinationLength = testData.destinationLength;
const destinationFiller = destinationData.filler;
const encoder = new TextEncoder();
const buffer = createBuffer(arrayBufferOrSharedArrayBuffer, bufferLength);
const view = new Uint8Array(buffer, destinationOffset, destinationLength);
const fullView = new Uint8Array(buffer);
const control = new Array(bufferLength);
let byte = destinationFiller;
for (let i = 0; i < bufferLength; i++) {
if (destinationFiller === "random") {
byte = Math.floor(Math.random() * 256);
}
control[i] = byte;
fullView[i] = byte;
}
// It's happening
const result = encoder.encodeInto(testData.input, view);
// Basics
assert_equals(view.byteLength, destinationLength);
assert_equals(view.length, destinationLength);
// Remainder
assert_equals(result.read, testData.read);
assert_equals(result.written, testData.written.length);
for (let i = 0; i < bufferLength; i++) {
if (i < destinationOffset || i >= (destinationOffset + testData.written.length)) {
assert_equals(fullView[i], control[i]);
} else {
assert_equals(fullView[i], testData.written[i - destinationOffset]);
}
}
}, "encodeInto() into " + arrayBufferOrSharedArrayBuffer + " with " + testData.input + " and destination length " + testData.destinationLength + ", offset " + destinationData.destinationOffset + ", filler " + destinationData.filler);
})
});
});
["DataView",
"Int8Array",
"Int16Array",
"Int32Array",
"Uint16Array",
"Uint32Array",
"Uint8ClampedArray",
"BigInt64Array",
"BigUint64Array",
"Float16Array",
"Float32Array",
"Float64Array"].forEach(type => {
["ArrayBuffer", "SharedArrayBuffer"].forEach((arrayBufferOrSharedArrayBuffer) => {
test(() => {
const viewInstance = new self[type](createBuffer(arrayBufferOrSharedArrayBuffer, 0));
assert_throws_js(TypeError, () => new TextEncoder().encodeInto("", viewInstance));
}, "Invalid encodeInto() destination: " + type + ", backed by: " + arrayBufferOrSharedArrayBuffer);
});
});
["ArrayBuffer", "SharedArrayBuffer"].forEach((arrayBufferOrSharedArrayBuffer) => {
test(() => {
assert_throws_js(TypeError, () => new TextEncoder().encodeInto("", createBuffer(arrayBufferOrSharedArrayBuffer, 10)));
}, "Invalid encodeInto() destination: " + arrayBufferOrSharedArrayBuffer);
});
test(() => {
const buffer = new ArrayBuffer(10),
view = new Uint8Array(buffer);
let { read, written } = new TextEncoder().encodeInto("", view);
assert_equals(read, 0);
assert_equals(written, 0);
new MessageChannel().port1.postMessage(buffer, [buffer]);
({ read, written } = new TextEncoder().encodeInto("", view));
assert_equals(read, 0);
assert_equals(written, 0);
({ read, written } = new TextEncoder().encodeInto("test", view));
assert_equals(read, 0);
assert_equals(written, 0);
}, "encodeInto() and a detached output buffer");