diff --git a/libunwind/CMakeLists.txt b/libunwind/CMakeLists.txt index 08ebd632ade9..379b813c0f71 100644 --- a/libunwind/CMakeLists.txt +++ b/libunwind/CMakeLists.txt @@ -51,6 +51,7 @@ option(LIBUNWIND_USE_FRAME_HEADER_CACHE "Cache frame headers for unwinding. Requ option(LIBUNWIND_REMEMBER_HEAP_ALLOC "Use heap instead of the stack for .cfi_remember_state." OFF) option(LIBUNWIND_INSTALL_HEADERS "Install the libunwind headers." OFF) option(LIBUNWIND_SANDBOX_OTYPES "Use a libunwind implementation that assumes a c18n RTLD using otypes." OFF) +option(LIBUNWIND_SANDBOX_HARDENED "Harden the current sandbox implementation." OFF) set(LIBUNWIND_LIBDIR_SUFFIX "${LLVM_LIBDIR_SUFFIX}" CACHE STRING "Define suffix of library directory name (32/64)") @@ -302,6 +303,11 @@ if (LIBUNWIND_SANDBOX_OTYPES) add_compile_flags(-D_LIBUNWIND_SANDBOX_OTYPES) endif() +# Hardening +if (LIBUNWIND_SANDBOX_HARDENED) + add_compile_flags(-D_LIBUNWIND_SANDBOX_HARDENED) +endif() + # ARM WMMX register support if (LIBUNWIND_ENABLE_ARM_WMMX) # __ARM_WMMX is a compiler pre-define (as per the ACLE 2.0). Clang does not diff --git a/libunwind/include/__libunwind_config.h b/libunwind/include/__libunwind_config.h index 7507b58a6619..7fc88e009b8a 100644 --- a/libunwind/include/__libunwind_config.h +++ b/libunwind/include/__libunwind_config.h @@ -11,6 +11,14 @@ #define _LIBUNWIND_VERSION 15000 +#if defined(_LIBUNWIND_SANDBOX_HARDENED) && !defined(_LIBUNWIND_SANDBOX_OTYPES) +#error "_LIBUNWIND_SANDBOX_HARDENED is invalid without a sandboxing mechanism" +#endif + +#if defined(_LIBUNWIND_SANDBOX_OTYPES) && defined(_LIBUNWIND_NO_HEAP) +#error "_LIBUNWIND_NO_HEAP cannot be used with _LIBUNWIND_SANDBOX_OTYPES" +#endif + #if defined(__arm__) && !defined(__USING_SJLJ_EXCEPTIONS__) && \ !defined(__ARM_DWARF_EH__) && !defined(__SEH__) #define _LIBUNWIND_ARM_EHABI diff --git a/libunwind/src/AddressSpace.hpp b/libunwind/src/AddressSpace.hpp index c97ccb9deeaf..e7c040f4cb4e 100644 --- a/libunwind/src/AddressSpace.hpp +++ b/libunwind/src/AddressSpace.hpp @@ -417,7 +417,9 @@ inline uint64_t LocalAddressSpace::getRegister(pint_t addr) { #if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES) extern "C" { -/// Call into the RTLD to get a sealer capability. +/// Call into the RTLD to get a sealer capability. This sealer will be used to +/// seal information in the unwinding context if _LIBUNWIND_SANDBOX_HARDENED is +/// specified. LocalAddressSpace::pint_t _rtld_unw_getsealer(void); LocalAddressSpace::pint_t __rtld_unw_getsealer(); _LIBUNWIND_HIDDEN LocalAddressSpace::pint_t __rtld_unw_getsealer() { diff --git a/libunwind/src/DwarfInstructions.hpp b/libunwind/src/DwarfInstructions.hpp index baf9fccfd3c6..652e301bd21a 100644 --- a/libunwind/src/DwarfInstructions.hpp +++ b/libunwind/src/DwarfInstructions.hpp @@ -87,6 +87,10 @@ class DwarfInstructions { #if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES) CHERI_DBG("getRegister(%d) = %#p\n", (int)prolog.cfaRegister, (void *)result); +#ifdef _LIBUNWIND_SANDBOX_HARDENED + if (__builtin_cheri_sealed_get(result)) + result = __builtin_cheri_unseal(result, addressSpace.getUnwindSealer()); +#endif // _LIBUNWIND_SANDBOX_HARDENED #endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES result = (pint_t)((sint_t)result + prolog.cfaRegisterOffset); } else if (prolog.cfaExpression != 0) { @@ -270,12 +274,21 @@ size_t DwarfInstructions::restoreCalleeSavedRegisters(pint_t csp, pint_t sealer) { // Restore callee-saved registers. We seal these if they aren't sealed // already. + // + // XXX: When _LIBUNWIND_SANDBOX_HARDENED is specified, sentries get handed out + // and we can't really prevent the untrusted context from using those right + // now. size_t i; size_t offset; // Restore: c19-c28 for (i = 0, offset = CI.kCalleeSavedOffset; i < CI.kCalleeSavedCount; ++i, offset += CI.kCalleeSavedSize) { pint_t regValue = addressSpace.getCapability(csp + offset); +#ifdef _LIBUNWIND_SANDBOX_HARDENED + if (addressSpace.isValidSealer(sealer) && + !__builtin_cheri_sealed_get(regValue)) + regValue = __builtin_cheri_seal(regValue, sealer); +#endif newRegisters.setCapabilityRegister(UNW_ARM64_C19 + i, regValue); CHERI_DBG("SETTING CALLEE SAVED CAPABILITY REGISTER: %lu (%s): %#p " "(offset=%zu)\n", @@ -296,12 +309,20 @@ typename A::pint_t DwarfInstructions::restoreRegistersFromSandbox( "Executive stack should be tagged!"); // Derive the new executive CSP pint_t nextCSP = addressSpace.getCapability(csp + CI.kNextOffset); +#ifdef _LIBUNWIND_SANDBOX_HARDENED + // Seal ECSP + nextCSP = __builtin_cheri_seal(nextCSP, sealer); +#endif assert(__builtin_cheri_tag_get((void *)nextCSP) && "Next executive stack should be tagged!"); CHERI_DBG("SANDBOX: SETTING EXECUTIVE CSP %#p\n", (void *)nextCSP); newRegisters.setTrustedStack(nextCSP); // Restore the next RCSP pint_t nextRCSP = addressSpace.getCapability(csp + CI.kNewSPOffset); +#ifdef _LIBUNWIND_SANDBOX_HARDENED + // Seal RCSP + nextRCSP = __builtin_cheri_seal(nextRCSP, sealer); +#endif newRegisters.setSP(nextRCSP); CHERI_DBG("SANDBOX: SETTING RESTRICTED CSP: %#p\n", (void *)newRegisters.getSP()); @@ -309,6 +330,9 @@ typename A::pint_t DwarfInstructions::restoreRegistersFromSandbox( restoreCalleeSavedRegisters(csp, addressSpace, newRegisters, CI, sealer); // Restore the frame pointer pint_t newFP = addressSpace.getCapability(csp); +#ifdef _LIBUNWIND_SANDBOX_HARDENED + newFP = __builtin_cheri_seal(newFP, sealer); +#endif CHERI_DBG("SANDBOX: SETTING CFP %#p (offset=%zu)\n", (void *)newFP, offset); newRegisters.setFP(newFP); // Get the new return address. We can't seal this because a return address @@ -385,6 +409,10 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pc_t pc, pint_t newSP = cfa; #if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES) pint_t sealer = addressSpace.getUnwindSealer(); +#ifdef _LIBUNWIND_SANDBOX_HARDENED + if (addressSpace.isValidSealer(sealer)) + newSP = __builtin_cheri_seal(newSP, sealer); +#endif // _LIBUNWIND_SANDBOX_HARDENED #endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES CHERI_DBG("SETTING SP: %#p\n", (void *)newSP); newRegisters.setSP(newSP); @@ -415,6 +443,16 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pc_t pc, } else if (registers.validCapabilityRegister(i)) { capability_t savedReg = getSavedCapabilityRegister( addressSpace, registers, cfa, prolog.savedRegisters[i]); +#if defined(__CHERI_PURE_CAPABILITY__) && \ + defined(_LIBUNWIND_SANDBOX_OTYPES) && defined(_LIBUNWIND_SANDBOX_HARDENED) + // Seal all the capability registers. This enforces the invariant + // that unsealed capabilities are never stored in the context that + // aren't explicitly set through unw_set_reg() by a consumer. + if (addressSpace.isValidSealer(sealer) && + !__builtin_cheri_sealed_get(savedReg)) + savedReg = __builtin_cheri_seal(savedReg, sealer); +#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES && + // _LIBUNWIND_SANDBOX_HARDENED newRegisters.setCapabilityRegister(i, savedReg); CHERI_DBG("SETTING CAPABILITY REGISTER %d (%s): %#p \n", i, newRegisters.getRegisterName(i), (void *)savedReg); diff --git a/libunwind/src/Registers.hpp b/libunwind/src/Registers.hpp index 80874ea69d9a..d6b0b01db40f 100644 --- a/libunwind/src/Registers.hpp +++ b/libunwind/src/Registers.hpp @@ -1852,6 +1852,45 @@ class _LIBUNWIND_HIDDEN Registers_arm64 { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto() { __libunwind_Registers_arm64_jumpto(this); } +#if defined(__CHERI_PURE_CAPABILITY__) && \ + defined(_LIBUNWIND_SANDBOX_OTYPES) && defined(_LIBUNWIND_SANDBOX_HARDENED) + void unsealSP(uintptr_t sealer) { + assert(__builtin_cheri_sealed_get(_registers.__sp) && "Value must be sealed"); + _registers.__sp = __builtin_cheri_unseal(_registers.__sp, sealer); + } + void unsealFP(uintptr_t sealer) { + assert(__builtin_cheri_sealed_get(_registers.__fp) && "Value must be sealed"); + _registers.__fp = __builtin_cheri_unseal(_registers.__fp, sealer); + } + void unsealCalleeSavedRegisters(uintptr_t sealer) { + for (auto i = 0; i < 10; ++i) { + uintptr_t sealedValue = getRegister(UNW_ARM64_C19 + i); + // FIXME: Would be nice to enforce this invariant, but right now + // unw_set_reg() gets called to set what ends up in private_1, and it + // would require breaking libunwind's public API to seal registers through + // that particular path, and therefore we can't assert this. +#if 0 + assert((!__builtin_cheri_tag_get(sealedValue) || + __builtin_cheri_sealed_get(sealedValue)) && + "Value must be sealed"); +#endif + uintptr_t unsealedValue = sealedValue; + // If the tag gets cleared when we attempt to unseal our value, that means + // that we either have a capability that was sealed to begin with, and + // therefore we should just return it that way, or we have a sentry which + // we cannot unseal. + if (__builtin_cheri_tag_get(sealedValue) && + __builtin_cheri_sealed_get(sealedValue)) { + unsealedValue = __builtin_cheri_unseal(sealedValue, sealer); + if (!__builtin_cheri_tag_get(unsealedValue)) { + unsealedValue = sealedValue; + } + } + setCapabilityRegister(UNW_ARM64_C19 + i, unsealedValue); + } + } +#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES && + // _LIBUNWIND_SANDBOX_HARDENED static constexpr int lastDwarfRegNum() { #ifdef __CHERI_PURE_CAPABILITY__ return _LIBUNWIND_HIGHEST_DWARF_REGISTER_MORELLO; @@ -2000,6 +2039,10 @@ inline bool Registers_arm64::validCapabilityRegister(int regNum) const { inline uintptr_t Registers_arm64::getUnsealedTrustedStack(uintptr_t sealer) const { uintptr_t csp = _registers.__ecsp; +#ifdef _LIBUNWIND_SANDBOX_HARDENED + if (__builtin_cheri_sealed_get(csp)) + csp = __builtin_cheri_unseal(csp, sealer); +#endif // _LIBUNWIND_SANDBOX_HARDENED return csp; } #endif // _LIBUNWIND_SANDBOX_OTYPES diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp index 6506d30f6355..c2a47b8be60e 100644 --- a/libunwind/src/UnwindCursor.hpp +++ b/libunwind/src/UnwindCursor.hpp @@ -474,6 +474,19 @@ class _LIBUNWIND_HIDDEN AbstractUnwindCursor { } #endif +#if defined(__CHERI_PURE_CAPABILITY__) && \ + defined(_LIBUNWIND_SANDBOX_OTYPES) && defined(_LIBUNWIND_SANDBOX_HARDENED) + virtual void unsealSP(uintptr_t) { + _LIBUNWIND_ABORT("unsealSP not implemented"); + } + virtual void unsealFP(uintptr_t) { + _LIBUNWIND_ABORT("unsealFP not implemented"); + } + virtual void unsealCalleeSavedRegisters(uintptr_t) { + _LIBUNWIND_ABORT("unsealCalleeSavedRegisters not implemented"); + } +#endif + #if defined(_LIBUNWIND_USE_CET) virtual void *get_registers() { _LIBUNWIND_ABORT("get_registers not implemented"); @@ -941,6 +954,12 @@ class UnwindCursor : public AbstractUnwindCursor{ virtual bool getFunctionName(char *buf, size_t len, unw_word_t *off); virtual void setInfoBasedOnIPRegister(bool isReturnAddress = false); virtual const char *getRegisterName(int num); +#if defined(__CHERI_PURE_CAPABILITY__) && \ + defined(_LIBUNWIND_SANDBOX_OTYPES) && defined(_LIBUNWIND_SANDBOX_HARDENED) + virtual void unsealSP(pint_t sealer); + virtual void unsealFP(pint_t sealer); + virtual void unsealCalleeSavedRegisters(pint_t sealer); +#endif #ifdef __arm__ virtual void saveVFPAsX(); #endif @@ -1384,6 +1403,23 @@ const char *UnwindCursor::getRegisterName(int regNum) { return _registers.getRegisterName(regNum); } +#if defined(__CHERI_PURE_CAPABILITY__) && \ + defined(_LIBUNWIND_SANDBOX_OTYPES) && defined(_LIBUNWIND_SANDBOX_HARDENED) +template +void UnwindCursor::unsealSP(pint_t sealer) { + return _registers.unsealSP(sealer); +} +template +void UnwindCursor::unsealFP(pint_t sealer) { + return _registers.unsealFP(sealer); +} +template +void UnwindCursor::unsealCalleeSavedRegisters(pint_t sealer) { + return _registers.unsealCalleeSavedRegisters(sealer); +} +#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES && + // _LIBUNWIND_SANDBOX_HARDENED + template bool UnwindCursor::isSignalFrame() { return _isSignalFrame; } diff --git a/libunwind/src/UnwindRegistersRestore.S b/libunwind/src/UnwindRegistersRestore.S index 68509c19c500..6d23fb7164d0 100644 --- a/libunwind/src/UnwindRegistersRestore.S +++ b/libunwind/src/UnwindRegistersRestore.S @@ -718,8 +718,12 @@ DEFINE_LIBUNWIND_FUNCTION(__rtld_unw_setcontext) ret #endif END_LIBUNWIND_FUNCTION(__rtld_unw_setcontext) +#ifdef _LIBUNWIND_SANDBOX_HARDENED +WEAK_ALIAS(__rtld_unw_setcontext, _rtld_unw_setcontext) +#else WEAK_ALIAS(__rtld_unw_setcontext, _rtld_unw_setcontext_unsealed) #endif +#endif // // extern "C" void __libunwind_Registers_arm64_jumpto(Registers_arm64 *); @@ -774,7 +778,11 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto) ldr c2, [c0, #0x1f0] add c3, c0, #0x210 ldp c0, c1, [c0, #0x000] +#ifdef _LIBUNWIND_SANDBOX_HARDENED + b _rtld_unw_setcontext +#else b _rtld_unw_setcontext_unsealed +#endif #else // skip restore of x0,x1 for now ldp x2, x3, [x0, #0x010] diff --git a/libunwind/src/UnwindRegistersSave.S b/libunwind/src/UnwindRegistersSave.S index d2ef1034bf11..56d6fa9a3b72 100644 --- a/libunwind/src/UnwindRegistersSave.S +++ b/libunwind/src/UnwindRegistersSave.S @@ -843,8 +843,12 @@ DEFINE_LIBUNWIND_FUNCTION(__rtld_unw_getcontext) str c2, [c1] ret c30 END_LIBUNWIND_FUNCTION(__rtld_unw_getcontext) +#ifdef _LIBUNWIND_SANDBOX_HARDENED +WEAK_ALIAS(__rtld_unw_getcontext, _rtld_unw_getcontext) +#else WEAK_ALIAS(__rtld_unw_getcontext, _rtld_unw_getcontext_unsealed) #endif +#endif // // extern int __unw_getcontext(unw_context_t* thread_state) @@ -897,7 +901,11 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) str d30, [c0, #0x0f0] str d31, [c0, #0x0f8] mov x0, #0 // return UNW_ESUCCESS +#ifdef _LIBUNWIND_SANDBOX_HARDENED + b _rtld_unw_getcontext +#else b _rtld_unw_getcontext_unsealed +#endif #else stp x0, x1, [x0, #0x000] stp x2, x3, [x0, #0x010] diff --git a/libunwind/src/libunwind.cpp b/libunwind/src/libunwind.cpp index 49ae34b488e9..ca067a4ad1be 100644 --- a/libunwind/src/libunwind.cpp +++ b/libunwind/src/libunwind.cpp @@ -234,6 +234,11 @@ _LIBUNWIND_HIDDEN int __unw_resume(unw_cursor_t *cursor) { LocalAddressSpace &addressSpace = LocalAddressSpace::sThisAddressSpace; pint_t sealer = addressSpace.getUnwindSealer(); if (addressSpace.isValidSealer(sealer)) { +#ifdef _LIBUNWIND_SANDBOX_HARDENED + co->unsealSP(sealer); + co->unsealFP(sealer); + co->unsealCalleeSavedRegisters(sealer); +#endif // _LIBUNWIND_SANDBOX_HARDENED } #endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES co->jumpto();