Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/jsc/bindings/webcore/SerializedScriptValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6642,6 +6642,23 @@ JSC::JSValue SerializedScriptValue::fromArrayBuffer(JSC::JSGlobalObject& domGlob

return JSC::jsUndefined();
}

auto size = std::min(arrayBuffer->byteLength(), maxByteLength);

// CloneDeserializer reads empty data as WebCore's null-value sentinel
// (SerializedScriptValue::nullValue()). Bytes handed to us by a caller carry no such
// meaning: zero bytes have no version header, so reject them like any other bad input.
if (!size) {
if (didFail)
*didFail = true;

if (throwExceptions == SerializationErrorMode::Throwing)
maybeThrowExceptionIfSerializationFailed(*globalObject, SerializationReturnCode::ValidationError);
RETURN_IF_EXCEPTION(throwScope, {});

return JSC::jsUndefined();
}

auto blobURLs = Vector<String> {};
auto blobFiles = Vector<String> {};

Expand All @@ -6651,7 +6668,6 @@ JSC::JSValue SerializedScriptValue::fromArrayBuffer(JSC::JSGlobalObject& domGlob
}

auto* data = static_cast<uint8_t*>(arrayBuffer->data()) + byteOffset;
auto size = std::min(arrayBuffer->byteLength(), maxByteLength);
auto span = std::span<uint8_t> { data, size };

auto result = CloneDeserializer::deserialize(&domGlobal, globalObject, {}, nullptr, span, blobURLs, blobFiles, nullptr
Expand Down
32 changes: 32 additions & 0 deletions test/js/bun/jsc/bun-jsc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from "bun:jsc";
import { describe, expect, it } from "bun:test";
import { bunEnv, bunExe, isBuildKite, isWindows } from "harness";
import v8 from "node:v8";

describe("bun:jsc", () => {
function count() {
Expand Down Expand Up @@ -223,6 +224,37 @@ describe("bun:jsc", () => {
});
});

describe("deserialize rejects input with no bytes", () => {
// Zero bytes cannot encode a value, so they must fail the same way any other
// payload without a version header does, instead of producing a plausible null.
function expectRejected(fn: () => unknown) {
expect(fn).toThrow(TypeError);
expect(fn).toThrow("Unable to deserialize data.");
}

it.each([
["Buffer.alloc(0)", () => Buffer.alloc(0)],
["new Uint8Array(0)", () => new Uint8Array(0)],
["new DataView(new ArrayBuffer(0))", () => new DataView(new ArrayBuffer(0))],
["new ArrayBuffer(0)", () => new ArrayBuffer(0)],
["new SharedArrayBuffer(0)", () => new SharedArrayBuffer(0)],
["a zero-length view of a non-empty buffer", () => new Uint8Array(new ArrayBuffer(8), 4, 0)],
])("%s", (_label, make) => {
expectRejected(() => deserialize(make()));
expectRejected(() => v8.deserialize(make() as any));
});

it("still deserializes a value that really is null", () => {
expect(deserialize(serialize(null))).toBeNull();
expect(v8.deserialize(v8.serialize(null))).toBeNull();
});

it("still deserializes a serialized empty buffer", () => {
expect(deserialize(serialize(Buffer.alloc(0)))).toStrictEqual(Buffer.alloc(0));
expect(v8.deserialize(v8.serialize(Buffer.alloc(0)))).toStrictEqual(Buffer.alloc(0));
});
});

it("deserialize rejects an object reference index outside the deserialized object pool", async () => {
// A payload whose first value is ObjectReferenceTag must have its pool index
// validated against the number of objects deserialized so far (zero here),
Expand Down
Loading