CVE-2026-95285
Overview
Background
- `RfhToIoThreadClientMap`
- the Android WebView browser-side map keying a
RenderFrameHost(by process id +LocalFrameToken) to the embedder’s Java IO-thread client, which decides that frame’s network policy (shouldInterceptRequest,shouldOverrideUrlLoading, blocking, etc.). - `SubFrameCreated` IPC
- a renderer-originated message reporting that a child frame was created under a parent, historically used by the browser to add the child’s entry to
RfhToIoThreadClientMap. - `ClientMapEntryUpdater`
- a browser-side
WebContentsObserverthat already populatesRfhToIoThreadClientMapauthoritatively as frames are created, independent of any renderer IPC. - Killswitch feature flag
- a
base::Featureshipped default-enabled so risky behavior can be disabled in the field and later removed once proven safe.
Root Cause Analysis
The browser handler AwContentsIoThreadClient::SubFrameCreated updated RfhToIoThreadClientMap using the parent and child LocalFrameTokens supplied by the renderer over the SubFrameCreated IPC. Because a renderer is untrusted and can be compromised, it could send SubFrameCreated with attacker-chosen frame tokens, inserting or overwriting a map entry so that a frame is associated with the IO-thread client — and therefore the network/authorization policy — of a different frame than the one the browser legitimately created. This is a missing-authorization condition: a security-relevant mapping that governs per-frame request handling was derived from renderer-controlled input rather than from browser-owned frame-lifecycle state.
The fix gates the handler behind kWebViewSubFrameCreatedDoNotUpdateClientMap (enabled by default), making it early-return so the renderer IPC no longer mutates the map; correctness is preserved because ClientMapEntryUpdater, a browser-side WebContentsObserver, already creates the entry from trusted frame-creation events.
Attack Path
- Compromise a renderer The attacker gains code execution in a WebView renderer process, or otherwise controls the messages it emits.
- Forge frame tokens
The renderer sends a
SubFrameCreatedIPC carrying parent/childLocalFrameTokens chosen to target a victim frame’s map slot. - Corrupt the client map
AwContentsIoThreadClient::SubFrameCreatedwrites the attacker-influenced entry intoRfhToIoThreadClientMap, associating a frame with the wrong embedder IO-thread client. - Bypass per-frame policy Subsequent network requests for that frame are routed through the mismatched client, letting the frame evade or misapply the embedder’s request-interception/authorization decisions.
Impact Assessment
SubFrameCreated IPC; the default-enabled killswitch closes the surface by ignoring that IPC, relying on the browser’s own ClientMapEntryUpdater for correct entries.Files Changed
android_webview/browser/aw_contents_io_thread_client.ccandroid_webview/common/aw_features.handroid_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.javaandroid_webview/common/aw_features.cc
Audit Directions
- Renderer-supplied identity into browser mapsAudit any browser-side structure keyed on
LocalFrameToken/frame identifiers taken from renderer IPCs; the browser’s own frame-lifecycle observers should be the source of truth for security-relevant mappings. - Redundant trust pathsLook for state that is populated both by a trusted browser observer and by an untrusted renderer message; the renderer path is pure attack surface and should be removed or ignored.
- WebView IO-thread client resolutionReview every consumer of
RfhToIoThreadClientMapfor assumptions that a frame’s entry is authentic, since a mismatched client silently changes network-authorization behavior.
Patch
From 05d64dba60f23209116c5355a1ee2f45abd1b790 Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni <[email protected]> Date: Mon, 24 Aug 2026 13:15:42 -0700 Subject: [PATCH] Add killswitch to disable updating client map on SubFrameCreated IPC This adds the kWebViewSubFrameCreatedDoNotUpdateClientMap feature flag (enabled by default) acting as a killswitch. When enabled, the browser ignores the SubFrameCreated IPC from the renderer and no longer updates RfhToIoThreadClientMap. The map entry is already created on the browser side via ClientMapEntryUpdater (WebContentsObserver) when frames are created. If no issues arise in ~5 months (~January 2027), this feature flag and the associated SubFrameCreated IPC can be completely removed as proposed in https://crrev.com/c/8159020. Fixed: 497094708 Change-Id: I1b9b60f0fa29de795d6dc6f68ea5af88c5ad7ec6 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8279597 Auto-Submit: Arthur Sonzogni <[email protected]> Commit-Queue: Arthur Sonzogni <[email protected]> Reviewed-by: Richard Coles <[email protected]> Commit-Queue: Richard Coles <[email protected]> Cr-Commit-Position: refs/heads/main@{#1684954} --- diff --git a/android_webview/browser/aw_contents_io_thread_client.cc b/android_webview/browser/aw_contents_io_thread_client.cc index 6d2a25e..6a5f872 100644 --- a/android_webview/browser/aw_contents_io_thread_client.cc +++ b/android_webview/browser/aw_contents_io_thread_client.cc @@ -431,6 +431,10 @@ int child_id, const blink::LocalFrameToken& parent_frame_token, const blink::LocalFrameToken& child_frame_token) { + if (base::FeatureList::IsEnabled( + features::kWebViewSubFrameCreatedDoNotUpdateClientMap)) { + return; + } RfhToIoThreadClientMap* map = RfhToIoThreadClientMap::GetInstance(); std::optional<JavaObjectWeakGlobalRef> opt_delegate_weak_ref = map->Get( content::GlobalRenderFrameHostToken(child_id, parent_frame_token)); diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc index f34247e..e5403e6 100644 --- a/android_webview/common/aw_features.cc +++ b/android_webview/common/aw_features.cc @@ -392,6 +392,13 @@ BASE_FEATURE(kWebViewStaticMethodsNotTriggerStartup, base::FEATURE_DISABLED_BY_DEFAULT); +// Kill switch for updating RfhToIoThreadClientMap on SubFrameCreated IPC. +// When enabled, the map is not updated from the SubFrameCreated IPC. +// TODO(crbug.com/497094708): Remove this flag and apply +// https://crrev.com/c/8159020 in ~5 months (~January 2027). +BASE_FEATURE(kWebViewSubFrameCreatedDoNotUpdateClientMap, + base::FEATURE_ENABLED_BY_DEFAULT); + // A Feature used for WebView variations tests. Not used in production. Please // do not clean up this stale feature: we intentionally keep this feature flag // around for testing purposes. diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h index 8e9a17cd..31b8c4e 100644 --- a/android_webview/common/aw_features.h +++ b/android_webview/common/aw_features.h @@ -98,6 +98,7 @@ BASE_DECLARE_FEATURE(kWebViewSingleSharedContextState); BASE_DECLARE_FEATURE(kWebViewSkipInterceptsForPrefetch); BASE_DECLARE_FEATURE(kWebViewStaticMethodsNotTriggerStartup); +BASE_DECLARE_FEATURE(kWebViewSubFrameCreatedDoNotUpdateClientMap); BASE_DECLARE_FEATURE(kWebViewTestFeature); BASE_DECLARE_FEATURE(kWebViewTestNonembeddedLowEntropySource); BASE_DECLARE_FEATURE(kWebViewUnreducedProductVersion); diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java index 27f6834..babf45e 100644 --- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java +++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java @@ -1460,6 +1460,10 @@ "Allows Webview to allocate and share a single shared context for all the" + " webview instances."), Flag.baseFeature("ComponentsBase32InRust", "Enables the Rust-based Base32 implementation."), + Flag.baseFeature( + AwFeatures.WEBVIEW_SUB_FRAME_CREATED_DO_NOT_UPDATE_CLIENT_MAP, + "When enabled, the browser ignores SubFrameCreated IPC and does not update" + + " RfhToIoThreadClientMap."), // Add new commandline switches and features above. The final entry should have a // trailing comma for cleaner diffs. };