// 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/mach_message_server.h" #include #include #include #include #include #include "base/cxx17_backports.h" #include "base/mac/scoped_mach_port.h" #include "base/macros.h" #include "gtest/gtest.h" #include "test/mac/mach_errors.h" #include "test/mac/mach_multiprocess.h" #include "util/file/file_io.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" #include "util/misc/implicit_cast.h" namespace crashpad { namespace test { namespace { class TestMachMessageServer : public MachMessageServer::Interface, public MachMultiprocess { public: struct Options { // The type of reply port that the client should put in its request message. enum ReplyPortType { // The normal reply port is the client’s local port, to which it holds // a receive right. This allows the server to respond directly to the // client. The client will expect a reply. kReplyPortNormal, // Use MACH_PORT_NULL as the reply port, which the server should detect // avoid attempting to send a message to, and return success. The client // will not expect a reply. kReplyPortNull, // Make the server see the reply port as a dead name by setting the reply // port to a receive right and then destroying that right before the // server processes the request. The server should return // MACH_SEND_INVALID_DEST, and the client will not expect a reply. kReplyPortDead, }; Options() : expect_server_interface_method_called(true), parent_wait_for_child_pipe(false), server_options(MACH_MSG_OPTION_NONE), server_persistent(MachMessageServer::kOneShot), server_receive_large(MachMessageServer::kReceiveLargeError), server_timeout_ms(kMachMessageTimeoutWaitIndefinitely), server_mig_retcode(KERN_SUCCESS), server_destroy_complex(true), expect_server_destroyed_complex(true), expect_server_result(KERN_SUCCESS), expect_server_transaction_count(1), child_wait_for_parent_pipe_early(false), client_send_request_count(1), client_send_complex(false), client_send_large(false), client_reply_port_type(kReplyPortNormal), client_expect_reply(true), child_send_all_requests_before_receiving_any_replies(false), child_wait_for_parent_pipe_late(false) { } // true if MachMessageServerFunction() is expected to be called. bool expect_server_interface_method_called; // true if the parent should wait for the child to write a byte to the pipe // as a signal that the child is ready for the parent to begin its side of // the test. This is used for nonblocking tests, which require that there // be something in the server’s queue before attempting a nonblocking // receive if the receive is to be successful. bool parent_wait_for_child_pipe; // Options to pass to MachMessageServer::Run() as the |options| parameter. mach_msg_options_t server_options; // Whether the server should run in one-shot or persistent mode. MachMessageServer::Persistent server_persistent; // The strategy for handling large messages. MachMessageServer::ReceiveLarge server_receive_large; // The server’s timeout in milliseconds, or kMachMessageTimeoutNonblocking // or kMachMessageTimeoutWaitIndefinitely. mach_msg_timeout_t server_timeout_ms; // The return code that the server returns to the client via the // mig_reply_error_t::RetCode field. A client would normally see this as // a Mach RPC return value. kern_return_t server_mig_retcode; // The value that the server function should set its destroy_complex_request // parameter to. This is true if resources sent in complex request messages // should be destroyed, and false if they should not be destroyed, assuming // that the server function indicates success. bool server_destroy_complex; // Whether to expect the server to destroy a complex message. Even if // server_destroy_complex is false, a complex message will be destroyed if // the MIG return code was unsuccessful. bool expect_server_destroyed_complex; // The expected return value from MachMessageServer::Run(). kern_return_t expect_server_result; // The number of transactions that the server is expected to handle. size_t expect_server_transaction_count; // true if the child should wait for the parent to signal that it’s ready // for the child to begin sending requests via the pipe. This is done if the // parent needs to perform operations on its receive port before the child // should be permitted to send anything to it. Currently, this is used to // allow the parent to ensure that the receive port’s queue length is high // enough before the child begins attempting to fill it. bool child_wait_for_parent_pipe_early; // The number of requests that the client should send to the server. size_t client_send_request_count; // true if the client should send a complex message, one that carries a port // descriptor in its body. Normally false. bool client_send_complex; // true if the client should send a larger message than the server has // allocated space to receive. The server’s response is directed by // server_receive_large. bool client_send_large; // The type of reply port that the client should provide in its request’s // mach_msg_header_t::msgh_local_port, which will appear to the server as // mach_msg_header_t::msgh_remote_port. ReplyPortType client_reply_port_type; // true if the client should wait for a reply from the server. For // non-normal reply ports or requests which the server responds to with no // reply (MIG_NO_REPLY), the server will either not send a reply or not // succeed in sending a reply, and the child process should not wait for // one. bool client_expect_reply; // true if the client should send all requests before attempting to receive // any replies from the server. This is used for the persistent nonblocking // test, which requires the client to fill the server’s queue before the // server can attempt processing it. bool child_send_all_requests_before_receiving_any_replies; // true if the child should wait to receive a byte from the parent before // exiting. This can be used to keep a receive right in the child alive // until the parent has a chance to verify that it’s holding a send right. // Otherwise, the right might appear in the parent as a dead name if the // child exited before the parent had a chance to examine it. This would be // a race. bool child_wait_for_parent_pipe_late; }; explicit TestMachMessageServer(const Options& options) : MachMessageServer::Interface(), MachMultiprocess(), options_(options), child_complex_message_port_(), parent_complex_message_port_(MACH_PORT_NULL) { } // Runs the test. void Test() { EXPECT_EQ(replies_, requests_); uint32_t start = requests_; Run(); EXPECT_EQ(replies_, requests_); EXPECT_EQ(requests_ - start, options_.expect_server_transaction_count); } // MachMessageServerInterface: virtual bool MachMessageServerFunction( const mach_msg_header_t* in, mach_msg_header_t* out, bool* destroy_complex_request) override { *destroy_complex_request = options_.server_destroy_complex; EXPECT_TRUE(options_.expect_server_interface_method_called); if (!options_.expect_server_interface_method_called) { return false; } struct ReceiveRequestMessage : public RequestMessage { mach_msg_trailer_t trailer; }; struct ReceiveLargeRequestMessage : public LargeRequestMessage { mach_msg_trailer_t trailer; }; const ReceiveRequestMessage* request = reinterpret_cast(in); const mach_msg_bits_t expect_msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MOVE_SEND) | (options_.client_send_complex ? MACH_MSGH_BITS_COMPLEX : 0); EXPECT_EQ(request->header.msgh_bits, expect_msgh_bits); EXPECT_EQ(request->header.msgh_size, options_.client_send_large ? sizeof(LargeRequestMessage) : sizeof(RequestMessage)); if (options_.client_reply_port_type == Options::kReplyPortNormal) { EXPECT_EQ(request->header.msgh_remote_port, RemotePort()); } EXPECT_EQ(request->header.msgh_local_port, LocalPort()); EXPECT_EQ(request->header.msgh_id, kRequestMessageID); if (options_.client_send_complex) { EXPECT_EQ(request->body.msgh_descriptor_count, 1u); EXPECT_NE(request->port_descriptor.name, kMachPortNull); parent_complex_message_port_ = request->port_descriptor.name; EXPECT_EQ(request->port_descriptor.disposition, implicit_cast(MACH_MSG_TYPE_MOVE_SEND)); EXPECT_EQ( request->port_descriptor.type, implicit_cast(MACH_MSG_PORT_DESCRIPTOR)); } else { EXPECT_EQ(request->body.msgh_descriptor_count, 0u); EXPECT_EQ(request->port_descriptor.name, kMachPortNull); EXPECT_EQ(request->port_descriptor.disposition, 0u); EXPECT_EQ(request->port_descriptor.type, 0u); } EXPECT_EQ(memcmp(&request->ndr, &NDR_record, sizeof(NDR_record)), 0); EXPECT_EQ(request->number, requests_); // Look for the trailer in the right spot, depending on whether the request // message was a RequestMessage or a LargeRequestMessage. const mach_msg_trailer_t* trailer; if (options_.client_send_large) { const ReceiveLargeRequestMessage* large_request = reinterpret_cast(request); for (size_t index = 0; index < sizeof(large_request->data); ++index) { EXPECT_EQ(large_request->data[index], '!'); } trailer = &large_request->trailer; } else { trailer = &request->trailer; } EXPECT_EQ( trailer->msgh_trailer_type, implicit_cast(MACH_MSG_TRAILER_FORMAT_0)); EXPECT_EQ(trailer->msgh_trailer_size, MACH_MSG_TRAILER_MINIMUM_SIZE); ++requests_; ReplyMessage* reply = reinterpret_cast(out); reply->Head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); reply->Head.msgh_size = sizeof(*reply); reply->Head.msgh_remote_port = request->header.msgh_remote_port; reply->Head.msgh_local_port = MACH_PORT_NULL; reply->Head.msgh_id = kReplyMessageID; reply->NDR = NDR_record; reply->RetCode = options_.server_mig_retcode; reply->number = replies_++; return true; } std::set MachMessageServerRequestIDs() override { static constexpr mach_msg_id_t request_ids[] = {kRequestMessageID}; return std::set(&request_ids[0], &request_ids[base::size(request_ids)]); } mach_msg_size_t MachMessageServerRequestSize() override { return sizeof(RequestMessage); } mach_msg_size_t MachMessageServerReplySize() override { return sizeof(ReplyMessage); } private: struct RequestMessage : public mach_msg_base_t { // If body.msgh_descriptor_count is 0, port_descriptor will still be // present, but it will be zeroed out. It wouldn’t normally be present in a // message froma MIG-generated interface, but it’s harmless and simpler to // leave it here and just treat it as more data. mach_msg_port_descriptor_t port_descriptor; NDR_record_t ndr; uint32_t number; }; // LargeRequestMessage is larger enough than a regular RequestMessage to // ensure that whatever buffer was allocated to receive a RequestMessage is // not large enough to receive a LargeRequestMessage. struct LargeRequestMessage : public RequestMessage { uint8_t data[4 * PAGE_MAX_SIZE]; }; struct ReplyMessage : public mig_reply_error_t { uint32_t number; }; // MachMultiprocess: void MachMultiprocessParent() override { mach_port_t local_port = LocalPort(); kern_return_t kr; if (options_.child_send_all_requests_before_receiving_any_replies) { // On OS X 10.10, the queue limit of a new Mach port seems to be 2 by // default, which is below the value of MACH_PORT_QLIMIT_DEFAULT. Set the // port’s queue limit explicitly here. mach_port_limits limits = {}; limits.mpl_qlimit = MACH_PORT_QLIMIT_DEFAULT; kr = mach_port_set_attributes(mach_task_self(), local_port, MACH_PORT_LIMITS_INFO, reinterpret_cast(&limits), MACH_PORT_LIMITS_INFO_COUNT); ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "mach_port_set_attributes"); } if (options_.child_wait_for_parent_pipe_early) { // Tell the child to begin sending messages. char c = '\0'; CheckedWriteFile(WritePipeHandle(), &c, 1); } if (options_.parent_wait_for_child_pipe) { // Wait until the child is done sending what it’s going to send. char c; CheckedReadFileExactly(ReadPipeHandle(), &c, 1); EXPECT_EQ(c, '\0'); } ASSERT_EQ((kr = MachMessageServer::Run(this, local_port, options_.server_options, options_.server_persistent, options_.server_receive_large, options_.server_timeout_ms)), options_.expect_server_result) << MachErrorMessage(kr, "MachMessageServer"); if (options_.client_send_complex) { EXPECT_NE(parent_complex_message_port_, kMachPortNull); mach_port_type_t type; if (!options_.expect_server_destroyed_complex) { // MachMessageServer should not have destroyed the resources sent in the // complex request message. kr = mach_port_type( mach_task_self(), parent_complex_message_port_, &type); EXPECT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "mach_port_type"); EXPECT_EQ(type, MACH_PORT_TYPE_SEND); // Destroy the resources here. kr = mach_port_deallocate(mach_task_self(), parent_complex_message_port_); EXPECT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "mach_port_deallocate"); } // The kernel won’t have reused the same name for another Mach port in // this task so soon. It’s possible that something else in this task could // have reused the name, but it’s unlikely for that to have happened in // this test environment. kr = mach_port_type(mach_task_self(), parent_complex_message_port_, &type); EXPECT_EQ(kr, KERN_INVALID_NAME) << MachErrorMessage(kr, "mach_port_type"); } if (options_.child_wait_for_parent_pipe_late) { // Let the child know it’s safe to exit. char c = '\0'; CheckedWriteFile(WritePipeHandle(), &c, 1); } } void MachMultiprocessChild() override { if (options_.child_wait_for_parent_pipe_early) { // Wait until the parent is done setting things up on its end. char c; CheckedReadFileExactly(ReadPipeHandle(), &c, 1); EXPECT_EQ(c, '\0'); } for (size_t index = 0; index < options_.client_send_request_count; ++index) { if (options_.child_send_all_requests_before_receiving_any_replies) { // For this test, all of the messages need to go into the queue before // the parent is allowed to start processing them. Don’t attempt to // process replies before all of the requests are sent, because the // server won’t have sent any replies until all of the requests are in // its queue. ASSERT_NO_FATAL_FAILURE(ChildSendRequest()); } else { ASSERT_NO_FATAL_FAILURE(ChildSendRequestAndWaitForReply()); } } if (options_.parent_wait_for_child_pipe && options_.child_send_all_requests_before_receiving_any_replies) { // Now that all of the requests have been sent, let the parent know that // it’s safe to begin processing them, and then wait for the replies. ASSERT_NO_FATAL_FAILURE(ChildNotifyParentViaPipe()); for (size_t index = 0; index < options_.client_send_request_count; ++index) { ASSERT_NO_FATAL_FAILURE(ChildWaitForReply()); } } if (options_.child_wait_for_parent_pipe_late) { char c; CheckedReadFileExactly(ReadPipeHandle(), &c, 1); ASSERT_EQ(c, '\0'); } } // In the child process, sends a request message to the server. void ChildSendRequest() { // local_receive_port_owner will the receive right that is created in this // scope and intended to be destroyed when leaving this scope, after it has // been carried in a Mach message. base::mac::ScopedMachReceiveRight local_receive_port_owner; // A LargeRequestMessage is always allocated, but the message that will be // sent will be a normal RequestMessage due to the msgh_size field // indicating the size of the smaller base structure unless // options_.client_send_large is true. LargeRequestMessage request = {}; request.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND) | (options_.client_send_complex ? MACH_MSGH_BITS_COMPLEX : 0); request.header.msgh_size = options_.client_send_large ? sizeof(LargeRequestMessage) : sizeof(RequestMessage); request.header.msgh_remote_port = RemotePort(); kern_return_t kr; switch (options_.client_reply_port_type) { case Options::kReplyPortNormal: request.header.msgh_local_port = LocalPort(); break; case Options::kReplyPortNull: request.header.msgh_local_port = MACH_PORT_NULL; break; case Options::kReplyPortDead: { // Use a newly-allocated receive right that will be destroyed when this // method returns. A send right will be made from this receive right and // carried in the request message to the server. By the time the server // looks at the right, it will have become a dead name. local_receive_port_owner.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE)); ASSERT_TRUE(local_receive_port_owner.is_valid()); request.header.msgh_local_port = local_receive_port_owner.get(); break; } } request.header.msgh_id = kRequestMessageID; if (options_.client_send_complex) { // Allocate a new receive right in this process and make a send right that // will appear in the parent process. This is used to test that the server // properly handles ownership of resources received in complex messages. request.body.msgh_descriptor_count = 1; child_complex_message_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE)); ASSERT_TRUE(child_complex_message_port_.is_valid()); request.port_descriptor.name = child_complex_message_port_.get(); request.port_descriptor.disposition = MACH_MSG_TYPE_MAKE_SEND; request.port_descriptor.type = MACH_MSG_PORT_DESCRIPTOR; } else { request.body.msgh_descriptor_count = 0; request.port_descriptor.name = MACH_PORT_NULL; request.port_descriptor.disposition = 0; request.port_descriptor.type = 0; } request.ndr = NDR_record; request.number = requests_++; if (options_.client_send_large) { memset(request.data, '!', sizeof(request.data)); } kr = mach_msg(&request.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, request.header.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); ASSERT_EQ(kr, MACH_MSG_SUCCESS) << MachErrorMessage(kr, "mach_msg"); } // In the child process, waits for a reply message from the server. void ChildWaitForReply() { if (!options_.client_expect_reply) { // The client shouldn’t expect a reply when it didn’t send a good reply // port with its request, or when testing the server behaving in a way // that doesn’t send replies. return; } struct ReceiveReplyMessage : public ReplyMessage { mach_msg_trailer_t trailer; }; ReceiveReplyMessage reply = {}; kern_return_t kr = mach_msg(&reply.Head, MACH_RCV_MSG, 0, sizeof(reply), LocalPort(), MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); ASSERT_EQ(kr, MACH_MSG_SUCCESS) << MachErrorMessage(kr, "mach_msg"); ASSERT_EQ(reply.Head.msgh_bits, implicit_cast( MACH_MSGH_BITS(0, MACH_MSG_TYPE_MOVE_SEND))); ASSERT_EQ(reply.Head.msgh_size, sizeof(ReplyMessage)); ASSERT_EQ(reply.Head.msgh_remote_port, kMachPortNull); ASSERT_EQ(reply.Head.msgh_local_port, LocalPort()); ASSERT_EQ(reply.Head.msgh_id, kReplyMessageID); ASSERT_EQ(memcmp(&reply.NDR, &NDR_record, sizeof(NDR_record)), 0); ASSERT_EQ(reply.RetCode, options_.server_mig_retcode); ASSERT_EQ(reply.number, replies_); ASSERT_EQ( reply.trailer.msgh_trailer_type, implicit_cast(MACH_MSG_TRAILER_FORMAT_0)); ASSERT_EQ(reply.trailer.msgh_trailer_size, MACH_MSG_TRAILER_MINIMUM_SIZE); ++replies_; } // For test types where the child needs to notify the server in the parent // that the child is ready, this method will send a byte via the POSIX pipe. // The parent will be waiting in a read() on this pipe, and will proceed to // running MachMessageServer() once it’s received. void ChildNotifyParentViaPipe() { char c = '\0'; CheckedWriteFile(WritePipeHandle(), &c, 1); } // In the child process, sends a request message to the server and then // receives a reply message. void ChildSendRequestAndWaitForReply() { ASSERT_NO_FATAL_FAILURE(ChildSendRequest()); if (options_.parent_wait_for_child_pipe && !options_.child_send_all_requests_before_receiving_any_replies) { // The parent is waiting to read a byte to indicate that the message has // been placed in the queue. ASSERT_NO_FATAL_FAILURE(ChildNotifyParentViaPipe()); } ASSERT_NO_FATAL_FAILURE(ChildWaitForReply()); } const Options& options_; // A receive right allocated in the child process. A send right will be // created from this right and sent to the parent parent process in the // request message. base::mac::ScopedMachReceiveRight child_complex_message_port_; // The send right received in the parent process. This right is stored in a // member variable to test that resources carried in complex messages are // properly destroyed in the server when expected. mach_port_t parent_complex_message_port_; static uint32_t requests_; static uint32_t replies_; static constexpr mach_msg_id_t kRequestMessageID = 16237; static constexpr mach_msg_id_t kReplyMessageID = kRequestMessageID + 100; DISALLOW_COPY_AND_ASSIGN(TestMachMessageServer); }; uint32_t TestMachMessageServer::requests_; uint32_t TestMachMessageServer::replies_; constexpr mach_msg_id_t TestMachMessageServer::kRequestMessageID; constexpr mach_msg_id_t TestMachMessageServer::kReplyMessageID; TEST(MachMessageServer, Basic) { // The client sends one message to the server, which will wait indefinitely in // blocking mode for it. TestMachMessageServer::Options options; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, NonblockingNoMessage) { // The server waits in nonblocking mode and the client sends nothing, so the // server should return immediately without processing any message. TestMachMessageServer::Options options; options.expect_server_interface_method_called = false; options.server_timeout_ms = kMachMessageTimeoutNonblocking; options.expect_server_result = MACH_RCV_TIMED_OUT; options.expect_server_transaction_count = 0; options.client_send_request_count = 0; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, TimeoutNoMessage) { // The server waits in blocking mode for one message, but with a timeout. The // client sends no message, so the server returns after the timeout. TestMachMessageServer::Options options; options.expect_server_interface_method_called = false; options.server_timeout_ms = 10; options.expect_server_result = MACH_RCV_TIMED_OUT; options.expect_server_transaction_count = 0; options.client_send_request_count = 0; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, Nonblocking) { // The client sends one message to the server and then signals the server that // it’s safe to start waiting for it in nonblocking mode. The message is in // the server’s queue, so it’s able to receive it when it begins listening in // nonblocking mode. TestMachMessageServer::Options options; options.parent_wait_for_child_pipe = true; options.server_timeout_ms = kMachMessageTimeoutNonblocking; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, Timeout) { // The client sends one message to the server, which will wait in blocking // mode for it up to a specific timeout. TestMachMessageServer::Options options; options.server_timeout_ms = 10; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, PersistentTenMessages) { // The server waits for as many messages as it can receive in blocking mode // with a timeout. The client sends several messages, and the server processes // them all. TestMachMessageServer::Options options; options.server_persistent = MachMessageServer::kPersistent; options.server_timeout_ms = 10; options.expect_server_result = MACH_RCV_TIMED_OUT; options.expect_server_transaction_count = 10; options.client_send_request_count = 10; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, PersistentNonblockingFourMessages) { // The client sends several messages to the server and then signals the server // that it’s safe to start waiting for them in nonblocking mode. The server // then listens for them in nonblocking persistent mode, and receives all of // them because they’ve been queued up. The client doesn’t wait for the // replies until after it’s put all of its requests into the server’s queue. // // This test is sensitive to the length of the IPC queue limit. Mach ports // normally have a queue length limit of MACH_PORT_QLIMIT_DEFAULT (which is // MACH_PORT_QLIMIT_BASIC, or 5). The number of messages sent for this test // must be below this, because the server does not begin dequeueing request // messages until the client has finished sending them. // // The queue limit on new ports has been seen to be below // MACH_PORT_QLIMIT_DEFAULT, so it will explicitly be set by // mach_port_set_attributes() for this test. This needs to happen before the // child is allowed to begin sending messages, so // child_wait_for_parent_pipe_early is used to make the child wait until the // parent is ready. constexpr size_t kTransactionCount = 4; static_assert(kTransactionCount <= MACH_PORT_QLIMIT_DEFAULT, "must not exceed queue limit"); TestMachMessageServer::Options options; options.parent_wait_for_child_pipe = true; options.server_persistent = MachMessageServer::kPersistent; options.server_timeout_ms = kMachMessageTimeoutNonblocking; options.expect_server_result = MACH_RCV_TIMED_OUT; options.expect_server_transaction_count = kTransactionCount; options.child_wait_for_parent_pipe_early = true; options.client_send_request_count = kTransactionCount; options.child_send_all_requests_before_receiving_any_replies = true; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, ReturnCodeInvalidArgument) { // This tests that the mig_reply_error_t::RetCode field is properly returned // to the client. TestMachMessageServer::Options options; options.server_mig_retcode = KERN_INVALID_ARGUMENT; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, ReturnCodeNoReply) { // This tests that when mig_reply_error_t::RetCode is set to MIG_NO_REPLY, no // response is sent to the client. TestMachMessageServer::Options options; options.server_mig_retcode = MIG_NO_REPLY; options.client_expect_reply = false; options.child_wait_for_parent_pipe_late = true; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, ReplyPortNull) { // The client sets its reply port to MACH_PORT_NULL. The server should see // this and avoid sending a message to the null port. No reply message is // sent and the server returns success. TestMachMessageServer::Options options; options.client_reply_port_type = TestMachMessageServer::Options::kReplyPortNull; options.client_expect_reply = false; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, ReplyPortDead) { // The client allocates a new port and uses it as the reply port in its // request message, and then deallocates its receive right to that port. It // then signals the server to process the request message. The server’s view // of the port is that it is a dead name. The server function will return // MACH_SEND_INVALID_DEST because it’s not possible to send a message to a // dead name. TestMachMessageServer::Options options; options.parent_wait_for_child_pipe = true; options.expect_server_result = MACH_SEND_INVALID_DEST; options.client_reply_port_type = TestMachMessageServer::Options::kReplyPortDead; options.client_expect_reply = false; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, Complex) { // The client allocates a new receive right and sends a complex request // message to the server with a send right made out of this receive right. The // server receives this message and is instructed to destroy the send right // when it is done handling the request-reply transaction. The former send // right is verified to be invalid after the server runs. This test ensures // that resources transferred to a server process temporarily aren’t leaked. TestMachMessageServer::Options options; options.client_send_complex = true; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, ComplexNotDestroyed) { // As in MachMessageServer.Complex, but the server is instructed not to // destroy the send right. After the server runs, the send right is verified // to continue to exist in the server task. The client process is then // signalled by pipe that it’s safe to exit so that the send right in the // server task doesn’t prematurely become a dead name. This test ensures that // rights that are expected to be retained in the server task are properly // retained. TestMachMessageServer::Options options; options.server_destroy_complex = false; options.expect_server_destroyed_complex = false; options.client_send_complex = true; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, ComplexDestroyedInvalidArgument) { // As in MachMessageServer.ComplexNotDestroyed, but the server does not return // a successful code via MIG. The server is expected to destroy resources in // this case, because server_destroy_complex = false is only honored when a // MIG request is handled successfully or with no reply. TestMachMessageServer::Options options; options.server_mig_retcode = KERN_INVALID_TASK; options.server_destroy_complex = false; options.client_send_complex = true; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, ComplexNotDestroyedNoReply) { // As in MachMessageServer.ComplexNotDestroyed, but the server does not send // a reply message and is expected to retain the send right in the server // task. TestMachMessageServer::Options options; options.server_mig_retcode = MIG_NO_REPLY; options.server_destroy_complex = false; options.expect_server_destroyed_complex = false; options.client_send_complex = true; options.client_expect_reply = false; options.child_wait_for_parent_pipe_late = true; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, ReceiveLargeError) { // The client sends a request to the server that is larger than the server is // expecting. server_receive_large is kReceiveLargeError, so the request is // destroyed and the server returns a MACH_RCV_TOO_LARGE error. The client // does not receive a reply. TestMachMessageServer::Options options; options.expect_server_result = MACH_RCV_TOO_LARGE; options.expect_server_transaction_count = 0; options.client_send_large = true; options.client_expect_reply = false; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, ReceiveLargeRetry) { // The client sends a request to the server that is larger than the server is // initially expecting. server_receive_large is kReceiveLargeResize, so a new // buffer is allocated to receive the message. The server receives the large // request message, processes it, and returns a reply to the client. TestMachMessageServer::Options options; options.server_receive_large = MachMessageServer::kReceiveLargeResize; options.client_send_large = true; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } TEST(MachMessageServer, ReceiveLargeIgnore) { // The client sends a request to the server that is larger than the server is // expecting. server_receive_large is kReceiveLargeIgnore, so the request is // destroyed but the server does not consider this an error. The server is // running in blocking mode with a timeout, and continues to wait for a // message until it times out. The client does not receive a reply. TestMachMessageServer::Options options; options.server_receive_large = MachMessageServer::kReceiveLargeIgnore; options.server_timeout_ms = 10; options.expect_server_result = MACH_RCV_TIMED_OUT; options.expect_server_transaction_count = 0; options.client_send_large = true; options.client_expect_reply = false; TestMachMessageServer test_mach_message_server(options); test_mach_message_server.Test(); } } // namespace } // namespace test } // namespace crashpad