CVE-2026-79256
Overview
Background
- `InterceptedRequest`
- the per-request object in Android WebView’s proxying URL loader that mediates a navigation/resource load between the renderer’s client and the real network loader.
- `FollowRedirect`
- the mojo-exposed method the renderer calls to continue a load after a redirect, optionally supplying a
new_urlto follow. - `content::IsSafeRedirectTarget()`
- a helper that decides whether redirecting from one URL to another is permitted (e.g. blocking
http(s)→content:///file://). - `content://` / `file://` URLs
- local Android resource schemes that expose app content providers and local files, which web-origin pages must not be able to reach via redirect.
Root Cause Analysis
Android WebView relied on a renderer-side check to stop an http(s) load from redirecting to a content:// or file:// URL, but the browser-side proxying loader (InterceptedRequest) performed no equivalent validation when the renderer invoked FollowRedirect. A compromised renderer could therefore bypass the renderer check and drive a redirect whose target is a local content/file URL, causing the browser to follow it. The violated invariant is that redirect targets crossing into local schemes must be authorized on the browser side, which is the only trustworthy boundary against a compromised renderer.
The fix tracks the pre-redirect URL in a new last_url_ field — initialized to request.url in the constructor and updated to request_.url inside OnReceiveRedirect before request_.url is overwritten with the new target — and then, in FollowRedirect, computes target_url = new_url.value_or(request_.url) and, when request_was_redirected_ is set, rejects the redirect via SendErrorAndCompleteImmediately(net::ERR_UNSAFE_REDIRECT) (after target_loader_.reset()) unless content::IsSafeRedirectTarget(last_url_, target_url) allows it. This adds the missing browser-side enforcement.
IsSafeRedirectTarget(last_url_, target_url) gate in InterceptedRequest::FollowRedirect so a compromised renderer can no longer follow an unsafe redirect.Attack Path
- Compromise the renderer The attacker gains code execution in a WebView renderer process (inferred; a compromised renderer is the stated threat model).
- Start a web-origin load
A request begins for an
http(s)URL, recorded aslast_url_. - Receive a redirect
The server (or the renderer) drives a redirect;
OnReceiveRedirectruns, updatinglast_url_and settingrequest_was_redirected_. - Call `FollowRedirect` with a local target
The compromised renderer invokes
FollowRedirectwith anew_urlofcontent://…orfile:///android_asset/…, bypassing the renderer-side check that would normally block it. - Load local content (pre-fix)
Without the browser-side check, WebView follows the redirect into the local scheme, exposing local files or content-provider data to the web origin; post-fix this is rejected with
net::ERR_UNSAFE_REDIRECT.
Impact Assessment
content:// or file:// URL, giving the attacker access to local files / content-provider data that the web origin should never reach — an externally controlled reference / local-resource disclosure enforced in the browser process. The precondition is a renderer already compromised or otherwise able to bypass the renderer-side redirect check. The advisory classifies this as a logic error (externally controlled reference) in WebView; no in-the-wild exploitation is stated, and the initial renderer compromise is (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
ifandroid_webview/browser/network_service/aw_proxying_url_loader_factory.cc |
modified | |
AwProxyingURLLoaderFactoryTestandroid_webview/browser/network_service/aw_proxying_url_loader_factory_unittest.cc |
modified | |
test_url_loader_factory_android_webview/browser/network_service/aw_proxying_url_loader_factory_unittest.cc |
modified | |
ifandroid_webview/browser/network_service/aw_proxying_url_loader_factory_unittest.cc |
modified | |
TEST_Fandroid_webview/browser/network_service/aw_proxying_url_loader_factory_unittest.cc |
modified |
Files Changed
android_webview/browser/network_service/aw_proxying_url_loader_factory_unittest.ccandroid_webview/browser/network_service/aw_proxying_url_loader_factory.ccandroid_webview/test/BUILD.gn
Audit Directions
- Renderer-only security checksLook for WebView/loader restrictions enforced solely in the renderer with no matching browser-side enforcement, which a compromised renderer can bypass.
- Redirect scheme transitionsAudit redirect-following code paths for validation that a web-origin load cannot transition into local schemes (
content://,file://) or other privileged schemes. - State captured across redirectsReview loaders that must compare pre- and post-redirect URLs to ensure the source URL is captured before
request_.urlis mutated, avoiding checks against the already-updated value.
Patch
From 751df049a7db402799a586c0d1fa92a64fe5be68 Mon Sep 17 00:00:00 2001 From: Joanne de Abreu <[email protected]> Date: Wed, 08 Jul 2026 03:07:34 -0700 Subject: [PATCH] Add browser side check for redirect to content/file urls There is a renderer side check to stop http/s urls redirecting to content/file urls. This cl adds a check on the browser side for android_webview to protect against a compromised renderer that bypasses such checks. Fixed: 499007248 Change-Id: Ifed48190982e55d4ed4d1122b80604509a169145 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7950860 Reviewed-by: Nate Fischer <[email protected]> Commit-Queue: Joanne de Abreu <[email protected]> Cr-Commit-Position: refs/heads/main@{#1658621} --- diff --git a/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc b/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc index 7c29e9f..a640907 100644 --- a/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc +++ b/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc @@ -247,6 +247,7 @@ // error didn't occur. int error_status_ = net::OK; + GURL last_url_; network::ResourceRequest request_; const net::MutableNetworkTrafficAnnotationTag traffic_annotation_; @@ -368,6 +369,7 @@ options_(options), intercept_only_(intercept_only), security_options_(security_options), + last_url_(request.url), request_(std::move(request)), traffic_annotation_(traffic_annotation), proxied_loader_receiver_(this, std::move(loader_receiver)), @@ -793,6 +795,7 @@ network::mojom::URLResponseHeadPtr head) { // TODO(timvolodine): handle redirect override. request_was_redirected_ = true; + last_url_ = request_.url; target_client_->OnReceiveRedirect(redirect_info, std::move(head)); request_.url = redirect_info.new_url; request_.method = redirect_info.new_method; @@ -827,6 +830,14 @@ void InterceptedRequest::FollowRedirect( network::HttpRequestHeadersUpdateParams headers_update_params, const std::optional<GURL>& new_url) { + GURL target_url = new_url.value_or(request_.url); + if (request_was_redirected_ && + !content::IsSafeRedirectTarget(last_url_, target_url)) { + target_loader_.reset(); + SendErrorAndCompleteImmediately(net::ERR_UNSAFE_REDIRECT); + return; + } + if (target_loader_) { if (!origin_matched_headers_.empty()) { ApplyOriginMatchedHeaders(&headers_update_params.removed_headers, diff --git a/android_webview/browser/network_service/aw_proxying_url_loader_factory_unittest.cc b/android_webview/browser/network_service/aw_proxying_url_loader_factory_unittest.cc new file mode 100644 index 0000000..032caff --- /dev/null +++ b/android_webview/browser/network_service/aw_proxying_url_loader_factory_unittest.cc @@ -0,0 +1,150 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "android_webview/browser/network_service/aw_proxying_url_loader_factory.h" + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "android_webview/browser/aw_origin_matched_header.h" +#include "android_webview/browser/network_service/aw_browser_context_io_thread_handle.h" +#include "base/functional/bind.h" +#include "base/run_loop.h" +#include "base/test/run_until.h" +#include "base/test/scoped_feature_list.h" +#include "content/public/test/browser_task_environment.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "net/base/net_errors.h" +#include "net/traffic_annotation/network_traffic_annotation_test_helper.h" +#include "services/network/public/cpp/http_request_headers_update_params.h" +#include "services/network/public/cpp/resource_request.h" +#include "services/network/public/mojom/url_loader_factory.mojom.h" +#include "services/network/test/test_url_loader_client.h" +#include "services/network/test/test_url_loader_factory.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace android_webview { + +class AwProxyingURLLoaderFactoryTest : public testing::Test { + public: + AwProxyingURLLoaderFactoryTest() + : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP), + test_url_loader_factory_(true) {} + + protected: + int FollowRedirect(const GURL& original_url, const GURL& redirect_url) { + mojo::Remote<network::mojom::URLLoaderFactory> proxy_factory_remote; + mojo::Receiver<network::mojom::URLLoaderFactory> target_factory_receiver( + &test_url_loader_factory_); + + auto factory = std::make_unique<AwProxyingURLLoaderFactory>( + /*cookie_manager=*/std::nullopt, &cookie_access_policy_, + /*isolation_info=*/std::nullopt, + /*key=*/std::nullopt, content::FrameTreeNodeId(), + proxy_factory_remote.BindNewPipeAndPassReceiver(), + target_factory_receiver.BindNewPipeAndPassRemote(), + /*intercept_only=*/false, + /*security_options=*/std::nullopt, + /*origin_matched_headers=*/ + std::vector<scoped_refptr<AwOriginMatchedHeader>>(), + /*browser_context_handle=*/nullptr, + /*navigation_id=*/std::nullopt); + + network::ResourceRequest request; + request.url = original_url; + request.method = "GET"; + + mojo::Remote<network::mojom::URLLoader> loader_remote; + network::TestURLLoaderClient client; + + proxy_factory_remote->CreateLoaderAndStart( + loader_remote.BindNewPipeAndPassReceiver(), + /*request_id=*/1, + /*options=*/0, request, client.CreateRemote(), + net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); + + if (test_url_loader_factory_.NumPending() != 1) { + ADD_FAILURE() << "Expected 1 pending request"; + return net::ERR_FAILED; + } + network::TestURLLoaderFactory::PendingRequest* pending_request = + test_url_loader_factory_.GetPendingRequest(0); + if (!pending_request) { + ADD_FAILURE() << "Pending request is null"; + return net::ERR_FAILED; + } + + net::RedirectInfo redirect_info; + redirect_info.status_code = 302; + redirect_info.new_url = redirect_url; + redirect_info.new_method = "GET"; + + network::mojom::URLResponseHeadPtr response_head = + network::mojom::URLResponseHead::New(); + + pending_request->client->OnReceiveRedirect(redirect_info, + std::move(response_head)); + + client.RunUntilRedirectReceived(); + EXPECT_TRUE(client.has_received_redirect()); + EXPECT_EQ(redirect_url, client.redirect_info().new_url); + + network::HttpRequestHeadersUpdateParams update_params; + loader_remote->FollowRedirect(std::move(update_params), redirect_url); + + bool condition_met = base::test::RunUntil([&]() { + return client.has_received_completion() || + (pending_request->test_url_loader && + !pending_request->test_url_loader->follow_redirect_params() + .empty()); + }); + EXPECT_TRUE(condition_met); + + if (pending_request->test_url_loader && + !pending_request->test_url_loader->follow_redirect_params().empty()) { + pending_request->client->OnReceiveResponse( + network::mojom::URLResponseHead::New(), + mojo::ScopedDataPipeConsumerHandle(), std::nullopt); + pending_request->client->OnComplete( + network::URLLoaderCompletionStatus(net::OK)); + } + + client.RunUntilComplete(); + return client.completion_status().error_code; + } + + content::BrowserTaskEnvironment task_environment_; + AwCookieAccessPolicy cookie_access_policy_; + network::TestURLLoaderFactory test_url_loader_factory_; +}; + +TEST_F(AwProxyingURLLoaderFactoryTest, BlocksUnsafeRedirectToContentUrl) { + EXPECT_EQ(net::ERR_UNSAFE_REDIRECT, + FollowRedirect(GURL("http://example.com"), + GURL("content://com.example.provider/file"))); +} + +TEST_F(AwProxyingURLLoaderFactoryTest, BlocksUnsafeRedirectToFile) { + EXPECT_EQ(net::ERR_UNSAFE_REDIRECT, + FollowRedirect(GURL("http://example.com"), + GURL("file:///android_asset/test.html"))); +} + +TEST_F(AwProxyingURLLoaderFactoryTest, AllowsSafeRedirectToFile) { + EXPECT_EQ(net::OK, + FollowRedirect(GURL("file:///foo/bar"), GURL("file:///foo/baz"))); +} + +TEST_F(AwProxyingURLLoaderFactoryTest, AllowsSafeRedirect) { + EXPECT_EQ(net::OK, FollowRedirect(GURL("http://example.com"), + GURL("http://otherexample.com"))); +} + +} // namespace android_webview diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn index a30bbef10..e3fdf26a 100644 --- a/android_webview/test/BUILD.gn +++ b/android_webview/test/BUILD.gn @@ -870,6 +870,7 @@ "../browser/metrics/aw_server_side_allowlist_metrics_provider_unittest.cc", "../browser/metrics/visibility_metrics_logger_unittest.cc", "../browser/network_service/aw_proxying_restricted_cookie_manager_unittest.cc", + "../browser/network_service/aw_proxying_url_loader_factory_unittest.cc", "../browser/permission/media_access_permission_request_unittest.cc", "../browser/permission/permission_request_handler_unittest.cc", "../browser/prefetch/aw_prefetch_manager_unittest.cc",
Regression Test / PoC
diff --git a/android_webview/browser/network_service/aw_proxying_url_loader_factory_unittest.cc b/android_webview/browser/network_service/aw_proxying_url_loader_factory_unittest.cc
new file mode 100644
index 0000000..032caff
--- /dev/null
+++ b/android_webview/browser/network_service/aw_proxying_url_loader_factory_unittest.cc
@@ -0,0 +1,150 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "android_webview/browser/network_service/aw_proxying_url_loader_factory.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "android_webview/browser/aw_origin_matched_header.h"
+#include "android_webview/browser/network_service/aw_browser_context_io_thread_handle.h"
+#include "base/functional/bind.h"
+#include "base/run_loop.h"
+#include "base/test/run_until.h"
+#include "base/test/scoped_feature_list.h"
+#include "content/public/test/browser_task_environment.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/net_errors.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/http_request_headers_update_params.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "services/network/test/test_url_loader_client.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace android_webview {
+
+class AwProxyingURLLoaderFactoryTest : public testing::Test {
+ public:
+ AwProxyingURLLoaderFactoryTest()
+ : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
+ test_url_loader_factory_(true) {}
+
+ protected:
+ int FollowRedirect(const GURL& original_url, const GURL& redirect_url) {
+ mojo::Remote<network::mojom::URLLoaderFactory> proxy_factory_remote;
+ mojo::Receiver<network::mojom::URLLoaderFactory> target_factory_receiver(
+ &test_url_loader_factory_);
+
+ auto factory = std::make_unique<AwProxyingURLLoaderFactory>(
+ /*cookie_manager=*/std::nullopt, &cookie_access_policy_,
+ /*isolation_info=*/std::nullopt,
+ /*key=*/std::nullopt, content::FrameTreeNodeId(),
+ proxy_factory_remote.BindNewPipeAndPassReceiver(),
+ target_factory_receiver.BindNewPipeAndPassRemote(),
+ /*intercept_only=*/false,
+ /*security_options=*/std::nullopt,
+ /*origin_matched_headers=*/
+ std::vector<scoped_refptr<AwOriginMatchedHeader>>(),
+ /*browser_context_handle=*/nullptr,
+ /*navigation_id=*/std::nullopt);
+
+ network::ResourceRequest request;
+ request.url = original_url;
+ request.method = "GET";
+
+ mojo::Remote<network::mojom::URLLoader> loader_remote;
+ network::TestURLLoaderClient client;
+
+ proxy_factory_remote->CreateLoaderAndStart(
+ loader_remote.BindNewPipeAndPassReceiver(),
+ /*request_id=*/1,
+ /*options=*/0, request, client.CreateRemote(),
+ net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+
+ if (test_url_loader_factory_.NumPending() != 1) {
+ ADD_FAILURE() << "Expected 1 pending request";
+ return net::ERR_FAILED;
+ }
+ network::TestURLLoaderFactory::PendingRequest* pending_request =
+ test_url_loader_factory_.GetPendingRequest(0);
+ if (!pending_request) {
+ ADD_FAILURE() << "Pending request is null";
+ return net::ERR_FAILED;
+ }
+
+ net::RedirectInfo redirect_info;
+ redirect_info.status_code = 302;
+ redirect_info.new_url = redirect_url;
+ redirect_info.new_method = "GET";
+
+ network::mojom::URLResponseHeadPtr response_head =
+ network::mojom::URLResponseHead::New();
+
+ pending_request->client->OnReceiveRedirect(redirect_info,
+ std::move(response_head));
+
+ client.RunUntilRedirectReceived();
+ EXPECT_TRUE(client.has_received_redirect());
+ EXPECT_EQ(redirect_url, client.redirect_info().new_url);
+
+ network::HttpRequestHeadersUpdateParams update_params;
+ loader_remote->FollowRedirect(std::move(update_params), redirect_url);
+
+ bool condition_met = base::test::RunUntil([&]() {
+ return client.has_received_completion() ||
+ (pending_request->test_url_loader &&
+ !pending_request->test_url_loader->follow_redirect_params()
+ .empty());
+ });
+ EXPECT_TRUE(condition_met);
+
+ if (pending_request->test_url_loader &&
+ !pending_request->test_url_loader->follow_redirect_params().empty()) {
+ pending_request->client->OnReceiveResponse(
+ network::mojom::URLResponseHead::New(),
+ mojo::ScopedDataPipeConsumerHandle(), std::nullopt);
+ pending_request->client->OnComplete(
+ network::URLLoaderCompletionStatus(net::OK));
+ }
+
+ client.RunUntilComplete();
+ return client.completion_status().error_code;
+ }
+
+ content::BrowserTaskEnvironment task_environment_;
+ AwCookieAccessPolicy cookie_access_policy_;
+ network::TestURLLoaderFactory test_url_loader_factory_;
+};
+
+TEST_F(AwProxyingURLLoaderFactoryTest, BlocksUnsafeRedirectToContentUrl) {
+ EXPECT_EQ(net::ERR_UNSAFE_REDIRECT,
+ FollowRedirect(GURL("http://example.com"),
+ GURL("content://com.example.provider/file")));
+}
+
+TEST_F(AwProxyingURLLoaderFactoryTest, BlocksUnsafeRedirectToFile) {
+ EXPECT_EQ(net::ERR_UNSAFE_REDIRECT,
+ FollowRedirect(GURL("http://example.com"),
+ GURL("file:///android_asset/test.html")));
+}
+
+TEST_F(AwProxyingURLLoaderFactoryTest, AllowsSafeRedirectToFile) {
+ EXPECT_EQ(net::OK,
+ FollowRedirect(GURL("file:///foo/bar"), GURL("file:///foo/baz")));
+}
+
+TEST_F(AwProxyingURLLoaderFactoryTest, AllowsSafeRedirect) {
+ EXPECT_EQ(net::OK, FollowRedirect(GURL("http://example.com"),
+ GURL("http://otherexample.com")));
+}
+
+} // namespace android_webview
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index a30bbef10..e3fdf26a 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -870,6 +870,7 @@
"../browser/metrics/aw_server_side_allowlist_metrics_provider_unittest.cc",
"../browser/metrics/visibility_metrics_logger_unittest.cc",
"../browser/network_service/aw_proxying_restricted_cookie_manager_unittest.cc",
+ "../browser/network_service/aw_proxying_url_loader_factory_unittest.cc",
"../browser/permission/media_access_permission_request_unittest.cc",
"../browser/permission/permission_request_handler_unittest.cc",
"../browser/prefetch/aw_prefetch_manager_unittest.cc",
Original Bug Report
Potential Android WebView Sandbox Escape via Redirect and Mojo Double-Bind
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.
Overview: A compromised renderer in Android WebView can potentially read sensitive host-app data by exploiting an insecure redirect handling mechanism. Missing scheme validation in the network stack combined with a Mojo receiver double-bind in WebView’s proxy loader allows an attacker to bypass security checks and access local content:// URIs.
Affected files:
android_webview/browser/network_service/aw_proxying_url_loader_factory.ccservices/network/cors/cors_url_loader.ccandroid_webview/browser/network_service/net_helpers.cc
Estimated timestamp from git blame: 2024-06-28
Description
A potential vulnerability exists in Android WebView’s request interception logic that could allow a compromised renderer process to read data from any ContentProvider the host application has permission to access. This includes sensitive information such as contacts, SMS, and private app data.
The issue arises from a combination of missing scheme validation for redirects in the Network Service and a Mojo receiver double-bind bug in AwProxyingURLLoaderFactory.
Root Cause Analysis
- Insecure Redirect Handling: When an HTTP subresource fetch (e.g.,
kNoCors) receives a 302 redirect to a local URI likecontent://com.android.contacts/data/1, the Network Service evaluates the redirect.URLRequestJobFactory::IsSafeRedirectTargetdefaults totruefor unregistered schemes.CorsURLLoader::OnReceiveRedirectrelies on this and forwards the redirect to the renderer without explicitly blockingcontent://. - Renderer Check Bypass: A compromised renderer can bypass Blink’s
SecurityOrigin::CanDisplaychecks (which normally block local redirects) and send aFollowRedirectIPC directly to the browser. - Mojo Double-Bind in WebView Proxy: The
FollowRedirectIPC reachesInterceptedRequest::FollowRedirectinAwProxyingURLLoaderFactory. It updates the target URL to thecontent://URI and callsRestart(), which synchronously reachesContinueAfterIntercept(). - Silent Pipe Replacement:
ContinueAfterIntercept()detects thecontent://scheme and creates anAndroidStreamReaderURLLoaderto read the local data. To connect this new loader, it callsproxied_client_receiver_.BindNewPipeAndPassRemote(). Because the receiver is already bound to the active network loader, this violates Mojo invariants. However, in Release builds, theDCHECK(!is_bound())is compiled out. Mojo silently drops the old pipe (orphaning the network loader and suppressing its subsequentnet::ERR_UNKNOWN_URL_SCHEMEerror) and binds the new local loader. - Sandbox Escape: The
AndroidStreamReaderURLLoadersuccessfully reads the sensitivecontent://URI using the host app’s elevated permissions and streams the data back to the compromised renderer.
Potential Reproduction Steps
Note: These are suggested/potential steps to trigger the vulnerability. Our tooling agent does not currently have the ability to run code or verify a working proof of concept.
- Gain code execution in an Android WebView renderer process.
- Initiate a subresource fetch (e.g., via
fetch()withmode: 'no-cors') to an attacker-controlled HTTP server. - Have the attacker-controlled server respond with a
302 Foundand aLocationheader pointing to a sensitive local URI (e.g.,Location: content://com.android.contacts/data/1). - Upon receiving the redirect IPC in the compromised renderer, ignore Blink-side security checks and manually send a
FollowRedirectMojo IPC back to the browser process. - The browser process will inadvertently re-bind the data pipe to a local
AndroidStreamReaderURLLoader. - Read the sensitive host application data from the response body data pipe.
Suggested Fix
- Fix the Mojo Double-Bind: In
InterceptedRequest::ContinueAfterIntercept()(withinaw_proxying_url_loader_factory.cc), check ifproxied_client_receiver_.is_bound()before attempting to bind a new pipe. If it is already bound (e.g., during a redirect to a local scheme), the request should be explicitly aborted or reset properly. - Block Local Redirects: Explicitly block redirects from external schemes (HTTP/HTTPS) to local WebView schemes (
content://,file:///android_asset/, etc.) withinInterceptedRequest::FollowRedirectorCorsURLLoader::CheckRedirectLocation.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.