2023-01-28 04:54:20 +00:00
|
|
|
|
// Copyright 2014 The Crashpad Authors
|
2022-04-02 01:21:55 +00:00
|
|
|
|
//
|
|
|
|
|
// 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
|