Skip to content

Commit

Permalink
libunwind, c18n: Support C++ exceptions.
Browse files Browse the repository at this point in the history
Implement an initial version of DWARF unwinding for Morello with the
c18n runtime linker.

This implementations adds two new defines:
 - _LIBUNWIND_SANDBOX_OTYPES: signifies that the implementation is
   designed around the use of otypes to protect the data inside the
   unwind context (since the c18n linker uses it).
 - _LIBUNWIND_SANDBOX_HARDENED: specifies to actually use the otypes to
   protect the data.

By default, both are enabled in the build.

These changes support:
 - plain aarch64 (no change expected)
 - purecap (no change expected)
 - compartmentalization ABI with our current c18n RTLD
 - benchmark ABI

The unwinding happens as follows:
  (1) unw_getcontext() -- optionally call into the RTLD and get the
                          (optionally) sealed executive stack using
                          otypes.
  (2) stepWithDwarf() -- optionally unseal the executive stack locally
                         when needed and get the next frame information.
                         Then, if supported, reseal the executive stack
                         and all the registers that are not sealed or
                         sentries.
  • Loading branch information
dstolfa committed Feb 21, 2024
1 parent 2c2aa02 commit 52d9d7f
Show file tree
Hide file tree
Showing 17 changed files with 615 additions and 86 deletions.
10 changes: 7 additions & 3 deletions contrib/subrepo-cheri-libunwind/include/__libunwind_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

#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(__arm__) && !defined(__USING_SJLJ_EXCEPTIONS__) && \
!defined(__ARM_DWARF_EH__) && !defined(__SEH__)
#define _LIBUNWIND_ARM_EHABI
Expand All @@ -20,7 +24,7 @@
#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_X86_64 32
#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC 112
#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC64 116
#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_MORELLO 229
#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_MORELLO 230
#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64 95
#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM 287
#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_OR1K 32
Expand Down Expand Up @@ -76,11 +80,11 @@
# elif defined(__aarch64__)
# define _LIBUNWIND_TARGET_AARCH64 1
# if defined(__CHERI_PURE_CAPABILITY__)
# define _LIBUNWIND_CONTEXT_SIZE 100
# define _LIBUNWIND_CONTEXT_SIZE 102
# if defined(__SEH__)
# error "Pure-capability aarch64 SEH not supported"
# else
# define _LIBUNWIND_CURSOR_SIZE 124
# define _LIBUNWIND_CURSOR_SIZE 126
# endif
# define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_MORELLO
# else
Expand Down
3 changes: 2 additions & 1 deletion contrib/subrepo-cheri-libunwind/include/libunwind.h
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,8 @@ enum {
UNW_ARM64_C30 = 228,
UNW_ARM64_CLR = 228,
UNW_ARM64_C31 = 229,
UNW_ARM64_CSP = 229
UNW_ARM64_CSP = 229,
UNW_ARM64_ECSP = 230,
};

// 32-bit ARM registers. Numbers match DWARF for ARM spec #3.1 Table 1.
Expand Down
26 changes: 25 additions & 1 deletion contrib/subrepo-cheri-libunwind/src/AddressSpace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "dwarf2.h"
#include "EHHeaderParser.hpp"
#include "Registers.hpp"
#include "unwind_cheri.h"

// We can no longer include C++ headers so duplicate std::min() here
template<typename T> T uw_min(T a, T b) { return a < b ? a : b; }
Expand Down Expand Up @@ -320,6 +321,9 @@ class _LIBUNWIND_HIDDEN LocalAddressSpace {
return get<v128>(addr);
}
capability_t getCapability(pint_t addr) { return get<capability_t>(addr); }
#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES)
static uintcap_t getUnwindSealer();
#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES
__attribute__((always_inline))
uintptr_t getP(pint_t addr);
uint64_t getRegister(pint_t addr);
Expand Down Expand Up @@ -408,6 +412,25 @@ inline uint64_t LocalAddressSpace::getRegister(pint_t addr) {
#endif
}

