Add a ReceiveLarge parameter to MachMessageServer::Run().

Previously, MachMessageServer::Run() only provided two strategies for
dealing with large messages, indicated by mach_msg() returning
MACH_RCV_TOO_LARGE: the receive buffer could be reallocated and the
message received, or the entire function could return MACH_RCV_TOO_LARGE
to the caller. There are situations where an intermediate behavior might
be desirable. This intermediate behavior would allow the function to
continue waiting for another message without returning an error to the
caller or attempting to receive the large message. This is desirable
when dealing with fixed-sized messages and a receiver that might be sent
messages by unknown, possibly-malicious callers. This can happen when
the corresponding send right is published with the bootstrap server, for
example.

Existing users continue to request their existing behavior, typically
receiving an error when encountering a large message.
catch_exception_tool will use the new “ignore” behavior when running in
persistent mode.

TEST=util_test MachMessageServer.*
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/756803002
This commit is contained in:
Mark Mentovai 2014-11-25 14:48:44 -05:00
parent 04aaa36026
commit 79b4434c81
9 changed files with 119 additions and 34 deletions

View File

@ -247,6 +247,7 @@ class TestSimulateCrashMac final : public MachMultiprocess,
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kBlocking,
MachMessageServer::kReceiveLargeError,
MACH_MSG_TIMEOUT_NONE);
EXPECT_EQ(MACH_MSG_SUCCESS, mr)
<< MachErrorMessage(mr, "MachMessageServer::Run");
@ -258,6 +259,7 @@ class TestSimulateCrashMac final : public MachMultiprocess,
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kBlocking,
MachMessageServer::kReceiveLargeError,
MACH_MSG_TIMEOUT_NONE);
EXPECT_EQ(MACH_MSG_SUCCESS, mr)
<< MachErrorMessage(mr, "MachMessageServer::Run");

View File

@ -224,6 +224,7 @@ class TestMachOImageAnnotationsReader final : public MachMultiprocess,
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kBlocking,
MachMessageServer::kReceiveLargeError,
MACH_MSG_TIMEOUT_NONE);
EXPECT_EQ(MACH_MSG_SUCCESS, mr)
<< MachErrorMessage(mr, "MachMessageServer::Run");

View File

