From be0fe1a84e2e88d7e77f99f2a55b421b2fd7a425 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 | 48 ++++++++++++++++++++++++++ libunwind/src/UnwindCursor.hpp | 36 +++++++++++++++++++ libunwind/src/UnwindRegistersRestore.S | 8 +++++ libunwind/src/UnwindRegistersSave.S | 8 +++++ libunwind/src/libunwind.cpp | 5 +++ 9 files changed, 160 insertions(+), 1 deletion(-) diff --git a/libunwind/CMakeLists.txt b/libunwind/CMakeLists.txt index 39e8e853f795..1fdd5be89bda 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." ON) +option(LIBUNWIND_SANDBOX_HARDENED "Harden the current sandbox implementation." ON) set(LIBUNWIND_LIBDIR_SUFFIX "${LLVM_LIBDIR_SUFFIX}" CACHE STRING "Define suffix of library directory name (32/64)") @@ -299,6 +300,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 5d0146ddc27f..de23bf8e21a6 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 46c8cd3af0b1..76123e57ef3d 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.setECSP(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 bb3e091aba9a..24690f6b188f 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::getUnsealedECSP(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 @@ -4566,6 +4609,11 @@ class _LIBUNWIND_HIDDEN Registers_riscv { void setIP(reg_t value) { _registers[0] = value; } #if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES) reg_t getUnsealedECSP(uintptr_t sealer) { return getSP(); } +#ifdef _LIBUNWIND_SANDBOX_HARDENED + void unsealSP(uintptr_t sealer) {} + void unsealFP(uintptr_t sealer) {} + void unsealCalleeSavedRegisters(uintptr_t sealer) {} +#endif // _LIBUNWIND_SANDBOX_HARDENED #endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES && private: 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 c4952eac2fa9..beec52460443 100644 --- a/libunwind/src/UnwindRegistersRestore.S +++ b/libunwind/src/UnwindRegistersRestore.S @@ -715,8 +715,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 *); @@ -771,7 +775,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 6d3429ff66dc..5b2875002dbd 100644 --- a/libunwind/src/libunwind.cpp +++ b/libunwind/src/libunwind.cpp @@ -231,6 +231,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();