Skip to content

Commit

Permalink
[libunwind] Support rtld-c18n as the runtime linker.
Browse files Browse the repository at this point in the history
This commit adds support for backtrace and exception handling in
libunwind when the process is running with the compartmentalization
runtime linker. The unwinding process remains the same until a
trampoline is encountered as the return address. This means that we are
crossing compartment boundaries and we need to gather the unwind
information from the runtime linker. We do this by reading information
from the executive stack that the runtime linker populates for us in
unw_getcontext.

It also adds a new class, CompartmentInfo, which is responsible for
abstracting away the details of c18n compartments. Currently, it is only
used to define the constants relating to the trusted frame layout.

The otype allocated to libunwind is given to libunwind by the runtime
linker via the _rtld_unw_getsealer function, and as such this code is
guarded by a LIBUNWIND_SANDBOX_OTYPES define. The sealer is used to
internally access the executive stack pointer in order to unwind through
compartment boundaries. This design may change in the future.

This functionality only works on Morello right now.
  • Loading branch information
dstolfa committed Jun 10, 2024
1 parent b89cd6a commit 6643eaf
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 38 deletions.
9 changes: 9 additions & 0 deletions libunwind/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ option(LIBUNWIND_IS_BAREMETAL "Build libunwind for baremetal targets." OFF)
option(LIBUNWIND_USE_FRAME_HEADER_CACHE "Cache frame headers for unwinding. Requires locking dl_iterate_phdr." OFF)
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_CHERI_C18N_SUPPORT "Use a libunwind implementation that supports a CHERI c18n RTLD." OFF)

set(LIBUNWIND_LIBDIR_SUFFIX "${LLVM_LIBDIR_SUFFIX}" CACHE STRING
"Define suffix of library directory name (32/64)")
Expand Down Expand Up @@ -293,6 +294,14 @@ if (NOT LIBUNWIND_ENABLE_THREADS)
add_compile_flags(-D_LIBUNWIND_HAS_NO_THREADS)
endif()

# Sandboxing and c18n support
if (LIBUNWIND_CHERI_C18N_SUPPORT)
if (NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^aarch64c" OR NOT CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
message(FATAL_ERROR "LIBUNWIND_CHERI_C18N_SUPPORT is not supported for ${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
endif()
add_compile_flags(-D_LIBUNWIND_CHERI_C18N_SUPPORT)
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
11 changes: 8 additions & 3 deletions 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 Expand Up @@ -235,4 +235,9 @@
# define _LIBUNWIND_HIGHEST_DWARF_REGISTER 287
#endif // _LIBUNWIND_IS_NATIVE_ONLY

#if defined(_LIBUNWIND_CHERI_C18N_SUPPORT) && \
!(defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_ARM))
# error "LIBUNWIND_CHERI_C18N_SUPPORT is only supported on Morello architectures."
#endif

#endif // ____LIBUNWIND_CONFIG_H__
3 changes: 2 additions & 1 deletion 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 = 240,
};

// 32-bit ARM registers. Numbers match DWARF for ARM spec #3.1 Table 1.
Expand Down
27 changes: 26 additions & 1 deletion libunwind/src/AddressSpace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,12 @@ 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_CHERI_C18N_SUPPORT)
static pint_t getUnwindSealer();
static bool isValidSealer(pint_t sealer) {
return __builtin_cheri_tag_get(sealer);
}
#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_CHERI_C18N_SUPPORT
__attribute__((always_inline))
uintptr_t getP(pint_t addr);
uint64_t getRegister(pint_t addr);
Expand Down Expand Up @@ -408,6 +414,24 @@ inline uint64_t LocalAddressSpace::getRegister(pint_t addr) {
#endif
}

#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_CHERI_C18N_SUPPORT)
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)0;
}
_LIBUNWIND_WEAK_ALIAS(__rtld_unw_getsealer, _rtld_unw_getsealer)
}

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

