From dbd8191623aa834406755d2ca57301e6512833b5 Mon Sep 17 00:00:00 2001 From: Domagoj Stolfa Date: Mon, 29 Apr 2024 18:37:43 +0100 Subject: [PATCH] [libunwind] Implement a baseline sandbox using otypes. This commit adds a new compile-time option (and enables it by default) called LIBUNWIND_SANDBOX_HARDENED. This causes libunwind to seal all of the capabilities it finds on the stack before returning control to the caller. Furthermore, it ensures that a compartmentalizing RTLD returns a sealed pointer to the trusted frame, rather than an unsealed one that leaks to the caller. This sandboxing model is experimental and should not yet be taken to be secure, as there are a number of known deficiencies with it. --- libunwind/CMakeLists.txt | 6 ++++ libunwind/include/__libunwind_config.h | 8 +++++ libunwind/src/AddressSpace.hpp | 4 ++- libunwind/src/DwarfInstructions.hpp | 38 +++++++++++++++++++++++ libunwind/src/Registers.hpp | 43 ++++++++++++++++++++++++++ libunwind/src/UnwindCursor.hpp | 36 +++++++++++++++++++++ libunwind/src/UnwindRegistersRestore.S | 8 +++++ libunwind/src/UnwindRegistersSave.S | 8 +++++ libunwind/src/libunwind.cpp | 5 +++ 9 files changed, 155 insertions(+), 1 deletion(-) 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();