crashpad/util/mach/child_port_handshake_test.cc

383 lines
14 KiB
C++
Raw Normal View History

// 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:
test: Use (actual, [un]expected) in gtest {ASSERT,EXPECT}_{EQ,NE} gtest used to require (expected, actual) ordering for arguments to EXPECT_EQ and ASSERT_EQ, and in failed test assertions would identify each side as “expected” or “actual.” Tests in Crashpad adhered to this traditional ordering. After a gtest change in February 2016, it is now agnostic with respect to the order of these arguments. This change mechanically updates all uses of these macros to (actual, expected) by reversing them. This provides consistency with our use of the logging CHECK_EQ and DCHECK_EQ macros, and makes for better readability by ordinary native speakers. The rough (but working!) conversion tool is https://chromium-review.googlesource.com/c/466727/1/rewrite_expectassert_eq.py, and “git cl format” cleaned up its output. EXPECT_NE and ASSERT_NE never had a preferred ordering. gtest never made a judgment that one side or the other needed to provide an “unexpected” value. Consequently, some code used (unexpected, actual) while other code used (actual, unexpected). For consistency with the new EXPECT_EQ and ASSERT_EQ usage, as well as consistency with CHECK_NE and DCHECK_NE, this change also updates these use sites to (actual, unexpected) where one side can be called “unexpected” as, for example, std::string::npos can be. Unfortunately, this portion was a manual conversion. References: https://github.com/google/googletest/blob/master/googletest/docs/Primer.md#binary-comparison https://github.com/google/googletest/commit/77d6b173380332b1c1bc540532641f410ec82d65 https://github.com/google/googletest/pull/713 Change-Id: I978fef7c94183b8b1ef63f12f5ab4d6693626be3 Reviewed-on: https://chromium-review.googlesource.com/466727 Reviewed-by: Scott Graham <scottmg@chromium.org>
2017-04-04 00:35:21 -04:00
EXPECT_EQ(send_right, bootstrap_port);
break;
case TestType::kClientChecksIn_SendOnceRight:
EXPECT_TRUE(send_right.is_valid());
test: Use (actual, [un]expected) in gtest {ASSERT,EXPECT}_{EQ,NE} gtest used to require (expected, actual) ordering for arguments to EXPECT_EQ and ASSERT_EQ, and in failed test assertions would identify each side as “expected” or “actual.” Tests in Crashpad adhered to this traditional ordering. After a gtest change in February 2016, it is now agnostic with respect to the order of these arguments. This change mechanically updates all uses of these macros to (actual, expected) by reversing them. This provides consistency with our use of the logging CHECK_EQ and DCHECK_EQ macros, and makes for better readability by ordinary native speakers. The rough (but working!) conversion tool is https://chromium-review.googlesource.com/c/466727/1/rewrite_expectassert_eq.py, and “git cl format” cleaned up its output. EXPECT_NE and ASSERT_NE never had a preferred ordering. gtest never made a judgment that one side or the other needed to provide an “unexpected” value. Consequently, some code used (unexpected, actual) while other code used (actual, unexpected). For consistency with the new EXPECT_EQ and ASSERT_EQ usage, as well as consistency with CHECK_NE and DCHECK_NE, this change also updates these use sites to (actual, unexpected) where one side can be called “unexpected” as, for example, std::string::npos can be. Unfortunately, this portion was a manual conversion. References: https://github.com/google/googletest/blob/master/googletest/docs/Primer.md#binary-comparison https://github.com/google/googletest/commit/77d6b173380332b1c1bc540532641f410ec82d65 https://github.com/google/googletest/pull/713 Change-Id: I978fef7c94183b8b1ef63f12f5ab4d6693626be3 Reviewed-on: https://chromium-review.googlesource.com/466727 Reviewed-by: Scott Graham <scottmg@chromium.org>
2017-04-04 00:35:21 -04:00
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: {
// Dont 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: {
// Dont 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: {
// Dont 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: {
test: Use (actual, [un]expected) in gtest {ASSERT,EXPECT}_{EQ,NE} gtest used to require (expected, actual) ordering for arguments to EXPECT_EQ and ASSERT_EQ, and in failed test assertions would identify each side as “expected” or “actual.” Tests in Crashpad adhered to this traditional ordering. After a gtest change in February 2016, it is now agnostic with respect to the order of these arguments. This change mechanically updates all uses of these macros to (actual, expected) by reversing them. This provides consistency with our use of the logging CHECK_EQ and DCHECK_EQ macros, and makes for better readability by ordinary native speakers. The rough (but working!) conversion tool is https://chromium-review.googlesource.com/c/466727/1/rewrite_expectassert_eq.py, and “git cl format” cleaned up its output. EXPECT_NE and ASSERT_NE never had a preferred ordering. gtest never made a judgment that one side or the other needed to provide an “unexpected” value. Consequently, some code used (unexpected, actual) while other code used (actual, unexpected). For consistency with the new EXPECT_EQ and ASSERT_EQ usage, as well as consistency with CHECK_NE and DCHECK_NE, this change also updates these use sites to (actual, unexpected) where one side can be called “unexpected” as, for example, std::string::npos can be. Unfortunately, this portion was a manual conversion. References: https://github.com/google/googletest/blob/master/googletest/docs/Primer.md#binary-comparison https://github.com/google/googletest/commit/77d6b173380332b1c1bc540532641f410ec82d65 https://github.com/google/googletest/pull/713 Change-Id: I978fef7c94183b8b1ef63f12f5ab4d6693626be3 Reviewed-on: https://chromium-review.googlesource.com/466727 Reviewed-by: Scott Graham <scottmg@chromium.org>
2017-04-04 00:35:21 -04:00
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 theres 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