blob: 3fa04e3c324520077d0a8783147354c8c80c5548 [file] [log] [blame]
mark a. foltz54537f02023-08-31 04:33:411// Copyright 2022 The Chromium Authors
Patrick Monette2b743d32022-09-23 23:40:372// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/public/browser/browser_child_process_observer.h"
6
Avi Drissmanadac21992023-01-11 23:46:397#include "base/functional/bind.h"
Patrick Monette2b743d32022-09-23 23:40:378#include "base/run_loop.h"
9#include "build/build_config.h"
10#include "content/browser/browser_child_process_host_impl.h"
Will Harriscd57b832023-01-05 20:03:1011#include "content/browser/child_process_host_impl.h"
Emily Andrews2677e32f2025-02-24 18:37:0512#include "content/browser/service_host/utility_process_host.h"
Patrick Monette2b743d32022-09-23 23:40:3713#include "content/public/browser/browser_child_process_host.h"
14#include "content/public/browser/browser_child_process_host_delegate.h"
15#include "content/public/browser/child_process_data.h"
16#include "content/public/common/content_switches.h"
17#include "content/public/common/process_type.h"
18#include "content/public/common/sandboxed_process_launcher_delegate.h"
19#include "content/public/test/browser_test.h"
20#include "content/public/test/content_browser_test.h"
21#include "content/public/test/test_service.mojom.h"
22#include "sandbox/policy/sandbox_type.h"
23#include "testing/gmock/include/gmock/gmock.h"
24
25namespace content {
26
27namespace {
28
29// An enum that represent the different type of notitifcations that exist in
30// BrowserChildProcessObserver.
31enum class Notification {
32 kLaunchedAndConnected,
33 kDisconnected,
34 kCrashed,
35 kKilled,
36 kLaunchFailed,
Patrick Monette3ded71b32022-09-28 14:53:0437 kExitedNormally,
Patrick Monette2b743d32022-09-23 23:40:3738};
39
40// Nicer test output.
41std::ostream& operator<<(std::ostream& os, Notification notification) {
42 switch (notification) {
43 case Notification::kLaunchedAndConnected:
44 os << "LaunchedAndConnected";
45 break;
46 case Notification::kDisconnected:
47 os << "Disconnected";
48 break;
49 case Notification::kCrashed:
50 os << "Crashed";
51 break;
52 case Notification::kKilled:
53 os << "Killed";
54 break;
55 case Notification::kLaunchFailed:
56 os << "LaunchFailed";
57 break;
Patrick Monette3ded71b32022-09-28 14:53:0458 case Notification::kExitedNormally:
59 os << "ExitedNormally";
60 break;
Patrick Monette2b743d32022-09-23 23:40:3761 }
62 return os;
63}
64
65// Returns true if a child process whose ID is |child_id| is still alive.
66bool IsHostAlive(int child_id) {
67 return BrowserChildProcessHost::FromID(child_id) != nullptr;
68}
69
70} // namespace
71
72// A test BrowserChildProcessObserver that transforms every call to one of the
73// observer's method to a call to the notification callback.
74class BrowserChildProcessNotificationObserver
75 : public BrowserChildProcessObserver {
76 public:
77 using OnNotificationCallback =
78 base::RepeatingCallback<void(Notification notification)>;
79
80 BrowserChildProcessNotificationObserver(
81 int child_id,
82 OnNotificationCallback on_notification_callback)
83 : child_id_(child_id),
84 on_notification_callback_(std::move(on_notification_callback)) {
85 BrowserChildProcessObserver::Add(this);
86 }
87
88 ~BrowserChildProcessNotificationObserver() override {
89 BrowserChildProcessObserver::Remove(this);
90 }
91
Ben Bamesberger1db126aa2024-10-07 18:52:1292 protected:
Patrick Monette2b743d32022-09-23 23:40:3793 // BrowserChildProcessObserver:
94 void BrowserChildProcessLaunchedAndConnected(
95 const ChildProcessData& data) override {
96 OnNotification(data, Notification::kLaunchedAndConnected);
97 }
98 void BrowserChildProcessHostDisconnected(
99 const ChildProcessData& data) override {
100 OnNotification(data, Notification::kDisconnected);
101 }
102 void BrowserChildProcessCrashed(
103 const ChildProcessData& data,
104 const ChildProcessTerminationInfo& info) override {
105 OnNotification(data, Notification::kCrashed);
106 }
107 void BrowserChildProcessKilled(
108 const ChildProcessData& data,
109 const ChildProcessTerminationInfo& info) override {
110 OnNotification(data, Notification::kKilled);
111 }
112 void BrowserChildProcessLaunchFailed(
113 const ChildProcessData& data,
114 const ChildProcessTerminationInfo& info) override {
115 OnNotification(data, Notification::kLaunchFailed);
116 }
Patrick Monette3ded71b32022-09-28 14:53:04117 void BrowserChildProcessExitedNormally(
118 const ChildProcessData& data,
119 const ChildProcessTerminationInfo& info) override {
120 OnNotification(data, Notification::kExitedNormally);
121 }
Patrick Monette2b743d32022-09-23 23:40:37122
123 void OnNotification(const ChildProcessData& data, Notification notification) {
124 if (data.id == child_id_)
125 on_notification_callback_.Run(notification);
126 }
127
Ben Bamesberger1db126aa2024-10-07 18:52:12128 private:
Patrick Monette2b743d32022-09-23 23:40:37129 // Every notification coming for a child with a different ID will be ignored.
130 int child_id_;
131
132 // The callback to invoke every time a method of the observer is called.
133 OnNotificationCallback on_notification_callback_;
134};
135
136// A helper class that allows the user to wait until a specific |notification|
137// is sent for a child process whose ID matches |child_id|.
138class WaitForNotificationObserver {
139 public:
140 WaitForNotificationObserver(int child_id, Notification notification)
141 : inner_observer_(
142 child_id,
143 base::BindRepeating(&WaitForNotificationObserver::OnNotification,
144 base::Unretained(this))),
145 notification_(notification) {}
146
147 ~WaitForNotificationObserver() = default;
148
149 // Waits until the notification is received. Returns immediately if it was
150 // already received.
151 void Wait() {
152 if (notification_received_)
153 return;
154
155 DCHECK(!run_loop_.running());
156 run_loop_.Run();
157 }
158
159 private:
160 void OnNotification(Notification notification) {
161 if (notification != notification_)
162 return;
163
164 notification_received_ = true;
165 if (run_loop_.running())
166 run_loop_.Quit();
167 }
168
169 BrowserChildProcessNotificationObserver inner_observer_;
170 Notification notification_;
171 base::RunLoop run_loop_;
172 bool notification_received_ = false;
173};
174
175class TestSandboxedProcessLauncherDelegate
176 : public SandboxedProcessLauncherDelegate {
177 public:
178 explicit TestSandboxedProcessLauncherDelegate(
179 sandbox::mojom::Sandbox sandbox_type)
180 : sandbox_type_(sandbox_type) {}
181 ~TestSandboxedProcessLauncherDelegate() override = default;
182
183 // SandboxedProcessLauncherDelegate:
184 sandbox::mojom::Sandbox GetSandboxType() override { return sandbox_type_; }
185
186 private:
187 sandbox::mojom::Sandbox sandbox_type_;
188};
189
190// A test-specific type of process host. Self-owned.
Tom Sepezf3f28e12025-07-21 22:39:13191class TestProcessHost final : public BrowserChildProcessHostDelegate {
Patrick Monette2b743d32022-09-23 23:40:37192 public:
193 static base::WeakPtr<TestProcessHost> Create() {
194 auto* instance = new TestProcessHost();
195 return instance->GetWeakPtr();
196 }
197
198 TestProcessHost()
Tom Sepez7bf89002025-07-17 23:44:04199 : process_(BrowserChildProcessHost::Create(PROCESS_TYPE_UTILITY, this)) {}
Patrick Monette2b743d32022-09-23 23:40:37200 ~TestProcessHost() override = default;
201
202 // Returns the ID of the child process.
Emily Andrewsd15fd762024-12-10 20:41:54203 int GetID() { return process_->GetData().id; }
Patrick Monette2b743d32022-09-23 23:40:37204
205 // Binds to the test service on the child process and returns the bound
206 // remote.
207 mojo::Remote<mojom::TestService> BindTestService() {
208 mojo::Remote<mojom::TestService> test_service;
209
210 static_cast<ChildProcessHostImpl*>(process_->GetHost())
211 ->child_process()
212 ->BindServiceInterface(test_service.BindNewPipeAndPassReceiver());
213
214 return test_service;
215 }
216
217 // Returns the command line used to launch the child process.
218 std::unique_ptr<base::CommandLine> GetChildCommandLine() {
219 base::FilePath child_path =
220 ChildProcessHost::GetChildPath(ChildProcessHost::CHILD_NORMAL);
221 auto command_line = std::make_unique<base::CommandLine>(child_path);
222
223 command_line->AppendSwitchASCII(switches::kProcessType,
224 switches::kUtilityProcess);
225 command_line->AppendSwitchASCII(switches::kUtilitySubType,
226 "Test Utility Process");
227 sandbox::policy::SetCommandLineFlagsForSandboxType(command_line.get(),
228 sandbox_type_);
229
230 return command_line;
231 }
232
Ben Bamesberger1db126aa2024-10-07 18:52:12233 // Launches the child process using the default test launcher delegate.
Patrick Monette2b743d32022-09-23 23:40:37234 void LaunchProcess() {
Ben Bamesberger1db126aa2024-10-07 18:52:12235 LaunchProcessWithDelegate(
236 std::make_unique<TestSandboxedProcessLauncherDelegate>(sandbox_type_));
237 }
238
239 // Launches the child process using a supplied sandbox delegate.
240 void LaunchProcessWithDelegate(
241 std::unique_ptr<SandboxedProcessLauncherDelegate>
242 sandboxed_process_launcher_delegate) {
Patrick Monette2b743d32022-09-23 23:40:37243 process_->SetName(u"Test utility process");
244
Patrick Monette2b743d32022-09-23 23:40:37245 auto command_line = GetChildCommandLine();
246 bool terminate_on_shutdown = true;
247
248 process_->Launch(std::move(sandboxed_process_launcher_delegate),
249 std::move(command_line), terminate_on_shutdown);
250
251 test_service_ = BindTestService();
252 }
253
254 // Requests the child process to shutdown.
255 void ForceShutdown() { process_->GetHost()->ForceShutdown(); }
256
257 // Disconnects the bound remote from the test service.
258 void Disconnect() { test_service_.reset(); }
259
260 // Sets the sandbox type to use for the child process.
261 void SetSandboxType(sandbox::mojom::Sandbox sandbox_type) {
262 sandbox_type_ = sandbox_type;
263 }
264
265 mojom::TestService* service() const { return test_service_.get(); }
266
267 base::WeakPtr<TestProcessHost> GetWeakPtr() {
268 return weak_ptr_factory_.GetWeakPtr();
269 }
270
271 private:
272 sandbox::mojom::Sandbox sandbox_type_ = sandbox::mojom::Sandbox::kUtility;
273
274 std::unique_ptr<BrowserChildProcessHost> process_;
275
276 mojo::Remote<mojom::TestService> test_service_;
277
278 base::WeakPtrFactory<TestProcessHost> weak_ptr_factory_{this};
279};
280
281// A helper class that exposes which notifications were sent for a specific
282// child process.
283class TestBrowserChildProcessObserver {
284 public:
285 explicit TestBrowserChildProcessObserver(int child_id)
286 : inner_observer_(child_id,
287 base::BindRepeating(
288 &TestBrowserChildProcessObserver::OnNotification,
289 base::Unretained(this))) {}
290
291 ~TestBrowserChildProcessObserver() = default;
292
293 // Returns the notifications received for |child_id|.
294 const std::vector<Notification>& notifications() const {
295 return notifications_;
296 }
297
298 private:
299 void OnNotification(Notification notification) {
300 notifications_.push_back(notification);
301 }
302
303 BrowserChildProcessNotificationObserver inner_observer_;
304
305 std::vector<Notification> notifications_;
306};
307
308class BrowserChildProcessObserverBrowserTest : public ContentBrowserTest {};
309
310// Tests that launching and then using ForceShutdown() results in a normal
311// termination.
312#if defined(ADDRESS_SANITIZER)
Alison Gale770f3fc2024-04-27 00:39:58313// TODO(crbug.com/40238612): Fix ASAN failures on trybot.
Patrick Monette2b743d32022-09-23 23:40:37314#define MAYBE_LaunchAndForceShutdown DISABLED_LaunchAndForceShutdown
315#else
316#define MAYBE_LaunchAndForceShutdown LaunchAndForceShutdown
317#endif
318IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest,
319 MAYBE_LaunchAndForceShutdown) {
320 base::WeakPtr<TestProcessHost> host = TestProcessHost::Create();
Emily Andrewsd15fd762024-12-10 20:41:54321 int child_id = host->GetID();
Patrick Monette2b743d32022-09-23 23:40:37322
323 TestBrowserChildProcessObserver observer(child_id);
324
325 {
326 WaitForNotificationObserver waiter(child_id,
327 Notification::kLaunchedAndConnected);
328 host->LaunchProcess();
329 waiter.Wait();
330 }
331
332 {
333 WaitForNotificationObserver waiter(child_id, Notification::kDisconnected);
334 host->ForceShutdown();
335 waiter.Wait();
336 }
337
Patrick Monette3ded71b32022-09-28 14:53:04338 Notification kExitNotification =
Patrick Monette2b743d32022-09-23 23:40:37339#if BUILDFLAG(IS_ANDROID)
Patrick Monette3ded71b32022-09-28 14:53:04340 // TODO(pmonette): On Android, this currently causes a killed
341 // notification. Consider fixing.
342 Notification::kKilled;
343#else
344 Notification::kExitedNormally;
345#endif // BUILDFLAG(IS_ANDROID)
Patrick Monette2b743d32022-09-23 23:40:37346
347 // The host should be deleted now.
348 EXPECT_FALSE(host);
349 EXPECT_FALSE(IsHostAlive(child_id));
350 EXPECT_THAT(observer.notifications(),
Patrick Monette3ded71b32022-09-28 14:53:04351 testing::ElementsAreArray({Notification::kLaunchedAndConnected,
352 kExitNotification,
353 Notification::kDisconnected}));
Patrick Monette2b743d32022-09-23 23:40:37354}
355
356// Tests that launching and then deleting the host results in a normal
357// termination.
358IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest,
359 LaunchAndDelete) {
360 base::WeakPtr<TestProcessHost> host = TestProcessHost::Create();
Emily Andrewsd15fd762024-12-10 20:41:54361 int child_id = host->GetID();
Patrick Monette2b743d32022-09-23 23:40:37362
363 TestBrowserChildProcessObserver observer(child_id);
364
365 {
366 WaitForNotificationObserver waiter(child_id,
367 Notification::kLaunchedAndConnected);
368 host->LaunchProcess();
369 waiter.Wait();
370 }
371
372 {
373 WaitForNotificationObserver waiter(child_id, Notification::kDisconnected);
374 delete host.get();
375 waiter.Wait();
376 }
377
378 // The host should be deleted now.
379 EXPECT_FALSE(host);
380 EXPECT_FALSE(IsHostAlive(child_id));
381 EXPECT_THAT(observer.notifications(),
382 testing::ElementsAreArray({Notification::kLaunchedAndConnected,
Patrick Monette3ded71b32022-09-28 14:53:04383 Notification::kExitedNormally,
Patrick Monette2b743d32022-09-23 23:40:37384 Notification::kDisconnected}));
385}
386
387// Tests that launching and then disconnecting the service channel results in a
388// normal termination.
389// Note: This only works for services bound using BindServiceInterface(), not
390// BindReceiver().
391#if defined(ADDRESS_SANITIZER)
Alison Gale770f3fc2024-04-27 00:39:58392// TODO(crbug.com/40238612): Fix ASAN failures on trybot.
Patrick Monette2b743d32022-09-23 23:40:37393#define MAYBE_LaunchAndDisconnect DISABLED_LaunchAndDisconnect
394#else
395#define MAYBE_LaunchAndDisconnect LaunchAndDisconnect
396#endif
397IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest,
398 MAYBE_LaunchAndDisconnect) {
399 base::WeakPtr<TestProcessHost> host = TestProcessHost::Create();
Emily Andrewsd15fd762024-12-10 20:41:54400 int child_id = host->GetID();
Patrick Monette2b743d32022-09-23 23:40:37401
402 TestBrowserChildProcessObserver observer(child_id);
403
404 {
405 WaitForNotificationObserver waiter(child_id,
406 Notification::kLaunchedAndConnected);
407 host->LaunchProcess();
408 waiter.Wait();
409 }
410
411 {
412 WaitForNotificationObserver waiter(child_id, Notification::kDisconnected);
413 host->Disconnect();
414 waiter.Wait();
415 }
416
Patrick Monette3ded71b32022-09-28 14:53:04417 Notification kExitNotification =
Patrick Monette2b743d32022-09-23 23:40:37418#if BUILDFLAG(IS_ANDROID)
Patrick Monette3ded71b32022-09-28 14:53:04419 // On Android, kKilled is always sent in the case of a crash.
420 Notification::kKilled;
421#else
422 Notification::kExitedNormally;
423#endif // BUILDFLAG(IS_ANDROID)
Patrick Monette2b743d32022-09-23 23:40:37424
425 // The host should be deleted now.
426 EXPECT_FALSE(host);
427 EXPECT_FALSE(IsHostAlive(child_id));
Patrick Monette3ded71b32022-09-28 14:53:04428 EXPECT_THAT(observer.notifications(), testing::ElementsAreArray({
429 Notification::kLaunchedAndConnected,
430 kExitNotification,
431 Notification::kDisconnected,
432 }));
Patrick Monette2b743d32022-09-23 23:40:37433}
434
435// Tests that launching and then causing a crash the host results in a crashed
436// notification.
Alison Gale47d1537d2024-04-19 21:31:46437// TODO(crbug.com/40868150): Times out on Android tests.
Nohemi Fernandez98658b42022-09-28 11:10:48438#if BUILDFLAG(IS_ANDROID)
Patrick Monette1ea741852022-09-27 18:39:44439#define MAYBE_LaunchAndCrash DISABLED_LaunchAndCrash
440#else
441#define MAYBE_LaunchAndCrash LaunchAndCrash
442#endif
443IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest,
444 MAYBE_LaunchAndCrash) {
Patrick Monette2b743d32022-09-23 23:40:37445 base::WeakPtr<TestProcessHost> host = TestProcessHost::Create();
Emily Andrewsd15fd762024-12-10 20:41:54446 int child_id = host->GetID();
Patrick Monette2b743d32022-09-23 23:40:37447
448 TestBrowserChildProcessObserver observer(child_id);
449
450 {
451 WaitForNotificationObserver waiter(child_id,
452 Notification::kLaunchedAndConnected);
453 host->LaunchProcess();
454 waiter.Wait();
455 }
456
457 {
458 WaitForNotificationObserver waiter(child_id, Notification::kDisconnected);
459 host->service()->DoCrashImmediately(base::DoNothing());
460 waiter.Wait();
461 }
462
463 Notification kCrashedNotification =
464#if BUILDFLAG(IS_ANDROID)
465 // On Android, kKilled is always sent in the case of a crash.
466 Notification::kKilled;
467#else
468 Notification::kCrashed;
469#endif // BUILDFLAG(IS_ANDROID)
470
471 // The host should be deleted now.
472 EXPECT_FALSE(host);
473 EXPECT_FALSE(IsHostAlive(child_id));
474 EXPECT_THAT(observer.notifications(),
475 testing::ElementsAreArray({Notification::kLaunchedAndConnected,
476 kCrashedNotification,
477 Notification::kDisconnected}));
478}
479
480// Tests that kLaunchFailed is correctly sent when the child process fails to
481// launch.
482//
483// This test won't work as-is on POSIX platforms, where fork()+exec() is used to
484// launch child processes, failure does not happen until exec(), therefore the
485// test will see a valid child process followed by a
486// TERMINATION_STATUS_ABNORMAL_TERMINATION of the forked process. However,
487// posix_spawn() is used on macOS.
488// See also ServiceProcessLauncherTest.FailToLaunchProcess.
489#if !BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_MAC)
490IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest, LaunchFailed) {
491 base::WeakPtr<TestProcessHost> host = TestProcessHost::Create();
Emily Andrewsd15fd762024-12-10 20:41:54492 int child_id = host->GetID();
Patrick Monette2b743d32022-09-23 23:40:37493
494#if BUILDFLAG(IS_WIN)
495 // The Windows sandbox does not like the child process being a different
496 // process, so launch unsandboxed for the purpose of this test.
497 host->SetSandboxType(sandbox::mojom::Sandbox::kNoSandbox);
498#endif
499
500 // Simulate a catastrophic launch failure for all child processes by
501 // making the path to the process non-existent.
502 base::CommandLine::ForCurrentProcess()->AppendSwitchPath(
503 switches::kBrowserSubprocessPath,
504 base::FilePath(FILE_PATH_LITERAL("non_existent_path")));
505
506 TestBrowserChildProcessObserver observer(child_id);
507
508 {
509 WaitForNotificationObserver waiter(child_id, Notification::kLaunchFailed);
510 host->LaunchProcess();
511 waiter.Wait();
512 }
513
514 // The host should be deleted now.
515 EXPECT_FALSE(host);
516 EXPECT_FALSE(IsHostAlive(child_id));
517 EXPECT_THAT(observer.notifications(),
518 testing::ElementsAreArray({Notification::kLaunchFailed}));
519}
520#endif // !BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_MAC)
521
Ben Bamesberger1db126aa2024-10-07 18:52:12522#if BUILDFLAG(IS_WIN)
523class TestPreSpawnTargetFailureSandboxedProcessLauncherDelegate
524 : public TestSandboxedProcessLauncherDelegate {
525 public:
526 using TestSandboxedProcessLauncherDelegate::
527 TestSandboxedProcessLauncherDelegate;
528
529 // SandboxedProcessLauncherDelegate:
530 bool PreSpawnTarget(sandbox::TargetPolicy* policy) override {
531 // Force a failure in PreSpawnTarget().
532 return false;
533 }
534};
535
536// Override the observer to verify the error occurred in PreSpawnTarget().
537class TestPreSpawnTargetFailureBrowserChildProcessNotificationObserver
538 : public BrowserChildProcessNotificationObserver {
539 public:
540 using BrowserChildProcessNotificationObserver::
541 BrowserChildProcessNotificationObserver;
542
543 // BrowserChildProcessObserver:
544 void BrowserChildProcessLaunchFailed(
545 const ChildProcessData& data,
546 const ChildProcessTerminationInfo& info) override {
547 EXPECT_EQ(info.exit_code, sandbox::SBOX_ERROR_DELEGATE_PRE_SPAWN);
548 BrowserChildProcessNotificationObserver::OnNotification(
549 data, Notification::kLaunchFailed);
550 }
551};
552
553// Tests that a pre spawn failure results in a failed launch.
554IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest,
555 LaunchPreSpawnFailed) {
556 base::WeakPtr<TestProcessHost> host = TestProcessHost::Create();
Emily Andrewsd15fd762024-12-10 20:41:54557 int child_id = host->GetID();
Ben Bamesberger1db126aa2024-10-07 18:52:12558
559 TestBrowserChildProcessObserver observer(child_id);
560
561 {
562 WaitForNotificationObserver waiter(child_id, Notification::kLaunchFailed);
563 host->LaunchProcessWithDelegate(
564 std::make_unique<
565 TestPreSpawnTargetFailureSandboxedProcessLauncherDelegate>(
566 sandbox::mojom::Sandbox::kUtility));
567 waiter.Wait();
568 }
569
570 // The host should be deleted now.
571 EXPECT_FALSE(host);
572 EXPECT_FALSE(IsHostAlive(child_id));
573 EXPECT_THAT(observer.notifications(),
574 testing::ElementsAreArray({Notification::kLaunchFailed}));
575}
576#endif // BUILDFLAG(IS_WIN)
577
Patrick Monette2b743d32022-09-23 23:40:37578} // namespace content