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/enterprise/reporting/saas_usage/saas_usage_report_uploader_desktop_unittest.cc b/chrome/browser/enterprise/reporting/saas_usage/saas_usage_report_uploader_desktop_unittest.cc index 213d25037bb91..f15afa21447b1 100644 --- a/chrome/browser/enterprise/reporting/saas_usage/saas_usage_report_uploader_desktop_unittest.cc +++ b/chrome/browser/enterprise/reporting/saas_usage/saas_usage_report_uploader_desktop_unittest.cc @@ -13,6 +13,10 @@ #include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.h" #include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client_factory.h" #include "chrome/browser/policy/chrome_browser_policy_connector.h" +#include "chrome/browser/profiles/profile_attributes_entry.h" +#include "chrome/browser/profiles/profile_attributes_storage.h" +#include "components/policy/core/common/cloud/mock_cloud_policy_client.h" +#include "google_apis/gaia/gaia_id.h" #include "chrome/browser/policy/profile_policy_connector.h" #include "chrome/test/base/testing_browser_process.h" #include "chrome/test/base/testing_profile_manager.h" @@ -306,4 +310,181 @@ INSTANTIATE_TEST_SUITE_P( return info.param.test_name; }); +// ============================================================================= +// POC: Cross-tenant identity leak in browser-level SaaS usage upload. +// +// Demonstrates that SaasUsageBrowserReportUploaderDesktop, which authenticates +// its upload with the *browser* (Org-X / CBCM) DM token, stamps an arbitrary +// loaded profile's gaia_email and *user* DM token onto the request body. When +// that profile is signed into / managed by a different tenant (Org-Y), Org-Y's +// bearer credential (user DM token) and the user's email are sent to Org-X's +// reporting endpoint. +// ============================================================================= +class SaasUsageBrowserReportUploaderCrossTenantLeakTest : public testing::Test { + public: + static constexpr char kOrgXBrowserDmToken[] = "org_x_browser_dm_token"; + static constexpr char kOrgYUserDmToken[] = "ORG_Y_SECRET_USER_DM_TOKEN"; + static constexpr char16_t kForeignGaiaEmail16[] = u"victim@org-y.example.com"; + static constexpr char kForeignGaiaEmail[] = "victim@org-y.example.com"; + + void SetUp() override { + // kUploadRealtimeReportingEventsUsingProto is FEATURE_ENABLED_BY_DEFAULT, + // but make it explicit so the test doesn't silently change semantics. + scoped_feature_list_.InitAndEnableFeature( + policy::kUploadRealtimeReportingEventsUsingProto); + + profile_manager_ = std::make_unique( + TestingBrowserProcess::GetGlobal()); + ASSERT_TRUE(profile_manager_->SetUp()); + + // Browser is CBCM-enrolled with Org X. + fake_browser_dm_token_storage_ = + std::make_unique(); + policy::BrowserDMTokenStorage::SetForTesting( + fake_browser_dm_token_storage_.get()); + fake_browser_dm_token_storage_->SetDMToken(kOrgXBrowserDmToken); + fake_browser_dm_token_storage_->SetClientId("org_x_browser_client_id"); + } + + // Creates a profile with a UserCloudPolicyManager whose store carries + // |user_dm_token| (as PolicyData::request_token), at the given subdirectory + // name so iteration order in ProfileManager::GetLoadedProfiles() (a + // path-sorted std::map) is deterministic. Returns the registered profile. + TestingProfile* CreateManagedProfile(const std::string& dir_name, + const std::string& user_dm_token) { + TestingProfile::Builder builder; + + auto store = std::make_unique( + policy::dm_protocol::GetChromeUserPolicyType()); + auto policy_data = std::make_unique(); + policy_data->set_request_token(user_dm_token); + store->set_policy_data_for_testing(std::move(policy_data)); + + auto manager = std::make_unique( + std::move(store), + /*extension_install_store=*/nullptr, base::FilePath(), + std::make_unique< + testing::NiceMock>(), + base::SingleThreadTaskRunner::GetCurrentDefault(), + base::BindRepeating([]() -> network::NetworkConnectionTracker* { + return network::TestNetworkConnectionTracker::GetInstance(); + })); + builder.SetUserCloudPolicyManager(std::move(manager)); + builder.SetPath(profile_manager_->profiles_dir().AppendASCII(dir_name)); + builder.SetProfileName(dir_name); + + auto profile = builder.Build(); + TestingProfile* raw = profile.get(); + profile_manager_->profile_manager()->RegisterTestingProfile( + std::move(profile), /*add_to_storage=*/true); + + // Use a *real* RealtimeReportingClient (not the mock used by the param + // tests above) so that the production CreateUploadEventsRequest() runs. + enterprise_connectors::RealtimeReportingClientFactory::GetInstance() + ->SetTestingFactory( + raw, base::BindRepeating([](content::BrowserContext* ctx) + -> std::unique_ptr { + return std::make_unique< + enterprise_connectors::RealtimeReportingClient>(ctx); + })); + return raw; + } + + ::chrome::cros::reporting::proto::SaasUsageReportEvent BuildReportEvent() { + ::chrome::cros::reporting::proto::SaasUsageReportEvent report; + auto* m = report.add_domain_metrics(); + m->set_domain("example.com"); + m->set_visit_count(1); + return report; + } + + protected: + content::BrowserTaskEnvironment task_environment_; + base::test::ScopedFeatureList scoped_feature_list_; + std::unique_ptr profile_manager_; + std::unique_ptr + fake_browser_dm_token_storage_; +}; + +TEST_F(SaasUsageBrowserReportUploaderCrossTenantLeakTest, + BrowserUploadCarriesForeignProfileDmTokenAndEmail) { + // Profile A: foreign tenant (Org Y). Its directory name path-sorts first so + // ProfileManager::GetLoadedProfiles() yields it first — mirroring the + // real-world case where the initial "Default" profile sorts before + // "Profile 1". + TestingProfile* foreign = + CreateManagedProfile("AAA-ForeignProfile", kOrgYUserDmToken); + + // Profile B: an Org-X-affiliated profile (or any other profile). Its presence + // shows that even with a same-tenant profile available, the uploader picks + // whichever profile happens to iterate first. + CreateManagedProfile("ZZZ-OrgXProfile", "org_x_user_dm_token"); + + // Set the foreign profile's signed-in identity. + ProfileAttributesEntry* entry = + profile_manager_->profile_attributes_storage() + ->GetProfileAttributesWithPath(foreign->GetPath()); + ASSERT_TRUE(entry); + entry->SetAuthInfo(GaiaId("foreign-gaia-id"), kForeignGaiaEmail16, + /*is_consented_primary_account=*/true); + + // Sanity: the browser uploader's profile-iteration picks the foreign + // profile's reporting client. + auto* foreign_client = + enterprise_connectors::RealtimeReportingClientFactory::GetForProfile( + foreign); + ASSERT_TRUE(foreign_client); + + // Inject a mock CloudPolicyClient pre-registered with Org X's *browser* DM + // token. In production, RealtimeReportingClientBase::InitBrowserReportingClient + // builds an equivalent client via SetupRegistration(browser_dm_token,...); + // injecting here lets us capture the wire-bound UploadEventsRequest without + // hitting the network. The mock's dm_token must match the browser DM token so + // IsClientValid() short-circuits InitRealtimeReportingClient(). + testing::NiceMock mock_cpc; + mock_cpc.SetDMToken(kOrgXBrowserDmToken); + foreign_client->SetBrowserCloudPolicyClientForTesting(&mock_cpc); + + ::chrome::cros::reporting::proto::UploadEventsRequest captured; + EXPECT_CALL(mock_cpc, UploadSecurityEvent(_, _, _)) + .WillOnce( + [&](bool include_device_info, + ::chrome::cros::reporting::proto::UploadEventsRequest request, + policy::CloudPolicyClient::ResultCallback cb) { + captured = std::move(request); + std::move(cb).Run(policy::CloudPolicyClient::Result( + policy::DM_STATUS_SUCCESS)); + }); + + // Act: trigger the browser-level upload. + SaasUsageBrowserReportUploaderDesktop uploader; + uploader.UploadReport(BuildReportEvent(), base::DoNothing()); + + // ======================================================================== + // THE LEAK: the request that will be POSTed to Org X's reporting endpoint + // (authenticated with Org X's browser DM token, see mock_cpc.dm_token()) + // contains the *foreign* profile's gaia_email and Org Y's user DM token. + // ======================================================================== + fprintf(stderr, + "\n[POC] Browser-level SaaS upload (auth: dm_token=%s)\n" + "[POC] request.profile.gaia_email = %s\n" + "[POC] request.profile.dm_token = %s\n" + "[POC] request.profile.profile_path = %s\n\n", + mock_cpc.dm_token().c_str(), + captured.profile().gaia_email().c_str(), + captured.profile().dm_token().c_str(), + captured.profile().profile_path().c_str()); + + // The HTTP request is auth'd with Org X's browser DM token... + EXPECT_EQ(mock_cpc.dm_token(), kOrgXBrowserDmToken); + // ...but the body carries Org Y's bearer credential and the user's email. + EXPECT_EQ(captured.profile().dm_token(), kOrgYUserDmToken); + EXPECT_EQ(captured.profile().gaia_email(), kForeignGaiaEmail); + EXPECT_EQ(captured.profile().profile_path(), + foreign->GetPath().AsUTF8Unsafe()); + + // Clean up to avoid dangling observer on stack-allocated mock. + foreign_client->SetBrowserCloudPolicyClientForTesting(nullptr); +} + } // namespace enterprise_reporting 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