#include "ds/MemoryProtectionExceptionHandler.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#if defined(XP_WIN)
# include "util/Windows.h"
#elif defined(XP_UNIX) && !defined(XP_DARWIN)
# include <signal.h>
# include <sys/types.h>
# include <unistd.h>
#elif defined(XP_DARWIN)
# include <mach/mach.h>
# include <unistd.h>
#endif
#ifdef ANDROID
# include <android/log.h>
#endif
#include "ds/SplayTree.h"
#include "threading/LockGuard.h"
#include "threading/Thread.h"
#include "vm/MutexIDs.h"
#include "vm/Runtime.h"
namespace js {
static mozilla::Atomic<bool, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
sProtectedRegionsInit(false);
class ProtectedRegionTree {
struct Region {
uintptr_t first;
uintptr_t last;
Region(uintptr_t addr, size_t size)
: first(addr), last(addr + (size - 1)) {}
static int compare(const Region& A, const Region& B) {
if (A.last < B.first) {
return -1;
}
if (A.first > B.last) {
return 1;
}
return 0;
}
};
Mutex lock;
LifoAlloc alloc;
SplayTree<Region, Region> tree;
public:
ProtectedRegionTree()
: lock(mutexid::ProtectedRegionTree),
alloc(4096),
tree(&alloc) {
sProtectedRegionsInit = true;
}
~ProtectedRegionTree() { sProtectedRegionsInit = false; }
void insert(uintptr_t addr, size_t size) {
MOZ_ASSERT(addr && size);
LockGuard<Mutex> guard(lock);
AutoEnterOOMUnsafeRegion oomUnsafe;
if (!tree.insert(Region(addr, size))) {
oomUnsafe.crash("Failed to store allocation ID.");
}
}
void remove(uintptr_t addr) {
MOZ_ASSERT(addr);
LockGuard<Mutex> guard(lock);
tree.remove(Region(addr, 1));
}
bool isProtected(uintptr_t addr) {
if (!addr) {
return false;
}
LockGuard<Mutex> guard(lock);
return tree.maybeLookup(Region(addr, 1));
}
};
static bool sExceptionHandlerInstalled = false;
static ProtectedRegionTree sProtectedRegions;
bool MemoryProtectionExceptionHandler::isDisabled() {
#if defined(XP_WIN) && (defined(MOZ_ASAN) || defined(JS_ENABLE_UWP))
return true;
#elif !defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
return true;
#else
return false;
#endif
}
void MemoryProtectionExceptionHandler::addRegion(void* addr, size_t size) {
if (sExceptionHandlerInstalled && sProtectedRegionsInit) {
sProtectedRegions.insert(uintptr_t(addr), size);
}
}
void MemoryProtectionExceptionHandler::removeRegion(void* addr) {
if (sExceptionHandlerInstalled && sProtectedRegionsInit) {
sProtectedRegions.remove(uintptr_t(addr));
}
}
static MOZ_COLD MOZ_ALWAYS_INLINE void ReportCrashIfDebug(const char* aStr)
MOZ_PRETEND_NORETURN_FOR_STATIC_ANALYSIS {
#ifdef DEBUG
# if defined(XP_WIN)
DWORD bytesWritten;
BOOL ret = WriteFile(GetStdHandle(STD_ERROR_HANDLE), aStr, strlen(aStr) + 1,
&bytesWritten, nullptr);
# elif defined(ANDROID)
int ret = __android_log_write(ANDROID_LOG_FATAL, "MOZ_CRASH", aStr);
# else
ssize_t ret = write(STDERR_FILENO, aStr, strlen(aStr) + 1);
# endif
(void)ret; #endif
}
#if defined(XP_WIN)
static void* sVectoredExceptionHandler = nullptr;
static mozilla::Atomic<bool> sHandlingException(false);
static long __stdcall VectoredExceptionHandler(
EXCEPTION_POINTERS* ExceptionInfo) {
#ifndef JS_ENABLE_UWP
EXCEPTION_RECORD* ExceptionRecord = ExceptionInfo->ExceptionRecord;
if (ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
if (sHandlingException.compareExchange(false, true)) {
MOZ_ALWAYS_TRUE(
RemoveVectoredExceptionHandler(sVectoredExceptionHandler));
sExceptionHandlerInstalled = false;
uintptr_t address = uintptr_t(ExceptionRecord->ExceptionInformation[1]);
if (sProtectedRegionsInit && sProtectedRegions.isProtected(address)) {
ReportCrashIfDebug(
"Hit MOZ_CRASH(Tried to access a protected region!)\n");
MOZ_CRASH_ANNOTATE("MOZ_CRASH(Tried to access a protected region!)");
}
}
}
#endif
return EXCEPTION_CONTINUE_SEARCH;
}
bool MemoryProtectionExceptionHandler::install() {
MOZ_ASSERT(!sExceptionHandlerInstalled);
if (MemoryProtectionExceptionHandler::isDisabled()) {
return true;
}
#ifndef JS_ENABLE_UWP
sVectoredExceptionHandler = AddVectoredExceptionHandler(
true, VectoredExceptionHandler);
#endif
sExceptionHandlerInstalled = sVectoredExceptionHandler != nullptr;
return sExceptionHandlerInstalled;
}
void MemoryProtectionExceptionHandler::uninstall() {
if (sExceptionHandlerInstalled) {
MOZ_ASSERT(!sHandlingException);
#ifndef JS_ENABLE_UWP
MOZ_ALWAYS_TRUE(RemoveVectoredExceptionHandler(sVectoredExceptionHandler));
#endif
sExceptionHandlerInstalled = false;
}
}
#elif defined(XP_UNIX) && !defined(XP_DARWIN)
static struct sigaction sPrevSEGVHandler = {};
static mozilla::Atomic<bool> sHandlingException(false);
static void UnixExceptionHandler(int signum, siginfo_t* info, void* context) {
if (sHandlingException.compareExchange(false, true)) {
MOZ_ALWAYS_FALSE(sigaction(SIGSEGV, &sPrevSEGVHandler, nullptr));
MOZ_ASSERT(signum == SIGSEGV && info->si_signo == SIGSEGV);
if (info->si_code == SEGV_ACCERR) {
uintptr_t address = uintptr_t(info->si_addr);
if (sProtectedRegionsInit && sProtectedRegions.isProtected(address)) {
ReportCrashIfDebug(
"Hit MOZ_CRASH(Tried to access a protected region!)\n");
MOZ_CRASH_ANNOTATE("MOZ_CRASH(Tried to access a protected region!)");
}
}
}
if (sPrevSEGVHandler.sa_flags & SA_SIGINFO) {
sPrevSEGVHandler.sa_sigaction(signum, info, context);
} else if (sPrevSEGVHandler.sa_handler == SIG_DFL ||
sPrevSEGVHandler.sa_handler == SIG_IGN) {
sigaction(SIGSEGV, &sPrevSEGVHandler, nullptr);
} else {
sPrevSEGVHandler.sa_handler(signum);
}
}
bool MemoryProtectionExceptionHandler::install() {
MOZ_ASSERT(!sExceptionHandlerInstalled);
if (MemoryProtectionExceptionHandler::isDisabled()) {
return true;
}
struct sigaction faultHandler = {};
faultHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
faultHandler.sa_sigaction = UnixExceptionHandler;
sigemptyset(&faultHandler.sa_mask);
sExceptionHandlerInstalled =
!sigaction(SIGSEGV, &faultHandler, &sPrevSEGVHandler);
return sExceptionHandlerInstalled;
}
void MemoryProtectionExceptionHandler::uninstall() {
if (sExceptionHandlerInstalled) {
MOZ_ASSERT(!sHandlingException);
MOZ_ALWAYS_FALSE(sigaction(SIGSEGV, &sPrevSEGVHandler, nullptr));
sExceptionHandlerInstalled = false;
}
}
#elif defined(XP_DARWIN)
static const mach_msg_id_t sIDRequest32 = 2401;
static const mach_msg_id_t sIDRequestState32 = 2402;
static const mach_msg_id_t sIDRequestStateIdentity32 = 2403;
static const mach_msg_id_t sIDRequest64 = 2405;
static const mach_msg_id_t sIDRequestState64 = 2406;
static const mach_msg_id_t sIDRequestStateIdentity64 = 2407;
# define REQUEST_HEADER_FIELDS mach_msg_header_t header;
# define REQUEST_IDENTITY_FIELDS \
mach_msg_body_t msgh_body; \
mach_msg_port_descriptor_t thread; \
mach_msg_port_descriptor_t task;
# define REQUEST_GENERAL_FIELDS(bits) \
NDR_record_t NDR; \
exception_type_t exception; \
mach_msg_type_number_t code_count; \
int##bits##_t code[2];
# define REQUEST_STATE_FIELDS \
int flavor; \
mach_msg_type_number_t old_state_count; \
natural_t old_state[THREAD_STATE_MAX];
# define REQUEST_TRAILER_FIELDS mach_msg_trailer_t trailer;
# define EXCEPTION_REQUEST(bits) \
struct ExceptionRequest##bits { \
REQUEST_HEADER_FIELDS \
REQUEST_IDENTITY_FIELDS \
REQUEST_GENERAL_FIELDS(bits) \
REQUEST_TRAILER_FIELDS \
};
# define EXCEPTION_REQUEST_STATE(bits) \
struct ExceptionRequestState##bits { \
REQUEST_HEADER_FIELDS \
REQUEST_GENERAL_FIELDS(bits) \
REQUEST_STATE_FIELDS \
REQUEST_TRAILER_FIELDS \
};
# define EXCEPTION_REQUEST_STATE_IDENTITY(bits) \
struct ExceptionRequestStateIdentity##bits { \
REQUEST_HEADER_FIELDS \
REQUEST_IDENTITY_FIELDS \
REQUEST_GENERAL_FIELDS(bits) \
REQUEST_STATE_FIELDS \
REQUEST_TRAILER_FIELDS \
};
# ifdef __MigPackStructs
# pragma pack(4)
# endif
EXCEPTION_REQUEST(32)
EXCEPTION_REQUEST(64)
EXCEPTION_REQUEST_STATE(32)
EXCEPTION_REQUEST_STATE(64)
EXCEPTION_REQUEST_STATE_IDENTITY(32)
EXCEPTION_REQUEST_STATE_IDENTITY(64)
union ExceptionRequestUnion {
mach_msg_header_t header;
ExceptionRequest32 r32;
ExceptionRequest64 r64;
ExceptionRequestState32 rs32;
ExceptionRequestState64 rs64;
ExceptionRequestStateIdentity32 rsi32;
ExceptionRequestStateIdentity64 rsi64;
};
struct ExceptionReply {
mach_msg_header_t header;
NDR_record_t NDR;
kern_return_t RetCode;
};
# ifdef __MigPackStructs
# pragma pack()
# endif
# undef EXCEPTION_REQUEST_STATE_IDENTITY
# undef EXCEPTION_REQUEST_STATE
# undef EXCEPTION_REQUEST
# undef REQUEST_STATE_FIELDS
# undef REQUEST_GENERAL_FIELDS
# undef REQUEST_IDENTITY_FIELDS
# undef REQUEST_HEADER_FIELDS
# define COPY_REQUEST_COMMON(bits, id) \
dst.header = src.header; \
dst.header.msgh_id = id; \
dst.header.msgh_size = \
static_cast<mach_msg_size_t>(sizeof(dst) - sizeof(dst.trailer)); \
dst.NDR = src.NDR; \
dst.exception = src.exception; \
dst.code_count = src.code_count; \
dst.code[0] = int##bits##_t(src.code[0]); \
dst.code[1] = int##bits##_t(src.code[1]);
# define COPY_REQUEST_IDENTITY \
dst.msgh_body = src.msgh_body; \
dst.thread = src.thread; \
dst.task = src.task;
# define COPY_REQUEST_STATE(flavor, stateCount, state) \
mach_msg_size_t stateSize = stateCount * sizeof(natural_t); \
dst.header.msgh_size = \
static_cast<mach_msg_size_t>(sizeof(dst) - sizeof(dst.trailer) - \
sizeof(dst.old_state) + stateSize); \
dst.flavor = flavor; \
dst.old_state_count = stateCount; \
memcpy(dst.old_state, state, stateSize);
# define COPY_EXCEPTION_REQUEST(bits) \
static void CopyExceptionRequest##bits(ExceptionRequest64& src, \
ExceptionRequest##bits& dst) { \
COPY_REQUEST_COMMON(bits, sIDRequest##bits) \
COPY_REQUEST_IDENTITY \
}
# define COPY_EXCEPTION_REQUEST_STATE(bits) \
static void CopyExceptionRequestState##bits( \
ExceptionRequest64& src, ExceptionRequestState##bits& dst, \
thread_state_flavor_t flavor, mach_msg_type_number_t stateCount, \
thread_state_t state) { \
COPY_REQUEST_COMMON(bits, sIDRequestState##bits) \
COPY_REQUEST_STATE(flavor, stateCount, state) \
}
# define COPY_EXCEPTION_REQUEST_STATE_IDENTITY(bits) \
static void CopyExceptionRequestStateIdentity##bits( \
ExceptionRequest64& src, ExceptionRequestStateIdentity##bits& dst, \
thread_state_flavor_t flavor, mach_msg_type_number_t stateCount, \
thread_state_t state) { \
COPY_REQUEST_COMMON(bits, sIDRequestStateIdentity##bits) \
COPY_REQUEST_IDENTITY \
COPY_REQUEST_STATE(flavor, stateCount, state) \
}
COPY_EXCEPTION_REQUEST(32)
COPY_EXCEPTION_REQUEST_STATE(32)
COPY_EXCEPTION_REQUEST_STATE_IDENTITY(32)
COPY_EXCEPTION_REQUEST(64)
COPY_EXCEPTION_REQUEST_STATE(64)
COPY_EXCEPTION_REQUEST_STATE_IDENTITY(64)
# undef COPY_EXCEPTION_REQUEST_STATE_IDENTITY
# undef COPY_EXCEPTION_REQUEST_STATE
# undef COPY_EXCEPTION_REQUEST
# undef COPY_REQUEST_STATE
# undef COPY_REQUEST_IDENTITY
# undef COPY_REQUEST_COMMON
struct MachExceptionParameters {
exception_mask_t mask;
mach_port_t port;
exception_behavior_t behavior;
thread_state_flavor_t flavor;
};
struct ExceptionHandlerState {
MachExceptionParameters current;
MachExceptionParameters previous;
Thread handlerThread;
};
static const mach_msg_id_t sIDQuit = 42;
static ExceptionHandlerState* sMachExceptionState = nullptr;
static void MachExceptionHandler() {
kern_return_t ret;
MachExceptionParameters& current = sMachExceptionState->current;
MachExceptionParameters& previous = sMachExceptionState->previous;
ExceptionRequest64 request = {};
request.header.msgh_local_port = current.port;
request.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(request));
ret = mach_msg(&request.header, MACH_RCV_MSG, 0, request.header.msgh_size,
current.port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
task_set_exception_ports(mach_task_self(), previous.mask, previous.port,
previous.behavior, previous.flavor);
if (ret != MACH_MSG_SUCCESS) {
MOZ_CRASH("MachExceptionHandler: mach_msg failed to receive a message!");
}
if (request.header.msgh_id == sIDQuit) {
return;
}
if (request.header.msgh_id != sIDRequest64) {
MOZ_CRASH("MachExceptionHandler: Unexpected Message ID!");
}
if (request.exception != EXC_BAD_ACCESS || request.code_count != 2) {
MOZ_CRASH("MachExceptionHandler: Unexpected exception type!");
}
uintptr_t address = uintptr_t(request.code[1]);
if (sProtectedRegionsInit && sProtectedRegions.isProtected(address)) {
ReportCrashIfDebug("Hit MOZ_CRASH(Tried to access a protected region!)\n");
MOZ_CRASH_ANNOTATE("MOZ_CRASH(Tried to access a protected region!)");
}
if (previous.port != MACH_PORT_NULL) {
mach_msg_type_number_t stateCount;
thread_state_data_t state;
if ((uint32_t(previous.behavior) & ~MACH_EXCEPTION_CODES) !=
EXCEPTION_DEFAULT) {
stateCount = THREAD_STATE_MAX;
ret = thread_get_state(request.thread.name, previous.flavor, state,
&stateCount);
if (ret != KERN_SUCCESS) {
MOZ_CRASH(
"MachExceptionHandler: Could not get the thread state to forward!");
}
}
ExceptionRequestUnion forward;
switch (uint32_t(previous.behavior)) {
case EXCEPTION_DEFAULT:
CopyExceptionRequest32(request, forward.r32);
break;
case EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES:
CopyExceptionRequest64(request, forward.r64);
break;
case EXCEPTION_STATE:
CopyExceptionRequestState32(request, forward.rs32, previous.flavor,
stateCount, state);
break;
case EXCEPTION_STATE | MACH_EXCEPTION_CODES:
CopyExceptionRequestState64(request, forward.rs64, previous.flavor,
stateCount, state);
break;
case EXCEPTION_STATE_IDENTITY:
CopyExceptionRequestStateIdentity32(request, forward.rsi32,
previous.flavor, stateCount, state);
break;
case EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES:
CopyExceptionRequestStateIdentity64(request, forward.rsi64,
previous.flavor, stateCount, state);
break;
default:
MOZ_CRASH("MachExceptionHandler: Unknown previous handler behavior!");
}
forward.header.msgh_bits =
(request.header.msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) |
MACH_MSGH_BITS(MACH_MSGH_BITS_LOCAL(request.header.msgh_bits),
MACH_MSGH_BITS_REMOTE(request.header.msgh_bits));
forward.header.msgh_local_port = forward.header.msgh_remote_port;
forward.header.msgh_remote_port = previous.port;
ret = mach_msg(&forward.header, MACH_SEND_MSG, forward.header.msgh_size, 0,
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (ret != MACH_MSG_SUCCESS) {
MOZ_CRASH(
"MachExceptionHandler: Failed to forward to the previous handler!");
}
} else {
ExceptionReply reply = {};
reply.header.msgh_bits =
MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.header.msgh_bits), 0);
reply.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(reply));
reply.header.msgh_remote_port = request.header.msgh_remote_port;
reply.header.msgh_local_port = MACH_PORT_NULL;
reply.header.msgh_id = request.header.msgh_id + 100;
reply.NDR = request.NDR;
reply.RetCode = KERN_FAILURE;
ret = mach_msg(&reply.header, MACH_SEND_MSG, reply.header.msgh_size, 0,
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (ret != MACH_MSG_SUCCESS) {
MOZ_CRASH("MachExceptionHandler: Failed to forward to the host level!");
}
}
}
static void TerminateMachExceptionHandlerThread() {
mach_msg_header_t msg;
msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
msg.msgh_size = static_cast<mach_msg_size_t>(sizeof(msg));
msg.msgh_remote_port = sMachExceptionState->current.port;
msg.msgh_local_port = MACH_PORT_NULL;
msg.msgh_reserved = 0;
msg.msgh_id = sIDQuit;
kern_return_t ret =
mach_msg(&msg, MACH_SEND_MSG, sizeof(msg), 0, MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (ret == MACH_MSG_SUCCESS) {
sMachExceptionState->handlerThread.join();
} else {
MOZ_CRASH("MachExceptionHandler: Handler thread failed to terminate!");
}
}
bool MemoryProtectionExceptionHandler::install() {
MOZ_ASSERT(!sExceptionHandlerInstalled);
MOZ_ASSERT(!sMachExceptionState);
if (MemoryProtectionExceptionHandler::isDisabled()) {
return true;
}
sMachExceptionState = js_new<ExceptionHandlerState>();
if (!sMachExceptionState) {
return false;
}
kern_return_t ret;
mach_port_t task = mach_task_self();
sMachExceptionState->current = {};
MachExceptionParameters& current = sMachExceptionState->current;
ret = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, ¤t.port);
if (ret != KERN_SUCCESS) {
return false;
}
ret = mach_port_insert_right(task, current.port, current.port,
MACH_MSG_TYPE_MAKE_SEND);
if (ret != KERN_SUCCESS) {
mach_port_deallocate(task, current.port);
current = {};
return false;
}
if (!sMachExceptionState->handlerThread.init(MachExceptionHandler)) {
mach_port_deallocate(task, current.port);
current = {};
return false;
}
current.mask = EXC_MASK_BAD_ACCESS;
current.behavior =
exception_behavior_t(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES);
current.flavor = THREAD_STATE_NONE;
sMachExceptionState->previous = {};
MachExceptionParameters& previous = sMachExceptionState->previous;
mach_msg_type_number_t previousCount = 1;
ret = task_swap_exception_ports(
task, current.mask, current.port, current.behavior, current.flavor,
&previous.mask, &previousCount, &previous.port, &previous.behavior,
&previous.flavor);
if (ret != KERN_SUCCESS) {
TerminateMachExceptionHandlerThread();
mach_port_deallocate(task, current.port);
previous = {};
current = {};
return false;
}
MOZ_ASSERT(previousCount == 1);
sExceptionHandlerInstalled = true;
return sExceptionHandlerInstalled;
}
void MemoryProtectionExceptionHandler::uninstall() {
if (sExceptionHandlerInstalled) {
MOZ_ASSERT(sMachExceptionState);
mach_port_t task = mach_task_self();
MachExceptionParameters& previous = sMachExceptionState->previous;
task_set_exception_ports(task, previous.mask, previous.port,
previous.behavior, previous.flavor);
TerminateMachExceptionHandlerThread();
mach_port_deallocate(task, sMachExceptionState->current.port);
sMachExceptionState->current = {};
sMachExceptionState->previous = {};
js_delete(sMachExceptionState);
sMachExceptionState = nullptr;
sExceptionHandlerInstalled = false;
}
}
#else
# error "This platform is not supported!"
#endif
}