-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Fix null deref in Bun.inspect when Proxy getPrototypeOf trap throws
#30318
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
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
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 (pre-existing, not introduced by this PR): the same unguarded pattern lives at
src/jsc/bindings/napi.cpp:1837—current_object->getPrototype(globalObject).getObject()— reachable vianapi_get_all_property_nameswithnapi_key_include_prototypes+ a writable/enumerable/configurable filter. A Proxy whosegetPrototypeOftrap throws will cause the same nullJSCell*deref there; theif (!proto)on the next line doesn't help because the UB happens inside.getObject(). Since you're fixing the same root cause here, you may want to apply the sameJSValue-then-check guard there too.Extended reasoning...
What the bug is
The exact pattern this PR fixes — calling
.getObject()directly on the result ofgetPrototype(globalObject)without first checking whether the returnedJSValueis empty — also exists atsrc/jsc/bindings/napi.cpp:1837:When
current_object(or something in its prototype chain) is aProxyObjectwhosegetPrototypeOftrap throws,getPrototype()returns an emptyJSValue. As demonstrated by the UBSAN trace in this PR's description, an emptyJSValue(encoded as0) passes theisCell()check (it doesn't haveNotCellMaskbits set), so.getObject()proceeds to callasCell()->isObject()on a nullJSCell*— undefined behavior / null deref.Why the existing
if (!proto)doesn't helpIt's tempting to think the
if (!proto) break;on line 1838 covers this, but it doesn't: the null dereference happens inside.getObject()on line 1837, beforeprotois ever assigned. This is exactly the failure mode the PR fixes inbindings.cpp(the original code there wasiterating->getPrototype(globalObject).getObject(), and the fix splits it intoJSValue proto = ...; iterating = proto ? proto.getObject() : nullptr;).How it's reachable
This loop sits inside
napi_get_all_property_namesand runs when called withkey_mode = napi_key_include_prototypesand akey_filterthat includes any ofnapi_key_writable | napi_key_enumerable | napi_key_configurable(so the per-key descriptor-lookup loop at line 1829 executes). TheNAPI_RETURN_IF_EXCEPTIONat line 1823 catches an exception thrown during the initialallPropertyKeyswalk, but the per-key loop callsgetPrototypeagain for every key — so a stateful trap (succeeds the first N calls, throws on call N+1), or a trap that only throws aftergetOwnPropertyDescriptorhas run, will reach line 1837 with a pending exception and an empty return value.Step-by-step proof
napi_get_all_property_names(env, obj, napi_key_include_prototypes, napi_key_writable, napi_key_numbers_to_strings, &result)whereobjhas a Proxy in its prototype chain whosegetPrototypeOftrap is stateful (e.g., returns{}the first few times, thenthrow new Error("trap")).allPropertyKeyswalks the chain — trap returns normally, no exception. Line 1823 passes.key_filter & filter_by_any_descriptoris true → enter the per-key loop.getOwnPropertyDescriptorreturns false onobject, so we enter thewhilebody and callcurrent_object->getPrototype(globalObject).getPrototype()returns an emptyJSValue(encoded0)..getObject()→isCell()on encoded0returns true →asCell()returnsnullptr→nullptr->isObject()→ null deref, identical to the UBSAN trace in this PR.Impact and fix
Impact: a native module using this N-API combination on a user-controlled object with a hostile Proxy prototype hits UB / a crash. Same severity class as the bug being fixed in this PR, just on a less-traveled path.
Fix: apply the same guard this PR adds in
bindings.cpp:This is pre-existing — the PR doesn't touch
napi.cppand doesn't make it any worse — but since it's the same root cause and the same one-line fix, it seemed worth flagging here for completeness.