/// 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 @@ -932,7 +956,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
36 changes: 36 additions & 0 deletions libunwind/src/CompartmentInfo.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//
// Abstracts unwind information when used with a compartmentalizing runtime
// linker.
//
//===----------------------------------------------------------------------===//

#ifndef __COMPARTMENT_INFO_HPP__
#define __COMPARTMENT_INFO_HPP__

namespace libunwind {
class _LIBUNWIND_HIDDEN CompartmentInfo {
public:
#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_CHERI_C18N_SUPPORT)
static CompartmentInfo sThisCompartmentInfo;
// Per-architecture trusted stack frame layout.
#if defined(_LIBUNWIND_TARGET_AARCH64)
static const uint32_t kNewSPOffset = 12 * sizeof(void *);
static const uint32_t kNextOffset = 14 * sizeof(void *);
static const uint32_t kFPOffset = 0;
static const uint32_t kCalleeSavedOffset = 2 * sizeof(void *);
static const uint32_t kCalleeSavedCount = 10;
static const uint32_t kCalleeSavedSize = sizeof(void *);
static const uint32_t kReturnAddressOffset = 15 * sizeof(void *) + 8;
static const uint32_t kPCOffset = sizeof(void *);
static const uint32_t kTrustedFrameSize = 16 * sizeof(void *);
#endif // _LIBUNWIND_TARGET_AARCH64
#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_CHERI_C18N_SUPPORT
};
} // namespace libunwind
#endif // __COMPARTMENT_INFO_HPP__
152 changes: 137 additions & 15 deletions libunwind/src/DwarfInstructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "Registers.hpp"
#include "DwarfParser.hpp"
#include "config.h"
#include "CompartmentInfo.hpp"


namespace libunwind {
Expand Down Expand Up @@ -54,6 +55,17 @@ class DwarfInstructions {
typedef typename CFI_Parser<A>::FDE_Info FDE_Info;
typedef typename CFI_Parser<A>::CIE_Info CIE_Info;

#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_CHERI_C18N_SUPPORT)
static size_t restoreCalleeSavedRegisters(pint_t csp, A &addressSpace,
R &newRegisters,
CompartmentInfo &CI);
static pint_t restoreRegistersFromSandbox(pint_t csp, A &addressSpace,
R &newRegisters,
CompartmentInfo &CI, pint_t sealer);
static bool isCompartmentTransitionTrampoline(pint_t ecsp, A &addressSpace,
CompartmentInfo &CI,
pint_t returnAddress);
#endif
static pint_t evaluateExpression(pint_t expression, A &addressSpace,
const R &registers,
pint_t initialStackValue);
Expand All @@ -72,9 +84,8 @@ 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);
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 @@ -246,6 +257,93 @@ bool DwarfInstructions<A, R>::getRA_SIGN_STATE(A &addressSpace, R registers,
}
#endif

#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_CHERI_C18N_SUPPORT)
#if defined(_LIBUNWIND_TARGET_AARCH64)
template <typename A, typename R>
size_t DwarfInstructions<A, R>::restoreCalleeSavedRegisters(
pint_t csp, A &addressSpace, R &newRegisters, CompartmentInfo &CI) {
// Restore callee-saved registers
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);
newRegisters.setCapabilityRegister(UNW_ARM64_C19 + i, regValue);
CHERI_DBG("SETTING CALLEE SAVED CAPABILITY REGISTER: %lu (%s): %#p "
"(offset=%zu)\n",
UNW_ARM64_C19 + i,
newRegisters.getRegisterName(UNW_ARM64_C19 + i), (void *)regValue,
offset);
}

return offset;
}

