| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/net/system_network_context_manager.h" |
| |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/process/process.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/values_test_util.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/net/stub_resolver_config_reader.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/common/channel_info.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/test_launcher_utils.h" |
| #include "components/component_updater/installer_policies/first_party_sets_component_installer_policy.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/version_info/version_info.h" |
| #include "content/public/browser/cookie_access_details.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/browser/network_service_util.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/service_process_host.h" |
| #include "content/public/browser/service_process_info.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/test_utils.h" |
| #include "mojo/public/cpp/bindings/sync_call_restrictions.h" |
| #include "net/base/features.h" |
| #include "net/cookies/canonical_cookie_test_helpers.h" |
| #include "net/cookies/cookie_access_result.h" |
| #include "net/cookies/cookie_constants.h" |
| #include "net/cookies/cookie_inclusion_status.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/net_buildflags.h" |
| #include "sandbox/policy/features.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/network_service_buildflags.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "services/network/public/mojom/network_service.mojom.h" |
| #include "services/network/public/mojom/network_service_test.mojom.h" |
| #include "testing/gmock/include/gmock/gmock-matchers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "base/process/process_info.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "chrome/browser/browser_features.h" |
| #include "chrome/common/chrome_version.h" |
| #include "content/public/browser/storage_partition.h" |
| #endif // BUILDFLAG(IS_WIN) |
| |
| #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) |
| #include "sandbox/policy/linux/sandbox_seccomp_bpf_linux.h" |
| #endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) |
| |
| using SystemNetworkContextManagerBrowsertest = InProcessBrowserTest; |
| |
| const char* kCookieName = "Cookie"; |
| const char* kHostA = "a.test"; |
| const char* kHostB = "b.test"; |
| |
| IN_PROC_BROWSER_TEST_F(SystemNetworkContextManagerBrowsertest, |
| StaticAuthParams) { |
| // Test defaults. |
| network::mojom::HttpAuthStaticParamsPtr static_params = |
| SystemNetworkContextManager::GetHttpAuthStaticParamsForTesting(); |
| EXPECT_EQ("", static_params->gssapi_library_name); |
| #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS) |
| // Test that prefs are reflected in params. |
| |
| PrefService* local_state = g_browser_process->local_state(); |
| const char dev_null[] = "/dev/null"; |
| local_state->SetString(prefs::kGSSAPILibraryName, dev_null); |
| static_params = |
| SystemNetworkContextManager::GetHttpAuthStaticParamsForTesting(); |
| EXPECT_EQ(dev_null, static_params->gssapi_library_name); |
| #endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) && |
| // !BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SystemNetworkContextManagerBrowsertest, AuthParams) { |
| // Test defaults. |
| network::mojom::HttpAuthDynamicParamsPtr dynamic_params = |
| SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting(); |
| EXPECT_THAT(*dynamic_params->allowed_schemes, |
| testing::ElementsAre("basic", "digest", "ntlm", "negotiate")); |
| EXPECT_FALSE(dynamic_params->negotiate_disable_cname_lookup); |
| EXPECT_FALSE(dynamic_params->enable_negotiate_port); |
| EXPECT_TRUE(dynamic_params->basic_over_http_enabled); |
| EXPECT_EQ("", dynamic_params->server_allowlist); |
| EXPECT_EQ("", dynamic_params->delegate_allowlist); |
| EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy); |
| EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty()); |
| |
| PrefService* local_state = g_browser_process->local_state(); |
| |
| local_state->SetBoolean(prefs::kDisableAuthNegotiateCnameLookup, true); |
| dynamic_params = |
| SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting(); |
| EXPECT_THAT(*dynamic_params->allowed_schemes, |
| testing::ElementsAre("basic", "digest", "ntlm", "negotiate")); |
| EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup); |
| EXPECT_FALSE(dynamic_params->enable_negotiate_port); |
| EXPECT_TRUE(dynamic_params->basic_over_http_enabled); |
| EXPECT_EQ("", dynamic_params->server_allowlist); |
| EXPECT_EQ("", dynamic_params->delegate_allowlist); |
| EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy); |
| EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty()); |
| |
| local_state->SetBoolean(prefs::kEnableAuthNegotiatePort, true); |
| dynamic_params = |
| SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting(); |
| EXPECT_THAT(*dynamic_params->allowed_schemes, |
| testing::ElementsAre("basic", "digest", "ntlm", "negotiate")); |
| EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup); |
| EXPECT_TRUE(dynamic_params->enable_negotiate_port); |
| EXPECT_TRUE(dynamic_params->basic_over_http_enabled); |
| EXPECT_EQ("", dynamic_params->server_allowlist); |
| EXPECT_EQ("", dynamic_params->delegate_allowlist); |
| EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy); |
| EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty()); |
| |
| local_state->SetBoolean(prefs::kBasicAuthOverHttpEnabled, false); |
| dynamic_params = |
| SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting(); |
| EXPECT_THAT(*dynamic_params->allowed_schemes, |
| testing::ElementsAre("basic", "digest", "ntlm", "negotiate")); |
| EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup); |
| EXPECT_TRUE(dynamic_params->enable_negotiate_port); |
| EXPECT_FALSE(dynamic_params->basic_over_http_enabled); |
| EXPECT_EQ("", dynamic_params->server_allowlist); |
| EXPECT_EQ("", dynamic_params->delegate_allowlist); |
| EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy); |
| EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty()); |
| |
| const char kServerAllowList[] = "foo"; |
| local_state->SetString(prefs::kAuthServerAllowlist, kServerAllowList); |
| dynamic_params = |
| SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting(); |
| EXPECT_THAT(*dynamic_params->allowed_schemes, |
| testing::ElementsAre("basic", "digest", "ntlm", "negotiate")); |
| EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup); |
| EXPECT_TRUE(dynamic_params->enable_negotiate_port); |
| EXPECT_FALSE(dynamic_params->basic_over_http_enabled); |
| EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist); |
| EXPECT_EQ("", dynamic_params->delegate_allowlist); |
| EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty()); |
| |
| const char kDelegateAllowList[] = "bar, baz"; |
| local_state->SetString(prefs::kAuthNegotiateDelegateAllowlist, |
| kDelegateAllowList); |
| dynamic_params = |
| SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting(); |
| EXPECT_THAT(*dynamic_params->allowed_schemes, |
| testing::ElementsAre("basic", "digest", "ntlm", "negotiate")); |
| EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup); |
| EXPECT_TRUE(dynamic_params->enable_negotiate_port); |
| EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist); |
| EXPECT_FALSE(dynamic_params->basic_over_http_enabled); |
| EXPECT_EQ(kDelegateAllowList, dynamic_params->delegate_allowlist); |
| EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy); |
| EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty()); |
| |
| local_state->SetString(prefs::kAuthSchemes, "basic"); |
| dynamic_params = |
| SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting(); |
| EXPECT_THAT(*dynamic_params->allowed_schemes, testing::ElementsAre("basic")); |
| EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup); |
| EXPECT_TRUE(dynamic_params->enable_negotiate_port); |
| EXPECT_FALSE(dynamic_params->basic_over_http_enabled); |
| EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist); |
| EXPECT_EQ(kDelegateAllowList, dynamic_params->delegate_allowlist); |
| EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy); |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) |
| local_state->SetString(prefs::kAuthSchemes, "basic"); |
| local_state->SetBoolean(prefs::kAuthNegotiateDelegateByKdcPolicy, true); |
| dynamic_params = |
| SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting(); |
| EXPECT_THAT(*dynamic_params->allowed_schemes, testing::ElementsAre("basic")); |
| EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup); |
| EXPECT_TRUE(dynamic_params->enable_negotiate_port); |
| EXPECT_FALSE(dynamic_params->basic_over_http_enabled); |
| EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist); |
| EXPECT_EQ(kDelegateAllowList, dynamic_params->delegate_allowlist); |
| EXPECT_TRUE(dynamic_params->delegate_by_kdc_policy); |
| EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty()); |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // The kerberos.enabled pref is false and the device is not Active Directory |
| // managed by default. |
| EXPECT_FALSE(dynamic_params->allow_gssapi_library_load); |
| local_state->SetBoolean(prefs::kKerberosEnabled, true); |
| dynamic_params = |
| SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting(); |
| EXPECT_TRUE(dynamic_params->allow_gssapi_library_load); |
| EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty()); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| base::Value::List patterns_allowed_to_use_all_schemes; |
| patterns_allowed_to_use_all_schemes.Append("*.allowed.google.com"); |
| patterns_allowed_to_use_all_schemes.Append("*.youtube.com"); |
| local_state->SetList(prefs::kAllHttpAuthSchemesAllowedForOrigins, |
| std::move(patterns_allowed_to_use_all_schemes)); |
| dynamic_params = |
| SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting(); |
| |
| EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup); |
| EXPECT_TRUE(dynamic_params->enable_negotiate_port); |
| EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist); |
| EXPECT_FALSE(dynamic_params->basic_over_http_enabled); |
| EXPECT_EQ(kDelegateAllowList, dynamic_params->delegate_allowlist); |
| EXPECT_EQ((std::vector<std::string>{"*.allowed.google.com", "*.youtube.com"}), |
| dynamic_params->patterns_allowed_to_use_all_schemes); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) |
| // GSSAPI is currently incompatible with the network service sandbox |
| // (crbug.com/1474362). It isn't known until the browser is already started |
| // whether GSSAPI is desired, so if Chrome detects that GSSAPI is desired after |
| // the network service has already started sandboxed, the network |
| // service must be restarted so the sandbox can be removed. |
| class SystemNetworkContextManagerNetworkServiceSandboxBrowsertest |
| : public SystemNetworkContextManagerBrowsertest, |
| public content::ServiceProcessHost::Observer, |
| public testing::WithParamInterface<bool> { |
| public: |
| // On both ChromeOS and Linux, a pref determines whether GSSAPI is desired in |
| // the network service. This pref will determine whether the network service |
| // is sandboxed, and when it changes from false to true this should trigger a |
| // network service restart to remove the sandbox. |
| const char* kGssapiDesiredPref = |
| #if BUILDFLAG(IS_CHROMEOS) |
| prefs::kKerberosEnabled; |
| #elif BUILDFLAG(IS_LINUX) |
| prefs::kReceivedHttpAuthNegotiateHeader; |
| #endif |
| |
| SystemNetworkContextManagerNetworkServiceSandboxBrowsertest() { |
| sandbox_desired_ = GetParam(); |
| scoped_feature_list_.InitWithFeatureState( |
| sandbox::policy::features::kNetworkServiceSandbox, sandbox_desired_); |
| } |
| |
| void SetUpOnMainThread() override { |
| // If the sandbox or the seccomp policy is disabled, these tests are |
| // meaningless. |
| if (!sandbox::policy::SandboxSeccompBPF::IsSeccompBPFDesired()) { |
| GTEST_SKIP(); |
| } |
| |
| SystemNetworkContextManagerBrowsertest::SetUpOnMainThread(); |
| |
| content::ServiceProcessHost::AddObserver(this); |
| auto running_processes = |
| content::ServiceProcessHost::GetRunningProcessInfo(); |
| for (const auto& info : running_processes) { |
| if (info.IsService<network::mojom::NetworkService>()) { |
| network_process_ = info.GetProcess().Duplicate(); |
| break; |
| } |
| } |
| } |
| |
| void WaitForNextLaunch() { |
| launch_run_loop_.emplace(); |
| launch_run_loop_->Run(); |
| } |
| |
| void ExpectNetworkService(bool seccomp_sandboxed, |
| bool allows_gssapi_library_load) { |
| // The network service may have been launched but has not yet sandboxed |
| // itself. So, wait for the Mojo endpoints to start accepting messages. |
| mojo::Remote<network::mojom::NetworkServiceTest> network_service_test; |
| content::GetNetworkService()->BindTestInterfaceForTesting( |
| network_service_test.BindNewPipeAndPassReceiver()); |
| mojo::ScopedAllowSyncCallForTesting allow_sync_call; |
| // Log() is sync so this thread will wait for this call to succeed. |
| network_service_test->Log( |
| "Logging in network service to ensure it's ready."); |
| |
| // Now the test can check if the seccomp sandbox has been applied. |
| EXPECT_EQ(seccomp_sandboxed, |
| GetNetworkServiceProcess().IsSeccompSandboxed()); |
| |
| bool network_service_allows_gssapi_library_load; |
| ASSERT_TRUE(network_service_test->AllowsGSSAPILibraryLoad( |
| &network_service_allows_gssapi_library_load)); |
| EXPECT_EQ(allows_gssapi_library_load, |
| network_service_allows_gssapi_library_load); |
| } |
| |
| base::Process GetNetworkServiceProcess() { |
| CHECK(content::IsOutOfProcessNetworkService()); |
| return network_process_.Duplicate(); |
| } |
| |
| protected: |
| bool sandbox_desired_; |
| |
| private: |
| void OnServiceProcessLaunched( |
| const content::ServiceProcessInfo& info) override { |
| if (!info.IsService<network::mojom::NetworkService>()) { |
| return; |
| } |
| network_process_ = info.GetProcess().Duplicate(); |
| if (launch_run_loop_) { |
| launch_run_loop_->Quit(); |
| } |
| } |
| |
| void OnServiceProcessTerminatedNormally( |
| const content::ServiceProcessInfo& info) override {} |
| |
| void OnServiceProcessCrashed( |
| const content::ServiceProcessInfo& info) override {} |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| base::Process network_process_; |
| std::optional<base::RunLoop> launch_run_loop_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P( |
| SystemNetworkContextManagerNetworkServiceSandboxBrowsertest, |
| NetworkServiceRestartsUnsandboxedOnGssapiDesired) { |
| PrefService* local_state = g_browser_process->local_state(); |
| |
| // Ensure GSSAPI starts as "undesired". |
| EXPECT_FALSE(local_state->GetBoolean(kGssapiDesiredPref)); |
| // Ensure the network service starts sandboxed (if desired) and cannot load |
| // GSSAPI libraries. |
| ExpectNetworkService(/*seccomp_sandboxed=*/sandbox_desired_, |
| /*allows_gssapi_library_load=*/false); |
| |
| // Now signal that GSSAPI is desired. |
| local_state->SetBoolean(kGssapiDesiredPref, true); |
| EXPECT_TRUE(local_state->GetBoolean(kGssapiDesiredPref)); |
| // If the network service was sandboxed it should automatically restart and |
| // be unsandboxed. In any case it should now respect the pref and allow GSSAPI |
| // library loads. |
| if (sandbox_desired_) { |
| WaitForNextLaunch(); |
| } |
| ExpectNetworkService(/*seccomp_sandboxed=*/false, |
| /*allows_gssapi_library_load=*/true); |
| |
| // After killing the network service, it should still restart unsandboxed and |
| // allow GSSAPI library loads. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(base::IgnoreResult(&content::RestartNetworkService))); |
| WaitForNextLaunch(); |
| ExpectNetworkService(/*seccomp_sandboxed=*/false, |
| /*allows_gssapi_library_load=*/true); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SystemNetworkContextManagerNetworkServiceSandboxBrowsertest, |
| PRE_NetworkServiceStartsUnsandboxedWithGssapiDesired) { |
| PrefService* local_state = g_browser_process->local_state(); |
| // Signal that GSSAPI is desired. This should persist across browser restarts |
| // like any pref. |
| local_state->SetBoolean(kGssapiDesiredPref, true); |
| EXPECT_TRUE(local_state->GetBoolean(kGssapiDesiredPref)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SystemNetworkContextManagerNetworkServiceSandboxBrowsertest, |
| NetworkServiceStartsUnsandboxedWithGssapiDesired) { |
| // Ensure the network service starts sandboxed and allows GSSAPI library |
| // loads. |
| ExpectNetworkService(/*seccomp_sandboxed=*/false, |
| /*allows_gssapi_library_load=*/true); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| SystemNetworkContextManagerNetworkServiceSandboxBrowsertest, |
| testing::Bool(), |
| [](const testing::TestParamInfo<bool>& info) { |
| return info.param ? "NetworkSandboxDesired" |
| : "NetworkSandboxFullyDisabled"; |
| }); |
| |
| #endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) |
| |
| #if BUILDFLAG(IS_WIN) |
| |
| class SystemNetworkContextManagerNetworkServiceSandboxBrowsertest |
| : public SystemNetworkContextManagerBrowsertest, |
| public content::ServiceProcessHost::Observer { |
| public: |
| SystemNetworkContextManagerNetworkServiceSandboxBrowsertest() { |
| scoped_feature_list_.InitWithFeatures( |
| {sandbox::policy::features::kNetworkServiceSandbox, |
| features::kRestartNetworkServiceUnsandboxedForFailedLaunch}, |
| {}); |
| } |
| |
| void SetUpOnMainThread() override { |
| SystemNetworkContextManagerBrowsertest::SetUpOnMainThread(); |
| launch_run_loop_.emplace(); |
| content::ServiceProcessHost::AddObserver(this); |
| auto running_processes = |
| content::ServiceProcessHost::GetRunningProcessInfo(); |
| for (const auto& info : running_processes) { |
| if (info.IsService<network::mojom::NetworkService>()) { |
| RecordProcessSandboxState(info.GetProcess()); |
| break; |
| } |
| } |
| } |
| |
| void TearDownOnMainThread() override { |
| content::ServiceProcessHost::RemoveObserver(this); |
| } |
| |
| protected: |
| size_t launches_seen_ = 0; |
| bool network_service_sandboxed_; |
| std::optional<base::RunLoop> launch_run_loop_; |
| |
| private: |
| void RecordProcessSandboxState(const base::Process& process) { |
| const auto integrity_level = base::GetProcessIntegrityLevel(process.Pid()); |
| CHECK_NE(base::INTEGRITY_UNKNOWN, integrity_level); |
| network_service_sandboxed_ = integrity_level < base::MEDIUM_INTEGRITY; |
| } |
| |
| void OnServiceProcessLaunched( |
| const content::ServiceProcessInfo& info) override { |
| if (!info.IsService<network::mojom::NetworkService>()) { |
| return; |
| } |
| RecordProcessSandboxState(info.GetProcess()); |
| // Expect two launches, first for the restart due to intentional crash, then |
| // for the restart after the intentional crash at startup. |
| if (++launches_seen_ >= 2u) { |
| launch_run_loop_->Quit(); |
| } |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // This is a four stage test to verify the early launch crash behavior for the |
| // network service sandbox on Windows. |
| // |
| // In the first stage, the network service is already started as normal during |
| // browser launch. The test verifies that the service is currently sandboxed. |
| // |
| // In the second stage, the artificial early crash is added, then the service is |
| // crashed to cause a restart. This results in the service starting, crashing |
| // immediately then starting again unsandboxed. The test verifies that the |
| // service is now unsandboxed and the pref has been set to mark the sandbox as |
| // being disabled. |
| // |
| // In the third stage, a crash is induced again and it's checked that the |
| // unsandboxed state is correctly persisted and used on the second restart. |
| // |
| // In the fourth stage, the crashing milestone is set to one milestone previous |
| // to current, and then the service then crashed again to cause a restart. This |
| // time the service will start sandboxed again because the early crash was in a |
| // previous milestone. |
| IN_PROC_BROWSER_TEST_F( |
| SystemNetworkContextManagerNetworkServiceSandboxBrowsertest, |
| NetworkServiceRestartsFailingLaunches) { |
| if (!sandbox::policy::features::IsNetworkSandboxSupported()) { |
| GTEST_SKIP() << "This test requires platform sandbox support."; |
| } |
| |
| // Check network service is sandboxed initially. |
| EXPECT_TRUE(network_service_sandboxed_); |
| |
| { |
| base::HistogramTester histograms; |
| |
| // The next restart will fail to bootstrap correctly. |
| content::SetNetworkServiceCrashOnNextStartupForTesting(); |
| // Cause a crash in the network service, which will cause it to restart, |
| // then fail to start, then restart again unsandboxed. |
| SimulateNetworkServiceCrash(); |
| // The second restart (unsandboxed) must be ignored otherwise the test will |
| // fail. |
| IgnoreNetworkServiceCrashes(); |
| // Make sure the network service is fully up and running after having to |
| // restart twice. |
| browser() |
| ->profile() |
| ->GetDefaultStoragePartition() |
| ->FlushNetworkInterfaceForTesting(); |
| |
| launch_run_loop_->Run(); |
| EXPECT_FALSE(network_service_sandboxed_); |
| EXPECT_TRUE(SystemNetworkContextManager::GetInstance() |
| ->HasFailedPreviousRecentLaunch()); |
| int crashing_major_version = g_browser_process->local_state()->GetInteger( |
| prefs::kNetworkServiceFailedLaunchMajorVersion); |
| CHECK_EQ(crashing_major_version, CHROME_VERSION_MAJOR); |
| histograms.ExpectUniqueSample( |
| "Chrome.SystemNetworkContextManager.NetworkSandboxEarlyLaunchCrashed", |
| true, 1u); |
| // Two sets of histograms are emitted in this part: first the network |
| // service is launched with sandbox enabled, then the second re-launch is |
| // with sandbox disabled because of failed launch. Note these histograms |
| // have varied counts. This is because |
| // Chrome.SystemNetworkContextManager.NetworkSandboxState is logged from |
| // SystemNetworkContextManager::IsNetworkSandboxEnabled which is called from |
| // various places in mojo and service manager. This test merely has to |
| // verify that there exist only entries in the two buckets and no other |
| // bucket. |
| const auto enabled_count = histograms.GetBucketCount( |
| "Chrome.SystemNetworkContextManager.NetworkSandboxState", |
| /*kEnabledByPlatform*/ 1); |
| const auto disabled_because_of_failed_launch_count = |
| histograms.GetBucketCount( |
| "Chrome.SystemNetworkContextManager.NetworkSandboxState", |
| /*kDisabledBecauseOfFailedLaunch*/ 4); |
| CHECK_GT(disabled_because_of_failed_launch_count, 1u); |
| CHECK_GT(enabled_count, 1u); |
| histograms.ExpectTotalCount( |
| "Chrome.SystemNetworkContextManager.NetworkSandboxState", |
| enabled_count + disabled_because_of_failed_launch_count); |
| } |
| |
| { |
| base::HistogramTester histograms; |
| // Crash again, this time to verify that the pref persists and causes the |
| // network service to remain unsandboxed even on a second restart. |
| SimulateNetworkServiceCrash(); |
| |
| // Make sure the network service is fully up and running after having to |
| // restart. |
| browser() |
| ->profile() |
| ->GetDefaultStoragePartition() |
| ->FlushNetworkInterfaceForTesting(); |
| |
| // Sandbox should still be disabled as the pref stored the state. |
| EXPECT_FALSE(network_service_sandboxed_); |
| EXPECT_TRUE(SystemNetworkContextManager::GetInstance() |
| ->HasFailedPreviousRecentLaunch()); |
| histograms.ExpectTotalCount( |
| "Chrome.SystemNetworkContextManager.NetworkSandboxEarlyLaunchCrashed", |
| 0); |
| const auto disabled_because_of_failed_launch_count = |
| histograms.GetBucketCount( |
| "Chrome.SystemNetworkContextManager.NetworkSandboxState", |
| /*kDisabledBecauseOfFailedLaunch*/ 4); |
| CHECK_GT(disabled_because_of_failed_launch_count, 1u); |
| histograms.ExpectTotalCount( |
| "Chrome.SystemNetworkContextManager.NetworkSandboxState", |
| disabled_because_of_failed_launch_count); |
| } |
| |
| { |
| base::HistogramTester histograms; |
| // Simulate that a previous milestone was crashing. |
| g_browser_process->local_state()->SetInteger( |
| prefs::kNetworkServiceFailedLaunchMajorVersion, |
| CHROME_VERSION_MAJOR - 1); |
| |
| // Crash again, this time to get a sandboxed service again. |
| SimulateNetworkServiceCrash(); |
| |
| // Make sure the network service is fully up and running after having to |
| // restart. |
| browser() |
| ->profile() |
| ->GetDefaultStoragePartition() |
| ->FlushNetworkInterfaceForTesting(); |
| |
| // Sandbox should now be engaged again because the major version |
| // incremented. |
| EXPECT_TRUE(network_service_sandboxed_); |
| EXPECT_FALSE(SystemNetworkContextManager::GetInstance() |
| ->HasFailedPreviousRecentLaunch()); |
| histograms.ExpectTotalCount( |
| "Chrome.SystemNetworkContextManager.NetworkSandboxEarlyLaunchCrashed", |
| 0); |
| const auto enabled_count = histograms.GetBucketCount( |
| "Chrome.SystemNetworkContextManager.NetworkSandboxState", |
| /*kEnabledByPlatform*/ 1); |
| CHECK_GT(enabled_count, 1u); |
| histograms.ExpectTotalCount( |
| "Chrome.SystemNetworkContextManager.NetworkSandboxState", |
| enabled_count); |
| } |
| } |
| |
| #endif // BUILDFLAG(IS_WIN) |
| |
| #if BUILDFLAG(IS_LINUX) |
| class SystemNetworkContextManagerHttpNegotiateHeader |
| : public SystemNetworkContextManagerBrowsertest { |
| public: |
| static constexpr char kHttpsNegotiateAuthPath[] = "/http_negotiate_auth"; |
| |
| SystemNetworkContextManagerHttpNegotiateHeader() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} |
| |
| void SetUpOnMainThread() override { |
| SystemNetworkContextManagerBrowsertest::SetUpOnMainThread(); |
| |
| https_server_.AddDefaultHandlers( |
| base::FilePath(FILE_PATH_LITERAL("content/test/data"))); |
| https_server_.RegisterRequestHandler( |
| base::BindRepeating(&SystemNetworkContextManagerHttpNegotiateHeader:: |
| SendBackHttpNegotiateHeader, |
| base::Unretained(this))); |
| ASSERT_TRUE(https_server_.Start()); |
| } |
| |
| std::unique_ptr<net::test_server::HttpResponse> SendBackHttpNegotiateHeader( |
| const net::test_server::HttpRequest& request) { |
| if (request.relative_url != kHttpsNegotiateAuthPath) { |
| return nullptr; |
| } |
| |
| auto http_response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| http_response->set_code(net::HTTP_UNAUTHORIZED); |
| http_response->AddCustomHeader("WWW-Authenticate", "Negotiate"); |
| return http_response; |
| } |
| |
| content::WebContents* web_contents() { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| protected: |
| net::test_server::EmbeddedTestServer https_server_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SystemNetworkContextManagerHttpNegotiateHeader, |
| DISABLED_SetsPrefOnHttpNegotiateHeader) { |
| PrefService* local_state = g_browser_process->local_state(); |
| |
| // Ensure the pref starts false. |
| EXPECT_FALSE( |
| local_state->GetBoolean(prefs::kReceivedHttpAuthNegotiateHeader)); |
| |
| PrefChangeRegistrar pref_change_registrar; |
| pref_change_registrar.Init(local_state); |
| |
| base::RunLoop wait_for_set_pref_loop; |
| pref_change_registrar.Add(prefs::kReceivedHttpAuthNegotiateHeader, |
| wait_for_set_pref_loop.QuitClosure()); |
| |
| // Navigate to a URL that requests negotiate authentication. |
| EXPECT_FALSE(NavigateToURL(web_contents(), |
| https_server_.GetURL(kHttpsNegotiateAuthPath))); |
| wait_for_set_pref_loop.Run(); |
| |
| // Ensure the pref is now true. |
| EXPECT_TRUE(local_state->GetBoolean(prefs::kReceivedHttpAuthNegotiateHeader)); |
| } |
| #endif // BUILDFLAG(IS_LINUX) |
| |
| namespace { |
| struct CookieAccess { |
| content::CookieAccessDetails::Type type; |
| std::string cookie_name; |
| std::string cookie_value; |
| net::CookieAccessResult cookie_access_result; |
| |
| friend std::ostream& operator<<(std::ostream& o, const CookieAccess& d) { |
| o << (d.type == content::CookieAccessDetails::Type::kRead ? "read" |
| : "change"); |
| o << " name=" << d.cookie_name; |
| o << " value=" << d.cookie_value; |
| o << " access_result="; |
| net::PrintTo(d.cookie_access_result, &o); |
| return o; |
| } |
| |
| public: |
| bool operator==(const CookieAccess&) const = default; |
| }; |
| |
| class CookieTracker : public content::WebContentsObserver { |
| public: |
| explicit CookieTracker(content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents) {} |
| |
| void OnCookiesAccessed(content::NavigationHandle* navigation, |
| const content::CookieAccessDetails& details) override { |
| OnCookiesAccessed(details); |
| } |
| |
| void OnCookiesAccessed(content::RenderFrameHost* rfh, |
| const content::CookieAccessDetails& details) override { |
| OnCookiesAccessed(details); |
| } |
| |
| void WaitForCookies(size_t count) { |
| waiting_for_cookies_count_ = count; |
| |
| base::RunLoop run_loop; |
| quit_closure_ = run_loop.QuitClosure(); |
| QuitIfReady(); |
| run_loop.Run(); |
| } |
| |
| std::vector<CookieAccess>& cookie_accesses() { return cookie_accesses_; } |
| |
| private: |
| void OnCookiesAccessed(const content::CookieAccessDetails& details) { |
| for (const auto& cookie_with_access_result : |
| details.cookie_access_result_list) { |
| cookie_accesses_.emplace_back(details.type, |
| cookie_with_access_result.cookie.Name(), |
| cookie_with_access_result.cookie.Value(), |
| cookie_with_access_result.access_result); |
| } |
| |
| QuitIfReady(); |
| } |
| |
| void QuitIfReady() { |
| if (quit_closure_.is_null()) { |
| return; |
| } |
| if (cookie_accesses_.size() < waiting_for_cookies_count_) { |
| return; |
| } |
| std::move(quit_closure_).Run(); |
| } |
| |
| std::vector<CookieAccess> cookie_accesses_; |
| size_t waiting_for_cookies_count_ = 0; |
| base::OnceClosure quit_closure_; |
| }; |
| |
| } // namespace |
| |
| class SystemNetworkContextManagerWithFirstPartySetComponentBrowserTest |
| : public SystemNetworkContextManagerBrowsertest { |
| public: |
| SystemNetworkContextManagerWithFirstPartySetComponentBrowserTest() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} |
| |
| void SetUpOnMainThread() override { |
| SystemNetworkContextManagerBrowsertest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); |
| https_server()->AddDefaultHandlers( |
| base::FilePath(FILE_PATH_LITERAL("content/test/data"))); |
| ASSERT_TRUE(https_server()->Start()); |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| SystemNetworkContextManagerBrowsertest::SetUpInProcessBrowserTestFixture(); |
| // Since we set kWaitForFirstPartySetsInit, all cookie-carrying network |
| // requests are blocked until FPS is initialized. |
| feature_list_.InitWithFeatures( |
| {net::features::kWaitForFirstPartySetsInit, |
| net::features::kForceThirdPartyCookieBlocking}, |
| {}); |
| CHECK(component_dir_.CreateUniqueTempDir()); |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| |
| component_updater::FirstPartySetsComponentInstallerPolicy:: |
| WriteComponentForTesting(base::Version("1.2.3"), |
| component_dir_.GetPath(), |
| GetComponentContents()); |
| } |
| |
| protected: |
| void SetUpDefaultCommandLine(base::CommandLine* command_line) override { |
| SystemNetworkContextManagerBrowsertest::SetUpDefaultCommandLine( |
| command_line); |
| command_line->RemoveSwitch(switches::kDisableComponentUpdate); |
| } |
| |
| net::test_server::EmbeddedTestServer* https_server() { |
| return &https_server_; |
| } |
| |
| content::WebContents* web_contents() { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| private: |
| std::string GetComponentContents() const { |
| return "{\"primary\": \"https://a.test\", \"associatedSites\": [ " |
| "\"https://b.test\", \"https://associatedsite1.test\"]}\n" |
| "{\"primary\": \"https://c.test\", \"associatedSites\": [ " |
| "\"https://d.test\", \"https://associatedsite2.test\"]}"; |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| base::ScopedTempDir component_dir_; |
| net::test_server::EmbeddedTestServer https_server_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F( |
| SystemNetworkContextManagerWithFirstPartySetComponentBrowserTest, |
| PRE_ReloadsFirstPartySetsAfterCrash) { |
| // Network service is not running out of process, so cannot be crashed. |
| if (!content::IsOutOfProcessNetworkService()) |
| return; |
| |
| // Set a persistent cookie that will still be there after the network service |
| // is crashed. We don't use the system network context here (which wouldn't |
| // persist the cookie to disk), but that's ok - this test only cares that the |
| // NetworkService gets reconfigured after a crash, and that that |
| // reconfiguration includes setting up First-Party Sets. |
| const GURL host_root = https_server()->GetURL(kHostA, "/"); |
| ASSERT_TRUE(content::SetCookie( |
| browser()->profile(), host_root, |
| base::StrCat( |
| {kCookieName, "=1; SameSite=None; secure; max-age=2147483647"}))); |
| ASSERT_THAT(content::GetCookies(browser()->profile(), host_root), |
| net::CookieStringIs( |
| testing::UnorderedElementsAre(testing::Key(kCookieName)))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SystemNetworkContextManagerWithFirstPartySetComponentBrowserTest, |
| ReloadsFirstPartySetsAfterCrash) { |
| // Network service is not running out of process, so cannot be crashed. |
| if (!content::IsOutOfProcessNetworkService()) |
| return; |
| |
| CookieTracker cookie_tracker(web_contents()); |
| |
| const GURL url_a = https_server()->GetURL(kHostA, "/title1.html"); |
| ASSERT_TRUE(content::NavigateToURL(web_contents(), url_a)); |
| cookie_tracker.WaitForCookies(2); |
| const CookieAccess expected_first_party_access{ |
| content::CookieAccessDetails::Type::kRead, "Cookie", "1", |
| net::CookieAccessResult( |
| net::CookieEffectiveSameSite::NO_RESTRICTION, |
| net::CookieInclusionStatus::MakeFromReasonsForTesting( |
| /*exclusions=*/{}, |
| /*warnings=*/{net::CookieInclusionStatus::WarningReason:: |
| WARN_PORT_MISMATCH}), |
| net::CookieAccessSemantics::NONLEGACY, |
| net::CookieScopeSemantics::NONLEGACY, true)}; |
| EXPECT_THAT(cookie_tracker.cookie_accesses(), |
| testing::ElementsAre( |
| // a.test/title1.html |
| expected_first_party_access, |
| // a.test/favicon.ico |
| expected_first_party_access)); |
| cookie_tracker.cookie_accesses().clear(); |
| |
| const GURL url_b_cross_site(https_server()->GetURL( |
| kHostB, "/cross_site_iframe_factory.html?b.test(a.test)")); |
| EXPECT_TRUE(NavigateToURL(web_contents(), url_b_cross_site)); |
| cookie_tracker.WaitForCookies(2); |
| net::CookieInclusionStatus expected_third_party_inclusion_status; |
| // If the sites are in the same Related Website Sets, we're expecting the |
| // EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET exclusion reason. |
| expected_third_party_inclusion_status.AddExclusionReason( |
| net::CookieInclusionStatus::ExclusionReason:: |
| EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET); |
| expected_third_party_inclusion_status.AddExclusionReason( |
| net::CookieInclusionStatus::ExclusionReason:: |
| EXCLUDE_THIRD_PARTY_PHASEOUT); |
| expected_third_party_inclusion_status.AddWarningReason( |
| net::CookieInclusionStatus::WarningReason::WARN_PORT_MISMATCH); |
| const CookieAccess expected_third_party_access{ |
| content::CookieAccessDetails::Type::kRead, "Cookie", "1", |
| net::CookieAccessResult(net::CookieEffectiveSameSite::NO_RESTRICTION, |
| expected_third_party_inclusion_status, |
| net::CookieAccessSemantics::NONLEGACY, |
| net::CookieScopeSemantics::NONLEGACY, true)}; |
| EXPECT_THAT(cookie_tracker.cookie_accesses(), |
| testing::ElementsAre( |
| // a.test iframe under b.test |
| expected_third_party_access, |
| // a.test/tree_parser_util.js in an iframe under b.test |
| expected_third_party_access)); |
| cookie_tracker.cookie_accesses().clear(); |
| |
| SimulateNetworkServiceCrash(); |
| |
| EXPECT_TRUE(NavigateToURL(web_contents(), url_b_cross_site)); |
| cookie_tracker.WaitForCookies(2); |
| EXPECT_THAT(cookie_tracker.cookie_accesses(), |
| testing::ElementsAre( |
| // a.test iframe under b.test |
| expected_third_party_access, |
| // a.test/tree_parser_util.js in an iframe under b.test |
| expected_third_party_access)); |
| cookie_tracker.cookie_accesses().clear(); |
| } |
| |
| class SystemNetworkContextManagerReferrersFeatureBrowsertest |
| : public SystemNetworkContextManagerBrowsertest, |
| public testing::WithParamInterface<bool> { |
| public: |
| SystemNetworkContextManagerReferrersFeatureBrowsertest() { |
| scoped_feature_list_.InitWithFeatureState(features::kNoReferrers, |
| GetParam()); |
| } |
| ~SystemNetworkContextManagerReferrersFeatureBrowsertest() override = default; |
| |
| void SetUpOnMainThread() override {} |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Tests that toggling the kNoReferrers feature correctly changes the default |
| // value of the kEnableReferrers pref. |
| IN_PROC_BROWSER_TEST_P(SystemNetworkContextManagerReferrersFeatureBrowsertest, |
| TestDefaultReferrerReflectsFeatureValue) { |
| ASSERT_TRUE(g_browser_process); |
| PrefService* local_state = g_browser_process->local_state(); |
| ASSERT_TRUE(local_state); |
| EXPECT_NE(local_state->GetBoolean(prefs::kEnableReferrers), GetParam()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemNetworkContextManagerReferrersFeatureBrowsertest, |
| ::testing::Bool()); |
| |
| class SystemNetworkContextManagerFreezeQUICUaBrowsertest |
| : public SystemNetworkContextManagerBrowsertest { |
| public: |
| SystemNetworkContextManagerFreezeQUICUaBrowsertest() = default; |
| ~SystemNetworkContextManagerFreezeQUICUaBrowsertest() override = default; |
| |
| void SetUpOnMainThread() override {} |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| class SystemNetworkContextManagerWPADQuickCheckBrowsertest |
| : public SystemNetworkContextManagerBrowsertest, |
| public testing::WithParamInterface<bool> { |
| public: |
| SystemNetworkContextManagerWPADQuickCheckBrowsertest() = default; |
| ~SystemNetworkContextManagerWPADQuickCheckBrowsertest() override = default; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemNetworkContextManagerWPADQuickCheckBrowsertest, |
| WPADQuickCheckPref) { |
| PrefService* local_state = g_browser_process->local_state(); |
| local_state->SetBoolean(prefs::kQuickCheckEnabled, GetParam()); |
| |
| network::mojom::NetworkContextParamsPtr network_context_params = |
| g_browser_process->system_network_context_manager() |
| ->CreateDefaultNetworkContextParams(); |
| EXPECT_EQ(GetParam(), network_context_params->pac_quick_check_enabled); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemNetworkContextManagerWPADQuickCheckBrowsertest, |
| ::testing::Bool()); |