From 2dfbe39e2cca41c1ae80fcf00f16fad613f55960 Mon Sep 17 00:00:00 2001 From: robobun Date: Tue, 28 Apr 2026 07:36:45 +0000 Subject: [PATCH 1/2] Fix null deref in forEachProperty with Proxy prototype 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. --- src/jsc/bindings/bindings.cpp | 10 +++++++--- test/js/bun/util/inspect.test.js | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/jsc/bindings/bindings.cpp b/src/jsc/bindings/bindings.cpp index 22ac38063376..b952306033e5 100644 --- a/src/jsc/bindings/bindings.cpp +++ b/src/jsc/bindings/bindings.cpp @@ -5369,10 +5369,11 @@ static void JSC__JSValue__forEachPropertyImpl(JSC::EncodedJSValue JSValue0, JSC: } JSC::PropertySlot slot(object, PropertySlot::InternalMethodType::Get); - if (!object->getPropertySlot(globalObject, property, slot)) - continue; + bool hasSlot = object->getPropertySlot(globalObject, property, slot); // Ignore exceptions from "Get" proxy traps. CLEAR_IF_EXCEPTION(scope); + if (!hasSlot) + continue; if ((slot.attributes() & PropertyAttribute::DontEnum) != 0) { if (property == propertyNames->underscoreProto @@ -5444,7 +5445,10 @@ static void JSC__JSValue__forEachPropertyImpl(JSC::EncodedJSValue JSValue0, JSC: break; if (iterating == globalObject) break; - iterating = iterating->getPrototype(globalObject).getObject(); + JSValue nextProto = iterating->getPrototype(globalObject); + // Ignore exceptions from Proxy "getPrototype" trap. + CLEAR_IF_EXCEPTION(scope); + iterating = nextProto ? nextProto.getObject() : nullptr; } } diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index 32a70af30183..7233d2cf43de 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -451,6 +451,29 @@ const fixture = [ }, }, ), + () => + Object.create( + new Proxy( + { + get foo() { + throw new Error("boom"); + }, + bar: 1, + }, + {}, + ), + ), + () => + Object.create( + new Proxy( + { foo: 1 }, + { + getPrototypeOf() { + throw new Error("trap"); + }, + }, + ), + ), ]; describe("crash testing", () => { From 278af2f598671733b17154ed43b7ad044428d3fe Mon Sep 17 00:00:00 2001 From: robobun Date: Tue, 28 Apr 2026 07:46:42 +0000 Subject: [PATCH 2/2] retrigger