Skip to content

fix(http): guard Buffer/TypedArray in dynamic add ToPrimitive (#5131)#5176

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-fix-5131-http-body
Jun 15, 2026
Merged

fix(http): guard Buffer/TypedArray in dynamic add ToPrimitive (#5131)#5176
proggeramlug merged 1 commit into
mainfrom
worktree-fix-5131-http-body

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #5131 — a node:http server that consumes the request body via req.on("data", c => body += c) on a POST carrying a body segfaulted (SIGSEGV / exit 139).

Root cause

The un-typed data chunk is a Buffer, so body += c (with both operands dynamic) lowers to the fully-dynamic add helper js_dynamic_string_or_number_addto_primitive_default_for_add (crates/perry-runtime/src/value/dynamic_arith.rs).

That function had no Buffer/TypedArray guard, so it fell through to the js_url_href_if_url / try_read_as_search_params / OrdinaryToPrimitive probes — all of which bit-cast the operand pointer to an ObjectHeader and read its fields. A BufferHeader carries no ObjectHeader/GcHeader, so the probe dereferenced a fake header one word before the data and crashed.

The same concat with a statically-typed const c = Buffer.from(...) worked, because codegen proves c non-string and uses the safe js_string_concat_value coerce path (which routes through js_jsvalue_to_string's existing buffer guard). The crash only manifests when the operand is dynamic — exactly the case for an un-typed http data chunk.

Fix

Detect Buffers/TypedArrays via their registries (by-value lookups, no deref) in to_primitive_default_for_add before the ObjectHeader probes, and route them to js_jsvalue_to_string. This mirrors the guards js_jsvalue_to_string itself runs before its ordinary-object dispatch, and makes dynamic + consistent with String() and template-literal coercion (Buffer→utf8, TypedArray→join(",")).

Verification

  • The issue's exact repro now prints len:13 (was SIGSEGV).
  • Verified consistency: String(x), `${x}`, x.toString(), and dynamic a + x all agree for both Buffer and Uint8Array.
  • E2E with a >256-byte body (registry-path buffer, not slab) + JSON.parse works.
  • cargo test -p perry-runtime / -p perry-ext-http-server pass (single-threaded; the one multi-threaded failure, object::tests::builtin_prototype_methods_reject_dynamic_new, is a pre-existing concurrency flake that reproduces identically on main).
  • Adds regression test test-files/test_issue_5131_http_body_concat.ts (+ expected len:13).

Note for maintainer

Per the project's PR convention, this branch does not bump [workspace.package] version, the Current Version line, or CHANGELOG.md — left for the version/changelog fold at merge.

🤖 Generated with Claude Code

Summary by CodeRabbit

Bug Fixes

  • Fixed a critical runtime crash occurring during HTTP request body concatenation when Buffer and TypedArray objects were encountered in string concatenation operations. Added runtime safety guards to properly detect and convert these binary data types before processing, preventing segmentation faults and ensuring reliable, stable handling of streaming payloads.

A `node:http` server that consumes the request body via
`req.on("data", c => body += c)` on a POST carrying a body segfaulted
(SIGSEGV / exit 139).

The un-typed `data` chunk is a `Buffer`, so `body += c` lowers to the
fully-dynamic add helper `js_dynamic_string_or_number_add` →
`to_primitive_default_for_add`. That function had no Buffer/TypedArray
guard, so it fell through to the `js_url_href_if_url` /
`try_read_as_search_params` / `OrdinaryToPrimitive` probes, all of which
bit-cast the operand pointer to an `ObjectHeader` and read its fields. A
`BufferHeader` carries no `ObjectHeader`/`GcHeader`, so the probe
dereferenced a fake header one word before the data and crashed.

(The same concat with a statically-typed `const c = Buffer.from(...)`
worked: codegen proves `c` non-string and uses the safe
`js_string_concat_value` coerce path, which routes through
`js_jsvalue_to_string`'s existing buffer guard.)

Fix: detect Buffers/TypedArrays via their registries (by-value lookups,
no deref) in `to_primitive_default_for_add` before the ObjectHeader
probes and route them to `js_jsvalue_to_string` — yielding the same
string form as an explicit `.toString()` (Buffer→utf8,
TypedArray→`join(",")`) and matching the guards `js_jsvalue_to_string`
itself runs. This also makes dynamic `+` consistent with `String()` and
template-literal coercion.

Adds regression test `test_issue_5131_http_body_concat` (expected `len:13`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 439a2b6c-3a11-4788-8bf5-9f16fec93dec

📥 Commits

Reviewing files that changed from the base of the PR and between 08819f0 and 2058984.

📒 Files selected for processing (3)
  • crates/perry-runtime/src/value/dynamic_arith.rs
  • test-files/test_issue_5131_http_body_concat.ts
  • test-parity/expected/test_issue_5131_http_body_concat.txt

📝 Walkthrough

Walkthrough

Adds a runtime safety guard in to_primitive_default_for_add inside dynamic_arith.rs to detect Buffer and TypedArray operands via their registries and convert them to strings without touching a potentially invalid ObjectHeader. A regression test and expected output fixture for Issue #5131 are added alongside.

Changes

Buffer/TypedArray String Concat Safety Fix

Layer / File(s) Summary
Buffer/TypedArray early coercion guard
crates/perry-runtime/src/value/dynamic_arith.rs
Inserts a 20-line block before existing object-shape probes in to_primitive_default_for_add: checks the buffer registry and typed array kind lookup, calls js_jsvalue_to_string on a match, and returns the nanboxed result, avoiding a segfault on invalid ObjectHeader dereference.
HTTP POST body concat regression test and fixture
test-files/test_issue_5131_http_body_concat.ts, test-parity/expected/test_issue_5131_http_body_concat.txt
Adds an HTTP server that accumulates POST body chunks via body += c and responds with len:${body.length}, plus a parity fixture asserting the output len:13 for the "payload-bytes" payload.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • PerryTS/perry#5108: Modifies the same function to_primitive_default_for_add in dynamic_arith.rs by adding early operand-detection branches for URL/URLSearchParams coercion, directly parallel to this Buffer/TypedArray guard.
  • PerryTS/perry#5168: Targets the same Issue #5131 unsafe Buffer/POST-body concatenation behavior with complementary regression tests expecting len:13.

Poem

🐇 A Buffer once crashed through the add-op gate,
dereferencing headers that weren't quite straight.
Now the runtime peeks at the registry first,
converts to a string — avoids the worst!
len:13 prints clean, no SIGSEGV in sight,
the little green rabbit hops off into the night. 🌙

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: adding a guard for Buffer/TypedArray in the dynamic addition ToPrimitive operation, directly addressing issue #5131.
Description check ✅ Passed The PR description comprehensively covers the summary, root cause analysis, the implemented fix, and verification steps, following the template structure with all key sections filled in.
Linked Issues check ✅ Passed The code changes directly address issue #5131 by adding Buffer/TypedArray guards in to_primitive_default_for_add, fixing the SIGSEGV crash when consuming HTTP request bodies via string concatenation, and the regression test verifies the exact repro case.
Out of Scope Changes check ✅ Passed All changes are tightly scoped to fix issue #5131: runtime guard in dynamic arithmetic, regression test file, and expected output file—no extraneous modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-5131-http-body

Comment @coderabbitai help to get the list of available commands and usage tips.

@proggeramlug proggeramlug merged commit d39daf7 into main Jun 15, 2026
15 checks passed
@proggeramlug proggeramlug deleted the worktree-fix-5131-http-body branch June 15, 2026 07:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

node:http server segfaults when consuming request body (req.on('data')/'end') on a POST

1 participant