-
Notifications
You must be signed in to change notification settings - Fork 4.7k
fix(inspect): handle Proxy trap exceptions when walking prototype chain #30099
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
Open
robobun
wants to merge
4
commits into
main
Choose a base branch
from
farm/5b65ba0a/foreachproperty-proxy-exception
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 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.
🟣 Note: the same
getPrototype(globalObject).getObject()null-deref pattern still exists atsrc/bun.js/bindings/napi.cpp:1837, reachable vianapi_get_all_property_nameswithnapi_key_include_prototypes+ an enumerable/writable/configurable filter on an object with a throwing Proxy in its prototype chain. This is pre-existing (the PR doesn't touch napi.cpp), but it's the exact same root cause and might be worth applying the sameCLEAR_IF_EXCEPTION+ null-check there as a follow-up.Extended reasoning...
What the bug is
This PR correctly fixes the null dereference in
JSC__JSValue__forEachPropertyImplby splittingiterating->getPrototype(globalObject).getObject()into two steps, clearing any pending exception, and null-checking the returnedJSValuebefore calling.getObject(). However, the identical unsafe pattern still exists insrc/bun.js/bindings/napi.cpp:How it manifests
There are two ways for
getPrototype()to return an emptyJSValuehere, both via Proxy traps:getOwnPropertyDescriptortrap: The while-loop condition callscurrent_object->getOwnPropertyDescriptor(...). Ifcurrent_objectis a Proxy whosegetOwnPropertyDescriptortrap throws, JSC returnsfalseand leaves the exception pending. The loop body then callscurrent_object->getPrototype(globalObject)with a pending exception, which returns an emptyJSValue.getPrototypeOftrap: If the loop condition returnsfalsecleanly butcurrent_objectis a Proxy whosegetPrototypeOftrap throws,getPrototype()itself throws and returns an emptyJSValue.In both cases,
.getObject()is then called on an emptyJSValue. An emptyJSValueis encoded as0, which has noNotCellMaskbits set, soisCell()returnstrue,asCell()returnsnullptr, andnullptr->getObject()readsm_typefrom a null pointer — the same UBSAN null deref this PR fixes in bindings.cpp. Theif (!proto) break;on the next line doesn't help because the dereference has already happened inside.getObject().Why existing code doesn't prevent it
The
NAPI_RETURN_IF_EXCEPTION(env)at line 1823 only guards the initial key collection (allPropertyKeys/ownPropertyKeys). The per-key descriptor loop at lines 1829-1845 has noRETURN_IF_EXCEPTION,CLEAR_IF_EXCEPTION, or empty-value check aroundgetOwnPropertyDescriptororgetPrototype, so exceptions thrown by Proxy traps during the prototype walk are neither observed nor cleared.Step-by-step proof
napi_get_all_property_names(env, obj, napi_key_include_prototypes, napi_key_enumerable, napi_key_numbers_to_strings, &result)whereobjhasnew Proxy(proto, { getPrototypeOf() { throw new Error('boom'); } })somewhere in its prototype chain.allPropertyKeyscollects keys from the chain (this may or may not throw depending on trap order; assume it succeeds and returns at least one key not present as an own property ofobj).key_filter & filter_by_any_descriptoris truthy, we enter the filter loop. For a key not owned byobj,getOwnPropertyDescriptorreturnsfalseand we enter the while-loop body.object->getPrototype(globalObject)walks to the Proxy and invokes itsgetPrototypeOftrap, which throws.getPrototypereturns an emptyJSValue..getObject()→isCell()on encoded0→true→asCell()→nullptr→nullptr->getObject()readsJSCell::m_typeat offset from null → UBSAN null deref / crash.(The throwing-
getOwnPropertyDescriptorvariant is even simpler: trap throws → returnsfalse→ loop body runs with pending exception →getPrototypereturns empty → same crash.)Impact and fix
This is reachable from any native addon that calls
napi_get_all_property_nameswithnapi_key_include_prototypesand an enumerable/writable/configurable filter on user-supplied objects. The fix is the same as what this PR applies in bindings.cpp:plus an exception check after
getOwnPropertyDescriptorin the loop condition.Relationship to this PR
This PR does not touch
napi.cpp, add callers to it, or otherwise interact with this code path — so this is strictly a pre-existing issue and should not block merging. Mentioning it only because it is the exact same root cause fixed here, found by the same fuzzer pattern, and the author may want to apply the same fix in a follow-up.