crashpad/util/mach/mach_message_server_test.cc
Avi Drissman 6a9e2e6003 Adjust to movement of base/ files to base/apple
This CL rolls mini_chromium to pick up the move of a bunch of files
to base/apple, and makes changes to adjust.

Bug: chromium:1444927
Change-Id: Ib692e2a1628e2c0c8228795eaecdb7f35b1c09fa
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4786387
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Avi Drissman <avi@chromium.org>
2023-08-16 21:05:16 +00:00

861 lines
36 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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/mach_message_server.h"
#include <mach/mach.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <iterator>
#include <set>
#include "base/apple/scoped_mach_port.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 clients 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 servers 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 servers 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 its 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 ports 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 servers response is directed by
// server_receive_large.
bool client_send_large;
// The type of reply port that the client should provide in its requests
// 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 servers 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 its 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) {
}
TestMachMessageServer(const TestMachMessageServer&) = delete;
TestMachMessageServer& operator=(const TestMachMessageServer&) = delete;
// 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[std::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 wouldnt normally be present in a
// message froma MIG-generated interface, but its 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
// ports 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 its 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 wont have reused the same name for another Mach port in
// this task so soon. Its possible that something else in this task could
// have reused the name, but its 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 its 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. Dont attempt to
// process replies before all of the requests are sent, because the
// server wont 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
// its 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::apple::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 shouldnt expect a reply when it didnt send a good reply
// port with its request, or when testing the server behaving in a way
// that doesnt 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 its 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::apple::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;
};
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
// its safe to start waiting for it in nonblocking mode. The message is in
// the servers queue, so its 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 its 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 theyve been queued up. The client doesnt wait for the
// replies until after its put all of its requests into the servers 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 servers view
// of the port is that it is a dead name. The server function will return
// MACH_SEND_INVALID_DEST because its 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 arent 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 its safe to exit so that the send right in the
// server task doesnt 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