Skip to content

Fix null deref in forEachProperty with Proxy in prototype chain - #29816

Closed
robobun wants to merge 2 commits into
mainfrom
farm/8928bc04/fix-foreach-property-proxy-exception
Closed

Fix null deref in forEachProperty with Proxy in prototype chain#29816
robobun wants to merge 2 commits into
mainfrom
farm/8928bc04/fix-foreach-property-proxy-exception

Conversation

@robobun

@robobun robobun commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Fixes a null pointer dereference in JSC__JSValue__forEachPropertyImpl (used by Bun.inspect / console.log) when the object being inspected has a Proxy in its prototype chain.

Repro

const proto = {
  get foo() { throw new Error("boom"); },
};
const obj = Object.create(new Proxy(proto, {}));
console.log(obj); // crashed
const obj = Object.create(new Proxy({ foo: 1 }, {
  getPrototypeOf() { throw new Error("trap"); },
}));
console.log(obj); // crashed

Root cause

  1. When a Proxy sits in the prototype chain, getPropertySlot() with InternalMethodType::Get invokes the proxy's [[Get]], which (with the default trap) evaluates getters on the target. If a getter throws, getPropertySlot() returns false with the exception still pending. The code did if (!getPropertySlot(...)) continue;, skipping the CLEAR_IF_EXCEPTION that follows, so the exception stayed pending into the next prototype-walk step.
  2. iterating->getPrototype(globalObject) on a ProxyObject then bails out early due to the pending exception (or because its own getPrototypeOf trap throws) and returns an empty JSValue. Calling .getObject() on an empty JSValue passes isCell() and dereferences a null JSCell.

Fix

  • Clear the exception before continue, matching what forEachPropertyOrdered already does.
  • Guard the prototype walk against an empty return from getPrototype() and clear any exception from the getPrototypeOf trap, matching the fast-path branch just above.

How did you verify your code works?

Added inspect crash-test fixtures covering both a throwing getter behind a Proxy prototype and a throwing getPrototypeOf trap.

Found by Fuzzilli (JSCJSValueCell.h:92:33: member call on null pointer of type 'JSC::JSCell').

@robobun

robobun commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 3:30 AM PT - May 4th, 2026

@robobun, your commit 278af2f is building: #51058

@coderabbitai

coderabbitai Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7e0b1b6b-e3e6-4f79-a8ca-d4783d2ed09a

📥 Commits

Reviewing files that changed from the base of the PR and between ea43009 and 0d05e7c.

📒 Files selected for processing (2)
  • src/bun.js/bindings/bindings.cpp
  • test/js/bun/util/inspect.test.js

Walkthrough

Updates exception handling in property enumeration logic to record property slot discovery and clear proxy trap exceptions before continuing traversal. Adds test cases to verify that throwing getters and proxy getPrototypeOf traps don't cause crashes during inspection.

Changes

Cohort / File(s) Summary
Property Enumeration Exception Handling
src/bun.js/bindings/bindings.cpp
Updated property-enumeration logic to track property slot discovery with a hasSlot boolean and clear exceptions from proxy "Get" and "getPrototype" traps before continuing. Prototype-chain traversal now properly handles nullptr results from getPrototype instead of blindly calling .getObject().
Crash-Test Fixtures
test/js/bun/util/inspect.test.js
Added two new crash-test cases: one with a throwing property getter and one with a throwing proxy getPrototypeOf trap, both appended to the fixture list to verify Bun.inspect() handles them without crashes.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing a null dereference bug in forEachProperty when a Proxy exists in the prototype chain, which is the primary objective of this changeset.
Description check ✅ Passed The description fully addresses both required template sections: 'What does this PR do?' provides comprehensive context with repro cases and root cause analysis, and 'How did you verify your code works?' documents the test fixtures added.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


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

@github-actions

Copy link
Copy Markdown
Contributor

Found 2 issues this PR may fix:

  1. bun eslint.config.mjs | more panixs #17381 - Crash in forEachPropertyImpl during console output formatting (stack trace shows crash at bindings.cpp in the exception handling path this PR fixes)
  2. Crash while using node:net #23911 - Crash in forEachPropertyImpl when console.log triggers a throwing getter on a net.Socket object (exactly the uncleared exception scenario this PR addresses)

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #17381
Fixes #23911

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. inspect: handle throwing Proxy getPrototypeOf in forEachProperty #29814 - Fixes the exact same two bugs (exception leak on continue + null deref from empty getPrototype()) in forEachPropertyImpl in bindings.cpp
  2. Fix null JSCell deref in Bun lazy property callbacks and forEachProperty #29671 - Includes the same forEachPropertyImpl null deref fix in bindings.cpp, plus additional BunObject.cpp fixes
  3. fix(inspect): don't crash when a Proxy in the prototype chain throws #29642 - Fixes the same two root causes in forEachPropertyImpl in bindings.cpp, with broader scope also covering napi.cpp

🤖 Generated with Claude Code

@robobun

robobun commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator Author

CI failures are unrelated pre-existing flakes that appear on every recent PR build:

