Fix RELEASE_ASSERT in LazyProperty when node:util fails to load during Bun.inspect

Closed
RO
3mo ago
+98-11

Fuzzilli found a crash with fingerprint LazyPropertyInlines.h(104):

ASSERTION FAILED: !(initializer.property.m_pointer & lazyTag)
LazyPropertyInlines.h(104) : static ElementType *JSC::LazyProperty<JSC::JSGlobalObject, JSC::JSFunction>::callFunc(...)

Root cause

Bun.inspect() on an object with a [Symbol.for('nodejs.util.inspect.custom')] method triggers lazy initialization of m_utilInspectFunction, which loads node:util. If evaluating node:util (or one of its transitive dependencies) throws, the initializer returned early via RETURN_IF_EXCEPTION(scope, ) without calling init.set(). LazyProperty::callFunc requires the lazy tag to be cleared before it returns, so it hit the RELEASE_ASSERT.

The same issue existed in m_utilInspectStylizeColorFunction.

Repro

Object.defineProperty(Array.prototype, 'forEach', { get() { throw new Error('poisoned'); } });
Bun.inspect({ [Symbol.for('nodejs.util.inspect.custom')]() { return 'x'; } });

Before: aborts with RELEASE_ASSERT.
After: throws a catchable JS error.

Fix

On exception, call init.property.setMayBeNull(init.vm, init.owner, nullptr) so the tag bits are cleared and the exception propagates to the caller. This matches the pattern already used in JSX509Certificate. Callers of utilInspectFunction() / utilInspectStylizeColorFunction() are updated to handle nullptr on subsequent calls after a failed load (pass jsUndefined() to the custom inspect function, and fall back to a plain string for BroadcastChannel's custom inspect).