fix: Correctly handle shared memory for DataView initialization in deserialize_binary...
authorottomated <31470743+ottomated@users.noreply.github.com>
Fri, 5 Dec 2025 00:11:20 +0000 (16:11 -0800)
committerGitHub <noreply@github.com>
Fri, 5 Dec 2025 00:11:20 +0000 (17:11 -0700)
* Fix DataView initialization in form-utils.js:deserialize_binary_form

When you call get_buffer, it returns a Uint8Array. In many JavaScript runtime environments (like Cloudflare Workers, Node.js, or modern browsers), streams often allocate chunks from a shared memory pool (slab allocation). This means the Uint8Array you get back is a view into a much larger ArrayBuffer, and it often has a non-zero byteOffset.

When you pass header.buffer to DataView, it creates a view starting at the very beginning (byte 0) of the underlying memory allocation, completely ignoring the byteOffset of your header array.

Because the header data usually lives at a specific offset inside that buffer, your code reads 4 bytes from the wrong location (the start of the memory slab), interprets that garbage data as a 32-bit integer, and gets 16793089 (in my test case). It then tries to read ~16MB of data, runs out of stream, and throws "data too short".

You must pass the byteOffset and byteLength to the DataView constructor to ensure it looks at the correct slice of memory.

I have confirmed on my code that this fix resolves my problem.

* Remove unnecessary comment

* add test & changeset

---------

Co-authored-by: Marat Vyshegorodtsev <github@v.marat.to>
Co-authored-by: Tee Ming <chewteeming01@gmail.com>
.changeset/thick-spiders-invite.md [new file with mode: 0644]
packages/kit/src/runtime/form-utils.js
packages/kit/src/runtime/form-utils.spec.js

diff --git a/.changeset/thick-spiders-invite.md b/.changeset/thick-spiders-invite.md
new file mode 100644 (file)
index 0000000..366d65f
--- /dev/null
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: Correctly handle shared memory when decoding binary form data
index 5e5cc23e1bb8b1c8ea3771db20295ea2f62b11b8..78554b4e058b34972391e895c3a5efd85f7cfe12 100644 (file)
@@ -220,7 +220,7 @@ export async function deserialize_binary_form(request) {
                        `Could not deserialize binary form: got version ${header[0]}, expected version ${BINARY_FORM_VERSION}`
                );
        }
-       const header_view = new DataView(header.buffer);
+       const header_view = new DataView(header.buffer, header.byteOffset, header.byteLength);
        const data_length = header_view.getUint32(1, true);
        const file_offsets_length = header_view.getUint16(5, true);
 
index 88409435cbe0f2b97f2ff227eb39409105640431..96b153c4a3b76ece8e8d2317a1606eb30a0d1e9a 100644 (file)
@@ -213,4 +213,33 @@ describe('binary form serializer', () => {
                expect(await world_slice.text()).toBe('World');
                expect(world_slice.type).toBe(file.type);
        });
+
+       // Regression test for https://github.com/sveltejs/kit/issues/14971
+       test('DataView offset for shared memory', async () => {
+               const { blob } = serialize_binary_form({ a: 1 }, {});
+               const chunk = new Uint8Array(await blob.arrayBuffer());
+               // Simulate a stream that has extra bytes at the start in the underlying buffer
+               const stream = new ReadableStream({
+                       start(controller) {
+                               const offset_buffer = new Uint8Array(chunk.byteLength + 10);
+                               offset_buffer.fill(255);
+                               offset_buffer.set(chunk, 10);
+                               controller.enqueue(offset_buffer.subarray(10));
+                       }
+               });
+
+               const res = await deserialize_binary_form(
+                       new Request('http://test', {
+                               method: 'POST',
+                               body: stream,
+                               // @ts-expect-error duplex required in node
+                               duplex: 'half',
+                               headers: {
+                                       'Content-Type': BINARY_FORM_CONTENT_TYPE
+                               }
+                       })
+               );
+
+               expect(res.data).toEqual({ a: 1 });
+       });
 });