fix: snapshot SharedArrayBuffer bytes before UTF-8 string conversion

Closed
DY
4mo ago
+181-11

Bun's UTF-8 → UTF-16 conversion (strings.toUTF16Alloc / toUTF16AllocMaybeBuffered and WTF::String::fromUTF8) works in two passes: measure the output length, allocate exactly that, then convert. When the input is a view into a SharedArrayBuffer, a Worker can mutate the bytes between the two passes (e.g. 3-byte € sequences → ASCII), causing the second pass to write up to 3× more code units than were allocated — a heap buffer overflow.

This snapshots the input into owned memory at the JS boundary when isShared(), before handing it to the converter. Non-shared ArrayBuffers are stable for the duration of a synchronous native call, so they skip the copy.

Affected APIs:

  • Buffer.prototype.toString('utf8') / utf8Slice() — snapshot in jsBufferToStringFromBytes when the view is shared
  • TextDecoder.prototype.decode (both the generic and DOMJIT paths) — snapshot when ArrayBuffer.shared
  • StringDecoder.prototype.write/end/text — snapshot when view->isShared()
  • process.binding('http_parser').HTTPParser.prototype.execute — reject SharedArrayBuffer-backed input (internal binding, never legitimately receives one)

Also adds a JSUint8Array.isShared() Zig helper and documents the stability contract on toUTF16Alloc, toUTF16AllocMaybeBuffered, and BunString__fromUTF8.

The included test reliably segfaults on an unfixed build (fault address 0x41004100410041 — the racing 'A' bytes overwriting a pointer) and passes with this change.