| // 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 <mach/mach.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <sys/types.h> |
| |
| #include <set> |
| |
| #include "base/mac/scoped_mach_port.h" |
| #include "base/macros.h" |
| #include "base/stl_util.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<const ReceiveRequestMessage*>(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_name_t>(MACH_MSG_TYPE_MOVE_SEND)); |
| EXPECT_EQ( |
| request->port_descriptor.type, |
| implicit_cast<mach_msg_descriptor_type_t>(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<const ReceiveLargeRequestMessage*>(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_type_t>(MACH_MSG_TRAILER_FORMAT_0)); |
| EXPECT_EQ(trailer->msgh_trailer_size, MACH_MSG_TRAILER_MINIMUM_SIZE); |
| |
| ++requests_; |
| |
| ReplyMessage* reply = reinterpret_cast<ReplyMessage*>(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<mach_msg_id_t> MachMessageServerRequestIDs() override { |
| static constexpr mach_msg_id_t request_ids[] = {kRequestMessageID}; |
| return std::set<mach_msg_id_t>(&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_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<mach_port_info_t>(&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_msg_bits_t>( |
| 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_type_t>(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 |