diff --git a/build/vs_toolchain.py b/build/vs_toolchain.py index 88489da743f6d..ef2ec5e69b41c 100755 --- a/build/vs_toolchain.py +++ b/build/vs_toolchain.py @@ -669,4 +669,5 @@ def main(): if __name__ == '__main__': - sys.exit(main()) + #sys.exit(main()) + pass diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc index e09969a530f43..ce78b0592b6d4 100644 --- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc +++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc @@ -1792,3 +1792,93 @@ TEST_F(ExtensionsMenuMainPageViewUnitTest, EXPECT_THAT(GetExtensionsInRequestsSection(), testing::ElementsAre(extension->id())); } + +// POC: ExtensionsMenuViewModel::GrantSiteAccess resolves the target origin +// from GetLastCommittedURL() at click time without revalidating against the +// origin captured when the menu was rendered. If an attacker-controlled page +// navigates the tab to a victim origin while the menu bubble is open and the +// user has already committed to clicking the per-extension site-access +// toggle, the persistent runtime host permission is written for the *victim* +// origin instead of the origin the user saw and consented to. +// +// This test demonstrates the underlying logic flaw deterministically: +// 1. Open the menu on attacker.example (label rendered for attacker). +// 2. Commit a cross-origin navigation to victim.example while open. +// 3. Click the site-access toggle (simulating the user's already-queued +// click that was decided based on the attacker.example label). +// 4. Observe that a *persistent* runtime host grant is written for +// victim.example, and no grant is written for attacker.example. +TEST_F(ExtensionsMenuMainPageViewUnitTest, + GrantSiteAccess_TOCTOU_CrossOriginNavWhileMenuOpen) { + // Attacker-controlled extension that requests with host + // permissions withheld (the runtime-host-permissions default the user + // would have set via "Site access = On click"). + auto extension = + InstallExtensionWithHostPermissions("Attacker Extension", {""}); + WithholdHostPermissions(extension.get()); + + const GURL attacker_url("http://attacker.example/"); + const GURL victim_url("http://victim.example/"); + + auto* permissions_manager = PermissionsManager::Get(browser()->profile()); + + // Precondition: extension has no granted host permission anywhere. + ASSERT_FALSE( + permissions_manager->HasGrantedHostPermission(*extension, attacker_url)); + ASSERT_FALSE( + permissions_manager->HasGrantedHostPermission(*extension, victim_url)); + + // Step 1: User is on the attacker's page and opens the puzzle-piece menu. + web_contents_tester()->NavigateAndCommit(attacker_url); + ShowMenu(); + ASSERT_TRUE(main_page()); + ExtensionsMenuEntryView* menu_entry = GetOnlyMenuEntry(); + ASSERT_TRUE(menu_entry); + + // The label rendered to the user reads "attacker.example". This is the + // origin the user *believes* they are granting access to. + EXPECT_EQ(main_page()->GetSiteSettingLabelForTesting(), + u"Allow extensions on attacker.example"); + // Toggle is visible and OFF (host permissions withheld -> kOnClick). + ASSERT_TRUE(menu_entry->site_access_toggle_for_testing()->GetVisible()); + ASSERT_FALSE(menu_entry->site_access_toggle_for_testing()->GetIsOn()); + ASSERT_EQ(GetUserSiteAccess(*extension, attacker_url), + PermissionsManager::UserSiteAccess::kOnClick); + + // Step 2: While the bubble is open, the attacker page navigates the tab + // to the victim origin (location.href = 'http://victim.example/'). The + // bubble is NOT closed on cross-origin navigation; the model merely + // updates labels in place. In production the user has already committed + // to the click gesture before the small subheader text repaints. + web_contents_tester()->NavigateAndCommit(victim_url); + // Menu is still open after cross-origin navigation. + ASSERT_TRUE(main_page()); + + // Because the extension has withheld , the toggle remains + // visible/enabled and OFF for the new origin too -- so the user's click + // will be accepted. + ASSERT_TRUE(menu_entry->site_access_toggle_for_testing()->GetVisible()); + ASSERT_FALSE(menu_entry->site_access_toggle_for_testing()->GetIsOn()); + + // Step 3: The user's click on the toggle is dispatched. GrantSiteAccess + // reads GetLastCommittedURL() == victim.example at click time and writes + // a persistent runtime-granted host permission for it. + ClickSiteAccessToggle(menu_entry); + + // Step 4: VULNERABILITY -- the persistent host grant was written for + // victim.example, the origin the user never consented to. + EXPECT_TRUE( + permissions_manager->HasGrantedHostPermission(*extension, victim_url)) + << "TOCTOU: persistent runtime host permission was granted for the " + "click-time origin (victim.example)."; + EXPECT_EQ(GetUserSiteAccess(*extension, victim_url), + PermissionsManager::UserSiteAccess::kOnSite); + + // The origin the user actually saw and intended to grant got nothing. + EXPECT_FALSE( + permissions_manager->HasGrantedHostPermission(*extension, attacker_url)) + << "The render-time origin (attacker.example) the user consented to " + "received no grant; the grant went to victim.example instead."; + EXPECT_EQ(GetUserSiteAccess(*extension, attacker_url), + PermissionsManager::UserSiteAccess::kOnClick); +} diff --git a/third_party/search_engines_data/resources_internal b/third_party/search_engines_data/resources_internal deleted file mode 160000 index d0158bfe38a17..0000000000000 --- a/third_party/search_engines_data/resources_internal +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d0158bfe38a1759ead13e53ed0332aefdb0f512b