@ -275,6 +275,13 @@ int CatchExceptionToolMain(int argc, char* argv[]) {
int exceptions_handled = 0;
ExceptionServer exception_server(options, me, &exceptions_handled);
// Assume that if persistent mode has been requested, its desirable to ignore
// large messages and keep running.
MachMessageServer::ReceiveLarge receive_large =
(options.persistent == MachMessageServer::kPersistent)
? MachMessageServer::kReceiveLargeIgnore
: MachMessageServer::kReceiveLargeError;
mach_msg_timeout_t timeout_ms = options.timeout_secs
? options.timeout_secs * 1000
: MACH_MSG_TIMEOUT_NONE;
@ -284,6 +291,7 @@ int CatchExceptionToolMain(int argc, char* argv[]) {
MACH_MSG_OPTION_NONE,
options.persistent,
options.nonblocking,
receive_large,
timeout_ms);
if (mr == MACH_RCV_TIMED_OUT && options.timeout_secs && options.persistent &&
exceptions_handled) {

View File

@ -134,12 +134,14 @@ class TestExcClientVariants : public UniversalMachExcServer,
// MachMultiprocess:
void MachMultiprocessParent() override {
kern_return_t kr = MachMessageServer::Run(this,
LocalPort(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kBlocking,
0);
kern_return_t kr =
MachMessageServer::Run(this,
LocalPort(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kBlocking,
MachMessageServer::kReceiveLargeError,
0);
EXPECT_EQ(KERN_SUCCESS, kr)
<< MachErrorMessage(kr, "MachMessageServer::Run");

View File

@ -940,12 +940,14 @@ class TestExcServerVariants : public UniversalMachExcServer,
// MachMultiprocess:
void MachMultiprocessParent() override {
kern_return_t kr = MachMessageServer::Run(this,
LocalPort(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kBlocking,
0);
kern_return_t kr =
MachMessageServer::Run(this,
LocalPort(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kBlocking,
MachMessageServer::kReceiveLargeError,
0);
EXPECT_EQ(KERN_SUCCESS, kr)
<< MachErrorMessage(kr, "MachMessageServer::Run");

View File

@ -442,12 +442,14 @@ class TestExceptionPorts : public UniversalMachExcServer,
CheckedWriteFD(WritePipeFD(), &c, 1);
if (who_crashes_ != kNobodyCrashes) {
kern_return_t kr = MachMessageServer::Run(this,
local_port,
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kBlocking,
0);
kern_return_t kr =
MachMessageServer::Run(this,
local_port,
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kBlocking,
MachMessageServer::kReceiveLargeError,
0);
EXPECT_EQ(KERN_SUCCESS, kr)
<< MachErrorMessage(kr, "MachMessageServer::Run");

View File

@ -16,6 +16,7 @@
#include <limits>
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_vm.h"
#include "util/misc/clock.h"
@ -82,6 +83,7 @@ mach_msg_return_t MachMessageServer::Run(Interface* interface,
mach_msg_options_t options,
Persistent persistent,
Nonblocking nonblocking,
ReceiveLarge receive_large,
mach_msg_timeout_t timeout_ms) {
options &= ~(MACH_RCV_MSG | MACH_SEND_MSG);
@ -101,10 +103,16 @@ mach_msg_return_t MachMessageServer::Run(Interface* interface,
deadline = 0;
}
if (receive_large == kReceiveLargeResize) {
options |= MACH_RCV_LARGE;
} else {
options &= ~MACH_RCV_LARGE;
}
mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options);
mach_msg_size_t max_request_size = interface->MachMessageServerRequestSize();
mach_msg_size_t request_alloc = round_page(max_request_size + trailer_alloc);
mach_msg_size_t request_size = (options & MACH_RCV_LARGE)
mach_msg_size_t request_size = (receive_large == kReceiveLargeResize)
? request_alloc
: max_request_size + trailer_alloc;
@ -135,6 +143,7 @@ mach_msg_return_t MachMessageServer::Run(Interface* interface,
this_request_alloc);
request_header = reinterpret_cast<mach_msg_header_t*>(request_addr);
bool run_mach_msg_receive = false;
do {
// If |options| contains MACH_RCV_INTERRUPT, retry mach_msg() in a loop
// when it returns MACH_RCV_INTERRUPTED to recompute |remaining_ms|
@ -153,11 +162,19 @@ mach_msg_return_t MachMessageServer::Run(Interface* interface,
receive_port,
remaining_ms,
MACH_PORT_NULL);
} while (kr == MACH_RCV_INTERRUPTED);
if (kr == MACH_RCV_TOO_LARGE && receive_large == kReceiveLargeIgnore) {
MACH_LOG(WARNING, kr) << "mach_msg: ignoring large message";
run_mach_msg_receive = true;
} else if (kr == MACH_RCV_INTERRUPTED) {
run_mach_msg_receive = true;
}
} while (run_mach_msg_receive);
if (kr == MACH_MSG_SUCCESS) {
request_scoper.swap(trial_request_scoper);
} else if (kr == MACH_RCV_TOO_LARGE && options & MACH_RCV_LARGE) {
} else if (kr == MACH_RCV_TOO_LARGE &&
receive_large == kReceiveLargeResize) {
this_request_size =
round_page(request_header->msgh_size + trailer_alloc);
this_request_alloc = this_request_size;

View File

@ -103,6 +103,32 @@ class MachMessageServer {
kNonblocking,
};
//! \brief Determines how to handle the reception of messages larger than the
//! size of the buffer allocated to store them.
enum ReceiveLarge {
//! \brief Return `MACH_RCV_TOO_LARGE` upon receipt of a large message.
//!
//! This mimics the default behavior of `mach_msg_server()` when `options`
//! does not contain `MACH_RCV_LARGE`.
kReceiveLargeError = 0,
//! \brief Ignore large messages, and attempt to receive the next queued
//! message upon encountering one.
//!
//! When a large message is encountered, a warning will be logged.
//!
//! `mach_msg()` will be called to receive the next message after a large
//! one even when accompanied by a #Persistent value of #kOneShot.
kReceiveLargeIgnore,
//! \brief Allocate an appropriately-sized buffer upon encountering a large
//! message. The buffer will be used to receive the message. This
//!
//! This mimics the behavior of `mach_msg_server()` when `options` contains
//! `MACH_RCV_LARGE`.
kReceiveLargeResize,
};
//! \brief Runs a Mach message server to handle a Mach RPC request for MIG
//! servers.
//!
@ -124,9 +150,12 @@ class MachMessageServer {
//! handle the request and populate the reply.
//! \param[in] receive_port The port on which to receive the request message.
//! \param[in] options Options suitable for mach_msg. For the defaults, use
//! `MACH_MSG_OPTION_NONE`.
//! `MACH_MSG_OPTION_NONE`. `MACH_RCV_LARGE` when specified here is
//! ignored. Set \a receive_large to #kReceiveLargeResize instead.
//! \param[in] persistent Chooses between one-shot and persistent operation.
//! \param[in] nonblocking Chooses between blocking and nonblocking operation.
//! \param[in] receive_large Determines the behavior upon encountering a
//! message larger than the receive buffers size.
//! \param[in] timeout_ms When \a nonblocking is `false`, the the maximum
//! duration that this entire function will run, in milliseconds, or
//! `MACH_MSG_TIMEOUT_NONE` to specify no timeout (infinite waiting). When
@ -146,6 +175,7 @@ class MachMessageServer {
mach_msg_options_t options,
Persistent persistent,
Nonblocking nonblocking,
ReceiveLarge receive_large,
mach_msg_timeout_t timeout_ms);
private:

View File

@ -58,6 +58,7 @@ class TestMachMessageServer : public MachMessageServer::Interface,
server_options(MACH_MSG_OPTION_NONE),
server_persistent(MachMessageServer::kOneShot),
server_nonblocking(MachMessageServer::kBlocking),
server_receive_large(MachMessageServer::kReceiveLargeError),
server_timeout_ms(MACH_MSG_TIMEOUT_NONE),
server_mig_retcode(KERN_SUCCESS),
server_destroy_complex(true),
@ -93,6 +94,9 @@ class TestMachMessageServer : public MachMessageServer::Interface,
// Whether the server should run in blocking or nonblocking mode.
MachMessageServer::Nonblocking server_nonblocking;
// The strategy for handling large messages.
MachMessageServer::ReceiveLarge server_receive_large;
// The servers timeout.
mach_msg_timeout_t server_timeout_ms;
@ -134,9 +138,8 @@ class TestMachMessageServer : public MachMessageServer::Interface,
bool client_send_complex;
// true if the client should send a larger message than the server has
// allocated space to receive. If server_options contains MACH_RCV_LARGE,
// the server will resize its buffer to receive the message. Otherwise, the
// message will be destroyed and the server will return MACH_RCV_TOO_LARGE.
// 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
@ -342,6 +345,7 @@ class TestMachMessageServer : public MachMessageServer::Interface,
options_.server_options,
options_.server_persistent,
options_.server_nonblocking,
options_.server_receive_large,
options_.server_timeout_ms)))
<< MachErrorMessage(kr, "MachMessageServer");
@ -802,11 +806,11 @@ TEST(MachMessageServer, ComplexNotDestroyedNoReply) {
test_mach_message_server.Test();
}
TEST(MachMessageServer, LargeUnexpected) {
TEST(MachMessageServer, ReceiveLargeError) {
// The client sends a request to the server that is larger than the server is
// expecting. The server did not specify MACH_RCV_LARGE in its options, so the
// request is destroyed and the server returns a MACH_RCV_TOO_LARGE error. The
// client does not receive a reply.
// 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;
@ -816,18 +820,35 @@ TEST(MachMessageServer, LargeUnexpected) {
test_mach_message_server.Test();
}
TEST(MachMessageServer, LargeExpected) {
TEST(MachMessageServer, ReceiveLargeRetry) {
// The client sends a request to the server that is larger than the server is
// initially expecting. The server did specify MACH_RCV_LARGE in its options,
// 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.
// 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_options = MACH_RCV_LARGE;
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