Skip to content

Commit 3ac3082

Browse files
Claude Botclaude
andcommitted
fix(FormData): throw error instead of crashing on very large input
When `FormData.from()` is called with a very large ArrayBuffer (exceeding WebKit's String::MaxLength of INT32_MAX), it would crash with an assertion failure in WebKit's StringImpl. This fixes the issue by: 1. Adding a length check in the C++ `toString` function (helpers.h) for UTF8-tagged strings to check against both Bun's synthetic limit and WebKit's String::MaxLength before attempting to create the string. 2. Adding a length check in the Zig `FormData.toJS` function to throw a proper error message before attempting to create the string. Now `FormData.from(new Uint32Array(913148244))` throws a proper JavaScript error instead of crashing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent ddcec61 commit 3ac3082

File tree

3 files changed

+26
-1
lines changed

3 files changed

+26
-1
lines changed

src/bun.js/bindings/helpers.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ static const WTF::String toString(ZigString str)
7979
}
8080
if (isTaggedUTF8Ptr(str.ptr)) [[unlikely]] {
8181
ASSERT_WITH_MESSAGE(!isTaggedExternalPtr(str.ptr), "UTF8 and external ptr are mutually exclusive. The external will never be freed.");
82+
// This will fail if the string is too long. Let's make it explicit instead of an ASSERT.
83+
// Check both the Bun synthetic limit and WebKit's String::MaxLength.
84+
if (str.len > Bun__stringSyntheticAllocationLimit || str.len > WTF::String::MaxLength) [[unlikely]] {
85+
return {};
86+
}
8287
return WTF::String::fromUTF8ReplacingInvalidSequences(std::span { untag(str.ptr), str.len });
8388
}
8489

src/url.zig

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,12 @@ pub const FormData = struct {
983983
pub fn toJS(globalThis: *jsc.JSGlobalObject, input: []const u8, encoding: Encoding) !jsc.JSValue {
984984
switch (encoding) {
985985
.URLEncoded => {
986-
var str = jsc.ZigString.fromUTF8(strings.withoutUTF8BOM(input));
986+
const data = strings.withoutUTF8BOM(input);
987+
// Check against both Bun's synthetic limit and WebKit's String::MaxLength (INT32_MAX)
988+
if (data.len > bun.String.max_length() or data.len > std.math.maxInt(i32)) {
989+
return error.@"Cannot create a string longer than 2^32-1 characters";
990+
}
991+
var str = jsc.ZigString.fromUTF8(data);
987992
return jsc.DOMFormData.createFromURLQuery(globalThis, &str);
988993
},
989994
.Multipart => |boundary| return toJSFromMultipartData(globalThis, input, boundary),

test/js/web/html/FormData.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,21 @@ describe("FormData", () => {
277277
expect(fd.toJSON()).toEqual({ "1": "1" });
278278
});
279279

280+
test("FormData.from throws on very large input instead of crashing", () => {
281+
const { setSyntheticAllocationLimitForTesting } = require("bun:internal-for-testing");
282+
const originalLimit = setSyntheticAllocationLimitForTesting(1024 * 1024); // 1MB limit
283+
try {
284+
// Create a buffer larger than the limit
285+
const largeBuffer = new Uint8Array(2 * 1024 * 1024); // 2MB
286+
// @ts-expect-error
287+
expect(() => FormData.from(largeBuffer)).toThrow(
288+
"Cannot create a string longer than 2^32-1 characters while parsing FormData",
289+
);
290+
} finally {
291+
setSyntheticAllocationLimitForTesting(originalLimit);
292+
}
293+
});
294+
280295
it("should throw on bad boundary", async () => {
281296
const response = new Response('foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n', {
282297
headers: {

0 commit comments

Comments
 (0)