Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/send-exact-byte-views.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"capnweb": patch
---

Fix `MessagePort` sessions leaking a byte view's entire backing buffer instead of only the view's bytes.
29 changes: 29 additions & 0 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2939,6 +2939,35 @@ describe("MessagePorts", () => {
await expect(() => brokenPromise).rejects.toThrow(
new Error("Peer closed MessagePort connection."));
});

it("sends only the bytes a view covers, not the rest of its backing buffer", async () => {
if (typeof Buffer === "undefined") return; // skip in browsers

// Small Node Buffers are views into a shared pool of unrelated data, and structured clone
// copies a view's whole backing buffer. Buffer and Int16Array cover the unmarked and
// type-marked encode paths.
let backing = new Uint8Array(64).map((_, i) => i).buffer;
let buffer = Buffer.from(backing, 8, 4);
let int16 = new Int16Array(backing, 16, 2);
class BytesServer extends RpcTarget {
getBytes() { return [buffer, int16]; }
}

let channel = new MessageChannel();
// Inspect messages as they arrive: decoding rewrites them in place.
let crossedBufferSizes: number[] = [];
function collect(value: unknown) {
if (ArrayBuffer.isView(value)) crossedBufferSizes.push(value.buffer.byteLength);
else if (value instanceof Array) value.forEach(collect);
}
channel.port2.addEventListener("message", event => collect(event.data));

newMessagePortRpcSession(channel.port1, new BytesServer());
using stub = newMessagePortRpcSession<BytesServer>(channel.port2);

expect(await stub.getBytes()).toStrictEqual([new Uint8Array(buffer), int16]);
expect(crossedBufferSizes).toStrictEqual([4, 4]);
});
});

// =======================================================================================
Expand Down
4 changes: 4 additions & 0 deletions src/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,10 @@ export class Devaluator {
// At structuredClonable or jsonCompatibleWithBytes level, keep the bytes raw.
if (this.encodingLevel === "structuredClonable" ||
this.encodingLevel === "jsonCompatibleWithBytes") {
// Copy partial views so the transport can't send the whole backing buffer.
if (bytes.byteLength !== bytes.buffer.byteLength) {
bytes = new Uint8Array(bytes);
}
return alternateTypeName === undefined
? ["bytes", bytes] : ["bytes", bytes, alternateTypeName];
}
Expand Down
Loading