Build 48472 / 48777:

  • linux-x64-asan-debian-13-test-bun: fetch-http2-client.test.ts hangs and is killed by the runner (added in accbff6 / fetch: experimental HTTP/2 client #29766)
  • windows-*-test-bun: server.upgrade() Sec-WebSocket-Protocol UAF test, handleRejectStream promise GC-root test, bun create github-template tests, HTTP/3 tests

None of these touch the forEachPropertyImpl / Bun.inspect code path this PR changes. The shards running test/js/bun/util/inspect.test.js (with the new regression cases) pass on every platform.

For comparison, the same shard pattern fails on other concurrent PRs (48661, 48653, 48647, 48644, 48643, 48640, 48635, 48630, ...) — it's the current baseline.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — small, well-scoped crash fix that mirrors the existing exception-clearing patterns in forEachPropertyOrdered and the fast-path branch just above.

Extended reasoning...

Overview

This PR fixes a null pointer dereference in JSC__JSValue__forEachPropertyImpl (bindings.cpp), the C++ backing for Bun.inspect / console.log property enumeration. Two tightly-scoped changes: (1) reorder CLEAR_IF_EXCEPTION to run before the continue when getPropertySlot fails, and (2) null-check the result of getPrototype() before calling .getObject() on it, clearing any exception from a Proxy getPrototypeOf trap. Two regression fixtures are added to the existing crash-test loop in inspect.test.js.

Security risks

None. This is output-formatting code (Bun.inspect). The change adds defensive null/exception handling and does not introduce new data flow, parsing, or trust boundaries. Clearing exceptions here is intentional and matches surrounding behavior — inspect is designed to swallow getter/trap errors rather than propagate them.

Level of scrutiny

Low-to-moderate. The diff is ~7 lines of logic in a single function. Both changes are direct copies of patterns already present in the same file: the hasSlot reordering matches forEachPropertyOrdered at lines 5456-5461, and the nextProto ? nextProto.getObject() : nullptr + CLEAR_IF_EXCEPTION matches the fast-path prototype walk at lines 5243-5253. There is no novel design here, just bringing the slow path into parity with sibling code.

Other factors

  • Bug hunter found no issues.
  • Root-cause analysis in the PR description is precise and the Fuzzilli crash signature confirms the failure mode.
  • Regression tests cover both the throwing-getter-via-Proxy and throwing-getPrototypeOf-trap cases; CI confirms the inspect test shard passed on all platforms (remaining failures are documented pre-existing flakes unrelated to this code path).
  • No outstanding reviewer comments.

robobun added 2 commits May 4, 2026 10:29
When inspecting an object whose prototype chain contains a Proxy,
getPropertySlot() may throw (e.g. a throwing getter invoked through
the Proxy's default [[Get]]). If it returned false, we would continue
to the next property without clearing the pending exception, and a
later getPrototype() call on the Proxy would bail out early and return
an empty JSValue, on which .getObject() dereferences a null JSCell.

Clear the exception before skipping the property (matching
forEachPropertyOrdered), and guard the prototype walk against an empty
return from getPrototype() so a throwing getPrototypeOf trap cannot
crash either.
@Jarred-Sumner
Jarred-Sumner force-pushed the farm/8928bc04/fix-foreach-property-proxy-exception branch from 83239a1 to 278af2f Compare May 4, 2026 10:29

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — targeted crash fix that mirrors the existing fast-path exception handling in the same function.

Extended reasoning...

Overview

This PR touches two small hunks in src/jsc/bindings/bindings.cpp inside JSC__JSValue__forEachPropertyImpl (the property iterator backing Bun.inspect / console.log), plus two new crash-test fixtures appended to the existing fixture array in test/js/bun/util/inspect.test.js.

The first hunk reorders the getPropertySlot call so that CLEAR_IF_EXCEPTION(scope) runs before the continue, instead of being skipped when the slot lookup fails. The second hunk stores the result of iterating->getPrototype(globalObject) in a local, clears any pending exception from a Proxy getPrototypeOf trap, and null-checks before calling .getObject(). The enclosing while (iterating && ...) loop already handles nullptr as a clean termination condition.

Security risks

None. This is purely defensive exception/null handling in the object-inspection formatter. The change makes the code strictly more robust — it swallows exceptions that were already intended to be swallowed (the CLEAR_IF_EXCEPTION was already present, just unreachable on the early-continue path) and guards a null deref. No new user-controlled data flows, auth, crypto, or permissions are involved.

Level of scrutiny

Low-to-medium. While bindings.cpp is core C++ glue, this specific change is a mechanical defensive fix that exactly mirrors the pattern already used in the fast-path branch of the same function (lines ~5327–5337, which already does if (JSValue proto = ...getPrototype(...)) followed by CLEAR_IF_EXCEPTION). The PR description's root-cause analysis is precise and verifiable against the JSC semantics of getPropertySlot with InternalMethodType::Get on a Proxy.

Other factors

  • The bug hunting system found no issues.
  • Regression tests are added as crash-test fixtures and CI confirms the inspect.test.js shard passes on all platforms; remaining CI failures are documented pre-existing flakes unrelated to this code path.
  • The diff is ~10 lines of logic with no behavioral change beyond preventing the crash — successful paths are unaffected.
  • The duplicate-PR bot flagged three other PRs fixing the same root cause; that's a coordination question for maintainers but doesn't affect the correctness of this change.

@robobun robobun closed this May 4, 2026
@robobun
robobun deleted the farm/8928bc04/fix-foreach-property-proxy-exception branch May 4, 2026 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant