Skip to content

Commit

Permalink
libunwind: Add c18n exception support with otypes.
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 implementation uses otypes, and therefore
might not be compatible with RISC-V.

The unwinding happens as follows:
  (1) unw_getcontext() -- optionally call into the RTLD and get the
                          sealed executive stack.
  (2) stepWithDwarf() -- unseal the executive stack locally when needed
                         and get the next frame information. Then,
                         reseal the executive stack and all the
                         registers that are not sealed or sentries.

This implementation is under #ifdef _LIBUNWIND_SANDBOX_OTYPES.
  • Loading branch information
dstolfa committed Feb 7, 2024
1 parent 698d163 commit 5f265bf
Show file tree
Hide file tree
Showing 20 changed files with 416 additions and 39 deletions.
6 changes: 3 additions & 3 deletions contrib/subrepo-cheri-libunwind/include/__libunwind_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,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 +76,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
32 changes: 30 additions & 2 deletions 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 @@ -319,7 +320,15 @@ class _LIBUNWIND_HIDDEN LocalAddressSpace {
v128 getVector(pint_t addr) {
return get<v128>(addr);
}
capability_t getCapability(pint_t addr) { return get<capability_t>(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();
capability_t getSealedCapability(pint_t addr) {
capability_t sealer = getUnwindSealer();
assert(sealer != (capability_t)-1 && "Sealer not initialized");
return __builtin_cheri_seal(get<capability_t>(addr), sealer);
}
#endif
__attribute__((always_inline))
uintptr_t getP(pint_t addr);
uint64_t getRegister(pint_t addr);
Expand Down Expand Up @@ -408,6 +417,24 @@ 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.
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 +957,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
137 changes: 121 additions & 16 deletions contrib/subrepo-cheri-libunwind/src/DwarfInstructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
#include "DwarfParser.hpp"
#include "config.h"


namespace libunwind {


Expand All @@ -36,6 +35,10 @@ 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 void restoreRegistersFromSandbox(A &addressSpace, R &registers,
R &newRegisters, 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 +75,12 @@ 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)
if (__builtin_cheri_sealed_get(result))
result = __builtin_cheri_unseal(result, addressSpace.getUnwindSealer());
#endif
result = (pint_t)((sint_t)result + prolog.cfaRegisterOffset);
} else if (prolog.cfaExpression != 0) {
result = evaluateExpression((pint_t)prolog.cfaExpression,
addressSpace, registers, 0);
Expand All @@ -84,6 +90,7 @@ class DwarfInstructions {
*success = false;
return (pint_t)-1;
}

if (!is_pointer_in_bounds(result, true)) {
_LIBUNWIND_LOG("evaluated out-of-bounds/invalid CFA "
"expression for pc %#tx: " _LIBUNWIND_FMT_PTR "\n",
Expand Down Expand Up @@ -245,6 +252,64 @@ bool DwarfInstructions<A, R>::getRA_SIGN_STATE(A &addressSpace, R registers,
}
#endif

#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES)
template <typename A, typename R>
void DwarfInstructions<A, R>::restoreRegistersFromSandbox(A &addressSpace,
R &registers,
R &newRegisters,
uintcap_t sealer) {
// Get the unsealed executive CSP
uintcap_t csp = registers.getUnsealedExecutiveStack(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);
// Seal ECSP
nextCSP = __builtin_cheri_seal(nextCSP, sealer);
assert(__builtin_cheri_tag_get((void *)nextCSP) &&
"Next executive stack should be tagged!");
CHERI_DBG("SETTING EXECUTIVE CSP %#p\n", (void *)nextCSP);
newRegisters.setRegister(UNW_ARM64_ECSP, nextCSP, sealer);
// Restore the next RCSP from the executive stack
uintptr_t endOfOldRestrictedStack = addressSpace.getCapability(csp + 16);
uintptr_t newSP = addressSpace.getCapability(endOfOldRestrictedStack - 16);
// Seal RCSP
newSP = __builtin_cheri_seal(newSP, sealer);
newRegisters.setSP(newSP);
CHERI_DBG("SETTING SP %#p\n", (void *)newRegisters.getSP());
// Get the new return address. We can't seal this because a return address
// will be a sentry.
uintptr_t newIP = addressSpace.getCapability(csp + 32);
newRegisters.setIP(newIP);
CHERI_DBG("SETTING RETURN ADDRESS %#p\n", (void *)newRegisters.getIP());

// Restore callee-saved registers. We seal these if they aren't sealed
// already.
//
// XXX: Sentries get handed out and we can't really prevent the untrusted
// context from using those right now.
int i;
size_t offset;
for (i = 0, offset = 48; i < 10; ++i, offset += 16) {
uintcap_t regValue = addressSpace.getCapability(csp + offset);
if (!__builtin_cheri_sealed_get(regValue))
regValue = __builtin_cheri_seal(regValue, sealer);
newRegisters.setCapabilityRegister(UNW_ARM64_C19 + i, regValue);
CHERI_DBG("SETTING CALLEE SAVED CAPABILITY REGISTER:%d (%s): %#p\n",
UNW_ARM64_C19 + i,
newRegisters.getRegisterName(UNW_ARM64_C19 + i),
(void *)regValue);
}

// Restore the frame pointer
uintcap_t newFP = addressSpace.getCapability(csp + offset);
newFP = __builtin_cheri_seal(newFP, sealer);
CHERI_DBG("SETTING CFP %#p\n", (void *)newFP);
newRegisters.setFP(newFP);
}
#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 +338,13 @@ 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();
if (sealer != (uintcap_t)-1)
newSP = __builtin_cheri_seal(newSP, sealer);
#endif
newRegisters.setSP(newSP);

pint_t returnAddress = 0;
constexpr int lastReg = R::lastDwarfRegNum();
Expand All @@ -296,25 +367,43 @@ 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("SETTING RETURN REGISTER %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 (i == UNW_ARM64_CFP) {
// When sandboxing, we want to seal the frame pointer so that we
// make sure we can unseal it during resume. This is simply a
// decision to structure the code this way, instead of adding a
// check in resume. We don't really care if callee saved registers
// are unsealed because if we are resuming before the current
// compartment boundary, these arguments are never being leaked to
// another compartment.
#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_SANDBOX_OTYPES)
assert((savedReg == 0 || __builtin_cheri_tag_get(savedReg)) &&
"Value should be tagged");
if (sealer != (uintcap_t)-1)
savedReg = __builtin_cheri_seal(savedReg, sealer);
#endif
newRegisters.setFP(savedReg);
CHERI_DBG("SETTING FRAME POINTER %#p\n",
(void *)newRegisters.getFP());
} else {
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);
}
}

Expand Down Expand Up @@ -402,9 +491,25 @@ 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 && (__builtin_cheri_perms_get(returnAddress) &
_LIBUNWIND_CHERI_PERM_EXECUTIVE) != 0) {
restoreRegistersFromSandbox(addressSpace, registers, newRegisters,
sealer);
registers = newRegisters;
return UNW_STEP_SUCCESS;
}
#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 5f265bf

Please sign in to comment.