template <typename A, typename R>
typename A::pint_t DwarfInstructions<A, R>::restoreRegistersFromSandbox(
pint_t csp, A &addressSpace, R &newRegisters, CompartmentInfo &CI,
pint_t sealer) {
// Get the unsealed executive CSP
assert(__builtin_cheri_tag_get((void *)csp) &&
"Executive stack should be tagged!");
// Derive the new executive CSP
pint_t nextCSP = addressSpace.getCapability(csp + CI.kNextOffset);
// Seal ECSP
nextCSP = __builtin_cheri_seal(nextCSP, sealer);
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);
newRegisters.setSP(nextRCSP);
CHERI_DBG("SANDBOX: SETTING RESTRICTED CSP: %#p\n",
(void *)newRegisters.getSP());
size_t offset =
restoreCalleeSavedRegisters(csp, addressSpace, newRegisters, CI);
// Restore the frame pointer
pint_t newFP = addressSpace.getCapability(csp);
CHERI_DBG("SANDBOX: SETTING CFP %#p (offset=%zu)\n", (void *)newFP, offset);
newRegisters.setFP(newFP);
// Get the new return address.
return addressSpace.getCapability(csp + CI.kPCOffset);
}

template <typename A, typename R>
bool DwarfInstructions<A, R>::isCompartmentTransitionTrampoline(
pint_t ecsp, A &addressSpace, CompartmentInfo &CI, pint_t returnAddress) {
// TODO(cheri): Use a cfp-based approach rather than the cookie.
ptraddr_t expectedReturnAddress =
addressSpace.get64(ecsp + CI.kReturnAddressOffset);
CHERI_DBG(
"isCompartmentTransitionTrampoline(): expectedReturnAddress: 0x%lx\n",
expectedReturnAddress);
return expectedReturnAddress == returnAddress;
}
#else // _LIBUNWIND_TARGET_AARCH64
template <typename A, typename R>
size_t DwarfInstructions<A, R>::restoreCalleeSavedRegisters(
pint_t csp, A &addressSpace, R &newRegisters, CompartmentInfo &CI) {
assert(0 && "not implemented on this architecture");
return 0;
}
template <typename A, typename R>
typename A::pint_t DwarfInstructions<A, R>::restoreRegistersFromSandbox(
pint_t csp, A &addressSpace, R &newRegisters, CompartmentInfo &CI,
pint_t sealer) {
assert(0 && "not implemented on this architecture");
return (pint_t)0;
}
template <typename A, typename R>
bool DwarfInstructions<A, R>::isCompartmentTransitionTrampoline(
pint_t ecsp, A &addressSpace, CompartmentInfo &CI, pint_t returnAddress) {
assert(0 && "not implemented on this architecture");
return false;
}
#endif // _LIBUNWIND_TARGET_AARCH64
#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_CHERI_C18N_SUPPORT

template <typename A, typename R>
int DwarfInstructions<A, R>::stepWithDwarf(A &addressSpace, pc_t pc,
pint_t fdeStart, R &registers,
Expand Down Expand Up @@ -274,7 +372,9 @@ 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);
pint_t newSP = cfa;
CHERI_DBG("SETTING SP: %#p\n", (void *)newSP);
newRegisters.setSP(newSP);

pint_t returnAddress = 0;
constexpr int lastReg = R::lastDwarfRegNum();
Expand All @@ -297,25 +397,26 @@ 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)));
capability_t savedReg = getSavedCapabilityRegister(
addressSpace, registers, cfa, prolog.savedRegisters[i]);
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 @@ -403,9 +504,30 @@ int DwarfInstructions<A, R>::stepWithDwarf(A &addressSpace, pc_t pc,
}
#endif

#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_CHERI_C18N_SUPPORT)
// If the sealer is not valid (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.
uintptr_t sealer = addressSpace.getUnwindSealer();
if (addressSpace.isValidSealer(sealer)) {
pint_t csp = registers.getUnsealedTrustedStack(sealer);
CompartmentInfo &CI = CompartmentInfo::sThisCompartmentInfo;
if (csp != 0 && isCompartmentTransitionTrampoline(csp, addressSpace, CI,
returnAddress)) {
CHERI_DBG("%#p: detected a trampoline, unwinding from sandbox\n",
(void *)returnAddress);
returnAddress = restoreRegistersFromSandbox(
csp, addressSpace, newRegisters, CI, sealer);
}
}
#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 6643eaf

Please sign in to comment.