diff --git a/client/simulate_crash_mac_test.cc b/client/simulate_crash_mac_test.cc index e3a96ed2..8334e95d 100644 --- a/client/simulate_crash_mac_test.cc +++ b/client/simulate_crash_mac_test.cc @@ -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"); diff --git a/snapshot/mac/mach_o_image_annotations_reader_test.cc b/snapshot/mac/mach_o_image_annotations_reader_test.cc index 33fac005..194f6851 100644 --- a/snapshot/mac/mach_o_image_annotations_reader_test.cc +++ b/snapshot/mac/mach_o_image_annotations_reader_test.cc @@ -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"); diff --git a/tools/catch_exception_tool.cc b/tools/catch_exception_tool.cc index 65de2599..b18f9b5d 100644 --- a/tools/catch_exception_tool.cc +++ b/tools/catch_exception_tool.cc @@ -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, it’s 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) { diff --git a/util/mach/exc_client_variants_test.cc b/util/mach/exc_client_variants_test.cc index ec89fe6e..1643a751 100644 --- a/util/mach/exc_client_variants_test.cc +++ b/util/mach/exc_client_variants_test.cc @@ -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"); diff --git a/util/mach/exc_server_variants_test.cc b/util/mach/exc_server_variants_test.cc index 10d3fe2d..f7825bb6 100644 --- a/util/mach/exc_server_variants_test.cc +++ b/util/mach/exc_server_variants_test.cc @@ -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"); diff --git a/util/mach/exception_ports_test.cc b/util/mach/exception_ports_test.cc index 1b1f38e2..6e5253c7 100644 --- a/util/mach/exception_ports_test.cc +++ b/util/mach/exception_ports_test.cc @@ -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"); diff --git a/util/mach/mach_message_server.cc b/util/mach/mach_message_server.cc index 5ac9f42e..74a39260 100644 --- a/util/mach/mach_message_server.cc +++ b/util/mach/mach_message_server.cc @@ -16,6 +16,7 @@ #include +#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(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; diff --git a/util/mach/mach_message_server.h b/util/mach/mach_message_server.h index 855667d4..fb77b30c 100644 --- a/util/mach/mach_message_server.h +++ b/util/mach/mach_message_server.h @@ -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 buffer’s 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: diff --git a/util/mach/mach_message_server_test.cc b/util/mach/mach_message_server_test.cc index 7cfa9bb4..f5ccf42b 100644 --- a/util/mach/mach_message_server_test.cc +++ b/util/mach/mach_message_server_test.cc @@ -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 server’s 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 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 @@ -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