// Copyright 2021 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/metrics/content/content_stability_metrics_provider.h" #include #include "base/command_line.h" #include "base/files/file_path.h" #include "base/functional/callback.h" #include "base/metrics/histogram.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "components/metrics/content/extensions_helper.h" #include "components/prefs/testing_pref_service.h" #include "components/variations/hashing.h" #include "content/public/browser/browser_child_process_observer.h" #include "content/public/browser/child_process_data.h" #include "content/public/browser/child_process_termination_info.h" #include "content/public/browser/service_process_host.h" #include "content/public/common/content_features.h" #include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_base.h" #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" #include "content/public/test/test_service.mojom.h" #include "sandbox/policy/mojom/sandbox.mojom.h" #if BUILDFLAG(IS_WIN) #include #include "sandbox/win/src/sandbox_types.h" #endif namespace content { template <> sandbox::mojom::Sandbox GetServiceSandboxType() { // On Windows, the sandbox does not like having a different binary name // 'non_existent_path' from the browser process, so set no sandbox here. #if BUILDFLAG(IS_WIN) return sandbox::mojom::Sandbox::kNoSandbox; #else return sandbox::mojom::Sandbox::kService; #endif } } // namespace content namespace metrics { class ContentStabilityProviderBrowserTest : public content::ContentBrowserTest, content::BrowserChildProcessObserver { public: ContentStabilityProviderBrowserTest() { feature_list_.InitAndDisableFeature( features::kSpareRendererForSitePerProcess); } // Either the process launched, or did not launch. Both cause the run_loop to // terminate. void BrowserChildProcessLaunchFailed( const content::ChildProcessData& data, const content::ChildProcessTerminationInfo& info) override { if (data.metrics_name == content::mojom::TestService::Name_) std::move(done_closure_).Run(); } void BrowserChildProcessLaunchedAndConnected( const content::ChildProcessData& data) override { if (data.metrics_name == content::mojom::TestService::Name_) std::move(done_closure_).Run(); } protected: void AddObserver() { content::BrowserChildProcessObserver::Add(this); } void RemoveObserver() { content::BrowserChildProcessObserver::Remove(this); } base::OnceClosure done_closure_; TestingPrefServiceSimple prefs_; base::test::ScopedFeatureList feature_list_; }; IN_PROC_BROWSER_TEST_F(ContentStabilityProviderBrowserTest, FailedUtilityProcessLaunches) { base::RunLoop run_loop; done_closure_ = run_loop.QuitClosure(); AddObserver(); ContentStabilityMetricsProvider provider(&prefs_, nullptr); base::HistogramTester histogram_tester; // Simulate a catastrophic utility process launch failure by specifying a bad // path. base::CommandLine::ForCurrentProcess()->AppendSwitchPath( switches::kBrowserSubprocessPath, base::FilePath(FILE_PATH_LITERAL("non_existent_path"))); mojo::Remote test_service; content::ServiceProcessHost::Launch( test_service.BindNewPipeAndPassReceiver()); // run_loop runs until either the process launches or fails to launch. run_loop.Run(); RemoveObserver(); histogram_tester.ExpectUniqueSample( "ChildProcess.LaunchFailed.UtilityProcessHash", variations::HashName(content::mojom::TestService::Name_), 1); #if BUILDFLAG(IS_WIN) int expected_error_code = sandbox::SBOX_ERROR_CANNOT_LAUNCH_UNSANDBOXED_PROCESS; #else int expected_error_code = 1003; // content::LaunchResultCode::LAUNCH_RESULT_FAILURE. #endif histogram_tester.ExpectUniqueSample( "ChildProcess.LaunchFailed.UtilityProcessErrorCode", expected_error_code, 1); #if BUILDFLAG(IS_WIN) // Last Error is only recorded on Windows. histogram_tester.ExpectUniqueSample("ChildProcess.LaunchFailed.WinLastError", DWORD{ERROR_FILE_NOT_FOUND}, 1); #endif } // Class to execute a closure after we observer a renderer process launch or // launch failure. class RenderProcessCreationObserver : content::RenderProcessHostCreationObserver { public: RenderProcessCreationObserver(base::OnceClosure done_closure) : done_closure_(std::move(done_closure)) {} ~RenderProcessCreationObserver() override = default; void OnRenderProcessHostCreated( content::RenderProcessHost* process_host) override { if (done_closure_) { std::move(done_closure_).Run(); } } void OnRenderProcessHostCreationFailed( content::RenderProcessHost* host, const content::ChildProcessTerminationInfo& info) override { ASSERT_EQ(base::TERMINATION_STATUS_LAUNCH_FAILED, info.status); if (done_closure_) { std::move(done_closure_).Run(); } } private: base::OnceClosure done_closure_; }; IN_PROC_BROWSER_TEST_F(ContentStabilityProviderBrowserTest, FailedRendererProcessLaunches) { base::RunLoop run_loop; ContentStabilityMetricsProvider provider(&prefs_, nullptr); base::HistogramTester histogram_tester; { RenderProcessCreationObserver renderer_observer(run_loop.QuitClosure()); // Simulate a catastrophic renderer process launch failure by specifying a // bad path. base::CommandLine::ForCurrentProcess()->AppendSwitchPath( switches::kBrowserSubprocessPath, base::FilePath(FILE_PATH_LITERAL("non_existent_path"))); ASSERT_FALSE(content::NavigateToURL(shell(), GURL("about:blank"))); // run_loop runs until either the process launches or fails to launch. run_loop.Run(); } histogram_tester.ExpectUniqueSample( "BrowserRenderProcessHost.ChildLaunchFailures", 1 /* CoarseRendererType::kRenderer */, 1); #if BUILDFLAG(IS_WIN) int expected_error_code = sandbox::SBOX_ERROR_CREATE_PROCESS; #else int expected_error_code = 1003; // content::LaunchResultCode::LAUNCH_RESULT_FAILURE. #endif histogram_tester.ExpectUniqueSample( "BrowserRenderProcessHost.ChildLaunchFailureCodes", expected_error_code, 1); histogram_tester.ExpectBucketCount( "Stability.Counts2", StabilityEventType::kRendererFailedLaunch, 1); } } // namespace metrics