-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Fix null deref in Bun.inspect when Proxy getPrototypeOf trap throws #30517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+27
−1
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟣 Heads-up: the same unsafe pattern exists at
src/jsc/bindings/napi.cpp:1837innapi_get_all_property_names—current_object->getPrototype(globalObject).getObject()with no exception check, so a ProxygetPrototypeOftrap that throws will null-deref there too. This is pre-existing and not touched by this PR, but since a grep forgetPrototype(globalObject).getObject()finds it, you may want to apply the sameCLEAR_IF_EXCEPTION+ empty-check fix there in this PR or a follow-up. Non-blocking.Extended reasoning...
What the bug is
This PR correctly fixes a null dereference in
JSC__JSValue__forEachPropertyImpl(bindings.cpp:5447) whereiterating->getPrototype(globalObject).getObject()crashes when a Proxy'sgetPrototypeOftrap throws. However, the identical pattern remains atsrc/jsc/bindings/napi.cpp:1837insidenapi_get_all_property_names:There is no
RETURN_IF_EXCEPTION/CLEAR_IF_EXCEPTIONbetween thegetPrototype()call and.getObject(), so the exact crash class fixed by this PR still exists in the napi path.Code path that triggers it
A native addon calls
napi_get_all_property_nameswith:key_mode = napi_key_include_prototypes(so the inner while-loop walks the prototype chain), andkey_filtercontaining any ofnapi_key_enumerable | napi_key_writable | napi_key_configurable(so the descriptor-filtering block at line 1827 is entered),on an object whose prototype chain contains a Proxy with a throwing
getPrototypeOftrap.Why existing code doesn't prevent it
There's a
NAPI_RETURN_IF_EXCEPTION(env)at line 1823 after the initialallPropertyKeys()walk, so a trivially-always-throwing trap would be caught there. But a stateful trap — e.g., one that returns normally for the first N calls (duringallPropertyKeys's prototype walk) and throws on a subsequent call (during the per-key descriptor-filtering walk at line 1837) — passes the line-1823 check and reaches line 1837. At that pointgetPrototype()throws and returns an emptyJSValue; per the PR description's own analysis, an emptyJSValuepassesisCell()butasCell()isnullptr, so.getObject()invokesJSCell::getObject()on null → segfault.Step-by-step proof
let n = 0; const p = new Proxy({}, { getPrototypeOf() { if (n++ > 3) throw new Error('nope'); return null; } }); const obj = Object.create(p); obj.x = 1;napi_get_all_property_names(env, obj, napi_key_include_prototypes, napi_key_enumerable, napi_key_numbers_to_strings, &result).allPropertyKeys()walks the chain, calling the trap a few times;nincrements but stays under the threshold; no exception → line 1823 passes.key_filter & filter_by_any_descriptoris true → enter filtering loop."x", line 1836:getOwnPropertyDescriptoronobjsucceeds, loop body skipped. But for any key contributed by the prototype (or if the descriptor lookup misses onobj), the while-loop at 1836 runs and line 1837 callscurrent_object->getPrototype(globalObject).n > 3).getPrototype()returnsJSValue()(empty).JSValue().getObject()→isCell()is true for empty (bits == 0 has no number/undefined/null tag) →asCell()returnsnullptr→nullptr->getObject()readstype()through a nullthis→ crash.Impact
Process crash (SIGSEGV) reachable from any native addon that uses
napi_get_all_property_nameswith prototype inclusion + descriptor filtering on user-controlled objects. Lower exposure than theBun.inspectpath since it requires a native addon, but it's the same crash class.How to fix
Apply the same pattern as this PR:
Severity
Pre-existing — this PR does not touch
napi.cpp, add callers to it, or otherwise interact with this code path. Mentioning it only because the PR's stated goal is fixing exactly this crash class and a grep forgetPrototype(globalObject).getObject()finds this remaining instance. Non-blocking; fine to address in a follow-up.