// 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.h" #include #include #include "base/logging.h" #include "base/mac/mach_logging.h" #include "build/build_config.h" #include "util/misc/clock.h" #include "util/misc/implicit_cast.h" #if BUILDFLAG(IS_MAC) #include #endif // BUILDFLAG(IS_MAC) namespace crashpad { namespace { constexpr 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. constexpr int kHalfMillisecondInNanoseconds = kNanosecondsPerMillisecond / 2; if (remaining <= std::numeric_limits::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(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 didn’t specify these options, the // calls will be retried with a recomputed deadline. If these bits weren’t // 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, don’t 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_size = sizeof(mig_reply_error_t); out_header->msgh_remote_port = in_header->msgh_remote_port; out_header->msgh_local_port = MACH_PORT_NULL; out_header->msgh_reserved = 0; out_header->msgh_id = in_header->msgh_id + 100; reinterpret_cast(out_header)->NDR = NDR_record; } void SetMIGReplyError(mach_msg_header_t* out_header, kern_return_t error) { reinterpret_cast(out_header)->RetCode = error; } const mach_msg_trailer_t* MachMessageTrailerFromHeader( const mach_msg_header_t* header) { vm_address_t header_address = reinterpret_cast(header); vm_address_t trailer_address = header_address + round_msg(header->msgh_size); return reinterpret_cast(trailer_address); } 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; } } } #if BUILDFLAG(IS_MAC) 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(trailer); #if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_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; } #endif // BUILDFLAG(IS_MAC) } // namespace crashpad