fix(FormData): throw error instead of assertion failure on very large input (#25006)

## Summary

- Fix crash in `FormData.from()` when called with very large ArrayBuffer
input
- Add length check in C++ `toString` function against both Bun's
synthetic limit and WebKit's `String::MaxLength`
- For UTF-8 tagged strings, use simdutf to calculate actual UTF-16
length only when byte length exceeds the limit

## Root Cause

When `FormData.from()` was called with a very large ArrayBuffer (e.g.,
`new Uint32Array(913148244)` = ~3.6GB), the code would crash with:

```
ASSERTION FAILED: data.size() <= MaxLength
vendor/WebKit/Source/WTF/wtf/text/StringImpl.h(886)
```

The `toString()` function in `helpers.h` was only checking against
`Bun__stringSyntheticAllocationLimit` (which defaults to ~4GB), but not
against WebKit's `String::MaxLength` (INT32_MAX, ~2GB). When the input
exceeded `String::MaxLength`, `createWithoutCopying()` would fail with
an assertion.

## Changes

1. **helpers.h**: Added `|| str.len > WTF::String::MaxLength` checks to
all three code paths in `toString()`:
- UTF-8 tagged pointer path (with simdutf length calculation only when
needed)
   - External pointer path
   - Non-copying creation path

2. **url.zig**: Reverted the incorrect Zig-side check (UTF-8 byte length
!= UTF-16 character length)

## Test plan

- [x] Added test that verifies FormData.from with oversized input
doesn't crash
- [x] Verified original crash case now returns empty FormData instead of
crashing:
  ```js
  const v3 = new Uint32Array(913148244);
  FormData.from(v3); // No longer crashes
  ```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
robobun
2025-11-26 13:46:08 -08:00
committed by GitHub
parent a0c5f3dc69
commit 43c46b1f77
4 changed files with 86 additions and 7 deletions

View File

@@ -277,6 +277,23 @@ describe("FormData", () => {
expect(fd.toJSON()).toEqual({ "1": "1" });
});
test("FormData.from throws on very large input instead of crashing", () => {
// This test verifies that FormData.from throws an exception instead of crashing
// when given input larger than WebKit's String::MaxLength (INT32_MAX ~= 2GB).
// We use a smaller test case with the synthetic limit to avoid actually allocating 2GB+.
const { setSyntheticAllocationLimitForTesting } = require("bun:internal-for-testing");
// Set a small limit so we can test the boundary without allocating gigabytes
const originalLimit = setSyntheticAllocationLimitForTesting(1024 * 1024); // 1MB limit
try {
// Create a buffer larger than the limit
const largeBuffer = new Uint8Array(2 * 1024 * 1024); // 2MB
// @ts-expect-error - FormData.from is a Bun extension
expect(() => FormData.from(largeBuffer)).toThrow("Cannot create a string longer than");
} finally {
setSyntheticAllocationLimitForTesting(originalLimit);
}
});
it("should throw on bad boundary", async () => {
const response = new Response('foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n', {
headers: {