// 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/exc_server_variants.h"
#include <Availability.h>
#include <string.h>
#include <algorithm>
#include <iterator>
#include <vector>
#include "build/build_config.h"
#include "util/mac/mac_util.h"
#include "util/mach/composite_mach_message_server.h"
#include "util/mach/exc.h"
#include "util/mach/excServer.h"
#include "util/mach/exception_behaviors.h"
#include "util/mach/mach_exc.h"
#include "util/mach/mach_excServer.h"
#include "util/mach/mach_message.h"
namespace crashpad {
namespace {
// Traits for ExcServer<> and SimplifiedExcServer<> adapting them for use with
// the exc subsystem.
struct ExcTraits {
using ExceptionCode = exception_data_type_t;
using RequestUnion = __RequestUnion__exc_subsystem;
using ReplyUnion = __ReplyUnion__exc_subsystem;
using ExceptionRaiseRequest = __Request__exception_raise_t;
using ExceptionRaiseStateRequest = __Request__exception_raise_state_t;
using ExceptionRaiseStateIdentityRequest =
__Request__exception_raise_state_identity_t;
using ExceptionRaiseReply = __Reply__exception_raise_t;
using ExceptionRaiseStateReply = __Reply__exception_raise_state_t;
using ExceptionRaiseStateIdentityReply =
__Reply__exception_raise_state_identity_t;
// The MIG-generated __MIG_check__Request__*() functions are not declared as
// accepting const data, but they could have been because they in fact do not
// modify the data.
static kern_return_t MIGCheckRequestExceptionRaise(
const ExceptionRaiseRequest* in_request) {
return __MIG_check__Request__exception_raise_t(
const_cast<ExceptionRaiseRequest*>(in_request));
}
static kern_return_t MIGCheckRequestExceptionRaiseState(
const ExceptionRaiseStateRequest* in_request,
const ExceptionRaiseStateRequest** in_request_1) {
return __MIG_check__Request__exception_raise_state_t(
const_cast<ExceptionRaiseStateRequest*>(in_request),
const_cast<ExceptionRaiseStateRequest**>(in_request_1));
}
static kern_return_t MIGCheckRequestExceptionRaiseStateIdentity(
const ExceptionRaiseStateIdentityRequest* in_request,
const ExceptionRaiseStateIdentityRequest** in_request_1) {
return __MIG_check__Request__exception_raise_state_identity_t(
const_cast<ExceptionRaiseStateIdentityRequest*>(in_request),
const_cast<ExceptionRaiseStateIdentityRequest**>(in_request_1));
}
// There are no predefined constants for these.
static const mach_msg_id_t kMachMessageIDExceptionRaise = 2401;
static const mach_msg_id_t kMachMessageIDExceptionRaiseState = 2402;
static const mach_msg_id_t kMachMessageIDExceptionRaiseStateIdentity = 2403;
static const exception_behavior_t kExceptionBehavior = 0;
};
// Traits for ExcServer<> and SimplifiedExcServer<> adapting them for use with
// the mach_exc subsystem.
struct MachExcTraits {
using ExceptionCode = mach_exception_data_type_t;
using RequestUnion = __RequestUnion__mach_exc_subsystem;
using ReplyUnion = __ReplyUnion__mach_exc_subsystem;
using ExceptionRaiseRequest = __Request__mach_exception_raise_t;
using ExceptionRaiseStateRequest = __Request__mach_exception_raise_state_t;
using ExceptionRaiseStateIdentityRequest =
__Request__mach_exception_raise_state_identity_t;
using ExceptionRaiseReply = __Reply__mach_exception_raise_t;
using ExceptionRaiseStateReply = __Reply__mach_exception_raise_state_t;
using ExceptionRaiseStateIdentityReply =
__Reply__mach_exception_raise_state_identity_t;
// The MIG-generated __MIG_check__Request__*() functions are not declared as
// accepting const data, but they could have been because they in fact do not
// modify the data.
static kern_return_t MIGCheckRequestExceptionRaise(
const ExceptionRaiseRequest* in_request) {
return __MIG_check__Request__mach_exception_raise_t(
const_cast<ExceptionRaiseRequest*>(in_request));
}
static kern_return_t MIGCheckRequestExceptionRaiseState(
const ExceptionRaiseStateRequest* in_request,
const ExceptionRaiseStateRequest** in_request_1) {
return __MIG_check__Request__mach_exception_raise_state_t(
const_cast<ExceptionRaiseStateRequest*>(in_request),
const_cast<ExceptionRaiseStateRequest**>(in_request_1));
}
static kern_return_t MIGCheckRequestExceptionRaiseStateIdentity(
const ExceptionRaiseStateIdentityRequest* in_request,
const ExceptionRaiseStateIdentityRequest** in_request_1) {
return __MIG_check__Request__mach_exception_raise_state_identity_t(
const_cast<ExceptionRaiseStateIdentityRequest*>(in_request),
const_cast<ExceptionRaiseStateIdentityRequest**>(in_request_1));
}
// There are no predefined constants for these.
static const mach_msg_id_t kMachMessageIDExceptionRaise = 2405;
static const mach_msg_id_t kMachMessageIDExceptionRaiseState = 2406;
static const mach_msg_id_t kMachMessageIDExceptionRaiseStateIdentity = 2407;
static const exception_behavior_t kExceptionBehavior = MACH_EXCEPTION_CODES;
};
//! \brief A server interface for the `exc` or `mach_exc` Mach subsystems.
template <typename Traits>
class ExcServer : public MachMessageServer::Interface {
public:
//! \brief An interface that the different request messages that are a part of
//! the `exc` or `mach_exc` Mach subsystems can be dispatched to.
class Interface {
public:
//! \brief Handles exceptions raised by `exception_raise()` or
//! `mach_exception_raise()`.
//!
//! This behaves equivalently to a `catch_exception_raise()` function used
//! with `exc_server()`, or a `catch_mach_exception_raise()` function used
//! with `mach_exc_server()`.
//!
//! \param[in] trailer The trailer received with the request message.
//! \param[out] destroy_request `true` if the request message is to be
//! destroyed even when this method returns success. See
//! MachMessageServer::Interface.
virtual kern_return_t CatchExceptionRaise(
exception_handler_t exception_port,
thread_t thread,
task_t task,
exception_type_t exception,
const typename Traits::ExceptionCode* code,
mach_msg_type_number_t code_count,
const mach_msg_trailer_t* trailer,
bool* destroy_request) = 0;
//! \brief Handles exceptions raised by `exception_raise_state()` or
//! `mach_exception_raise_state()`.
//!
//! This behaves equivalently to a `catch_exception_raise_state()` function
//! used with `exc_server()`, or a `catch_mach_exception_raise_state()`
//! function used with `mach_exc_server()`.
//!
//! There is no \a destroy_request parameter because, unlike
//! CatchExceptionRaise() and CatchExceptionRaiseStateIdentity(), the
//! request message is not complex (it does not carry the \a thread or \a
//! task port rights) and thus there is nothing to destroy.
//!
//! \param[in] trailer The trailer received with the request message.
virtual kern_return_t CatchExceptionRaiseState(
exception_handler_t exception_port,
exception_type_t exception,
const typename Traits::ExceptionCode* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t* flavor,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count,
thread_state_t new_state,
mach_msg_type_number_t* new_state_count,
const mach_msg_trailer_t* trailer) = 0;
//! \brief Handles exceptions raised by `exception_raise_state_identity()`
//! or `mach_exception_raise_state_identity()`.
//!
//! This behaves equivalently to a `catch_exception_raise_state_identity()`
//! function used with `exc_server()`, or a
//! `catch_mach_exception_raise_state_identity()` function used with
//! `mach_exc_server()`.
//!
//! \param[in] trailer The trailer received with the request message.
//! \param[out] destroy_request `true` if the request message is to be
//! destroyed even when this method returns success. See
//! MachMessageServer::Interface.
virtual kern_return_t CatchExceptionRaiseStateIdentity(
exception_handler_t exception_port,
thread_t thread,
task_t task,
exception_type_t exception,
const typename Traits::ExceptionCode* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t* flavor,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count,
thread_state_t new_state,
mach_msg_type_number_t* new_state_count,
const mach_msg_trailer_t* trailer,
bool* destroy_request) = 0;
protected:
~Interface() {}
};
//! \brief Constructs an object of this class.
//!
//! \param[in] interface The interface to dispatch requests to. Weak.
explicit ExcServer(Interface* interface)
: MachMessageServer::Interface(), interface_(interface) {}
ExcServer(const ExcServer&) = delete;
ExcServer& operator=(const ExcServer&) = delete;
// MachMessageServer::Interface:
bool MachMessageServerFunction(const mach_msg_header_t* in_header,
mach_msg_header_t* out_header,
bool* destroy_complex_request) override;
std::set<mach_msg_id_t> MachMessageServerRequestIDs() override {
constexpr mach_msg_id_t request_ids[] = {
Traits::kMachMessageIDExceptionRaise,
Traits::kMachMessageIDExceptionRaiseState,
Traits::kMachMessageIDExceptionRaiseStateIdentity,
};
return std::set<mach_msg_id_t>(&request_ids[0],
&request_ids[std::size(request_ids)]);
}
mach_msg_size_t MachMessageServerRequestSize() override {
return sizeof(typename Traits::RequestUnion);
}
mach_msg_size_t MachMessageServerReplySize() override {
return sizeof(typename Traits::ReplyUnion);
}
private:
Interface* interface_; // weak
};
template <typename Traits>
bool ExcServer<Traits>::MachMessageServerFunction(
const mach_msg_header_t* in_header,
mach_msg_header_t* out_header,
bool* destroy_complex_request) {
PrepareMIGReplyFromRequest(in_header, out_header);
const mach_msg_trailer_t* in_trailer =
MachMessageTrailerFromHeader(in_header);
switch (in_header->msgh_id) {
case Traits::kMachMessageIDExceptionRaise: {
// exception_raise(), catch_exception_raise(), mach_exception_raise(),
// catch_mach_exception_raise().
using Request = typename Traits::ExceptionRaiseRequest;
const Request* in_request = reinterpret_cast<const Request*>(in_header);
kern_return_t kr = Traits::MIGCheckRequestExceptionRaise(in_request);
if (kr != MACH_MSG_SUCCESS) {
SetMIGReplyError(out_header, kr);
return true;
}
using Reply = typename Traits::ExceptionRaiseReply;
Reply* out_reply = reinterpret_cast<Reply*>(out_header);
out_reply->RetCode =
interface_->CatchExceptionRaise(in_header->msgh_local_port,
in_request->thread.name,
in_request->task.name,
in_request->exception,
in_request->code,
in_request->codeCnt,
in_trailer,
destroy_complex_request);
if (out_reply->RetCode != KERN_SUCCESS) {
return true;
}
out_header->msgh_size = sizeof(*out_reply);
return true;
}
case Traits::kMachMessageIDExceptionRaiseState: {
// exception_raise_state(), catch_exception_raise_state(),
// mach_exception_raise_state(), catch_mach_exception_raise_state().
using Request = typename Traits::ExceptionRaiseStateRequest;
const Request* in_request = reinterpret_cast<const Request*>(in_header);
// in_request_1 is used for the portion of the request after the codes,
// which in theory can be variable-length. The check function will set it.
const Request* in_request_1;
kern_return_t kr =
Traits::MIGCheckRequestExceptionRaiseState(in_request, &in_request_1);
if (kr != MACH_MSG_SUCCESS) {
SetMIGReplyError(out_header, kr);
return true;
}
using Reply = typename Traits::ExceptionRaiseStateReply;
Reply* out_reply = reinterpret_cast<Reply*>(out_header);
out_reply->flavor = in_request_1->flavor;
out_reply->new_stateCnt = std::size(out_reply->new_state);
out_reply->RetCode =
interface_->CatchExceptionRaiseState(in_header->msgh_local_port,
in_request->exception,
in_request->code,
in_request->codeCnt,
&out_reply->flavor,
in_request_1->old_state,
in_request_1->old_stateCnt,
out_reply->new_state,
&out_reply->new_stateCnt,
in_trailer);
if (out_reply->RetCode != KERN_SUCCESS) {
return true;
}
out_header->msgh_size =
sizeof(*out_reply) - sizeof(out_reply->new_state) +
sizeof(out_reply->new_state[0]) * out_reply->new_stateCnt;
return true;
}
case Traits::kMachMessageIDExceptionRaiseStateIdentity: {
// exception_raise_state_identity(),
// catch_exception_raise_state_identity(),
// mach_exception_raise_state_identity(),
// catch_mach_exception_raise_state_identity().
using Request = typename Traits::ExceptionRaiseStateIdentityRequest;
const Request* in_request = reinterpret_cast<const Request*>(in_header);
// in_request_1 is used for the portion of the request after the codes,
// which in theory can be variable-length. The check function will set it.
const Request* in_request_1;
kern_return_t kr = Traits::MIGCheckRequestExceptionRaiseStateIdentity(
in_request, &in_request_1);
if (kr != MACH_MSG_SUCCESS) {
SetMIGReplyError(out_header, kr);
return true;
}
using Reply = typename Traits::ExceptionRaiseStateIdentityReply;
Reply* out_reply = reinterpret_cast<Reply*>(out_header);
out_reply->flavor = in_request_1->flavor;
out_reply->new_stateCnt = std::size(out_reply->new_state);
out_reply->RetCode = interface_->CatchExceptionRaiseStateIdentity(
in_header->msgh_local_port,
in_request->thread.name,
in_request->task.name,
in_request->exception,
in_request->code,
in_request->codeCnt,
&out_reply->flavor,
in_request_1->old_state,
in_request_1->old_stateCnt,
out_reply->new_state,
&out_reply->new_stateCnt,
in_trailer,
destroy_complex_request);
if (out_reply->RetCode != KERN_SUCCESS) {
return true;
}
out_header->msgh_size =
sizeof(*out_reply) - sizeof(out_reply->new_state) +
sizeof(out_reply->new_state[0]) * out_reply->new_stateCnt;
return true;
}
default: {
SetMIGReplyError(out_header, MIG_BAD_ID);
return false;
}
}
}
//! \brief A server interface for the `exc` or `mach_exc` Mach subsystems,
//! simplified to have only a single interface method needing
//! implementation.
template <typename Traits>
class SimplifiedExcServer final : public ExcServer<Traits>,
public ExcServer<Traits>::Interface {
public:
//! \brief An interface that the different request messages that are a part of
//! the `exc` or `mach_exc` Mach subsystems can be dispatched to.
class Interface {
public:
//! \brief Handles exceptions raised by `exception_raise()`,
//! `exception_raise_state()`, and `exception_raise_state_identity()`;
//! or `mach_exception_raise()`, `mach_exception_raise_state()`, and
//! `mach_exception_raise_state_identity()`.
//!
//! For convenience in implementation, these different “behaviors” of
//! exception messages are all mapped to a single interface method. The
//! exception’s original “behavior” is specified in the \a behavior
//! parameter. Only parameters that were supplied in the request message
//! are populated, other parameters are set to reasonable default values.
//!
//! The meanings of most parameters are identical to that of
//! ExcServer<>::Interface::CatchExceptionRaiseStateIdentity().
//!
//! \param[in] behavior `EXCEPTION_DEFAULT`, `EXCEPTION_STATE`, or
//! `EXCEPTION_STATE_IDENTITY`, identifying which exception request
//! message was processed and thus which other parameters are valid.
//! When used with the `mach_exc` subsystem, `MACH_EXCEPTION_CODES` will
//! be ORed in to this parameter.
virtual kern_return_t CatchException(
exception_behavior_t behavior,
exception_handler_t exception_port,
thread_t thread,
task_t task,
exception_type_t exception,
const typename Traits::ExceptionCode* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t* flavor,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count,
thread_state_t new_state,
mach_msg_type_number_t* new_state_count,
const mach_msg_trailer_t* trailer,
bool* destroy_complex_request) = 0;
protected:
~Interface() {}
};
//! \brief Constructs an object of this class.
//!
//! \param[in] interface The interface to dispatch requests to. Weak.
explicit SimplifiedExcServer(Interface* interface)
: ExcServer<Traits>(this),
ExcServer<Traits>::Interface(),
interface_(interface) {}
SimplifiedExcServer(const SimplifiedExcServer&) = delete;
SimplifiedExcServer& operator=(const SimplifiedExcServer&) = delete;
// ExcServer::Interface:
kern_return_t CatchExceptionRaise(exception_handler_t exception_port,
thread_t thread,
task_t task,
exception_type_t exception,
const typename Traits::ExceptionCode* code,
mach_msg_type_number_t code_count,
const mach_msg_trailer_t* trailer,
bool* destroy_request) override {
thread_state_flavor_t flavor = THREAD_STATE_NONE;
mach_msg_type_number_t new_state_count = 0;
return interface_->CatchException(
Traits::kExceptionBehavior | EXCEPTION_DEFAULT,
exception_port,
thread,
task,
exception,
code_count ? code : nullptr,
code_count,
&flavor,
nullptr,
0,
nullptr,
&new_state_count,
trailer,
destroy_request);
}
kern_return_t CatchExceptionRaiseState(
exception_handler_t exception_port,
exception_type_t exception,
const typename Traits::ExceptionCode* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t* flavor,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count,
thread_state_t new_state,
mach_msg_type_number_t* new_state_count,
const mach_msg_trailer_t* trailer) override {
bool destroy_complex_request = false;
return interface_->CatchException(
Traits::kExceptionBehavior | EXCEPTION_STATE,
exception_port,
THREAD_NULL,
TASK_NULL,
exception,
code_count ? code : nullptr,
code_count,
flavor,
old_state_count ? old_state : nullptr,
old_state_count,
new_state_count ? new_state : nullptr,
new_state_count,
trailer,
&destroy_complex_request);
}
kern_return_t CatchExceptionRaiseStateIdentity(
exception_handler_t exception_port,
thread_t thread,
task_t task,
exception_type_t exception,
const typename Traits::ExceptionCode* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t* flavor,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count,
thread_state_t new_state,
mach_msg_type_number_t* new_state_count,
const mach_msg_trailer_t* trailer,
bool* destroy_request) override {
return interface_->CatchException(
Traits::kExceptionBehavior | EXCEPTION_STATE_IDENTITY,
exception_port,
thread,
task,
exception,
code_count ? code : nullptr,
code_count,
flavor,
old_state_count ? old_state : nullptr,
old_state_count,
new_state_count ? new_state : nullptr,
new_state_count,
trailer,
destroy_request);
}
private:
Interface* interface_; // weak
};
} // namespace
namespace internal {
class UniversalMachExcServerImpl final
: public CompositeMachMessageServer,
public SimplifiedExcServer<ExcTraits>::Interface,
public SimplifiedExcServer<MachExcTraits>::Interface {
public:
explicit UniversalMachExcServerImpl(
UniversalMachExcServer::Interface* interface)
: CompositeMachMessageServer(),
SimplifiedExcServer<ExcTraits>::Interface(),
SimplifiedExcServer<MachExcTraits>::Interface(),
exc_server_(this),
mach_exc_server_(this),
interface_(interface) {
AddHandler(&exc_server_);
AddHandler(&mach_exc_server_);
}
UniversalMachExcServerImpl(const UniversalMachExcServerImpl&) = delete;
UniversalMachExcServerImpl& operator=(const UniversalMachExcServerImpl&) =
delete;
~UniversalMachExcServerImpl() {}
// SimplifiedExcServer<ExcTraits>::Interface:
kern_return_t CatchException(exception_behavior_t behavior,
exception_handler_t exception_port,
thread_t thread,
task_t task,
exception_type_t exception,
const exception_data_type_t* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t* flavor,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count,
thread_state_t new_state,
mach_msg_type_number_t* new_state_count,
const mach_msg_trailer_t* trailer,
bool* destroy_complex_request) {
std::vector<mach_exception_data_type_t> mach_codes;
mach_codes.reserve(code_count);
for (size_t index = 0; index < code_count; ++index) {
mach_codes.push_back(code[index]);
}
return interface_->CatchMachException(behavior,
exception_port,
thread,
task,
exception,
code_count ? &mach_codes[0] : nullptr,
code_count,
flavor,
old_state_count ? old_state : nullptr,
old_state_count,
new_state_count ? new_state : nullptr,
new_state_count,
trailer,
destroy_complex_request);
}
// SimplifiedExcServer<MachExcTraits>::Interface:
kern_return_t CatchException(exception_behavior_t behavior,
exception_handler_t exception_port,
thread_t thread,
task_t task,
exception_type_t exception,
const mach_exception_data_type_t* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t* flavor,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count,
thread_state_t new_state,
mach_msg_type_number_t* new_state_count,
const mach_msg_trailer_t* trailer,
bool* destroy_complex_request) {
return interface_->CatchMachException(behavior,
exception_port,
thread,
task,
exception,
code_count ? code : nullptr,
code_count,
flavor,
old_state_count ? old_state : nullptr,
old_state_count,
new_state_count ? new_state : nullptr,
new_state_count,
trailer,
destroy_complex_request);
}
private:
SimplifiedExcServer<ExcTraits> exc_server_;
SimplifiedExcServer<MachExcTraits> mach_exc_server_;
UniversalMachExcServer::Interface* interface_; // weak
};
} // namespace internal
UniversalMachExcServer::UniversalMachExcServer(
UniversalMachExcServer::Interface* interface)
: MachMessageServer::Interface(),
impl_(new internal::UniversalMachExcServerImpl(interface)) {
}
UniversalMachExcServer::~UniversalMachExcServer() {
}
bool UniversalMachExcServer::MachMessageServerFunction(
const mach_msg_header_t* in_header,
mach_msg_header_t* out_header,
bool* destroy_complex_request) {
return impl_->MachMessageServerFunction(
in_header, out_header, destroy_complex_request);
}
std::set<mach_msg_id_t> UniversalMachExcServer::MachMessageServerRequestIDs() {
return impl_->MachMessageServerRequestIDs();
}
mach_msg_size_t UniversalMachExcServer::MachMessageServerRequestSize() {
return impl_->MachMessageServerRequestSize();
}
mach_msg_size_t UniversalMachExcServer::MachMessageServerReplySize() {
return impl_->MachMessageServerReplySize();
}
kern_return_t ExcServerSuccessfulReturnValue(exception_type_t exception,
exception_behavior_t behavior,
bool set_thread_state) {
if (exception == EXC_CRASH
#if BUILDFLAG(IS_MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_11
&& MacOSVersionNumber() >= 10'11'00
#endif
) {
return KERN_SUCCESS;
}
if (!set_thread_state && ExceptionBehaviorHasState(behavior)) {
return MACH_RCV_PORT_DIED;
}
return KERN_SUCCESS;
}
void ExcServerCopyState(exception_behavior_t behavior,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count,
thread_state_t new_state,
mach_msg_type_number_t* new_state_count) {
if (ExceptionBehaviorHasState(behavior)) {
*new_state_count = std::min(old_state_count, *new_state_count);
memcpy(new_state, old_state, *new_state_count * sizeof(old_state[0]));
}
}
} // namespace crashpad