|  | // Copyright 2014 The Crashpad Authors. All rights reserved. | 
|  | // | 
|  | // 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/mac/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() { | 
|  | } | 
|  |  | 
|  | private: | 
|  | void RunServer() { | 
|  | if (test_type_ == TestType::kServerDies) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::mac::ScopedMachReceiveRight receive_right; | 
|  | base::mac::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(); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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::mac::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_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(ChildPortHandshakeTest); | 
|  | }; | 
|  |  | 
|  | 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::mac::ScopedMachSendRight child_port(child_port_handshake.RunServer( | 
|  | ChildPortHandshake::PortRightType::kSendRight)); | 
|  | EXPECT_FALSE(child_port.is_valid()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace test | 
|  | }  // namespace crashpad |