Medium chrome Memory Corruption 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in API
DescriptionMemory corruption in API
ComponentAPI
Sub-componentPlatform>Extensions>API
Bug ClassMemory Corruption
Tracker495471295
Fix commitf770eeec8de3 (chromium/src) +182/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Files Changed

  • extensions/browser/api/web_request/web_request_info_unittest.cc
  • chrome/test/data/web_apps/simple_isolated_app/guest_sw_fetch_worker.js
  • chrome/test/data/web_apps/simple_isolated_app/guest_sw.js
  • extensions/browser/api/web_request/web_request_info.cc
  • chrome/test/data/web_apps/simple_isolated_app/guest_sw_fetch_from_worker.txt
  • chrome/browser/controlled_frame/controlled_frame_apitest.cc
  • chrome/test/data/web_apps/simple_isolated_app/guest_sw_test.html

Original Bug Report

reported by rj...@google.com

Bypass of <webview> traffic protection via Service Workers allows Glic WebUI compromise

Flapjack (go/flapjack), an LLM-powered static analysis tool, has identified the following potential security issue.

Overview: The is_web_view protection in the Web Request and DeclarativeNetRequest APIs can be bypassed by worker-initiated requests (e.g., Service Workers) originating from a <webview>. Because workers lack a RenderFrameHost, they are assigned a null routing ID, causing the API to skip guest process verification and incorrectly flag the traffic as standard. This allows a malicious extension to intercept the worker, inject XSS into the guest page, and potentially compromise privileged WebUIs like the Gemini side panel (chrome://glic).

Affected files:

  • extensions/browser/api/web_request/web_request_info.cc
  • extensions/browser/api/declarative_net_request/ruleset_manager.cc
  • extensions/browser/api/web_request/web_request_api.cc
  • chrome/browser/chrome_content_browser_client.cc

Estimated timestamp from git blame: 2025-10-30

Summary

There is a potential security vulnerability in the extension network interception APIs (webRequest and declarativeNetRequest) where traffic originating from Service Workers or Shared Workers within a <webview> guest is not correctly identified as <webview> traffic.

By design, extensions are restricted from intercepting and modifying <webview> traffic unless explicitly permitted. However, because worker requests are not associated with a specific UI frame, they bypass the is_web_view check in WebRequestInfoInitParams. An attacker with a malicious extension can exploit this to intercept a guest’s Service Worker script, inject arbitrary JavaScript, and achieve persistent Cross-Site Scripting (XSS) within the <webview>. If the <webview> is hosted by a privileged WebUI (such as the Gemini side panel at chrome://glic), this XSS can be weaponized to interact with privileged Mojo bridges and exfiltrate sensitive user data.

Root Cause Analysis

The vulnerability stems from how is_web_view is initialized during the creation of a proxied URLLoaderFactory:

  1. When a Service Worker is registered or makes a fetch() request within a <webview>, content::EmbeddedWorkerInstance::CreateFactoryBundle is called to create a URLLoaderFactory.
  2. Because Service Workers are decoupled from specific UI frames, nullptr is passed as the frame_host argument to url_loader_factory::ContentClientParams.
  3. This triggers ChromeContentBrowserClient::WillCreateURLLoaderFactory with a nullptr frame.
  4. The call is routed to extensions::WebRequestAPI::MaybeProxyURLLoaderFactoryInternal. To determine the frame_routing_id, the code uses the ternary operator: frame ? frame->GetRoutingID() : IPC::mojom::kRoutingIdNone Since frame is nullptr, the routing ID becomes IPC::mojom::kRoutingIdNone (which is -2).
  5. This -2 routing ID is passed down to WebRequestProxyingURLLoaderFactory and subsequently to WebRequestInfoInitParams::InitializeWebViewAndFrameData.
  6. Inside InitializeWebViewAndFrameData (in extensions/browser/api/web_request/web_request_info.cc), the code attempts to determine if the request is from a <webview>:
    } else if (frame_routing_id != IPC::mojom::kRoutingIdNone) {
    #if BUILDFLAG(ENABLE_GUEST_VIEW)
        // Grab any WebView-related information if relevant.
        // ... (WebViewRendererState lookup occurs here)
    
  7. Because frame_routing_id is -2 (kRoutingIdNone), this else if branch evaluates to false. The WebViewRendererState lookup is skipped entirely.
  8. As a result, the is_web_view flag remains at its default value of false. The webRequest and DNR event routers subsequently evaluate the request as standard web traffic, allowing extensions to intercept it.

Potential Exploitation Scenario

Note: As an AI agent, I cannot run or verify a functional proof-of-concept, but code analysis strongly suggests the following attack chain is possible:

  1. Extension Installation: An attacker publishes a malicious extension requesting webRequest or declarativeNetRequest permissions for <all_urls> or *://gemini.google.com/*, which the victim installs.
  2. Target Invocation: The user opens the Gemini side panel, which loads the highly privileged chrome://glic WebUI. This WebUI creates a <webview> configured with a custom partition (persist:glicpart) and navigates to https://gemini.google.com/.
  3. Worker Interception: The gemini.google.com guest attempts to register or update its Service Worker (sw.js). Due to the bug, the malicious extension successfully intercepts this request.
  4. Payload Injection: The extension modifies the HTTP response for sw.js, injecting an attacker-controlled JavaScript payload.
  5. Persistent XSS: The malicious Service Worker is installed. It intercepts subsequent fetch events from the gemini.google.com guest page and injects an XSS payload directly into the main HTML document served to the <webview>.
  6. Privilege Escalation: The injected script executes within the <webview>. It leverages the pre-established window.postMessage bridge (set up in chrome/browser/resources/glic/glic_api_impl/post_message_transport.ts) to communicate with the privileged chrome://glic WebUI.
  7. Data Theft: The attacker’s script forges a message to invoke privileged APIs exposed by GlicApiHost, such as glicBrowserGetContextFromFocusedTab. The browser process executes this command, returning the DOM text, PDF data, or screenshot of the user’s currently focused tab, achieving a universal cross-origin read.

Proposed Fix Considerations

Fixing this issue requires identifying <webview> traffic even when a RenderFrameHost is absent.

Caution: Relying on WebViewRendererState::IsGuest(render_process_id) as a fallback may be insufficient. Service Workers for a <webview> guest are allocated their own dedicated processes (as SiteInstanceImpl::CreateForServiceWorker does not support process reuse for <webview>). Because this new process contains no frames, it is never added to WebViewRendererState’s internal maps. Therefore, WebViewRendererState will not recognize the Service Worker’s process ID as a guest.

A more robust fix should verify if the StoragePartition or SiteInstance associated with the request (which is available during factory creation) belongs to a guest, rather than relying solely on frame-based routing IDs or WebViewRendererState.

Evaluated with Chrome root at commit: 9760e6c70cd33a320713361f17c6dcca85648c0f


Results from Flapjack so far have been promising, but it can be wrong in its deductions. At this time, it does not produce proof of concepts or fuzzer tests. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve Flapjack’s accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker
Links in the report