#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES)
extern "C" {
/// 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.
uintptr_t _rtld_unw_getsealer(void);
uintptr_t __rtld_unw_getsealer();
_LIBUNWIND_HIDDEN uintptr_t __rtld_unw_getsealer() {
return (uintptr_t)-1;
}
_LIBUNWIND_WEAK_ALIAS(__rtld_unw_getsealer, _rtld_unw_getsealer)
}

/// C++ wrapper for calling into RTLD.
inline uintcap_t LocalAddressSpace::getUnwindSealer() {
return _rtld_unw_getsealer();
}
#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES

/// Read a ULEB128 into a 64-bit word.
inline uint64_t LocalAddressSpace::getULEB128(pint_t &addr, pint_t end) {
const uint8_t *p = (uint8_t *)addr;
Expand Down Expand Up @@ -930,7 +953,8 @@ inline bool LocalAddressSpace::findUnwindSections(pc_t targetAddr,
return true;
#elif defined(_LIBUNWIND_USE_DL_ITERATE_PHDR)
dl_iterate_cb_data cb_data = {this, &info, targetAddr};
CHERI_DBG("Calling dl_iterate_phdr()\n");
CHERI_DBG("Calling dl_iterate_phdr(0x%jx)\n",
(uintmax_t)targetAddr.address());
int found = dl_iterate_phdr(findUnwindSectionsByPhdr, &cb_data);
return static_cast<bool>(found);
#endif
Expand Down
1 change: 1 addition & 0 deletions contrib/subrepo-cheri-libunwind/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ set(LIBUNWIND_HEADERS
Registers.hpp
RWMutex.hpp
Unwind-EHABI.h
unwind_cheri.h
UnwindCursor.hpp
../include/libunwind.h
../include/unwind.h
Expand Down
179 changes: 164 additions & 15 deletions contrib/subrepo-cheri-libunwind/src/DwarfInstructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ class DwarfInstructions {
typedef typename A::pc_t pc_t;
typedef typename A::capability_t capability_t;

#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES)
static uintptr_t restoreRegistersFromSandbox(A &addressSpace, R &registers,
R &newRegisters,
uintcap_t sealer);
static bool isTrampoline(A &addressSpace, R &registers,
uintcap_t returnAddress, uintcap_t sealer);
#endif
static int stepWithDwarf(A &addressSpace, pc_t pc, pint_t fdeStart,
R &registers, bool &isSignalFrame);

Expand Down Expand Up @@ -72,9 +79,16 @@ class DwarfInstructions {
*success = true;
pint_t result = (pint_t)-1;
if (prolog.cfaRegister != 0) {
result =
(pint_t)((sint_t)registers.getRegister((int)prolog.cfaRegister) +
prolog.cfaRegisterOffset);
result = registers.getRegister((int)prolog.cfaRegister);
#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) {
result = evaluateExpression((pint_t)prolog.cfaExpression,
addressSpace, registers, 0);
Expand Down Expand Up @@ -245,6 +259,100 @@ bool DwarfInstructions<A, R>::getRA_SIGN_STATE(A &addressSpace, R registers,
}
#endif

#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES)
#if defined(_LIBUNWIND_TARGET_AARCH64)
template <typename A, typename R>
size_t restoreCalleeSavedRegisters(uintcap_t csp, A &addressSpace, R &registers,
R &newRegisters, uintcap_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.
int i;
size_t offset;
// Restore: c19-c28
for (i = 0, offset = 48; i < 10; ++i, offset += 16) {
uintcap_t regValue = addressSpace.getCapability(csp + offset);
#ifdef _LIBUNWIND_SANDBOX_HARDENED
if (!__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:%d (%s): %#p (offset=%zu)\n",
UNW_ARM64_C19 + i, newRegisters.getRegisterName(UNW_ARM64_C19 + i),
(void *)regValue, offset);
}

return offset;
}
#else // _LIBUNWIND_TARGET_AARCH64
template <typename A, typename R>
size_t restoreCalleeSavedRegisters(A &addressSpace, R &registers,
R &newRegisters, uintcap_t sealer) {
assert(0 && "not implemented on this architecture");
return 0;
}
#endif

template <typename A, typename R>
uintptr_t DwarfInstructions<A, R>::restoreRegistersFromSandbox(
A &addressSpace, R &registers, R &newRegisters, uintcap_t sealer) {
// Get the unsealed executive CSP
uintcap_t csp = registers.getUnsealedECSP(sealer);
assert(__builtin_cheri_tag_get((void *)csp) &&
"Executive stack should be tagged!");
// Derive the new executive CSP
ptraddr_t nextCSPAddr = addressSpace.get64(csp);
uintcap_t nextCSP = __builtin_cheri_address_set(csp, nextCSPAddr);
#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("SETTING EXECUTIVE CSP %#p\n", (void *)nextCSP);
newRegisters.setECSP(nextCSP);
// Restore the next RCSP from the executive stack
uintptr_t oldStack = addressSpace.getCapability(csp + 16);
oldStack = __builtin_cheri_offset_set(oldStack,
__builtin_cheri_length_get(oldStack));
uintptr_t newSP = addressSpace.getCapability(oldStack - 16);
#ifdef _LIBUNWIND_SANDBOX_HARDENED
// Seal RCSP
newSP = __builtin_cheri_seal(newSP, sealer);
#endif
newRegisters.setSP(newSP);
CHERI_DBG("SETTING SP %#p\n", (void *)newRegisters.getSP());
size_t offset = restoreCalleeSavedRegisters(csp, addressSpace, registers,
newRegisters, sealer);
// Restore the frame pointer
uintcap_t newFP = addressSpace.getCapability(csp + offset);
#ifdef _LIBUNWIND_SANDBOX_HARDENED
newFP = __builtin_cheri_seal(newFP, sealer);
#endif
CHERI_DBG("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
// will be a sentry.
return addressSpace.getCapability(csp + 32);
}

template <typename A, typename R>
bool DwarfInstructions<A, R>::isTrampoline(A &addressSpace, R &registers,
uintcap_t returnAddress,
uintcap_t sealer) {
uintcap_t csp = registers.getUnsealedECSP(sealer);
CHERI_DBG("isTrampoline(): csp: %#p\n", (void *)csp);
ptraddr_t expectedReturnAddress = addressSpace.get64(csp + 8) & (~0b11ULL);
CHERI_DBG("isTrampoline(): expectedReturnAddress: 0x%lx\n",
expectedReturnAddress);
return expectedReturnAddress == returnAddress - 1;
}
#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES

template <typename A, typename R>
int DwarfInstructions<A, R>::stepWithDwarf(A &addressSpace, pc_t pc,
pint_t fdeStart, R &registers,
Expand Down Expand Up @@ -273,7 +381,15 @@ int DwarfInstructions<A, R>::stepWithDwarf(A &addressSpace, pc_t pc,
//
// We set the SP here to the CFA, allowing for it to be overridden
// by a CFI directive later on.
newRegisters.setSP(cfa);
uintptr_t newSP = cfa;
#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES)
uintcap_t sealer = addressSpace.getUnwindSealer();
#ifdef _LIBUNWIND_SANDBOX_HARDENED
if (sealer != (uintcap_t)-1)
newSP = __builtin_cheri_seal(newSP, sealer);
#endif // _LIBUNWIND_SANDBOX_HARDENED
#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES
newRegisters.setSP(newSP);

pint_t returnAddress = 0;
constexpr int lastReg = R::lastDwarfRegNum();
Expand All @@ -296,25 +412,39 @@ int DwarfInstructions<A, R>::stepWithDwarf(A &addressSpace, pc_t pc,
else if (i == (int)cieInfo.returnAddressRegister) {
returnAddress = getSavedRegister(i, addressSpace, registers, cfa,
prolog.savedRegisters[i]);
CHERI_DBG("SETTING RETURN REGISTER %d (%s): %#p \n",
i, newRegisters.getRegisterName(i), (void*)returnAddress);
CHERI_DBG("GETTING RETURN ADDRESS (saved) %d (%s): %#p \n", i,
newRegisters.getRegisterName(i), (void *)returnAddress);
} else if (registers.validCapabilityRegister(i)) {
newRegisters.setCapabilityRegister(
i, getSavedCapabilityRegister(addressSpace, registers, cfa,
prolog.savedRegisters[i]));
CHERI_DBG("SETTING CAPABILITY REGISTER %d (%s): %#p \n",
i, newRegisters.getRegisterName(i),
(void*)A::to_pint_t(newRegisters.getCapabilityRegister(i)));
uintcap_t savedReg = getSavedCapabilityRegister(
addressSpace, registers, cfa, prolog.savedRegisters[i]);
#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES)
#ifdef _LIBUNWIND_SANDBOX_HARDENED
// Seal all the capability registers. This is necessary because when
// running with rtld-c18n, we will always have the sealer. Even
// though we are not leaving the compartment boundary, we will
// attempt to unseal the registers during unw_resume(). Furthermore,
// this enforces the invariant that unsealed capabilities are never
// stored in the context.
if (sealer != (uintcap_t)-1 &&
!__builtin_cheri_sealed_get(savedReg))
savedReg = __builtin_cheri_seal(savedReg, sealer);
#endif // _LIBUNWIND_SANDBOX_HARDENED
#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_SANDBOX_OTYPES
newRegisters.setCapabilityRegister(i, savedReg);
CHERI_DBG("SETTING CAPABILITY REGISTER %d (%s): %#p \n", i,
newRegisters.getRegisterName(i), (void *)savedReg);
} else if (registers.validRegister(i))
newRegisters.setRegister(
i, getSavedRegister(i, addressSpace, registers, cfa,
prolog.savedRegisters[i]));
else
return UNW_EBADREG;
} else if (i == (int)cieInfo.returnAddressRegister) {
// Leaf function keeps the return address in register and there is no
// explicit intructions how to restore it.
returnAddress = registers.getRegister(cieInfo.returnAddressRegister);
// Leaf function keeps the return address in register and there is no
// explicit intructions how to restore it.
returnAddress = registers.getRegister(cieInfo.returnAddressRegister);
CHERI_DBG("GETTING RETURN ADDRESS (leaf) %d (%s): %#p \n", i,
registers.getRegisterName(i), (void *)returnAddress);
}
}

Expand Down Expand Up @@ -402,9 +532,28 @@ int DwarfInstructions<A, R>::stepWithDwarf(A &addressSpace, pc_t pc,
}
#endif

#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES)
// If the sealer is not -1 (only the case when we're running with c18n),
// check if the return address has the executive mode bit set. If so, we
// should be calling into the c18n RTLD as this is a compartment boundary.
// We need to restore registers from the executive stack and ask rtld for
// it.
if (sealer != (uintcap_t)-1) {
// Iteratively unwind all the executive mode return addresses. This is
// necessary to support tail calls to trampolines.
while (isTrampoline(addressSpace, registers, returnAddress, sealer)) {
returnAddress = restoreRegistersFromSandbox(addressSpace, registers,
newRegisters, sealer);
newRegisters.setIP(returnAddress);
registers = newRegisters;
}
}
#endif

// Return address is address after call site instruction, so setting IP to
// that does simualates a return.
newRegisters.setIP(returnAddress);
CHERI_DBG("SETTING RETURN ADDRESS %#p\n", (void *)returnAddress);

// Simulate the step by replacing the register set with the new ones.
registers = newRegisters;
Expand Down
Loading

0 comments on commit 52d9d7f

Please sign in to comment.