| /* |
| * Copyright (C) 2017-2024 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include <wtf/threads/Signals.h> |
| |
| #if OS(UNIX) |
| |
| #if HAVE(MACH_EXCEPTIONS) |
| extern "C" { |
| #include "MachExceptionsServer.h" |
| }; |
| #endif |
| |
| #include <cstdio> |
| #include <pthread.h> |
| #include <signal.h> |
| #include <wtf/StdLibExtras.h> |
| |
| #if HAVE(MACH_EXCEPTIONS) |
| #include <dispatch/dispatch.h> |
| #include <mach/mach.h> |
| #include <mach/port.h> |
| #include <mach/task.h> |
| #include <mach/thread_act.h> |
| #include <mach/thread_status.h> |
| #endif |
| |
| #if OS(DARWIN) |
| #endif |
| |
| #include <wtf/CryptographicallyRandomNumber.h> |
| #include <wtf/DataLog.h> |
| #include <wtf/NeverDestroyed.h> |
| #include <wtf/PlatformRegisters.h> |
| #include <wtf/ThreadGroup.h> |
| #include <wtf/Threading.h> |
| #include <wtf/WTFConfig.h> |
| |
| namespace WTF { |
| |
| #if HAVE(MACH_EXCEPTIONS) |
| static exception_mask_t toMachMask(Signal); |
| #endif |
| |
| void SignalHandlers::add(Signal signal, SignalHandler&& handler) |
| { |
| Config::AssertNotFrozenScope assertScope; |
| |
| ASSERT(signal < Signal::NumberOfSignals); |
| ASSERT(!useMach || signal != Signal::Usr); |
| RELEASE_ASSERT(initState == SignalHandlers::InitState::Initializing); |
| |
| size_t signalIndex = static_cast<size_t>(signal); |
| size_t nextFree = numberOfHandlers[signalIndex]; |
| #if HAVE(MACH_EXCEPTIONS) |
| if (signal != Signal::Usr) |
| addedExceptions |= toMachMask(signal); |
| #endif |
| RELEASE_ASSERT(nextFree < maxNumberOfHandlers); |
| SignalHandlerMemory* memory = &handlers[signalIndex][nextFree]; |
| new (memory) SignalHandler(WTF::move(handler)); |
| |
| numberOfHandlers[signalIndex]++; |
| } |
| |
| template<typename Func> |
| inline void SignalHandlers::forEachHandler(Signal signal, NOESCAPE const Func& func) const |
| { |
| size_t signalIndex = static_cast<size_t>(signal); |
| size_t handlerIndex = numberOfHandlers[signalIndex]; |
| while (handlerIndex--) { |
| auto* memory = const_cast<SignalHandlerMemory*>(&handlers[signalIndex][handlerIndex]); |
| const SignalHandler& handler = *std::bit_cast<SignalHandler*>(memory); |
| func(handler); |
| } |
| } |
| |
| #if HAVE(MACH_EXCEPTIONS) |
| // You can read more about mach exceptions here: |
| // http://www.cs.cmu.edu/afs/cs/project/mach/public/doc/unpublished/exception.ps |
| // and the Mach interface Generator (MiG) here: |
| // http://www.cs.cmu.edu/afs/cs/project/mach/public/doc/unpublished/mig.ps |
| |
| #if CPU(ARM64E) && HAVE(HARDENED_MACH_EXCEPTIONS) |
| // Our secret key which we use as random diversifier when signing our return PC in the handler callbacks. |
| // Be VERY careful to clear this before any web content is loaded. |
| static uint32_t secretSigningKey; |
| |
| void* SignalHandlers::presignReturnPCForHandler(CodePtr<NoPtrTag> returnPC) |
| { |
| ASSERT(initState < SignalHandlers::InitState::Finalized); |
| uint64_t diversifier = ptrauth_blend_discriminator(reinterpret_cast<void*>(secretSigningKey), ptrauth_string_discriminator("pc")); |
| return ptrauth_sign_unauthenticated(returnPC.untaggedPtr(), ptrauth_key_function_pointer, diversifier); |
| } |
| #endif |
| |
| static constexpr size_t maxMessageSize = 1 * KB; |
| |
| static void initMachExceptionHandlerThread() |
| { |
| Config::AssertNotFrozenScope assertScope; |
| SignalHandlers& handlers = g_wtfConfig.signalHandlers; |
| RELEASE_ASSERT_WITH_MESSAGE(!handlers.exceptionPort, "Mach exception handler thread was already created"); |
| ASSERT(handlers.useMach); |
| |
| // We need this because some processes (e.g. WebKit's GPU process) don't allow signal handling in their |
| // sandbox profiles. We don't use them there so there's no point in setting up a dispatch queue we're |
| // never going to use. |
| if (!handlers.addedExceptions) |
| return; |
| |
| uint16_t flags = MPO_INSERT_SEND_RIGHT | MPO_EXCEPTION_PORT; |
| |
| mach_port_options_t options { }; |
| options.flags = flags; |
| |
| kern_return_t kr = mach_port_construct(mach_task_self(), &options, 0, &handlers.exceptionPort); |
| RELEASE_ASSERT(kr == KERN_SUCCESS); |
| |
| #if CPU(ARM64) && HAVE(HARDENED_MACH_EXCEPTIONS) |
| #if !CPU(ARM64E) |
| uint32_t secretSigningKey = 0; |
| #endif |
| uint64_t exceptionsAllowed = handlers.addedExceptions; |
| uint64_t behaviorsAllowed = EXCEPTION_STATE_IDENTITY_PROTECTED | MACH_EXCEPTION_CODES; |
| uint64_t flavorsAllowed = MACHINE_THREAD_STATE; |
| |
| kr = task_register_hardened_exception_handler(current_task(), secretSigningKey, exceptionsAllowed, |
| behaviorsAllowed, flavorsAllowed, handlers.exceptionPort); |
| if (kr == KERN_SUCCESS) |
| handlers.useHardenedHandler = true; |
| else { |
| dataLog("Failed to register hardened exception handler due to ", mach_error_string(kr)); |
| if (kr == KERN_DENIED) |
| dataLog(" consider adding `task_register_hardened_exception_handler` and `thread_adopt_exception_handler` to your sandbox"); |
| dataLogLn(); |
| } |
| |
| // Clear the key since we no longer need it anymore and we don't want an attacker to find it. |
| secretSigningKey = 0; |
| #endif |
| |
| dispatch_source_t source = dispatch_source_create( |
| DISPATCH_SOURCE_TYPE_MACH_RECV, handlers.exceptionPort, 0, DISPATCH_TARGET_QUEUE_DEFAULT); |
| RELEASE_ASSERT(source); |
| |
| dispatch_source_set_event_handler(source, ^{ |
| UNUSED_PARAM(source); // Capture a pointer to source in user space to silence the leaks tool. |
| |
| kern_return_t kr = mach_msg_server_once( |
| mach_exc_server, maxMessageSize, handlers.exceptionPort, MACH_MSG_TIMEOUT_NONE); |
| RELEASE_ASSERT(kr == KERN_SUCCESS); |
| }); |
| |
| // No need for a cancel handler because we never destroy exceptionPort. |
| |
| dispatch_resume(source); |
| } |
| |
| static exception_mask_t NODELETE toMachMask(Signal signal) |
| { |
| switch (signal) { |
| case Signal::AccessFault: return EXC_MASK_BAD_ACCESS; |
| case Signal::IllegalInstruction: return EXC_MASK_BAD_INSTRUCTION; |
| case Signal::FloatingPoint: return EXC_MASK_ARITHMETIC; |
| case Signal::Breakpoint: return EXC_MASK_BREAKPOINT; |
| default: break; |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| static Signal NODELETE fromMachException(exception_type_t type) |
| { |
| switch (type) { |
| case EXC_BAD_ACCESS: return Signal::AccessFault; |
| case EXC_BAD_INSTRUCTION: return Signal::IllegalInstruction; |
| case EXC_ARITHMETIC: return Signal::FloatingPoint; |
| case EXC_BREAKPOINT: return Signal::Breakpoint; |
| default: break; |
| } |
| return Signal::Unknown; |
| } |
| |
| #if CPU(ARM64E) && OS(DARWIN) |
| inline ptrauth_generic_signature_t hashThreadState(std::span<const natural_t> source) |
| { |
| constexpr size_t threadStatePCPointerIndex = (offsetof(arm_unified_thread_state, ts_64) + offsetof(arm_thread_state64_t, __opaque_pc)) / sizeof(uintptr_t); |
| constexpr size_t threadStateSizeInPointers = sizeof(arm_unified_thread_state) / sizeof(uintptr_t); |
| |
| ptrauth_generic_signature_t hash = 0; |
| |
| hash = ptrauth_sign_generic_data(hash, pthread_mach_thread_np(pthread_self())); |
| |
| auto srcSpan = spanReinterpretCast<const uintptr_t>(source).first(threadStateSizeInPointers); |
| |
| // Exclude the __opaque_flags field which is reserved for OS use. |
| // __opaque_flags is at the end of the payload. |
| for (size_t i = 0; i < threadStateSizeInPointers - 1; ++i) { |
| if (i != threadStatePCPointerIndex) |
| hash = ptrauth_sign_generic_data(srcSpan[i], hash); |
| } |
| const uint32_t* cpsrPtr = reinterpret_cast<const uint32_t*>(&srcSpan[threadStateSizeInPointers - 1]); |
| hash = ptrauth_sign_generic_data(static_cast<uint64_t>(*cpsrPtr), hash); |
| |
| return hash; |
| } |
| #endif |
| |
| extern "C" { |
| |
| // We need to implement stubs for catch_mach_exception_raise and catch_mach_exception_raise_state_identity. |
| // The MiG generated file will fail to link otherwise, even though we don't use the functions. Only the |
| // catch_mach_exception_raise_state function should be called because we pass EXCEPTION_STATE to |
| // thread_set_exception_ports. |
| kern_return_t catch_mach_exception_raise(mach_port_t, mach_port_t, mach_port_t, exception_type_t, mach_exception_data_t, mach_msg_type_number_t) |
| { |
| dataLogLn("We should not have called catch_exception_raise(), please file a bug at bugs.webkit.org"); |
| return KERN_FAILURE; |
| } |
| |
| kern_return_t catch_mach_exception_raise_state_identity(mach_port_t, mach_port_t, mach_port_t, exception_type_t, mach_exception_data_t, mach_msg_type_number_t, int*, thread_state_t, mach_msg_type_number_t, thread_state_t, mach_msg_type_number_t*) |
| { |
| dataLogLn("We should not have called catch_mach_exception_raise_state_identity, please file a bug at bugs.webkit.org"); |
| return KERN_FAILURE; |
| } |
| |
| static kern_return_t runSignalHandlers(Signal signal, PlatformRegisters& registers, mach_msg_type_number_t dataCount, mach_exception_data_t rawExceptionData) |
| { |
| auto exceptionData = unsafeMakeSpan(rawExceptionData, dataCount); |
| |
| SigInfo info; |
| SignalHandlers& handlers = g_wtfConfig.signalHandlers; |
| if (signal == Signal::AccessFault) { |
| ASSERT_UNUSED(dataCount, dataCount == 2); |
| info.faultingAddress = reinterpret_cast<void*>(exceptionData[1]); |
| #if CPU(ADDRESS64) |
| // If the faulting address is out of the range of any valid memory, we would |
| // not have any reason to handle it. Just let the default handler take care of it. |
| static constexpr unsigned validAddressBits = OS_CONSTANT(EFFECTIVE_ADDRESS_WIDTH); |
| static constexpr uintptr_t invalidAddressMask = ~((1ull << validAddressBits) - 1); |
| if (std::bit_cast<uintptr_t>(info.faultingAddress) & invalidAddressMask) |
| return KERN_FAILURE; |
| #endif |
| } |
| |
| bool didHandle = false; |
| handlers.forEachHandler(signal, [&] (const SignalHandler& handler) { |
| SignalAction handlerResult = handler(signal, info, registers); |
| didHandle |= handlerResult == SignalAction::Handled; |
| }); |
| return didHandle ? KERN_SUCCESS : KERN_FAILURE; |
| } |
| |
| #if defined(EXCEPTION_STATE_IDENTITY_PROTECTED) |
| |
| kern_return_t catch_mach_exception_raise_state_identity_protected( |
| mach_port_t exceptionPort, |
| uint64_t threadID, |
| mach_port_t taskIDToken, |
| exception_type_t exceptionType, |
| mach_exception_data_t exceptionData, |
| mach_msg_type_number_t dataCount, |
| int* stateFlavor, |
| const thread_state_t inState, |
| mach_msg_type_number_t inStateCount, |
| thread_state_t outState, |
| mach_msg_type_number_t* outStateCount) |
| { |
| UNUSED_PARAM(threadID); |
| kern_return_t kr = catch_mach_exception_raise_state(exceptionPort, exceptionType, exceptionData, |
| dataCount, stateFlavor, inState, inStateCount, outState, outStateCount); |
| // MIG moves the task ID token send right into this routine, but only a successful return owns it: |
| // on failure mach_msg_server_once destroys the request message and releases the right for us. |
| if (kr == KERN_SUCCESS && MACH_PORT_VALID(taskIDToken)) |
| mach_port_deallocate(mach_task_self(), taskIDToken); |
| return kr; |
| } |
| |
| #endif // defined(EXCEPTION_STATE_IDENTITY_PROTECTED) |
| |
| kern_return_t catch_mach_exception_raise_state( |
| mach_port_t port, |
| exception_type_t exceptionType, |
| const mach_exception_data_t exceptionData, |
| mach_msg_type_number_t dataCount, |
| int* stateFlavor, |
| const thread_state_t rawInState, |
| mach_msg_type_number_t inStateCount, |
| thread_state_t rawOutState, |
| mach_msg_type_number_t* outStateCount) |
| { |
| auto inState = unsafeMakeSpan(rawInState, inStateCount); |
| auto outState = unsafeMakeSpan(rawOutState, inStateCount); |
| ASSERT(g_wtfConfig.isPermanentlyFrozen || g_wtfConfig.disabledFreezingForTesting); |
| SignalHandlers& handlers = g_wtfConfig.signalHandlers; |
| RELEASE_ASSERT(port == handlers.exceptionPort); |
| // If we wanted to distinguish between SIGBUS and SIGSEGV for EXC_BAD_ACCESS on Darwin we could do: |
| // if (exceptionData[0] == KERN_INVALID_ADDRESS) |
| // signal = SIGSEGV; |
| // else |
| // signal = SIGBUS; |
| Signal signal = fromMachException(exceptionType); |
| RELEASE_ASSERT(signal != Signal::Unknown); |
| |
| #if CPU(ARM64E) && HAVE(HARDENED_MACH_EXCEPTIONS) |
| ASSERT_WITH_MESSAGE(!secretSigningKey, "The secret key should have been cleared before any exception handlers are run"); |
| #endif |
| |
| #if CPU(ARM64E) && OS(DARWIN) |
| ptrauth_generic_signature_t inStateHash = hashThreadState(inState); |
| #endif |
| |
| memcpySpan(outState, inState); |
| |
| #if CPU(X86_64) |
| RELEASE_ASSERT(*stateFlavor == x86_THREAD_STATE); |
| PlatformRegisters& registers = reinterpretCastSpanStartTo<x86_thread_state_t>(outState).uts.ts64; |
| #elif CPU(X86) |
| RELEASE_ASSERT(*stateFlavor == x86_THREAD_STATE); |
| PlatformRegisters& registers = reinterpretCastSpanStartTo<x86_thread_state_t>(outState).uts.ts32; |
| #elif CPU(ARM64) |
| RELEASE_ASSERT(*stateFlavor == ARM_THREAD_STATE); |
| PlatformRegisters& registers = reinterpretCastSpanStartTo<arm_unified_thread_state>(outState).ts_64; |
| #elif CPU(ARM) |
| RELEASE_ASSERT(*stateFlavor == ARM_THREAD_STATE); |
| PlatformRegisters& registers = reinterpretCastSpanStartTo<arm_unified_thread_state>(outState).ts_32; |
| #endif |
| |
| kern_return_t kr = runSignalHandlers(signal, registers, dataCount, exceptionData); |
| if (kr != KERN_SUCCESS) |
| return kr; |
| |
| #if CPU(ARM64E) && OS(DARWIN) |
| RELEASE_ASSERT(inStateHash == hashThreadState(outState)); |
| #endif |
| *outStateCount = inStateCount; |
| return KERN_SUCCESS; |
| } |
| |
| }; // extern "C" |
| |
| void handleSignalsWithMach() |
| { |
| Config::AssertNotFrozenScope assertScope; |
| g_wtfConfig.signalHandlers.useMach = true; |
| } |
| |
| static exception_mask_t activeExceptions; |
| inline void setExceptionPorts(const AbstractLocker& threadGroupLocker, Thread& thread) |
| { |
| UNUSED_PARAM(threadGroupLocker); |
| SignalHandlers& handlers = g_wtfConfig.signalHandlers; |
| |
| #if CPU(ARM64) && HAVE(HARDENED_MACH_EXCEPTIONS) |
| if (handlers.useHardenedHandler) { |
| const exception_behavior_t newBehavior = MACH_EXCEPTION_CODES | EXCEPTION_STATE_IDENTITY_PROTECTED; |
| kern_return_t result = thread_adopt_exception_handler(thread.machThread(), handlers.exceptionPort, handlers.addedExceptions & activeExceptions, newBehavior, MACHINE_THREAD_STATE); |
| RELEASE_ASSERT(result == KERN_SUCCESS, result, handlers.exceptionPort, handlers.addedExceptions, activeExceptions); |
| return; |
| } |
| #endif // CPU(ARM64) && HAVE(HARDENED_MACH_EXCEPTIONS) |
| const exception_behavior_t newBehavior = MACH_EXCEPTION_CODES | EXCEPTION_STATE; |
| kern_return_t result = thread_set_exception_ports(thread.machThread(), handlers.addedExceptions & activeExceptions, handlers.exceptionPort, newBehavior, MACHINE_THREAD_STATE); |
| RELEASE_ASSERT(result == KERN_SUCCESS, result, handlers.exceptionPort, handlers.addedExceptions, activeExceptions); |
| } |
| |
| static ThreadGroup& activeThreads() |
| { |
| static NeverDestroyed<Ref<ThreadGroup>> activeThreads = ThreadGroup::create(); |
| return activeThreads.get(); |
| } |
| |
| void registerThreadForMachExceptionHandling(Thread& thread) |
| { |
| const SignalHandlers& signalHandlers = g_wtfConfig.signalHandlers; |
| RELEASE_ASSERT(signalHandlers.initState >= SignalHandlers::InitState::Initializing, signalHandlers.initState); |
| if (!signalHandlers.useMach || !signalHandlers.addedExceptions) |
| return; |
| |
| Locker locker { activeThreads().getLock() }; |
| if (Ref { activeThreads() }->add(locker, thread) == ThreadGroupAddResult::NewlyAdded) |
| setExceptionPorts(locker, thread); |
| } |
| |
| #endif // HAVE(MACH_EXCEPTIONS) |
| |
| inline std::tuple<int, std::optional<int>> toSystemSignal(Signal signal) |
| { |
| switch (signal) { |
| case Signal::AccessFault: return std::make_tuple(SIGSEGV, SIGBUS); |
| case Signal::IllegalInstruction: return std::make_tuple(SIGILL, std::nullopt); |
| case Signal::Usr: return std::make_tuple(SIGUSR2, std::nullopt); |
| case Signal::FloatingPoint: return std::make_tuple(SIGFPE, std::nullopt); |
| case Signal::Breakpoint: return std::make_tuple(SIGTRAP, std::nullopt); |
| #if !OS(DARWIN) |
| case Signal::Abort: return std::make_tuple(SIGABRT, std::nullopt); |
| #endif |
| default: break; |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| inline Signal NODELETE fromSystemSignal(int signal) |
| { |
| switch (signal) { |
| case SIGSEGV: return Signal::AccessFault; |
| case SIGBUS: return Signal::AccessFault; |
| case SIGFPE: return Signal::FloatingPoint; |
| case SIGTRAP: return Signal::Breakpoint; |
| case SIGILL: return Signal::IllegalInstruction; |
| case SIGUSR2: return Signal::Usr; |
| #if !OS(DARWIN) |
| case SIGABRT: return Signal::Abort; |
| #endif |
| default: return Signal::Unknown; |
| } |
| } |
| |
| inline size_t NODELETE offsetForSystemSignal(int sig) |
| { |
| Signal signal = fromSystemSignal(sig); |
| return static_cast<size_t>(signal) + (sig == SIGBUS); |
| } |
| |
| void activateSignalHandlersFor(Signal signal) |
| { |
| const SignalHandlers& handlers = g_wtfConfig.signalHandlers; |
| RELEASE_ASSERT(handlers.initState >= SignalHandlers::InitState::Initializing); |
| ASSERT_UNUSED(signal, signal < Signal::Unknown); |
| |
| #if HAVE(MACH_EXCEPTIONS) |
| if (handlers.useMach) { |
| ASSERT(signal != Signal::Usr); |
| Locker locker { activeThreads().getLock() }; |
| if (activeExceptions & toMachMask(signal)) |
| return; |
| |
| ASSERT(handlers.numberOfHandlers[static_cast<uint8_t>(signal)]); |
| activeExceptions |= toMachMask(signal); |
| // activeExceptions should be a subset of addedExceptions. |
| ASSERT(!(activeExceptions & ~handlers.addedExceptions)); |
| for (auto& thread : activeThreads().threads(locker)) |
| setExceptionPorts(locker, thread.get()); |
| return; |
| } |
| #endif |
| } |
| |
| void addSignalHandler(Signal signal, SignalHandler&& handler) |
| { |
| g_wtfConfig.signalHandlers.add(signal, WTF::move(handler)); |
| } |
| |
| static void jscSignalHandler(int sig, siginfo_t* info, void* ucontext) |
| { |
| Signal signal = fromSystemSignal(sig); |
| SignalHandlers& handlers = g_wtfConfig.signalHandlers; |
| |
| auto restoreDefault = [&] { |
| struct sigaction defaultAction; |
| defaultAction.sa_handler = SIG_DFL; |
| sigfillset(&defaultAction.sa_mask); |
| defaultAction.sa_flags = 0; |
| auto result = sigaction(sig, &defaultAction, nullptr); |
| dataLogLnIf(result == -1, "Unable to restore the default handler while processing signal ", sig, " the process is probably deadlocked. (errno: ", errno, ")"); |
| }; |
| |
| // This shouldn't happen but we might as well be careful. |
| if (signal == Signal::Unknown) { |
| dataLogLn("We somehow got called for an unknown signal ", sig, ", help."); |
| restoreDefault(); |
| return; |
| } |
| |
| SigInfo sigInfo; |
| if (signal == Signal::AccessFault) |
| sigInfo.faultingAddress = info->si_addr; |
| |
| #if HAVE(MACHINE_CONTEXT) |
| PlatformRegisters& registers = registersFromUContext(reinterpret_cast<ucontext_t*>(ucontext)); |
| #else |
| PlatformRegisters registers { }; |
| #endif |
| |
| bool didHandle = false; |
| bool restoreDefaultHandler = false; |
| handlers.forEachHandler(signal, [&] (const SignalHandler& handler) { |
| switch (handler(signal, sigInfo, registers)) { |
| case SignalAction::Handled: |
| didHandle = true; |
| break; |
| case SignalAction::ForceDefault: |
| restoreDefaultHandler = true; |
| break; |
| default: |
| break; |
| } |
| }); |
| |
| if (restoreDefaultHandler) { |
| restoreDefault(); |
| return; |
| } |
| |
| unsigned oldActionIndex = static_cast<size_t>(signal) + (sig == SIGBUS); |
| struct sigaction& oldAction = handlers.oldActions[oldActionIndex]; |
| if (signal == Signal::Usr) { |
| if (oldAction.sa_sigaction) |
| oldAction.sa_sigaction(sig, info, ucontext); |
| return; |
| } |
| |
| if (!didHandle) { |
| if (oldAction.sa_sigaction) { |
| oldAction.sa_sigaction(sig, info, ucontext); |
| return; |
| } |
| |
| restoreDefault(); |
| return; |
| } |
| } |
| |
| void SignalHandlers::initialize() |
| { |
| SignalHandlers& handlers = g_wtfConfig.signalHandlers; |
| RELEASE_ASSERT(handlers.initState == SignalHandlers::InitState::Uninitialized); |
| handlers.initState = SignalHandlers::InitState::Initializing; |
| |
| #if CPU(ARM64E) && HAVE(HARDENED_MACH_EXCEPTIONS) |
| // Set up our secret key which we use as random diversifier when signing our return PC in the handler callbacks. |
| // According to the ARM64 ABI ptrauth_blend_discriminator can't take zero as the first argument. |
| static_assert(__DARWIN_ARM_THREAD_STATE64_USER_DIVERSIFIER_MASK == 0xff000000); |
| do { |
| secretSigningKey = static_cast<uint32_t>(WTF::cryptographicallyRandomNumber<uint8_t>()) << 24; |
| } while (!secretSigningKey); |
| ASSERT(secretSigningKey == (secretSigningKey & __DARWIN_ARM_THREAD_STATE64_USER_DIVERSIFIER_MASK)); |
| #endif |
| } |
| |
| void SignalHandlers::finalize() |
| { |
| Config::AssertNotFrozenScope assertScope; |
| SignalHandlers& handlers = g_wtfConfig.signalHandlers; |
| RELEASE_ASSERT(handlers.initState == SignalHandlers::InitState::Initializing); |
| handlers.initState = SignalHandlers::InitState::Finalized; |
| |
| #if HAVE(MACH_EXCEPTIONS) |
| if (handlers.useMach) |
| initMachExceptionHandlerThread(); |
| #endif |
| |
| if (!handlers.useMach) { |
| for (unsigned i = 0; i < numberOfSignals; ++i) { |
| if (!handlers.numberOfHandlers[i]) |
| continue; |
| |
| Signal signal = static_cast<Signal>(i); |
| struct sigaction action; |
| action.sa_sigaction = jscSignalHandler; |
| auto result = sigfillset(&action.sa_mask); |
| RELEASE_ASSERT(!result); |
| // Do not block this signal since it is used on non-Darwin systems to suspend and resume threads. |
| RELEASE_ASSERT(g_wtfConfig.isThreadSuspendResumeSignalConfigured); |
| result = sigdelset(&action.sa_mask, g_wtfConfig.sigThreadSuspendResume); |
| RELEASE_ASSERT(!result); |
| action.sa_flags = SA_SIGINFO; |
| auto systemSignals = toSystemSignal(signal); |
| result = sigaction(std::get<0>(systemSignals), &action, &handlers.oldActions[offsetForSystemSignal(std::get<0>(systemSignals))]); |
| if (std::get<1>(systemSignals)) |
| result |= sigaction(*std::get<1>(systemSignals), &action, &handlers.oldActions[offsetForSystemSignal(*std::get<1>(systemSignals))]); |
| RELEASE_ASSERT(!result); |
| } |
| } |
| } |
| |
| } // namespace WTF |
| |
| #endif // OS(UNIX) |