Skip to content

Commit

Permalink
[libunwind] Implement a baseline sandbox using otypes.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dstolfa committed Apr 29, 2024
1 parent 9a1490e commit be0fe1a
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 1 deletion.
6 changes: 6 additions & 0 deletions libunwind/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions libunwind/include/__libunwind_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion libunwind/src/AddressSpace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
38 changes: 38 additions & 0 deletions libunwind/src/DwarfInstructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -270,12 +274,21 @@ size_t DwarfInstructions<A, R>::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",
Expand All @@ -296,19 +309,30 @@ typename A::pint_t DwarfInstructions<A, R>::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());
size_t offset =
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
Expand Down Expand Up @@ -385,6 +409,10 @@ int DwarfInstructions<A, R>::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);
Expand Down Expand Up @@ -415,6 +443,16 @@ int DwarfInstructions<A, R>::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);
Expand Down
48 changes: 48 additions & 0 deletions libunwind/src/Registers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
36 changes: 36 additions & 0 deletions libunwind/src/UnwindCursor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1384,6 +1403,23 @@ const char *UnwindCursor<A, R>::getRegisterName(int regNum) {
return _registers.getRegisterName(regNum);
}

#if defined(__CHERI_PURE_CAPABILITY__) && \
defined(_LIBUNWIND_SANDBOX_OTYPES) && defined(_LIBUNWIND_SANDBOX_HARDENED)
template <typename A, typename R>
void UnwindCursor<A, R>::unsealSP(pint_t sealer) {
return _registers.unsealSP(sealer);
}
template <typename A, typename R>
void UnwindCursor<A, R>::unsealFP(pint_t sealer) {
return _registers.unsealFP(sealer);
}
template <typename A, typename R>
void UnwindCursor<A, R>::unsealCalleeSavedRegisters(pint_t sealer) {
return _registers.unsealCalleeSavedRegisters(sealer);
}
#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES &&
// _LIBUNWIND_SANDBOX_HARDENED

template <typename A, typename R> bool UnwindCursor<A, R>::isSignalFrame() {
return _isSignalFrame;
}
Expand Down
8 changes: 8 additions & 0 deletions libunwind/src/UnwindRegistersRestore.S
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand Down Expand Up @@ -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]
Expand Down
8 changes: 8 additions & 0 deletions libunwind/src/UnwindRegistersSave.S
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]
Expand Down
5 changes: 5 additions & 0 deletions libunwind/src/libunwind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit be0fe1a

Please sign in to comment.