| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/child_process_task_port_provider_mac.h" |
| |
| #include <mach/mach.h> |
| |
| #include <vector> |
| |
| #include "base/clang_profiling_buildflags.h" |
| #include "base/mac/scoped_mach_port.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_timeouts.h" |
| #include "content/common/child_process.mojom.h" |
| #include "ipc/ipc_buildflags.h" |
| #include "mojo/public/cpp/system/platform_handle.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| |
| using testing::_; |
| using testing::WithArgs; |
| |
| class MockChildProcess : public mojom::ChildProcess { |
| public: |
| MOCK_METHOD0(ProcessShutdown, void()); |
| MOCK_METHOD1(GetTaskPort, void(GetTaskPortCallback)); |
| #if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED) |
| MOCK_METHOD1(SetIPCLoggingEnabled, void(bool)); |
| #endif |
| #if BUILDFLAG(CLANG_PROFILING_INSIDE_SANDBOX) |
| MOCK_METHOD1(SetProfilingFile, void(base::File)); |
| MOCK_METHOD1(WriteClangProfilingProfile, |
| void(WriteClangProfilingProfileCallback)); |
| #endif |
| MOCK_METHOD1(GetBackgroundTracingAgentProvider, |
| void(mojo::PendingReceiver< |
| tracing::mojom::BackgroundTracingAgentProvider>)); |
| MOCK_METHOD0(CrashHungProcess, void()); |
| MOCK_METHOD2(RunServiceDeprecated, |
| void(const std::string&, mojo::ScopedMessagePipeHandle)); |
| MOCK_METHOD1(BindServiceInterface, |
| void(mojo::GenericPendingReceiver receiver)); |
| MOCK_METHOD1(BindReceiver, void(mojo::GenericPendingReceiver receiver)); |
| MOCK_METHOD1(EnableSystemTracingService, |
| void(mojo::PendingRemote<tracing::mojom::SystemTracingService>)); |
| MOCK_METHOD1(SetPseudonymizationSalt, void(uint32_t salt)); |
| }; |
| |
| class ChildProcessTaskPortProviderTest : public testing::Test, |
| public base::PortProvider::Observer { |
| public: |
| ChildProcessTaskPortProviderTest() |
| : event_(base::WaitableEvent::ResetPolicy::AUTOMATIC) { |
| provider_.AddObserver(this); |
| } |
| ~ChildProcessTaskPortProviderTest() override { |
| provider_.RemoveObserver(this); |
| } |
| |
| void WaitForTaskPort() { event_.Wait(); } |
| |
| // There is no observer callback for when a process dies, so spin the run loop |
| // until the desired exit |condition| is met. |
| void WaitForCondition(base::RepeatingCallback<bool(void)> condition) { |
| base::TimeTicks start = base::TimeTicks::Now(); |
| do { |
| base::RunLoop().RunUntilIdle(); |
| if (condition.Run()) |
| break; |
| } while ((base::TimeTicks::Now() - start) < TestTimeouts::action_timeout()); |
| } |
| |
| mach_port_urefs_t GetSendRightRefCount(mach_port_t send_right) { |
| mach_port_urefs_t refs; |
| EXPECT_EQ(KERN_SUCCESS, mach_port_get_refs(mach_task_self(), send_right, |
| MACH_PORT_RIGHT_SEND, &refs)); |
| return refs; |
| } |
| |
| mach_port_urefs_t GetDeadNameRefCount(mach_port_t send_right) { |
| mach_port_urefs_t refs; |
| EXPECT_EQ(KERN_SUCCESS, |
| mach_port_get_refs(mach_task_self(), send_right, |
| MACH_PORT_RIGHT_DEAD_NAME, &refs)); |
| return refs; |
| } |
| |
| // base::PortProvider::Observer: |
| void OnReceivedTaskPort(base::ProcessHandle process) override { |
| received_processes_.push_back(process); |
| event_.Signal(); |
| } |
| |
| ChildProcessTaskPortProvider* provider() { return &provider_; } |
| |
| const std::vector<base::ProcessHandle>& received_processes() { |
| return received_processes_; |
| } |
| |
| private: |
| base::test::TaskEnvironment task_environment_; |
| ChildProcessTaskPortProvider provider_; |
| base::WaitableEvent event_; |
| std::vector<base::ProcessHandle> received_processes_; |
| }; |
| |
| static constexpr mach_port_t kMachPortNull = MACH_PORT_NULL; |
| |
| TEST_F(ChildProcessTaskPortProviderTest, InvalidProcess) { |
| EXPECT_EQ(kMachPortNull, provider()->TaskForPid(99)); |
| } |
| |
| TEST_F(ChildProcessTaskPortProviderTest, ChildLifecycle) { |
| EXPECT_EQ(kMachPortNull, provider()->TaskForPid(99)); |
| |
| // Create a fake task port for the fake process. |
| base::mac::ScopedMachReceiveRight receive_right; |
| base::mac::ScopedMachSendRight send_right; |
| ASSERT_TRUE(base::mac::CreateMachPort(&receive_right, &send_right)); |
| |
| EXPECT_EQ(1u, GetSendRightRefCount(send_right.get())); |
| EXPECT_EQ(0u, GetDeadNameRefCount(send_right.get())); |
| |
| // Return it when the ChildProcess interface is called. |
| MockChildProcess child_process; |
| EXPECT_CALL(child_process, GetTaskPort(_)) |
| .WillOnce(WithArgs<0>( |
| [&send_right](mojom::ChildProcess::GetTaskPortCallback callback) { |
| std::move(callback).Run(mojo::PlatformHandle( |
| base::mac::RetainMachSendRight(send_right.get()))); |
| })); |
| |
| provider()->OnChildProcessLaunched(99, &child_process); |
| |
| // Verify that the task-for-pid association is established. |
| WaitForTaskPort(); |
| EXPECT_EQ(std::vector<base::ProcessHandle>{99}, received_processes()); |
| EXPECT_EQ(receive_right.get(), provider()->TaskForPid(99)); |
| |
| // References owned by |send_right| and the map. |
| EXPECT_EQ(2u, GetSendRightRefCount(provider()->TaskForPid(99))); |
| EXPECT_EQ(0u, GetDeadNameRefCount(provider()->TaskForPid(99))); |
| |
| // "Kill" the process and verify that the association is deleted. |
| receive_right.reset(); |
| |
| WaitForCondition(base::BindRepeating( |
| [](ChildProcessTaskPortProvider* provider) -> bool { |
| return provider->TaskForPid(99) == MACH_PORT_NULL; |
| }, |
| base::Unretained(provider()))); |
| |
| EXPECT_EQ(kMachPortNull, provider()->TaskForPid(99)); |
| |
| // Send rights turned into a dead name right, which is owned by |send_right|. |
| EXPECT_EQ(0u, GetSendRightRefCount(send_right.get())); |
| EXPECT_EQ(1u, GetDeadNameRefCount(send_right.get())); |
| } |
| |
| TEST_F(ChildProcessTaskPortProviderTest, DeadTaskPort) { |
| EXPECT_EQ(kMachPortNull, provider()->TaskForPid(6)); |
| |
| // Create a fake task port for the fake process. |
| base::mac::ScopedMachReceiveRight receive_right; |
| base::mac::ScopedMachSendRight send_right; |
| ASSERT_TRUE(base::mac::CreateMachPort(&receive_right, &send_right)); |
| |
| scoped_refptr<base::SequencedTaskRunner> task_runner = |
| base::ThreadPool::CreateSequencedTaskRunner({}); |
| |
| MockChildProcess child_process; |
| EXPECT_CALL(child_process, GetTaskPort(_)) |
| .WillOnce( |
| WithArgs<0>([&task_runner, &receive_right, &send_right]( |
| mojom::ChildProcess::GetTaskPortCallback callback) { |
| mojo::PlatformHandle mach_handle( |
| base::mac::RetainMachSendRight(send_right.get())); |
| |
| // Destroy the receive right. |
| task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce(&base::mac::ScopedMachReceiveRight::reset, |
| base::Unretained(&receive_right), |
| kMachPortNull)); |
| // And then return a send right to the now-dead name. |
| task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), std::move(mach_handle))); |
| })); |
| |
| provider()->OnChildProcessLaunched(6, &child_process); |
| |
| // Create a second fake process. |
| base::mac::ScopedMachReceiveRight receive_right2; |
| base::mac::ScopedMachSendRight send_right2; |
| ASSERT_TRUE(base::mac::CreateMachPort(&receive_right2, &send_right2)); |
| |
| MockChildProcess child_contol2; |
| EXPECT_CALL(child_contol2, GetTaskPort(_)) |
| .WillOnce( |
| WithArgs<0>([&task_runner, &send_right2]( |
| mojom::ChildProcess::GetTaskPortCallback callback) { |
| task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| std::move(callback), |
| mojo::PlatformHandle( |
| base::mac::RetainMachSendRight(send_right2.get())))); |
| })); |
| |
| provider()->OnChildProcessLaunched(123, &child_contol2); |
| |
| WaitForTaskPort(); |
| |
| // Verify that the dead name does not register for the process. |
| EXPECT_EQ(std::vector<base::ProcessHandle>{123}, received_processes()); |
| EXPECT_EQ(kMachPortNull, provider()->TaskForPid(6)); |
| EXPECT_EQ(receive_right2.get(), provider()->TaskForPid(123)); |
| |
| // Clean up the second receive right. |
| receive_right2.reset(); |
| WaitForCondition(base::BindRepeating( |
| [](ChildProcessTaskPortProvider* provider) -> bool { |
| return provider->TaskForPid(123) == MACH_PORT_NULL; |
| }, |
| base::Unretained(provider()))); |
| EXPECT_EQ(kMachPortNull, provider()->TaskForPid(123)); |
| } |
| |
| TEST_F(ChildProcessTaskPortProviderTest, ReplacePort) { |
| EXPECT_EQ(kMachPortNull, provider()->TaskForPid(42)); |
| |
| // Create a fake task port for the fake process. |
| base::mac::ScopedMachReceiveRight receive_right; |
| base::mac::ScopedMachSendRight send_right; |
| ASSERT_TRUE(base::mac::CreateMachPort(&receive_right, &send_right)); |
| |
| EXPECT_EQ(1u, GetSendRightRefCount(send_right.get())); |
| EXPECT_EQ(0u, GetDeadNameRefCount(send_right.get())); |
| |
| // Return it when the ChildProcess interface is called. |
| MockChildProcess child_process; |
| EXPECT_CALL(child_process, GetTaskPort(_)) |
| .Times(2) |
| .WillRepeatedly(WithArgs<0>( |
| [&receive_right](mojom::ChildProcess::GetTaskPortCallback callback) { |
| std::move(callback).Run(mojo::PlatformHandle( |
| base::mac::RetainMachSendRight(receive_right.get()))); |
| })); |
| |
| provider()->OnChildProcessLaunched(42, &child_process); |
| WaitForTaskPort(); |
| |
| EXPECT_EQ(2u, GetSendRightRefCount(send_right.get())); |
| EXPECT_EQ(0u, GetDeadNameRefCount(send_right.get())); |
| |
| provider()->OnChildProcessLaunched(42, &child_process); |
| WaitForTaskPort(); |
| |
| EXPECT_EQ(2u, GetSendRightRefCount(send_right.get())); |
| EXPECT_EQ(0u, GetDeadNameRefCount(send_right.get())); |
| |
| // Verify that the task-for-pid association is established. |
| std::vector<base::ProcessHandle> expected_receive{42, 42}; |
| EXPECT_EQ(expected_receive, received_processes()); |
| EXPECT_EQ(receive_right.get(), provider()->TaskForPid(42)); |
| |
| // Now simulate PID reuse by replacing the task port with a new one. |
| base::mac::ScopedMachReceiveRight receive_right2; |
| base::mac::ScopedMachSendRight send_right2; |
| ASSERT_TRUE(base::mac::CreateMachPort(&receive_right2, &send_right2)); |
| EXPECT_EQ(1u, GetSendRightRefCount(send_right2.get())); |
| |
| MockChildProcess child_process2; |
| EXPECT_CALL(child_process2, GetTaskPort(_)) |
| .WillOnce( |
| [&send_right2](mojom::ChildProcess::GetTaskPortCallback callback) { |
| std::move(callback).Run(mojo::PlatformHandle( |
| base::mac::RetainMachSendRight(send_right2.get()))); |
| }); |
| |
| provider()->OnChildProcessLaunched(42, &child_process2); |
| WaitForTaskPort(); |
| |
| // Reference to |send_right| is dropped from the map and is solely owned |
| // by |send_right|. |
| EXPECT_EQ(1u, GetSendRightRefCount(send_right.get())); |
| EXPECT_EQ(0u, GetDeadNameRefCount(send_right.get())); |
| |
| EXPECT_EQ(2u, GetSendRightRefCount(send_right2.get())); |
| EXPECT_EQ(0u, GetDeadNameRefCount(send_right2.get())); |
| |
| expected_receive.push_back(42); |
| EXPECT_EQ(expected_receive, received_processes()); |
| EXPECT_EQ(receive_right2.get(), provider()->TaskForPid(42)); |
| } |
| |
| } // namespace content |