| // Copyright 2014 The Crashpad Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "util/mach/child_port_handshake.h" |
| |
| #include "base/apple/scoped_mach_port.h" |
| #include "gtest/gtest.h" |
| #include "test/multiprocess.h" |
| #include "util/mach/child_port_types.h" |
| #include "util/mach/mach_extensions.h" |
| |
| namespace crashpad { |
| namespace test { |
| namespace { |
| |
| class ChildPortHandshakeTest : public Multiprocess { |
| public: |
| enum class ClientProcess { |
| // The child runs the client and the parent runs the server. |
| kChildClient = 0, |
| |
| // The parent runs the client and the child runs the server. |
| kParentClient, |
| }; |
| |
| enum class TestType { |
| // The client checks in with the server, transferring a receive right. |
| kClientChecksIn_ReceiveRight = 0, |
| |
| // In this test, the client checks in with the server normally. It sends a |
| // copy of its bootstrap port to the server, because both parent and child |
| // should have the same bootstrap port, allowing for verification. |
| kClientChecksIn_SendRight, |
| |
| // The client checks in with the server, transferring a send-once right. |
| kClientChecksIn_SendOnceRight, |
| |
| // In this test, the client reads from its pipe, and subsequently exits |
| // without checking in. This tests that the server properly detects that it |
| // has lost its client after sending instructions to it via the pipe, while |
| // waiting for a check-in message. |
| kClientDoesNotCheckIn, |
| |
| // In this test, the client exits without checking in. This tests that the |
| // server properly detects that it has lost a client. Whether or not the |
| // client closes the pipe before the server writes to it is a race, and the |
| // server needs to be able to detect client loss in both cases, so the |
| // ClientDoesNotCheckIn_ReadsPipe and NoClient tests also exist to test |
| // these individual cases more deterministically. |
| kClientDoesNotCheckIn_ReadsPipe, |
| |
| // In this test, the client checks in with the server with an incorrect |
| // token value and a copy of its own task port. The server should reject the |
| // message because of the invalid token, and return MACH_PORT_NULL to its |
| // caller. |
| kTokenIncorrect, |
| |
| // In this test, the client checks in with the server with an incorrect |
| // token value and a copy of its own task port, and subsequently, the |
| // correct token value and a copy of its bootstrap port. The server should |
| // reject the first because of the invalid token, but it should continue |
| // waiting for a message with a valid token as long as the pipe remains |
| // open. It should wind wind up returning the bootstrap port, allowing for |
| // verification. |
| kTokenIncorrectThenCorrect, |
| |
| // The server dies. The failure should be reported in the client. This test |
| // type is only compatible with ClientProcess::kParentClient. |
| kServerDies, |
| }; |
| |
| ChildPortHandshakeTest(ClientProcess client_process, TestType test_type) |
| : Multiprocess(), |
| child_port_handshake_(), |
| client_process_(client_process), |
| test_type_(test_type) { |
| } |
| |
| ChildPortHandshakeTest(const ChildPortHandshakeTest&) = delete; |
| ChildPortHandshakeTest& operator=(const ChildPortHandshakeTest&) = delete; |
| |
| ~ChildPortHandshakeTest() { |
| } |
| |
| private: |
| void RunServer() { |
| if (test_type_ == TestType::kServerDies) { |
| return; |
| } |
| |
| base::apple::ScopedMachReceiveRight receive_right; |
| base::apple::ScopedMachSendRight send_right; |
| if (test_type_ == TestType::kClientChecksIn_ReceiveRight) { |
| receive_right.reset(child_port_handshake_.RunServer( |
| ChildPortHandshake::PortRightType::kReceiveRight)); |
| } else { |
| send_right.reset(child_port_handshake_.RunServer( |
| ChildPortHandshake::PortRightType::kSendRight)); |
| } |
| |
| switch (test_type_) { |
| case TestType::kClientChecksIn_ReceiveRight: |
| EXPECT_TRUE(receive_right.is_valid()); |
| break; |
| |
| case TestType::kClientChecksIn_SendRight: |
| case TestType::kTokenIncorrectThenCorrect: |
| EXPECT_EQ(send_right, bootstrap_port); |
| break; |
| |
| case TestType::kClientChecksIn_SendOnceRight: |
| EXPECT_TRUE(send_right.is_valid()); |
| EXPECT_NE(send_right, bootstrap_port); |
| break; |
| |
| case TestType::kClientDoesNotCheckIn: |
| case TestType::kClientDoesNotCheckIn_ReadsPipe: |
| case TestType::kTokenIncorrect: |
| EXPECT_FALSE(send_right.is_valid()); |
| break; |
| |
| case TestType::kServerDies: |
| // This was special-cased as an early return above. |
| FAIL(); |
| } |
| } |
| |
| void RunClient() { |
| switch (test_type_) { |
| case TestType::kClientChecksIn_SendRight: { |
| ASSERT_TRUE(child_port_handshake_.RunClient(bootstrap_port, |
| MACH_MSG_TYPE_COPY_SEND)); |
| break; |
| } |
| |
| case TestType::kClientChecksIn_ReceiveRight: { |
| mach_port_t receive_right = NewMachPort(MACH_PORT_RIGHT_RECEIVE); |
| ASSERT_TRUE(child_port_handshake_.RunClient( |
| receive_right, MACH_MSG_TYPE_MOVE_RECEIVE)); |
| break; |
| } |
| |
| case TestType::kClientChecksIn_SendOnceRight: { |
| base::apple::ScopedMachReceiveRight receive_right( |
| NewMachPort(MACH_PORT_RIGHT_RECEIVE)); |
| ASSERT_TRUE(child_port_handshake_.RunClient( |
| receive_right.get(), MACH_MSG_TYPE_MAKE_SEND_ONCE)); |
| break; |
| } |
| |
| case TestType::kClientDoesNotCheckIn: { |
| child_port_handshake_.ServerWriteFD().reset(); |
| child_port_handshake_.ClientReadFD().reset(); |
| break; |
| } |
| |
| case TestType::kClientDoesNotCheckIn_ReadsPipe: { |
| // Don’t run the standard client routine. Instead, drain the pipe, which |
| // will get the parent to the point that it begins waiting for a |
| // check-in message. Then, exit. The pipe is drained using the same |
| // implementation that the real client would use. |
| child_port_handshake_.ServerWriteFD().reset(); |
| base::ScopedFD client_read_fd = child_port_handshake_.ClientReadFD(); |
| child_port_token_t token; |
| std::string service_name; |
| ASSERT_TRUE(ChildPortHandshake::RunClientInternal_ReadPipe( |
| client_read_fd.get(), &token, &service_name)); |
| break; |
| } |
| |
| case TestType::kTokenIncorrect: { |
| // Don’t run the standard client routine. Instead, read the token and |
| // service name, mutate the token, and then check in with the bad token. |
| // The parent should reject the message. |
| child_port_handshake_.ServerWriteFD().reset(); |
| base::ScopedFD client_read_fd = child_port_handshake_.ClientReadFD(); |
| child_port_token_t token; |
| std::string service_name; |
| ASSERT_TRUE(ChildPortHandshake::RunClientInternal_ReadPipe( |
| client_read_fd.get(), &token, &service_name)); |
| child_port_token_t bad_token = ~token; |
| ASSERT_TRUE(ChildPortHandshake::RunClientInternal_SendCheckIn( |
| service_name, |
| bad_token, |
| mach_task_self(), |
| MACH_MSG_TYPE_COPY_SEND)); |
| break; |
| } |
| |
| case TestType::kTokenIncorrectThenCorrect: { |
| // Don’t run the standard client routine. Instead, read the token and |
| // service name. Mutate the token, and check in with the bad token, |
| // expecting the parent to reject the message. Then, check in with the |
| // correct token, expecting the parent to accept it. |
| child_port_handshake_.ServerWriteFD().reset(); |
| base::ScopedFD client_read_fd = child_port_handshake_.ClientReadFD(); |
| child_port_token_t token; |
| std::string service_name; |
| ASSERT_TRUE(ChildPortHandshake::RunClientInternal_ReadPipe( |
| client_read_fd.release(), &token, &service_name)); |
| child_port_token_t bad_token = ~token; |
| ASSERT_TRUE(ChildPortHandshake::RunClientInternal_SendCheckIn( |
| service_name, |
| bad_token, |
| mach_task_self(), |
| MACH_MSG_TYPE_COPY_SEND)); |
| ASSERT_TRUE(ChildPortHandshake::RunClientInternal_SendCheckIn( |
| service_name, token, bootstrap_port, MACH_MSG_TYPE_COPY_SEND)); |
| break; |
| } |
| |
| case TestType::kServerDies: { |
| ASSERT_EQ(client_process_, ClientProcess::kParentClient); |
| ASSERT_FALSE(child_port_handshake_.RunClient(bootstrap_port, |
| MACH_MSG_TYPE_COPY_SEND)); |
| break; |
| } |
| } |
| } |
| |
| // Multiprocess: |
| |
| void MultiprocessParent() override { |
| switch (client_process_) { |
| case ClientProcess::kChildClient: |
| RunServer(); |
| break; |
| case ClientProcess::kParentClient: |
| RunClient(); |
| break; |
| } |
| } |
| |
| void MultiprocessChild() override { |
| switch (client_process_) { |
| case ClientProcess::kChildClient: |
| RunClient(); |
| break; |
| case ClientProcess::kParentClient: |
| RunServer(); |
| break; |
| } |
| } |
| |
| private: |
| ChildPortHandshake child_port_handshake_; |
| ClientProcess client_process_; |
| TestType test_type_; |
| }; |
| |
| TEST(ChildPortHandshake, ChildClientChecksIn_ReceiveRight) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kChildClient, |
| ChildPortHandshakeTest::TestType::kClientChecksIn_ReceiveRight); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ChildClientChecksIn_SendRight) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kChildClient, |
| ChildPortHandshakeTest::TestType::kClientChecksIn_SendRight); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ChildClientChecksIn_SendOnceRight) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kChildClient, |
| ChildPortHandshakeTest::TestType::kClientChecksIn_SendOnceRight); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ChildClientDoesNotCheckIn) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kChildClient, |
| ChildPortHandshakeTest::TestType::kClientDoesNotCheckIn); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ChildClientDoesNotCheckIn_ReadsPipe) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kChildClient, |
| ChildPortHandshakeTest::TestType::kClientDoesNotCheckIn_ReadsPipe); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ChildClientTokenIncorrect) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kChildClient, |
| ChildPortHandshakeTest::TestType::kTokenIncorrect); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ChildClientTokenIncorrectThenCorrect) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kChildClient, |
| ChildPortHandshakeTest::TestType::kTokenIncorrectThenCorrect); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ParentClientChecksIn_ReceiveRight) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kParentClient, |
| ChildPortHandshakeTest::TestType::kClientChecksIn_ReceiveRight); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ParentClientChecksIn_SendRight) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kParentClient, |
| ChildPortHandshakeTest::TestType::kClientChecksIn_SendRight); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ParentClientChecksIn_SendOnceRight) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kParentClient, |
| ChildPortHandshakeTest::TestType::kClientChecksIn_SendOnceRight); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ParentClientDoesNotCheckIn) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kParentClient, |
| ChildPortHandshakeTest::TestType::kClientDoesNotCheckIn); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ParentClientDoesNotCheckIn_ReadsPipe) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kParentClient, |
| ChildPortHandshakeTest::TestType::kClientDoesNotCheckIn_ReadsPipe); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ParentClientTokenIncorrect) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kParentClient, |
| ChildPortHandshakeTest::TestType::kTokenIncorrect); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ParentClientTokenIncorrectThenCorrect) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kParentClient, |
| ChildPortHandshakeTest::TestType::kTokenIncorrectThenCorrect); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, ParentClientServerDies) { |
| ChildPortHandshakeTest test( |
| ChildPortHandshakeTest::ClientProcess::kParentClient, |
| ChildPortHandshakeTest::TestType::kServerDies); |
| test.Run(); |
| } |
| |
| TEST(ChildPortHandshake, NoClient) { |
| // In this test, the client never checks in with the server because it never |
| // even runs. This tests that the server properly detects that it has no |
| // client at all, and does not terminate execution with an error such as |
| // “broken pipe” when attempting to send instructions to the client. This test |
| // is similar to kClientDoesNotCheckIn, but because there’s no client at all, |
| // the server is guaranteed to see that its pipe partner is gone. |
| ChildPortHandshake child_port_handshake; |
| base::apple::ScopedMachSendRight child_port(child_port_handshake.RunServer( |
| ChildPortHandshake::PortRightType::kSendRight)); |
| EXPECT_FALSE(child_port.is_valid()); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace crashpad |