Chrome · WebAPKs › PWA
CVE-2026-106221
Authorization Error in WebAPKs (PWA)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/android/shortcut_helper.cc |
modified | |
ifchrome/browser/android/webapk/webapk_install_service.cc |
modified |
Files Changed
chrome/browser/android/webapk/webapk_installer_browsertest.ccchrome/browser/android/webapk/webapk_install_service.ccchrome/browser/android/webapk/webapk_install_service.hchrome/browser/android/webapk/BUILD.gnchrome/browser/android/shortcut_helper.cc
Patch
From 2eda634a59fa3b0aab224a38bb990257251e32b6 Mon Sep 17 00:00:00 2001 From: lukedeng <[email protected]> Date: Mon, 14 Sep 2026 12:52:54 -0700 Subject: [PATCH] [PWA] Prevent cross-origin splash fetch on WebAPK install fallback When a WebAPK installation fails, WebApkInstallService falls back to adding a home screen shortcut. If the initiating tab navigated away to a different (victim) origin while WebAPK minting was in flight, ShortcutHelper::AddToLauncherWithSkBitmap previously executed using the navigated tab context. This caused ManifestIconDownloader::Download to be called without an initiator frame ID, resolving to the active primary main frame of the victim origin in WebContentsImpl::DownloadImageInFrame. The splash image was then fetched with the victim origin's credentials and SameSite cookies attached, allowing a blind CSRF / SameSite cookie bypass. To fix this while maintaining expected shortcut installation behavior: - In WebApkInstallService::OnFinishedInstall: pass web_contents to ShortcutHelper::AddToLauncherWithSkBitmap only if still same-origin (or nullptr if navigated away or destroyed). Also document the AppBannerManager invariant with CHECK(app_banner_manager) and only notify AppBannerManager or launch notifications if still same-origin. - In ShortcutHelper::AddToLauncherWithSkBitmap: sanitize web_contents against cross-origin navigation as defense-in-depth. If web_contents has navigated away from the shortcut's origin, the pointer is sanitized to nullptr for splash downloading and UKM recording, preventing cross-origin fetches while still executing Java_ShortcutHelper_addWebapp to create the home screen shortcut using the snapshot data. - In ShortcutHelper::AddWebappWithSkBitmap: handle nullptr web_contents safely without initiating splash image download. When web_contents is present, explicitly pass the initiator frame's GlobalRenderFrameHostId to ManifestIconDownloader::Download to pin the fetch to that frame. - Add browser tests in webapk_installer_browsertest.cc covering ShortcutHelper::AddToLauncherWithSkBitmap and WebApkInstallService::OnFinishedInstall with nullptr, cross-origin, and same-origin WebContents, verifying both UKM recording behavior and that no splash image fetch is dispatched to the victim origin. TAG=agy CONV=0abcc8bf-ed69-44f0-9498-2d7ea473bdc2 Bug: 540049672 Change-Id: I1178fcd724da58e7b276dcd230e0f21dbe0406ea Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8394417 Reviewed-by: Daniel Murphy <[email protected]> Commit-Queue: Luke Deng <[email protected]> Reviewed-by: Yaron Friedman <[email protected]> Cr-Commit-Position: refs/heads/main@{#1697454} --- diff --git a/chrome/browser/android/shortcut_helper.cc b/chrome/browser/android/shortcut_helper.cc index 999e5db..97670c3d 100644 --- a/chrome/browser/android/shortcut_helper.cc +++ b/chrome/browser/android/shortcut_helper.cc @@ -5,6 +5,7 @@ #include "chrome/browser/android/shortcut_helper.h" #include <jni.h> + #include <limits> #include <string> #include <utility> @@ -16,6 +17,7 @@ #include "base/uuid.h" #include "components/webapps/browser/android/shortcut_info.h" #include "content/public/browser/manifest_icon_downloader.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "ui/android/color_utils_android.h" @@ -50,12 +52,18 @@ ui::OptionalSkColorToJavaColor(info.theme_color), ui::OptionalSkColorToJavaColor(info.background_color)); + if (!web_contents) { + return; + } + // Start downloading the splash image in parallel with the app install. content::ManifestIconDownloader::Download( web_contents, info.splash_image_url, info.ideal_splash_image_size_in_px, info.minimum_splash_image_size_in_px, /* maximum_icon_size_in_px= */ std::numeric_limits<int>::max(), - base::BindOnce(&ShortcutHelper::StoreWebappSplashImage, webapp_id)); + base::BindOnce(&ShortcutHelper::StoreWebappSplashImage, webapp_id), + /* square_only= */ true, + web_contents->GetPrimaryMainFrame()->GetGlobalId()); } // Adds a shortcut which opens in a browser tab to the launcher. @@ -96,13 +104,21 @@ const webapps::ShortcutInfo& info, const SkBitmap& icon_bitmap, webapps::InstallableStatusCode installable_status) { - RecordAddToHomeScreenUKM(web_contents, info, installable_status); + content::WebContents* same_origin_web_contents = + (web_contents && web_contents->GetPrimaryMainFrame() + ->GetLastCommittedOrigin() + .IsSameOriginWith(info.url)) + ? web_contents + : nullptr; + + RecordAddToHomeScreenUKM(same_origin_web_contents, info, installable_status); std::string webapp_id = base::Uuid::GenerateRandomV4().AsLowercaseString(); if (info.display == blink::mojom::DisplayMode::kStandalone || info.display == blink::mojom::DisplayMode::kFullscreen || info.display == blink::mojom::DisplayMode::kMinimalUi) { - AddWebappWithSkBitmap(web_contents, info, webapp_id, icon_bitmap); + AddWebappWithSkBitmap(same_origin_web_contents, info, webapp_id, + icon_bitmap); return; } AddShortcutWithSkBitmap(info, webapp_id, icon_bitmap); diff --git a/chrome/browser/android/webapk/webapk_install_service.cc b/chrome/browser/android/webapk/webapk_install_service.cc index 6e177be..d551df48 100644 --- a/chrome/browser/android/webapk/webapk_install_service.cc +++ b/chrome/browser/android/webapk/webapk_install_service.cc @@ -24,8 +24,10 @@ #include "components/webapps/browser/banners/app_banner_manager.h" #include "components/webapps/browser/features.h" #include "components/webapps/browser/installable/installable_logging.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "ui/gfx/android/java_bitmap.h" +#include "url/origin.h" WebApkInstallService::WebApkInstallService( content::BrowserContext* browser_context) @@ -112,19 +114,26 @@ const std::string& webapk_package_name) { install_ids_.erase(shortcut_info.manifest_id); + bool is_same_origin = + web_contents && web_contents->GetPrimaryMainFrame() + ->GetLastCommittedOrigin() + .IsSameOriginWith(shortcut_info.url); + bool show_failure_notification = base::FeatureList::IsEnabled( webapps::features::kWebApkInstallFailureNotification); HandleFinishInstallNotificationsAndMaybeLaunch( - web_contents.get(), shortcut_info.manifest_id, shortcut_info.url, - shortcut_info.short_name, primary_icon, + is_same_origin ? web_contents.get() : nullptr, shortcut_info.manifest_id, + shortcut_info.url, shortcut_info.short_name, primary_icon, shortcut_info.is_primary_icon_maskable, result, webapk_package_name, show_failure_notification); // If the app was successfully installed, we need to notify the app banner // manager so that the installability status is reflected elsewhere in the UI. - if (result == webapps::WebApkInstallResult::SUCCESS && web_contents) { - webapps::AppBannerManager::FromWebContents(web_contents.get()) - ->OnInstall(shortcut_info.display, true); + if (result == webapps::WebApkInstallResult::SUCCESS && is_same_origin) { + auto* app_banner_manager = + webapps::AppBannerManager::FromWebContents(web_contents.get()); + CHECK(app_banner_manager); + app_banner_manager->OnInstall(shortcut_info.display, true); } if (show_failure_notification) { @@ -137,13 +146,14 @@ // this one is still queued (and hence might succeed in the future). if (result != webapps::WebApkInstallResult::SUCCESS && result != webapps::WebApkInstallResult::PROBABLE_FAILURE) { - if (!web_contents) - return; - + // Pass the WebContents pointer if still on the same origin (or nullptr if + // destroyed or navigated away). ShortcutHelper::AddToLauncherWithSkBitmap + // will also verify same-origin as defense-in-depth before initiating splash + // image download or recording UKM. // TODO(crbug.com/40584062): Support maskable icons here. ShortcutHelper::AddToLauncherWithSkBitmap( - web_contents.get(), shortcut_info, primary_icon, - webapps::InstallableStatusCode::WEBAPK_INSTALL_FAILED); + is_same_origin ? web_contents.get() : nullptr, shortcut_info, + primary_icon, webapps::InstallableStatusCode::WEBAPK_INSTALL_FAILED); } } diff --git a/chrome/browser/android/webapk/webapk_install_service.h b/chrome/browser/android/webapk/webapk_install_service.h index 56147479..2a82df0 100644 --- a/chrome/browser/android/webapk/webapk_install_service.h +++ b/chrome/browser/android/webapk/webapk_install_service.h @@ -139,6 +139,8 @@ bool is_primary_icon_maskable, webapps::WebApkInstallResult result); + friend class WebApkInstallerBrowserTest; + raw_ptr<content::BrowserContext> browser_context_; // In progress installs's id. diff --git a/chrome/browser/android/webapk/webapk_installer_browsertest.cc b/chrome/browser/android/webapk/webapk_installer_browsertest.cc index ea26c2b..758a9e8 100644 --- a/chrome/browser/android/webapk/webapk_installer_browsertest.cc +++ b/chrome/browser/android/webapk/webapk_installer_browsertest.cc @@ -4,6 +4,7 @@ #include "chrome/browser/android/webapk/webapk_installer.h" +#include <atomic> #include <memory> #include <utility> @@ -14,7 +15,9 @@ #include "base/run_loop.h" #include "base/task/single_thread_task_runner.h" #include "base/threading/thread_restrictions.h" +#include "chrome/browser/android/shortcut_helper.h" #include "chrome/browser/android/webapk/webapk_install_service.h" +#include "chrome/browser/android/webapk/webapk_install_service_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/base/android/android_browser_test.h" @@ -27,6 +30,7 @@ #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" +#include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_response.h" #include "testing/gtest/include/gtest/gtest.h" @@ -233,6 +237,7 @@ ~WebApkInstallerBrowserTest() override = default; void SetUpOnMainThread() override { + host_resolver()->AddRule("*", "127.0.0.1"); embedded_test_server()->AddDefaultHandlers(base::FilePath(kTestDataDir)); embedded_test_server()->RegisterRequestHandler( base::BindRepeating(&WebApkInstallerBrowserTest::HandleWebApkRequest, @@ -311,6 +316,30 @@ return Profile::FromBrowserContext(web_contents()->GetBrowserContext()); } + webapps::ShortcutInfo CreateValidShortcutInfo(const GURL& url) { + webapps::ShortcutInfo info(url); + info.manifest_id = url; + info.user_title = u"Test App"; + info.name = u"Test App"; + info.short_name = u"Test"; + info.display = blink::mojom::DisplayMode::kStandalone; + info.splash_image_url = url.Resolve(kBestSplashIconUrl); + return info; + } + + void CallOnFinishedInstall(WebApkInstallService* service, + base::WeakPtr<content::WebContents> web_contents, + const webapps::ShortcutInfo& shortcut_info, + const SkBitmap& primary_icon, + webapps::WebApkInstallResult result) { + service->OnFinishedInstall(web_contents, shortcut_info, primary_icon, + result, + /*relax_updates=*/false, + /*webapk_package_name=*/""); + } + + int victim_splash_requests() const { return victim_splash_requests_.load(); } + private: // Sets default configuration for running WebApkInstaller. void SetDefaults() { @@ -321,11 +350,16 @@ std::unique_ptr<net::test_server::HttpResponse> HandleWebApkRequest( const net::test_server::HttpRequest& request) { + if (request.relative_url.starts_with("/victim_splash.png")) { + victim_splash_requests_++; + } return (request.relative_url == kServerUrl) ? webapk_response_builder_.Run() : std::unique_ptr<net::test_server::HttpResponse>(); } + std::atomic<int> victim_splash_requests_{0}; + // Builds response to the WebAPK creation request. WebApkResponseBuilder webapk_response_builder_; }; @@ -537,3 +571,84 @@ // Clean up base::DeletePathRecursively(outer_file_path); } + +// Tests that ShortcutHelper::AddToLauncherWithSkBitmap sanitizes a cross-origin +// WebContents and does not record UKM or fetch splash icons for the navigated +// origin. +IN_PROC_BROWSER_TEST_F(WebApkInstallerBrowserTest, + AddToLauncherSanitizesCrossOriginWebContents) { + GURL victim_url = embedded_test_server()->GetURL("victim.com", "/empty.html"); + ASSERT_TRUE(content::NavigateToURL(web_contents(), victim_url)); + + webapps::ShortcutInfo cross_origin_info = + CreateValidShortcutInfo(GURL("https://attacker.com/index.html")); + // Point the splash image URL to a path on the victim origin to simulate the + // confused-deputy attack scenario from b/540049672. + cross_origin_info.splash_image_url = victim_url.Resolve("/victim_splash.png"); + + ukm::TestAutoSetUkmRecorder ukm_recorder; + + // With a cross-origin WebContents, the pointer is sanitized to nullptr so no + // UKM entry is recorded and no splash icon request is sent to the victim. + ShortcutHelper::AddToLauncherWithSkBitmap( + web_contents(), cross_origin_info, + gfx::test::CreateBitmap(1, SK_ColorRED), + webapps::InstallableStatusCode::WEBAPK_INSTALL_FAILED); + EXPECT_TRUE(ukm_recorder.GetEntries("Webapp.AddToHomeScreen", {}).empty()); + EXPECT_EQ(0, victim_splash_requests()); + + // With a same-origin WebContents, the pointer is retained and UKM is + // recorded. + webapps::ShortcutInfo same_origin_info = CreateValidShortcutInfo(victim_url); + ShortcutHelper::AddToLauncherWithSkBitmap( + web_contents(), same_origin_info, gfx::test::CreateBitmap(1, SK_ColorRED), + webapps::InstallableStatusCode::WEBAPK_INSTALL_FAILED); + EXPECT_EQ(1u, ukm_recorder.GetEntries("Webapp.AddToHomeScreen", {}).size()); +} + +// Tests that ShortcutHelper::AddToLauncherWithSkBitmap handles nullptr +// WebContents safely without crashing or recording UKM. +IN_PROC_BROWSER_TEST_F(WebApkInstallerBrowserTest, + AddToLauncherHandlesNullWebContents) { + webapps::ShortcutInfo shortcut_info = + CreateValidShortcutInfo(GURL("https://example.com/index.html")); + + ukm::TestAutoSetUkmRecorder ukm_recorder; + + ShortcutHelper::AddToLauncherWithSkBitmap( + /*web_contents=*/nullptr, shortcut_info, + gfx::test::CreateBitmap(1, SK_ColorRED), + webapps::InstallableStatusCode::WEBAPK_INSTALL_FAILED); + EXPECT_TRUE(ukm_recorder.GetEntries("Webapp.AddToHomeScreen", {}).empty()); +} + +// Tests that WebApkInstallService::OnFinishedInstall on failure triggers +// shortcut fallback and does not leak a navigated cross-origin WebContents. +IN_PROC_BROWSER_TEST_F(WebApkInstallerBrowserTest, + OnFinishedInstallFallbackSanitizesNavigatedWebContents) { + GURL victim_url = embedded_test_server()->GetURL("victim.com", "/empty.html"); + ASSERT_TRUE(content::NavigateToURL(web_contents(), victim_url)); + + webapps::ShortcutInfo cross_origin_info = + CreateValidShortcutInfo(GURL("https://attacker.com/index.html")); + cross_origin_info.splash_image_url = victim_url.Resolve("/victim_splash.png"); + + ukm::TestAutoSetUkmRecorder ukm_recorder; + + auto* install_service = + WebApkInstallServiceFactory::GetForBrowserContext(profile()); + CallOnFinishedInstall(install_service, web_contents()->GetWeakPtr(), + cross_origin_info, + gfx::test::CreateBitmap(1, SK_ColorRED), + webapps::WebApkInstallResult::SERVER_ERROR); + EXPECT_TRUE(ukm_recorder.GetEntries("Webapp.AddToHomeScreen", {}).empty()); + EXPECT_EQ(0, victim_splash_requests()); + + // With a same-origin WebContents, UKM is recorded. + webapps::ShortcutInfo same_origin_info = CreateValidShortcutInfo(victim_url); + CallOnFinishedInstall(install_service, web_contents()->GetWeakPtr(), + same_origin_info, + gfx::test::CreateBitmap(1, SK_ColorRED), + webapps::WebApkInstallResult::SERVER_ERROR); + EXPECT_EQ(1u, ukm_recorder.GetEntries("Webapp.AddToHomeScreen", {}).size()); +} diff --git a/chrome/browser/android/webapk/BUILD.gn b/chrome/browser/android/webapk/BUILD.gn index a1535df3..df7eb76 100644 --- a/chrome/browser/android/webapk/BUILD.gn +++ b/chrome/browser/android/webapk/BUILD.gn @@ -62,6 +62,7 @@ ":webapk_sources", "//base/test:test_support", "//chrome/browser/android", + "//chrome/browser/android:shortcut_helper", "//chrome/browser/profiles:profile", "//chrome/common:constants", "//chrome/test/base:android_browser_test",
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/android/webapk/webapk_installer_browsertest.cc b/chrome/browser/android/webapk/webapk_installer_browsertest.cc
index ea26c2b..758a9e8 100644
--- a/chrome/browser/android/webapk/webapk_installer_browsertest.cc
+++ b/chrome/browser/android/webapk/webapk_installer_browsertest.cc
@@ -4,6 +4,7 @@
#include "chrome/browser/android/webapk/webapk_installer.h"
+#include <atomic>
#include <memory>
#include <utility>
@@ -14,7 +15,9 @@
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/android/shortcut_helper.h"
#include "chrome/browser/android/webapk/webapk_install_service.h"
+#include "chrome/browser/android/webapk/webapk_install_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/android/android_browser_test.h"
@@ -27,6 +30,7 @@
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
+#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -233,6 +237,7 @@
~WebApkInstallerBrowserTest() override = default;
void SetUpOnMainThread() override {
+ host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->AddDefaultHandlers(base::FilePath(kTestDataDir));
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(&WebApkInstallerBrowserTest::HandleWebApkRequest,
@@ -311,6 +316,30 @@
return Profile::FromBrowserContext(web_contents()->GetBrowserContext());
}
+ webapps::ShortcutInfo CreateValidShortcutInfo(const GURL& url) {
+ webapps::ShortcutInfo info(url);
+ info.manifest_id = url;
+ info.user_title = u"Test App";
+ info.name = u"Test App";
+ info.short_name = u"Test";
+ info.display = blink::mojom::DisplayMode::kStandalone;
+ info.splash_image_url = url.Resolve(kBestSplashIconUrl);
+ return info;
+ }
+
+ void CallOnFinishedInstall(WebApkInstallService* service,
+ base::WeakPtr<content::WebContents> web_contents,
+ const webapps::ShortcutInfo& shortcut_info,
+ const SkBitmap& primary_icon,
+ webapps::WebApkInstallResult result) {
+ service->OnFinishedInstall(web_contents, shortcut_info, primary_icon,
+ result,
+ /*relax_updates=*/false,
+ /*webapk_package_name=*/"");
+ }
+
+ int victim_splash_requests() const { return victim_splash_requests_.load(); }
+
private:
// Sets default configuration for running WebApkInstaller.
void SetDefaults() {
@@ -321,11 +350,16 @@
std::unique_ptr<net::test_server::HttpResponse> HandleWebApkRequest(
const net::test_server::HttpRequest& request) {
+ if (request.relative_url.starts_with("/victim_splash.png")) {
+ victim_splash_requests_++;
+ }
return (request.relative_url == kServerUrl)
? webapk_response_builder_.Run()
: std::unique_ptr<net::test_server::HttpResponse>();
}
+ std::atomic<int> victim_splash_requests_{0};
+
// Builds response to the WebAPK creation request.
WebApkResponseBuilder webapk_response_builder_;
};
@@ -537,3 +571,84 @@
// Clean up
base::DeletePathRecursively(outer_file_path);
}
+
+// Tests that ShortcutHelper::AddToLauncherWithSkBitmap sanitizes a cross-origin
+// WebContents and does not record UKM or fetch splash icons for the navigated
+// origin.
+IN_PROC_BROWSER_TEST_F(WebApkInstallerBrowserTest,
+ AddToLauncherSanitizesCrossOriginWebContents) {
+ GURL victim_url = embedded_test_server()->GetURL("victim.com", "/empty.html");
+ ASSERT_TRUE(content::NavigateToURL(web_contents(), victim_url));
+
+ webapps::ShortcutInfo cross_origin_info =
+ CreateValidShortcutInfo(GURL("https://attacker.com/index.html"));
+ // Point the splash image URL to a path on the victim origin to simulate the
+ // confused-deputy attack scenario from b/540049672.
+ cross_origin_info.splash_image_url = victim_url.Resolve("/victim_splash.png");
+
+ ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+ // With a cross-origin WebContents, the pointer is sanitized to nullptr so no
+ // UKM entry is recorded and no splash icon request is sent to the victim.
+ ShortcutHelper::AddToLauncherWithSkBitmap(
+ web_contents(), cross_origin_info,
+ gfx::test::CreateBitmap(1, SK_ColorRED),
+ webapps::InstallableStatusCode::WEBAPK_INSTALL_FAILED);
+ EXPECT_TRUE(ukm_recorder.GetEntries("Webapp.AddToHomeScreen", {}).empty());
+ EXPECT_EQ(0, victim_splash_requests());
+
+ // With a same-origin WebContents, the pointer is retained and UKM is
+ // recorded.
+ webapps::ShortcutInfo same_origin_info = CreateValidShortcutInfo(victim_url);
+ ShortcutHelper::AddToLauncherWithSkBitmap(
+ web_contents(), same_origin_info, gfx::test::CreateBitmap(1, SK_ColorRED),
+ webapps::InstallableStatusCode::WEBAPK_INSTALL_FAILED);
+ EXPECT_EQ(1u, ukm_recorder.GetEntries("Webapp.AddToHomeScreen", {}).size());
+}
+
+// Tests that ShortcutHelper::AddToLauncherWithSkBitmap handles nullptr
+// WebContents safely without crashing or recording UKM.
+IN_PROC_BROWSER_TEST_F(WebApkInstallerBrowserTest,
+ AddToLauncherHandlesNullWebContents) {
+ webapps::ShortcutInfo shortcut_info =
+ CreateValidShortcutInfo(GURL("https://example.com/index.html"));
+
+ ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+ ShortcutHelper::AddToLauncherWithSkBitmap(
+ /*web_contents=*/nullptr, shortcut_info,
+ gfx::test::CreateBitmap(1, SK_ColorRED),
+ webapps::InstallableStatusCode::WEBAPK_INSTALL_FAILED);
+ EXPECT_TRUE(ukm_recorder.GetEntries("Webapp.AddToHomeScreen", {}).empty());
+}
+
+// Tests that WebApkInstallService::OnFinishedInstall on failure triggers
+// shortcut fallback and does not leak a navigated cross-origin WebContents.
+IN_PROC_BROWSER_TEST_F(WebApkInstallerBrowserTest,
+ OnFinishedInstallFallbackSanitizesNavigatedWebContents) {
+ GURL victim_url = embedded_test_server()->GetURL("victim.com", "/empty.html");
+ ASSERT_TRUE(content::NavigateToURL(web_contents(), victim_url));
+
+ webapps::ShortcutInfo cross_origin_info =
+ CreateValidShortcutInfo(GURL("https://attacker.com/index.html"));
+ cross_origin_info.splash_image_url = victim_url.Resolve("/victim_splash.png");
+
+ ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+ auto* install_service =
+ WebApkInstallServiceFactory::GetForBrowserContext(profile());
+ CallOnFinishedInstall(install_service, web_contents()->GetWeakPtr(),
+ cross_origin_info,
+ gfx::test::CreateBitmap(1, SK_ColorRED),
+ webapps::WebApkInstallResult::SERVER_ERROR);
+ EXPECT_TRUE(ukm_recorder.GetEntries("Webapp.AddToHomeScreen", {}).empty());
+ EXPECT_EQ(0, victim_splash_requests());
+
+ // With a same-origin WebContents, UKM is recorded.
+ webapps::ShortcutInfo same_origin_info = CreateValidShortcutInfo(victim_url);
+ CallOnFinishedInstall(install_service, web_contents()->GetWeakPtr(),
+ same_origin_info,
+ gfx::test::CreateBitmap(1, SK_ColorRED),
+ webapps::WebApkInstallResult::SERVER_ERROR);
+ EXPECT_EQ(1u, ukm_recorder.GetEntries("Webapp.AddToHomeScreen", {}).size());
+}
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page