From 9cf3288a6d0e8daab8e2b74d8ae58e70e1c481a3 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Thu, 12 Nov 2026 18:57:46 -0300 Subject: [PATCH] fix(patches): own-property lookups through a non-masking interceptor If a receiver has a kNonMasking named interceptor and an object on its prototype chain has a named interceptor of its own, an own-property lookup on the receiver ended on that prototype instead of NOT_FOUND. LookupIterator::NextInternal meant to treat the prototype's interceptor as invisible, but it `continue`d inside `do { } while (!IsFound())` while the state was still INTERCEPTOR, so the walk stopped there. Prototype properties then read as own, `delete obj.m` removed `m` from the prototype, and sets and defineProperty reached the prototype's interceptor instead of the receiver's. v8_nonmasking_own_lookup.patch steps past the invisible interceptor (or a passed access check) within the same holder, so its real properties still count, and only moves on when nothing is found there. Indexed interceptors are no longer skipped, matching upstream since 892dda71342. Reported upstream as crbug 585053531; applied on every platform. The iOS runtime's wrapper canary mode puts kNonMasking interceptors on ObjC instances, whose prototypes already carry a named interceptor. --- README.md | 1 + patches/v8_nonmasking_own_lookup.patch | 37 ++++++++++++++++++++++++++ scripts/matrix/fetch.sh | 4 +++ 3 files changed, 42 insertions(+) create mode 100644 patches/v8_nonmasking_own_lookup.patch diff --git a/README.md b/README.md index 1074daa..361d8ef 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ platform), dispatch the workflow with `platforms: android` or `platforms: ios`. | Patch | Why | |---|---| | `v8_resurrecting_finalizers.patch` | Restores `WeakCallbackType::kFinalizer`, removed upstream immediately after 10.3.22. Both runtimes' object managers depend on resurrecting finalizers. It also lifts, for the finalizer drain only, the `DisallowJavascriptExecution` scope that `Heap::CollectGarbage` now holds across the whole collection: 14.9 turns entering JS from a GC callback into a `GRACEFUL_FATAL`, and the iOS runtime reaches JS whenever a released ObjC object's `-dealloc` hits a JS-backed override. | +| `v8_nonmasking_own_lookup.patch` | Fixes own-property lookups on an object with a `kNonMasking` named interceptor when its prototype has a named interceptor of its own ([crbug 585053531](https://issues.chromium.org/issues/585053531)). `LookupIterator::NextInternal` meant to treat the prototype's interceptor as invisible but `continue`d out of the walk while still in a found state, so the lookup ended on the prototype: its properties read as own, `delete` removed them from the prototype, and sets and `defineProperty` went to the prototype's interceptor instead of the receiver's. The patch steps past the invisible interceptor within the same holder. Indexed interceptors are no longer skipped, as upstream since 892dda71342. Applied on every platform. | | `android_build.patch` | Lowers chromium's minimum SDK to 21 (that floor is for Java/dex tooling; this builds only the native target), makes `android_ndk_root` a gn arg, and widens the hard Linux-host assert so macOS can build the 64-bit ABIs. | | `v8_catalyst_jit_write_protect.patch` | Mac Catalyst compiles as iOS (`TARGET_OS_IPHONE`), so V8 picks its iOS JIT-memory model, which assumes `MAP_JIT` pages are writable and executable at once. A Catalyst process runs on macOS, where Apple silicon makes `MAP_JIT` pages per-thread W^X, so the first JIT code write faults (`SIGBUS`). The patch gives Catalyst the macOS model: `pthread_jit_write_protect_np` around code writes (resolved with `dlsym`, since the SDK marks it unavailable on Catalyst) and the macOS code-range pre-commit. Applied to iOS fetches only; it is a no-op outside Catalyst. | diff --git a/patches/v8_nonmasking_own_lookup.patch b/patches/v8_nonmasking_own_lookup.patch new file mode 100644 index 0000000..0ced201 --- /dev/null +++ b/patches/v8_nonmasking_own_lookup.patch @@ -0,0 +1,37 @@ +diff --git a/src/objects/lookup.cc b/src/objects/lookup.cc +index 7b8afd6872d..cd4f600f572 100644 +--- a/src/objects/lookup.cc ++++ b/src/objects/lookup.cc +@@ -128,21 +128,18 @@ void LookupIterator::NextInternal(Tagged orig_map, + IsFound()) { + // In theory we also need to check JSPROXY but since the iterator doesn't + // know which question to ask, we assume the lookup succeeds. +- switch (state_) { +- // https://webidl.spec.whatwg.org/#dfn-named-property-visibility says a +- // second interceptor on the prototype chain is invisible. +- case INTERCEPTOR: +- continue; +- case ACCESS_CHECK: { +- // If an access check fails, we assume the lookup succeeds. +- if (HasAccess()) { +- continue; +- } +- break; +- } +- default: +- break; ++ // ++ // https://webidl.spec.whatwg.org/#dfn-named-property-visibility says a ++ // second named interceptor on the prototype chain is invisible, and a ++ // passed access check is too. The holder's real properties are still ++ // visible, so step past them within this holder instead of leaving it: ++ // `continue` here would re-test the loop condition while the state is ++ // still a found one and end the walk on this holder. ++ while ((state_ == INTERCEPTOR && !is_element) || ++ (state_ == ACCESS_CHECK && HasAccess())) { ++ state_ = LookupInSpecialHolder(map, holder); + } ++ if (!IsFound()) continue; + // We need fully reset state, since this is not a true hit. + number_ = InternalIndex::NotFound(); + property_details_ = PropertyDetails::Empty(); diff --git a/scripts/matrix/fetch.sh b/scripts/matrix/fetch.sh index 16ddf31..520c27b 100755 --- a/scripts/matrix/fetch.sh +++ b/scripts/matrix/fetch.sh @@ -87,6 +87,10 @@ git -C v8 apply "$ROOT_DIR/patches/v8_resurrecting_finalizers.patch" # reserved once per isolate. git -C v8 apply "$ROOT_DIR/patches/v8_js_dispatch_table_reservation.patch" +# Own-property lookups through a kNonMasking named interceptor ended on a +# prototype that has a named interceptor of its own (crbug 585053531). +git -C v8 apply "$ROOT_DIR/patches/v8_nonmasking_own_lookup.patch" + if [ "$PLATFORM" = "ios" ]; then # Mac Catalyst compiles as iOS but runs on macOS; this gives its JIT build # the macOS MAP_JIT + pthread_jit_write_protect_np handling.