crashpad/util/mach/mach_message.cc
Mark Mentovai 062138106c mac: ChildPortHandshake: allow receive rights to be sent
The intended use is to flip the client-server relationship in
CrashpadClient so that the initial client (parent process) furnishes the
handler process with a receive right. The parent can optionally receive
a port-destroyed notification allowing it to restart the handler if it
exits prematurely.

R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/1408473002 .
2015-10-29 14:14:15 -04:00

287 lines
10 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. All rights reserved.
//
// 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.h"
#include <AvailabilityMacros.h>
#include <bsm/libbsm.h>
#include <limits>
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "util/misc/clock.h"
#include "util/misc/implicit_cast.h"
namespace crashpad {
namespace {
const int kNanosecondsPerMillisecond = 1E6;
// TimerRunning() determines whether |deadline| has passed. If |deadline| is
// kMachMessageDeadlineWaitIndefinitely, |*timeout_options| is set to
// MACH_MSG_OPTION_NONE, |*remaining_ms| is set to MACH_MSG_TIMEOUT_NONE, and
// this function returns true. When used with mach_msg(), this will cause
// indefinite waiting. In any other case, |*timeout_options| is set to
// MACH_SEND_TIMEOUT | MACH_RCV_TIMEOUT, so mach_msg() will enforce a timeout
// specified by |*remaining_ms|. If |deadline| is in the future, |*remaining_ms|
// is set to the number of milliseconds remaining, which will always be a
// positive value, and this function returns true. If |deadline| is
// kMachMessageDeadlineNonblocking (indicating that no timer is in effect),
// |*remaining_ms| is set to zero and this function returns true. Otherwise,
// this function sets |*remaining_ms| to zero and returns false.
bool TimerRunning(uint64_t deadline,
mach_msg_timeout_t* remaining_ms,
mach_msg_option_t* timeout_options) {
if (deadline == kMachMessageDeadlineWaitIndefinitely) {
*remaining_ms = MACH_MSG_TIMEOUT_NONE;
*timeout_options = MACH_MSG_OPTION_NONE;
return true;
}
*timeout_options = MACH_SEND_TIMEOUT | MACH_RCV_TIMEOUT;
if (deadline == kMachMessageDeadlineNonblocking) {
*remaining_ms = 0;
return true;
}
uint64_t now = ClockMonotonicNanoseconds();
if (now >= deadline) {
*remaining_ms = 0;
} else {
uint64_t remaining = deadline - now;
// Round to the nearest millisecond, taking care not to overflow.
const int kHalfMillisecondInNanoseconds = kNanosecondsPerMillisecond / 2;
if (remaining <=
std::numeric_limits<uint64_t>::max() - kHalfMillisecondInNanoseconds) {
*remaining_ms = (remaining + kHalfMillisecondInNanoseconds) /
kNanosecondsPerMillisecond;
} else {
*remaining_ms = remaining / kNanosecondsPerMillisecond;
}
}
return *remaining_ms != 0;
}
// This is an internal implementation detail of MachMessageWithDeadline(). It
// determines whether |deadline| has expired, and what timeout value and
// timeout-related options to pass to mach_msg() based on the value of
// |deadline|. mach_msg() will only be called if TimerRunning() returns true or
// if run_even_if_expired is true.
mach_msg_return_t MachMessageWithDeadlineInternal(mach_msg_header_t* message,
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_timeout_t remaining_ms;
mach_msg_option_t timeout_options;
if (!TimerRunning(deadline, &remaining_ms, &timeout_options) &&
!run_even_if_expired) {
// Simulate the timed-out return values from mach_msg().
if (options & MACH_SEND_MSG) {
return MACH_SEND_TIMED_OUT;
}
if (options & MACH_RCV_MSG) {
return MACH_RCV_TIMED_OUT;
}
return MACH_MSG_SUCCESS;
}
// Turn off the passed-in timeout bits and replace them with the ones from
// TimerRunning(). Get the send_size value from message->msgh_size if sending
// a message.
return mach_msg(
message,
(options & ~(MACH_SEND_TIMEOUT | MACH_RCV_TIMEOUT)) | timeout_options,
options & MACH_SEND_MSG ? message->msgh_size : 0,
receive_size,
receive_port,
remaining_ms,
notify_port);
}
} // namespace
MachMessageDeadline MachMessageDeadlineFromTimeout(
mach_msg_timeout_t timeout_ms) {
switch (timeout_ms) {
case kMachMessageTimeoutNonblocking:
return kMachMessageDeadlineNonblocking;
case kMachMessageTimeoutWaitIndefinitely:
return kMachMessageDeadlineWaitIndefinitely;
default:
return ClockMonotonicNanoseconds() +
implicit_cast<uint64_t>(timeout_ms) * kNanosecondsPerMillisecond;
}
}
mach_msg_return_t MachMessageWithDeadline(mach_msg_header_t* message,
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() actaully does return MACH_MSG_SUCCESS when not asked to send or
// receive anything. See 10.9.5 xnu-1504.15.3/osfmk/ipc/mach_msg.c
// mach_msg_overwrite_trap().
mach_msg_return_t mr = MACH_MSG_SUCCESS;
// Break up the send and receive into separate operations, so that the timeout
// can be recomputed from the deadline for each. Otherwise, the computed
// timeout will apply individually to the send and then to the receive, and
// the desired deadline could be exceeded.
//
// During sends, always set MACH_SEND_INTERRUPT, and during receives, always
// set MACH_RCV_INTERRUPT. If the caller didnt specify these options, the
// calls will be retried with a recomputed deadline. If these bits werent
// set, the libsyscall wrapper (10.9.5
// xnu-2422.115.4/libsyscall/mach/mach_msg.c mach_msg() would restart
// interrupted calls with the original timeout value computed from the
// deadline, which would no longer correspond to the actual deadline. If the
// caller did specify these bits, dont restart anything, because the caller
// wants to be notified of any interrupted calls.
if (options & MACH_SEND_MSG) {
do {
mr = MachMessageWithDeadlineInternal(
message,
(options & ~MACH_RCV_MSG) | MACH_SEND_INTERRUPT,
0,
MACH_PORT_NULL,
deadline,
notify_port,
run_even_if_expired);
} while (mr == MACH_SEND_INTERRUPTED && !(options & MACH_SEND_INTERRUPT));
if (mr != MACH_MSG_SUCCESS) {
return mr;
}
}
if (options & MACH_RCV_MSG) {
do {
mr = MachMessageWithDeadlineInternal(
message,
(options & ~MACH_SEND_MSG) | MACH_RCV_INTERRUPT,
receive_size,
receive_port,
deadline,
notify_port,
run_even_if_expired);
} while (mr == MACH_RCV_INTERRUPTED && !(options & MACH_RCV_INTERRUPT));
}
return mr;
}
void PrepareMIGReplyFromRequest(const mach_msg_header_t* in_header,
mach_msg_header_t* out_header) {
out_header->msgh_bits =
MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(in_header->msgh_bits), 0);
out_header->msgh_remote_port = in_header->msgh_remote_port;
out_header->msgh_size = sizeof(mig_reply_error_t);
out_header->msgh_local_port = MACH_PORT_NULL;
out_header->msgh_id = in_header->msgh_id + 100;
reinterpret_cast<mig_reply_error_t*>(out_header)->NDR = NDR_record;
// MIG-generated dispatch routines dont do this, but they should.
out_header->msgh_reserved = 0;
}
void SetMIGReplyError(mach_msg_header_t* out_header, kern_return_t error) {
reinterpret_cast<mig_reply_error_t*>(out_header)->RetCode = error;
}
const mach_msg_trailer_t* MachMessageTrailerFromHeader(
const mach_msg_header_t* header) {
vm_address_t header_address = reinterpret_cast<vm_address_t>(header);
vm_address_t trailer_address = header_address + round_msg(header->msgh_size);
return reinterpret_cast<const mach_msg_trailer_t*>(trailer_address);
}
pid_t AuditPIDFromMachMessageTrailer(const mach_msg_trailer_t* trailer) {
if (trailer->msgh_trailer_type != MACH_MSG_TRAILER_FORMAT_0) {
LOG(ERROR) << "unexpected msgh_trailer_type " << trailer->msgh_trailer_type;
return -1;
}
if (trailer->msgh_trailer_size <
REQUESTED_TRAILER_SIZE(kMachMessageReceiveAuditTrailer)) {
LOG(ERROR) << "small msgh_trailer_size " << trailer->msgh_trailer_size;
return -1;
}
const mach_msg_audit_trailer_t* audit_trailer =
reinterpret_cast<const mach_msg_audit_trailer_t*>(trailer);
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
pid_t audit_pid;
audit_token_to_au32(audit_trailer->msgh_audit,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
&audit_pid,
nullptr,
nullptr);
#else
pid_t audit_pid = audit_token_to_pid(audit_trailer->msgh_audit);
#endif
return audit_pid;
}
bool MachMessageDestroyReceivedPort(mach_port_t port,
mach_msg_type_name_t port_right_type) {
// This implements a subset of 10.10.5
// xnu-2782.40.9/libsyscall/mach/mach_msg.c mach_msg_destroy_port() that deals
// only with port rights that can be received in Mach messages.
switch (port_right_type) {
case MACH_MSG_TYPE_PORT_RECEIVE: {
kern_return_t kr = mach_port_mod_refs(
mach_task_self(), port, MACH_PORT_RIGHT_RECEIVE, -1);
if (kr != KERN_SUCCESS) {
MACH_LOG(ERROR, kr) << "mach_port_mod_refs";
return false;
}
return true;
}
case MACH_MSG_TYPE_PORT_SEND:
case MACH_MSG_TYPE_PORT_SEND_ONCE: {
kern_return_t kr = mach_port_deallocate(mach_task_self(), port);
if (kr != KERN_SUCCESS) {
MACH_LOG(ERROR, kr) << "mach_port_deallocate";
return false;
}
return true;
}
default: {
LOG(ERROR) << "unexpected port right type " << port_right_type;
return false;
}
}
}
} // namespace crashpad