CVE-2026-19152
Overview
Background
- `common_params_->post_data`
- the request body attached to a
NavigationRequest; per the code comment it can carry file references, so it must not be sent to a renderer belonging to a differentSiteInstance. - `ComputeErrorPageProcess()` / `ErrorPageProcess`
- decides where a failed navigation’s error page commits:
kCurrentProcess(the current frame’s renderer), the isolated error-page process, orkDestinationProcess(the process for the target URL). - `net::IsRequestBlockedError()`
- identifies “blocked” network errors, whose error pages are, per the new comment, the other case where POST data could reach the wrong renderer.
- Confirm Form Resubmission
- the
ERR_CACHE_MISSerror page shown when going back to an uncacheable POST result; reloading it withcheck_for_repostre-sends the stored POST body after the user confirms.
Root Cause Analysis
The patched block, in a NavigationRequest method whose name is not visible in the hunk (it runs next to origin_related_state_.reset() when request state is reset, such as on redirects), decides when to drop common_params_->post_data. Before the patch, the condition was !IsPost() || DidEncounterError(), so every failed navigation lost its POST body. The goal was to stop file references from leaking to an error page that “may end up in another process”.
The patch relaxes this: on error, the POST body is now cleared only when ComputeErrorPageProcess() == ErrorPageProcess::kCurrentProcess or net::IsRequestBlockedError(net_error_). It is kept for error pages in the isolated error-page process and for kDestinationProcess failures such as ERR_CACHE_MISS. The real security invariant, spelled out in the new comment, is narrower than “any error”: POST data must not reach the renderer of the current page or a blocked error page, since those are the cases where it could land in a different SiteInstance. The commit’s main effect is functional, restoring the “Confirm Form Resubmission” flow (crbug.com/553614977, which the regression test exercises). For the security bug 531165110, it keeps the necessary clearing while dropping the over-broad part. The diff does not show the earlier change that introduced the DidEncounterError() clearing, so how this exact commit relates to the CVE can only be inferred from the shared bug ID.
SiteInstance. That risk exists only when the error page commits in the current process or the navigation was blocked, not for every network error. The fix ties the clearing decision to ComputeErrorPageProcess() and IsRequestBlockedError(), so the cross-SiteInstance cases stay protected and retryable kDestinationProcess failures keep their POST data.Attack Path
- POST navigation with a body A frame navigates with POST data that may contain file references, such as an uploaded file in a form submission (the file-reference concern comes from the code comment).
- Navigation fails The request ends in a network error, so an error page is committed instead of the target document.
- Error page lands in the wrong process
If the error page commits in
ErrorPageProcess::kCurrentProcess(the comment says subframe error pages are not yet isolated,crbug.com/40134629) or is a blocked-response error, the navigation’spost_datacould be handed to a renderer whoseSiteInstanceis not the destination’s. - Renderer gains access to POST contents That renderer could receive the POST body or the file references within it, which it should not have (inferred, since the diff does not show the downstream file-grant handling).
Impact Assessment
SiteInstance” comment). The leaky cases require a failed POST navigation whose error page commits in the current process (for example an unisolated subframe error page) or a blocked-response error, so it depends on a cross-site embedding or navigation setup rather than a single page load. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.3. The direct effect of this specific commit is to restore form resubmission after ERR_CACHE_MISS.Files Changed
content/browser/renderer_host/navigation_request.ccchrome/browser/repost_form_warning_browsertest.cc
Audit Directions
- Request bodies across process transitionsAudit every path where
post_data,ResourceRequestBodyor file-backed blobs travel with a navigation that may commit in a different process (redirects, error pages,about:blankfallbacks, prerender/BFCache activation). Check that the body is dropped or the access grant is scoped to the destinationSiteInstance. - Error-page process selectionWherever
ComputeErrorPageProcess()returnskCurrentProcess(subframes without error-page isolation), look for other navigation state (headers,initiator, origin-related state) that can be passed to the wrong renderer. - Over-broad mitigations that get narrowedWhen a security clear-out is later narrowed for compatibility, as here, check the narrowed condition against every error code and process outcome so that a blocked or cross-site case is not quietly reopened.
Patch
From 202324afa3f42ed4c5480eca5c0ea94a08935ec1 Mon Sep 17 00:00:00 2001
From: Charlie Reis <creis@chromium.org>
Date: Thu, 03 Sep 2026 14:00:37 -0700
Subject: [PATCH] Ensure Confirm Form Resubmission dialog works after an error page.
POST data should only be cleared in cases where it may incorrectly be
given to the renderer process of the current page or a blocked error
page, not on a failed navigation that may be retried.
This CL was partly written by Gemini.
Bug: 553614977, 531165110
Change-Id: Ic6f42f2db8e0ca36e231f56f7c7b49efe935a9bd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8341516
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Commit-Queue: Avi Drissman <avi@chromium.org>
Auto-Submit: Charlie Reis <creis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1691913}
---
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index b7e23d3..74daa15 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -3544,10 +3544,21 @@
origin_related_state_.reset();
// If this was not a redirect that preserves POST submissions (e.g., 307), or
- // if this will be an error page that may end up in another process, then
- // clear the post_data as well to prevent leaking file references to a
- // different SiteInstance.
- if (!IsPost() || DidEncounterError()) {
+ // if this will be a blocked error page or otherwise ends up in the current
+ // process, then clear the post_data as well to prevent leaking file
+ // references to a different SiteInstance.
+ //
+ // It is not necessary to reset POST data for error pages in the isolated
+ // error page process, and it is important not to reset it for failed cases
+ // that end up in kDestinationProcess (e.g. "Confirm Form Resubmission"
+ // pages with ERR_CACHE_MISS), when it might later be used successfully.
+ //
+ // TODO(crbug.com/40134629): Remove the error page exception if subframe error
+ // page isolation is enabled.
+ if (!IsPost() ||
+ (DidEncounterError() &&
+ (ComputeErrorPageProcess() == ErrorPageProcess::kCurrentProcess ||
+ net::IsRequestBlockedError(net_error_)))) {
common_params_->post_data.reset();
}
}
diff --git a/chrome/browser/repost_form_warning_browsertest.cc b/chrome/browser/repost_form_warning_browsertest.cc
index d7ce5b7..425e2300 100644
--- a/chrome/browser/repost_form_warning_browsertest.cc
+++ b/chrome/browser/repost_form_warning_browsertest.cc
@@ -11,14 +11,22 @@
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
+#include "components/strings/grit/components_strings.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
+#include "net/base/net_errors.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/base/l10n/l10n_util.h"
#include "ui/base/page_transition_types.h"
#include "ui/base/window_open_disposition.h"
+#include "ui/views/test/dialog_test.h"
+#include "ui/views/test/widget_test.h"
+#include "ui/views/widget/widget.h"
using web_modal::WebContentsModalDialogManager;
@@ -121,3 +129,98 @@
ShowAndVerifyUi();
}
#endif
+
+// Verifies that confirming form resubmission after navigating back to an
+// uncacheable POST page resubmits the POST data (https://crbug.com/553614977).
+IN_PROC_BROWSER_TEST_F(RepostFormWarningTest,
+ ConfirmResubmissionAfterBackNavigation) {
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Submit a form to an uncacheable URL (/echoall/nocache) with POST data.
+ GURL echo_url = embedded_test_server()->GetURL("/echoall/nocache");
+ {
+ content::TestNavigationObserver observer(web_contents);
+ ASSERT_TRUE(content::ExecJs(
+ web_contents, content::JsReplace(
+ R"(let form = document.createElement('form');
+ form.method = 'POST';
+ form.action = $1;
+ let input = document.createElement('input');
+ input.name = 'text';
+ input.value = 'val';
+ form.appendChild(input);
+ document.body.appendChild(form);
+ form.submit();)",
+ echo_url)));
+ observer.Wait();
+ EXPECT_TRUE(observer.last_navigation_succeeded());
+ }
+ EXPECT_EQ(echo_url, web_contents->GetLastCommittedURL());
+
+ // Verify that the initial POST was successful and body was echoed.
+ EXPECT_EQ(
+ "text=val\n",
+ content::EvalJs(web_contents,
+ "document.getElementsByTagName('pre')[0].innerText;"));
+
+ // Navigate forward to another page.
+ GURL other_url = embedded_test_server()->GetURL("/title2.html");
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), other_url));
+ EXPECT_EQ(other_url, web_contents->GetLastCommittedURL());
+
+ // Navigate back to the POST page. Because it is uncacheable, this results in
+ // net::ERR_CACHE_MISS and displays the "Confirm Form Resubmission" error
+ // page.
+ {
+ content::TestNavigationObserver back_observer(web_contents);
+ web_contents->GetController().GoBack();
+ back_observer.Wait();
+ EXPECT_FALSE(back_observer.last_navigation_succeeded());
+ EXPECT_EQ(net::ERR_CACHE_MISS, back_observer.last_net_error_code());
+ }
+ EXPECT_EQ(echo_url, web_contents->GetLastCommittedURL());
+
+ // Reload the page, checking for repost.
+ web_contents->GetController().Reload(content::ReloadType::NORMAL,
+ /*check_for_repost=*/true);
+
+ // Wait for the repost warning dialog to become active.
+ WebContentsModalDialogManager* modal_dialog_manager =
+ WebContentsModalDialogManager::FromWebContents(web_contents);
+ ASSERT_TRUE(base::test::RunUntil(
+ [&]() { return modal_dialog_manager->IsDialogActive(); }));
+
+ // Find the dialog widget.
+ views::Widget* dialog_widget = nullptr;
+ for (views::Widget* widget : views::test::WidgetTest::GetAllWidgets()) {
+ if (widget->widget_delegate() &&
+ widget->widget_delegate()->AsDialogDelegate() &&
+ widget->widget_delegate()->GetWindowTitle() ==
+ l10n_util::GetStringUTF16(IDS_HTTP_POST_WARNING_TITLE)) {
+ dialog_widget = widget;
+ break;
+ }
+ }
+ ASSERT_TRUE(dialog_widget);
+
+ // Accept the dialog ("Continue") and wait for the reload to finish.
+ content::TestNavigationObserver reload_observer(web_contents);
+ views::test::AcceptDialog(dialog_widget);
+ reload_observer.Wait();
+ EXPECT_TRUE(reload_observer.last_navigation_succeeded());
+ EXPECT_EQ(echo_url, web_contents->GetLastCommittedURL());
+
+ // Verify that the reload was a POST request.
+ std::string request_headers =
+ content::EvalJs(web_contents,
+ "document.getElementById('request-headers').innerText;")
+ .ExtractString();
+ EXPECT_THAT(request_headers, ::testing::HasSubstr("POST /echoall/nocache"));
+
+ // Verify that the POST body was resubmitted and echoed in the page body.
+ EXPECT_EQ(
+ "text=val\n",
+ content::EvalJs(web_contents,
+ "document.getElementsByTagName('pre')[0].innerText;"));
+}
Regression Test / PoC
diff --git a/chrome/browser/repost_form_warning_browsertest.cc b/chrome/browser/repost_form_warning_browsertest.cc
index d7ce5b7..425e2300 100644
--- a/chrome/browser/repost_form_warning_browsertest.cc
+++ b/chrome/browser/repost_form_warning_browsertest.cc
@@ -11,14 +11,22 @@
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
+#include "components/strings/grit/components_strings.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
+#include "net/base/net_errors.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/base/l10n/l10n_util.h"
#include "ui/base/page_transition_types.h"
#include "ui/base/window_open_disposition.h"
+#include "ui/views/test/dialog_test.h"
+#include "ui/views/test/widget_test.h"
+#include "ui/views/widget/widget.h"
using web_modal::WebContentsModalDialogManager;
@@ -121,3 +129,98 @@
ShowAndVerifyUi();
}
#endif
+
+// Verifies that confirming form resubmission after navigating back to an
+// uncacheable POST page resubmits the POST data (https://crbug.com/553614977).
+IN_PROC_BROWSER_TEST_F(RepostFormWarningTest,
+ ConfirmResubmissionAfterBackNavigation) {
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Submit a form to an uncacheable URL (/echoall/nocache) with POST data.
+ GURL echo_url = embedded_test_server()->GetURL("/echoall/nocache");
+ {
+ content::TestNavigationObserver observer(web_contents);
+ ASSERT_TRUE(content::ExecJs(
+ web_contents, content::JsReplace(
+ R"(let form = document.createElement('form');
+ form.method = 'POST';
+ form.action = $1;
+ let input = document.createElement('input');
+ input.name = 'text';
+ input.value = 'val';
+ form.appendChild(input);
+ document.body.appendChild(form);
+ form.submit();)",
+ echo_url)));
+ observer.Wait();
+ EXPECT_TRUE(observer.last_navigation_succeeded());
+ }
+ EXPECT_EQ(echo_url, web_contents->GetLastCommittedURL());
+
+ // Verify that the initial POST was successful and body was echoed.
+ EXPECT_EQ(
+ "text=val\n",
+ content::EvalJs(web_contents,
+ "document.getElementsByTagName('pre')[0].innerText;"));
+
+ // Navigate forward to another page.
+ GURL other_url = embedded_test_server()->GetURL("/title2.html");
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), other_url));
+ EXPECT_EQ(other_url, web_contents->GetLastCommittedURL());
+
+ // Navigate back to the POST page. Because it is uncacheable, this results in
+ // net::ERR_CACHE_MISS and displays the "Confirm Form Resubmission" error
+ // page.
+ {
+ content::TestNavigationObserver back_observer(web_contents);
+ web_contents->GetController().GoBack();
+ back_observer.Wait();
+ EXPECT_FALSE(back_observer.last_navigation_succeeded());
+ EXPECT_EQ(net::ERR_CACHE_MISS, back_observer.last_net_error_code());
+ }
+ EXPECT_EQ(echo_url, web_contents->GetLastCommittedURL());
+
+ // Reload the page, checking for repost.
+ web_contents->GetController().Reload(content::ReloadType::NORMAL,
+ /*check_for_repost=*/true);
+
+ // Wait for the repost warning dialog to become active.
+ WebContentsModalDialogManager* modal_dialog_manager =
+ WebContentsModalDialogManager::FromWebContents(web_contents);
+ ASSERT_TRUE(base::test::RunUntil(
+ [&]() { return modal_dialog_manager->IsDialogActive(); }));
+
+ // Find the dialog widget.
+ views::Widget* dialog_widget = nullptr;
+ for (views::Widget* widget : views::test::WidgetTest::GetAllWidgets()) {
+ if (widget->widget_delegate() &&
+ widget->widget_delegate()->AsDialogDelegate() &&
+ widget->widget_delegate()->GetWindowTitle() ==
+ l10n_util::GetStringUTF16(IDS_HTTP_POST_WARNING_TITLE)) {
+ dialog_widget = widget;
+ break;
+ }
+ }
+ ASSERT_TRUE(dialog_widget);
+
+ // Accept the dialog ("Continue") and wait for the reload to finish.
+ content::TestNavigationObserver reload_observer(web_contents);
+ views::test::AcceptDialog(dialog_widget);
+ reload_observer.Wait();
+ EXPECT_TRUE(reload_observer.last_navigation_succeeded());
+ EXPECT_EQ(echo_url, web_contents->GetLastCommittedURL());
+
+ // Verify that the reload was a POST request.
+ std::string request_headers =
+ content::EvalJs(web_contents,
+ "document.getElementById('request-headers').innerText;")
+ .ExtractString();
+ EXPECT_THAT(request_headers, ::testing::HasSubstr("POST /echoall/nocache"));
+
+ // Verify that the POST body was resubmitted and echoed in the page body.
+ EXPECT_EQ(
+ "text=val\n",
+ content::EvalJs(web_contents,
+ "document.getElementsByTagName('pre')[0].innerText;"));
+}
Original Bug Report
Potential Cross-Origin File Leak to Non-Target Process via Uncleared post_data in FailedNavigations
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A missing cleanup in NavigationRequest::ResetStateForSiteInstanceChange() allows a compromised renderer to obtain GrantReadFile permissions for local files previously uploaded to cross-origin sites. By triggering a blocked history navigation in a subframe, the attacker forces the browser to grant file access to the wrong process and leak the target file path via IPC.
Affected files:
content/browser/renderer_host/render_frame_host_impl.cccontent/browser/renderer_host/navigation_request.cccontent/browser/renderer_host/render_frame_host_manager.cc
Estimated timestamp from git blame: Unknown (Google3 checkout)
1. Summary of the Issue (Meant for Human Triage)
A severe potential cross-origin file read vulnerability exists in Chromium’s navigation stack due to a failure to clear POST request body state during navigation-state resets on failed navigations. When a renderer-initiated history subframe navigation is blocked (for example, by a Content Security Policy or a NavigationThrottle), NavigationRequest::ComputeErrorPageProcess() determines that the error page should be hosted in the current frame’s process (kCurrentProcess). Consequently, FailedNavigation is invoked on the current process’s RenderFrameHost, which calls UpdatePermissionsForNavigation to grant permissions to that process.
If the current process (Process A, locked to Site A) differs from the original destination process (Process B, locked to Site B), ResetStateForSiteInstanceChange() is triggered. While this correctly mitigates serialized PageState-based file access by clearing commit_params_->page_state, it critically fails to clear common_params_->post_data. As a result, the local file paths embedded in the post_data—which the user legitimately uploaded to Site B via a file picker in a prior session—are passed to GrantFileAccessFromResourceRequestBody. This issues a GrantReadFile permission to the attacker’s Process A.
Furthermore, the exact local file path is sent to the attacker’s process via the CommitFailedNavigation Mojo IPC. This allows a compromised renderer hosting Site A to bypass downstream CanReadFile checks and silently exfiltrate arbitrary files previously selected by the user on Site B.
2. Proof-of-Concept & Detailed Execution Flow
Note: Our tooling agent does not have the ability to run code. The following are potential, suggested steps an attacker would follow to trigger the vulnerability based on static codebase analysis.
Attacker Setup & Preconditions
- Compromised Target: The attacker controls a malicious website (Site A) and has compromised the renderer process hosting it (Process P_A).
- Target File Upload: The attacker embeds a cross-origin iframe to the victim site (Site B). The user interacts with Site B (e.g., via
<input type="file">) and uploads a local file (File F). - History State: The browser creates a
FrameNavigationEntry(FNE_B) for Site B. Since the form was submitted via POST,FNE_B->GetPostData()stores aResourceRequestBodycontaining aDataElementFilereferencing File F.
Potential Execution Trace
- Return to Site A: The compromised Process P_A navigates the iframe back to a same-site URL (Site A). The iframe’s active
RenderFrameHostis now hosted in Process P_A, but the joint history session retains FNE_B. - Block Policy Injection: Process P_A injects a Content Security Policy into its main frame that blocks subframe navigations to Site B (e.g.,
frame-src 'self'). - Trigger History Back: Process P_A initiates a history back navigation targeting the Site B entry (
LocalMainFrameHost::GoToEntryAtOffset(-1)). - Request Construction:
NavigationControllerImpl::CreateNavigationRequestFromEntry(content/browser/renderer_host/navigation_controller_impl.cc:4866-5006) extracts thepost_dataviaframe_entry->GetPostData()and assigns it to the newNavigationRequest’scommon_params->post_dataintact. - Navigation Blocked:
NavigationRequest::BeginNavigationImplevaluates the CSP, which blocks the navigation, yieldingERR_BLOCKED_BY_CSP. - Error Process Assignment:
NavigationRequest::SelectFrameHostForOnRequestFailedInternalcallsComputeErrorPageProcess()(navigation_request.cc:5814-5856). Because it is a subframe (IsErrorPageIsolationEnabled()evaluates tofalse), it is a blocked request (IsRequestBlockedError() == true), and it is renderer-initiated (!browser_initiated()), it explicitly returnsErrorPageProcess::kCurrentProcess(Process P_A). - State Reset Failure:
GetSiteInstanceForNavigationRequestassigns the error navigation to Site A’sSiteInstance. Because the destinationSiteInstanceof the history entry (Site B) differs from the assigned instance (Site A),NavigationRequest::ResetStateForSiteInstanceChange()is called (navigation_request.cc:3636-3661). This function resets bindings and clearscommit_params_->page_state, but fails to clear or resetcommon_params_->post_data. - Unauthorized File Grant:
NavigationRequest::CommitErrorPagecallsGetRenderFrameHost()->FailedNavigation()on Process P_A’sRenderFrameHost, which in turn invokesUpdatePermissionsForNavigation(render_frame_host_impl.cc:14048). - Sink Execution:
Because
// content/browser/renderer_host/render_frame_host_impl.cc:14092-14094 if (request->common_params().post_data) { GrantFileAccessFromResourceRequestBody(*request->common_params().post_data); }post_datasurvived the reset, this iterates overDataElementFileand invokesChildProcessSecurityPolicyImpl::GrantReadFile, permanently granting Process P_A (Site A) read access to File F. - Data Leakage & Exfiltration: The browser sends the
CommitFailedNavigationMojo IPC to Process P_A. As defined incontent/common/navigation_client.mojom, this IPC includesCommonNavigationParams. Inservices/network/public/cpp/url_request_mojom_traits.cc, the renderer deserializes theURLRequestBodyandDataElementFile, placing the exact local file path string directly into the attacker’s memory space. Process P_A can now initiate a newfetch()using the leaked path, successfully bypassing all browserCanReadFilechecks.
Suggested Fix
In content/browser/renderer_host/navigation_request.cc, inside NavigationRequest::ResetStateForSiteInstanceChange(), explicitly clear common_params_->post_data alongside commit_params_->page_state.
if (page_state.IsValid())
commit_params_->page_state =
blink::PageState::CreateFromURL(GetURL()).ToEncodedData();
+ // Also clear post_data to prevent leaking cross-origin file references or
+ // issuing unauthorized file grants to the wrong process.
+ common_params_->post_data = nullptr;
3. Technical Verification Details (Automated Audit Logs - Reviewers may skip this section)
Prior Critic Verdict
> Severity: High (S1)
> Brief Notes / Reasoning:
> The report describes a valid cross-origin file leak via ComputeErrorPageProcess and UpdatePermissionsForNavigation. When a renderer-initiated history navigation for a subframe is blocked (e.g. via CSP injected by a compromised renderer), ComputeErrorPageProcess() evaluates to kCurrentProcess (process P_A, locked to site A). FailedNavigation then invokes UpdatePermissionsForNavigation(request) on P_A’s RFH.
>
> UpdatePermissionsForNavigation calls GrantFileAccessFromResourceRequestBody with request->common_params().post_data. Because ResetStateForSiteInstanceChange() fails to clear post_data, the file paths in the post_data—which the user uploaded to site B in a prior interaction—are passed to GrantReadFile, granting P_A access to those files. Once P_A legitimately holds the GrantReadFile permission for file F, it can freely bypass downstream CanReadFile checks and exfiltrate the file to site A.
>
> The rewriter downrated this to Low (S3), claiming it duplicates a ’low’ finding and non-arbitrary file reads don’t meet the S1 bar. However, the severity guidelines explicitly state: ‘A ChildProcessSecurityPolicy grant for site B or file:// is issued to a process locked to site A’ is High (S1) severity. Furthermore, this constitutes an ability to ‘read cross-origin data’ (a file intended for another origin), which is also S1. The prerequisite user interaction (uploading a file via a file picker) is a standard web interaction, not ‘unusual or complex’, and does not warrant a severity downgrade.
Additional Agent Verifications & Logic Proofs
- IPC Serialization / Deserialization Confirmations:
- Verified via codebase tracing that
content/common/navigation_client.mojom:397-411definesCommitFailedNavigationtakingblink.mojom.CommonNavigationParams common_params. third_party/blink/public/mojom/navigation/navigation_params.mojom:273confirmsCommonNavigationParamscontainsnetwork.mojom.URLRequestBody? post_data.services/network/public/cpp/url_request_mojom_traits.cc:229-242confirms thatDataElementFiledeserializes the file path directly into the renderer process’s memory space intact (base::FilePath path), without any stripping.
- Verified via codebase tracing that
- Subframe Error Isolation Evaluation:
- Verified
FrameTreeNode::IsErrorPageIsolationEnabled(lines 1140-1143) routes toSiteIsolationPolicy::IsErrorPageIsolationEnabled(IsMainFrame()). - Traced to
ChromeContentBrowserClient::ShouldIsolateErrorPage(bool in_main_frame)(lines 2551-2555) which strictly returnsin_main_frame. For subframes, this is deterministicallyfalse.
- Verified
- Missing Cleanup Validation:
- Codebase audit of
NavigationRequest::ResetStateForSiteInstanceChange(navigation_request.cc:3636-3661) confirms it exclusively resetsbindings_,commit_params_->page_state, andorigin_related_state_.common_params_->post_dataremains completely unaddressed.
- Codebase audit of
- Data Preservation in Navigation Generation:
- Codebase audit of
NavigationControllerImpl::CreateNavigationRequestFromEntry(navigation_controller_impl.cc:4866-5006) confirmsrequest_bodyis extracted fromframe_entry->GetPostData()and placed un-sanitized intoentry->ConstructCommonNavigationParams().
- Codebase audit of
Evaluated with Chrome root at commit: b25acdb3da6209f69a155cbd1dbbaca3c21f535b
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.