README.md
Rendering markdown...
# CVE-2026-20643 — Navigation API cross-origin gate bug (binary diff verified)
## Scope
- Vulnerable: iOS 26.3.1 restore build `23D8133` (iPhone18,2), WebCore in dyld cache.
- Patched (BSI OTA): iOS 26.3.1(a) `23D771330a`, binary diff in `cryptex-system-arm64e`.
- Function diffed: `WebCore::Navigation::innerDispatchNavigateEvent(...)` at `0x1a1303304`.
## What changed (exact)
### Vulnerable logic (23D8133, IDA pseudocode)
```cpp
isSameSiteAs = SecurityOrigin::isSameSiteAs(currOrigin, targetOrigin);
isSameOriginAs = SecurityOrigin::isSameOriginAs(currOrigin, targetOrigin);
if ((isSameSiteAs & 1) == 0 && !isSameOriginAs)
v34 = 1; // deny intercept
else if ((*(_BYTE *)(targetURL + 32) & 2) != 0)
v34 = 0; // allow intercept
else if (!URL::protocolIs(targetURL, "file")
|| isEqualIgnoringQueryAndFragments(docURL, targetURL))
v34 = equalIgnoringFragmentIdentifier(docURL, targetURL) ^ 1;
else
v34 = 1;
```
Key point: gate allows the fast-path when `isSameSiteAs` is true (even if not same-origin).
### Patched logic (23D771330a)
```cpp
v34 = 1; // default deny
if (SecurityOrigin::isSameOriginAs(currOrigin, targetOrigin)) {
if (URL::user(docURL) == URL::user(targetURL) &&
URL::password(docURL) == URL::password(targetURL)) {
if ((*(_BYTE *)(targetURL + 32) & 2) != 0)
v34 = 0;
else if (!URL::protocolIs(targetURL, "file")
|| isEqualIgnoringQueryAndFragments(docURL, targetURL))
v34 = equalIgnoringFragmentIdentifier(docURL, targetURL) ^ 1;
}
}
```
Key point: old same-site gate was replaced by strict same-origin gate, plus explicit `user/password` validation.
## Evidence addresses
- Old build (`mcp__ida_headless_2`):
- `0x1a1303480` -> `SecurityOrigin::isSameSiteAs`
- `0x1a1303490` -> `SecurityOrigin::isSameOriginAs`
- `0x1a1303b44` pseudocode: `if ((isSameSiteAs & 1) == 0 && !isSameOriginAs) ...`
- Patched build (`mcp__ida_headless_3`):
- `0x1a1303484` -> `SecurityOrigin::isSameOriginAs`
- `0x1a1303494`/`0x1a13034a0` -> `URL::user`
- `0x1a13034bc`/`0x1a13034c8` -> `URL::password`
- `0x1a13034ac`/`0x1a13034d0` -> `WTF::equal(StringImpl*, StringImpl*)`
## `canIntercept` plumbing confirmation
- `WebCore::jsNavigateEvent_canIntercept` (`0x19f84ab84`) reads `*(event + 160)`.
- `WebCore::NavigateEvent::NavigateEvent` (`0x1a12fa834`) writes init dword into `event+160`.
- `innerDispatchNavigateEvent` computes this bit from the above gate (`v34` inversion path).
## Trigger model
- Same-site but cross-origin navigation (e.g. cross-port localhost) reaches permissive path in vulnerable build.
- Patched build blocks that path unless strict same-origin plus user/password match.
## Impact
- Wrong cross-origin authorization in Navigation API interception path.
- Attacker-controlled content can get interception capability where origin boundary should deny it.
- Practical abuse: navigation flow manipulation/confusion across origin boundaries.
## Confidence
- High for root cause class and patch location: direct vulnerable vs patched function-level diff.
- Medium-high for exact externally observable trigger variants: dynamic JS runtime behavior still depends on Navigation API availability/mode on target build.