282 lines
10 KiB
C++
282 lines
10 KiB
C++
// 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 <string.h>
|
||
|
||
#include <limits>
|
||
|
||
#include "base/logging.h"
|
||
#include "base/mac/mach_logging.h"
|
||
#include "base/mac/scoped_mach_vm.h"
|
||
#include "util/mach/mach_message.h"
|
||
|
||
namespace crashpad {
|
||
|
||
namespace {
|
||
|
||
//! \brief Manages a dynamically-allocated buffer to be used for Mach messaging.
|
||
class MachMessageBuffer {
|
||
public:
|
||
MachMessageBuffer() : vm_() {}
|
||
|
||
MachMessageBuffer(const MachMessageBuffer&) = delete;
|
||
MachMessageBuffer& operator=(const MachMessageBuffer&) = delete;
|
||
|
||
~MachMessageBuffer() {}
|
||
|
||
//! \return A pointer to the buffer.
|
||
mach_msg_header_t* Header() const {
|
||
return reinterpret_cast<mach_msg_header_t*>(vm_.address());
|
||
}
|
||
|
||
//! \brief Ensures that this object has a buffer of exactly \a size bytes
|
||
//! available.
|
||
//!
|
||
//! If the existing buffer is a different size, it will be reallocated without
|
||
//! copying any of the old buffer’s contents to the new buffer. The contents
|
||
//! of the buffer are unspecified after this call, even if no reallocation is
|
||
//! performed.
|
||
kern_return_t Reallocate(vm_size_t size) {
|
||
// This test uses == instead of > so that a large reallocation to receive a
|
||
// large message doesn’t cause permanent memory bloat for the duration of
|
||
// a MachMessageServer::Run() loop.
|
||
if (size != vm_.size()) {
|
||
// reset() first, so that two allocations don’t exist simultaneously.
|
||
vm_.reset();
|
||
|
||
if (size) {
|
||
vm_address_t address;
|
||
kern_return_t kr =
|
||
vm_allocate(mach_task_self(),
|
||
&address,
|
||
size,
|
||
VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG));
|
||
if (kr != KERN_SUCCESS) {
|
||
return kr;
|
||
}
|
||
|
||
vm_.reset(address, size);
|
||
}
|
||
}
|
||
|
||
#if !defined(NDEBUG)
|
||
// Regardless of whether the allocation was changed, scribble over the
|
||
// memory to make sure that nothing relies on zero-initialization or stale
|
||
// contents.
|
||
memset(Header(), 0x66, size);
|
||
#endif
|
||
|
||
return KERN_SUCCESS;
|
||
}
|
||
|
||
private:
|
||
base::mac::ScopedMachVM vm_;
|
||
};
|
||
|
||
// Wraps MachMessageWithDeadline(), using a MachMessageBuffer argument which
|
||
// will be resized to |receive_size| (after being page-rounded). MACH_RCV_MSG
|
||
// is always combined into |options|.
|
||
mach_msg_return_t MachMessageAllocateReceive(MachMessageBuffer* request,
|
||
mach_msg_option_t options,
|
||
mach_msg_size_t receive_size,
|
||
mach_port_name_t receive_port,
|
||
MachMessageDeadline deadline,
|
||
mach_port_name_t notify_port,
|
||
bool run_even_if_expired) {
|
||
mach_msg_size_t request_alloc = round_page(receive_size);
|
||
kern_return_t kr = request->Reallocate(request_alloc);
|
||
if (kr != KERN_SUCCESS) {
|
||
return kr;
|
||
}
|
||
|
||
return MachMessageWithDeadline(request->Header(),
|
||
options | MACH_RCV_MSG,
|
||
receive_size,
|
||
receive_port,
|
||
deadline,
|
||
notify_port,
|
||
run_even_if_expired);
|
||
}
|
||
|
||
} // namespace
|
||
|
||
// This method implements a server similar to 10.9.4
|
||
// xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(). The server
|
||
// callback function and |max_size| parameter have been replaced with a C++
|
||
// interface. The |persistent| parameter has been added, allowing this method to
|
||
// serve as a stand-in for mach_msg_server(). The |timeout_ms| parameter has
|
||
// been added, allowing this function to not block indefinitely.
|
||
//
|
||
// static
|
||
mach_msg_return_t MachMessageServer::Run(Interface* interface,
|
||
mach_port_t receive_port,
|
||
mach_msg_options_t options,
|
||
Persistent persistent,
|
||
ReceiveLarge receive_large,
|
||
mach_msg_timeout_t timeout_ms) {
|
||
options &= ~(MACH_RCV_MSG | MACH_SEND_MSG);
|
||
|
||
const MachMessageDeadline deadline =
|
||
MachMessageDeadlineFromTimeout(timeout_ms);
|
||
|
||
if (receive_large == kReceiveLargeResize) {
|
||
options |= MACH_RCV_LARGE;
|
||
} else {
|
||
options &= ~MACH_RCV_LARGE;
|
||
}
|
||
|
||
const mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options);
|
||
const mach_msg_size_t expected_receive_size =
|
||
round_msg(interface->MachMessageServerRequestSize()) + trailer_alloc;
|
||
const mach_msg_size_t request_size = (receive_large == kReceiveLargeResize)
|
||
? round_page(expected_receive_size)
|
||
: expected_receive_size;
|
||
DCHECK_GE(request_size, sizeof(mach_msg_empty_rcv_t));
|
||
|
||
// mach_msg_server() and mach_msg_server_once() would consider whether
|
||
// |options| contains MACH_SEND_TRAILER and include MAX_TRAILER_SIZE in this
|
||
// computation if it does, but that option is ineffective on macOS.
|
||
const mach_msg_size_t reply_size = interface->MachMessageServerReplySize();
|
||
DCHECK_GE(reply_size, sizeof(mach_msg_empty_send_t));
|
||
const mach_msg_size_t reply_alloc = round_page(reply_size);
|
||
|
||
MachMessageBuffer request;
|
||
MachMessageBuffer reply;
|
||
bool received_any_request = false;
|
||
bool retry;
|
||
|
||
kern_return_t kr;
|
||
|
||
do {
|
||
retry = false;
|
||
|
||
kr = MachMessageAllocateReceive(&request,
|
||
options,
|
||
request_size,
|
||
receive_port,
|
||
deadline,
|
||
MACH_PORT_NULL,
|
||
!received_any_request);
|
||
if (kr == MACH_RCV_TOO_LARGE) {
|
||
switch (receive_large) {
|
||
case kReceiveLargeError:
|
||
break;
|
||
|
||
case kReceiveLargeIgnore:
|
||
// Try again, even in one-shot mode. The caller is expecting this
|
||
// method to take action on the first message in the queue, and has
|
||
// indicated that they want large messages to be ignored. The
|
||
// alternatives, which might involve returning MACH_MSG_SUCCESS,
|
||
// MACH_RCV_TIMED_OUT, or MACH_RCV_TOO_LARGE to a caller that
|
||
// specified one-shot behavior, all seem less correct than retrying.
|
||
MACH_LOG(WARNING, kr) << "mach_msg: ignoring large message";
|
||
retry = true;
|
||
continue;
|
||
|
||
case kReceiveLargeResize: {
|
||
mach_msg_size_t this_request_size = round_page(
|
||
round_msg(request.Header()->msgh_size) + trailer_alloc);
|
||
DCHECK_GT(this_request_size, request_size);
|
||
|
||
kr = MachMessageAllocateReceive(&request,
|
||
options & ~MACH_RCV_LARGE,
|
||
this_request_size,
|
||
receive_port,
|
||
deadline,
|
||
MACH_PORT_NULL,
|
||
!received_any_request);
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (kr != MACH_MSG_SUCCESS) {
|
||
return kr;
|
||
}
|
||
|
||
received_any_request = true;
|
||
|
||
kr = reply.Reallocate(reply_alloc);
|
||
if (kr != KERN_SUCCESS) {
|
||
return kr;
|
||
}
|
||
|
||
mach_msg_header_t* request_header = request.Header();
|
||
mach_msg_header_t* reply_header = reply.Header();
|
||
bool destroy_complex_request = false;
|
||
interface->MachMessageServerFunction(
|
||
request_header, reply_header, &destroy_complex_request);
|
||
|
||
if (!(reply_header->msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
|
||
// This only works if the reply message is not complex, because otherwise,
|
||
// the location of the RetCode field is not known. It should be possible
|
||
// to locate the RetCode field by looking beyond the descriptors in a
|
||
// complex reply message, but this is not currently done. This behavior
|
||
// has not proven itself necessary in practice, and it’s not done by
|
||
// mach_msg_server() or mach_msg_server_once() either.
|
||
mig_reply_error_t* reply_mig =
|
||
reinterpret_cast<mig_reply_error_t*>(reply_header);
|
||
if (reply_mig->RetCode == MIG_NO_REPLY) {
|
||
reply_header->msgh_remote_port = MACH_PORT_NULL;
|
||
} else if (reply_mig->RetCode != KERN_SUCCESS &&
|
||
request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) {
|
||
destroy_complex_request = true;
|
||
}
|
||
}
|
||
|
||
if (destroy_complex_request &&
|
||
request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) {
|
||
request_header->msgh_remote_port = MACH_PORT_NULL;
|
||
mach_msg_destroy(request_header);
|
||
}
|
||
|
||
if (reply_header->msgh_remote_port != MACH_PORT_NULL) {
|
||
// Avoid blocking indefinitely. This duplicates the logic in 10.9.5
|
||
// xnu-2422.115.4/libsyscall/mach/mach_msg.c mach_msg_server_once(),
|
||
// although the special provision for sending to a send-once right is not
|
||
// made, because kernel keeps sends to a send-once right on the fast path
|
||
// without considering the user-specified timeout. See 10.9.5
|
||
// xnu-2422.115.4/osfmk/ipc/ipc_mqueue.c ipc_mqueue_send().
|
||
const MachMessageDeadline send_deadline =
|
||
deadline == kMachMessageDeadlineWaitIndefinitely
|
||
? kMachMessageDeadlineNonblocking
|
||
: deadline;
|
||
|
||
kr = MachMessageWithDeadline(reply_header,
|
||
options | MACH_SEND_MSG,
|
||
0,
|
||
MACH_PORT_NULL,
|
||
send_deadline,
|
||
MACH_PORT_NULL,
|
||
true);
|
||
|
||
if (kr != MACH_MSG_SUCCESS) {
|
||
if (kr == MACH_SEND_INVALID_DEST ||
|
||
kr == MACH_SEND_TIMED_OUT ||
|
||
kr == MACH_SEND_INTERRUPTED) {
|
||
mach_msg_destroy(reply_header);
|
||
}
|
||
return kr;
|
||
}
|
||
}
|
||
} while (persistent == kPersistent || retry);
|
||
|
||
return kr;
|
||
}
|
||
|
||
} // namespace crashpad
|