Skip to content

Commit

Permalink
libunwind: Use APIs exposed by RTLD to unwind the trusted stack
Browse files Browse the repository at this point in the history
Assembly stubs for _rtld_unw_{get,set}context are no longer needed.

Due to the significantly simplified implementation, the
LIBUNWIND_CHERI_C18N_SUPPORT option has been removed and c18n support is
now included by default for supported architectures (currently Morello
only).
  • Loading branch information
dpgao committed Oct 19, 2024
1 parent 0621ed3 commit 4c97ca9
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 203 deletions.
13 changes: 4 additions & 9 deletions libunwind/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ 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 @@ -294,14 +293,6 @@ 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 "ARM64" 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 Expand Up @@ -337,6 +328,10 @@ if (C_SUPPORTS_COMMENT_LIB_PRAGMA)
endif()
endif()

if (LIBUNWIND_HAS_CHERI_LIB_C18N)
add_compile_definitions(_LIBUNWIND_HAS_CHERI_LIB_C18N)
endif()

#===============================================================================
# Setup Source Code
#===============================================================================
Expand Down
2 changes: 2 additions & 0 deletions libunwind/cmake/config-ix.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,5 @@ else()
check_library_exists(dl dladdr "" LIBUNWIND_HAS_DL_LIB)
check_library_exists(pthread pthread_once "" LIBUNWIND_HAS_PTHREAD_LIB)
endif()

check_symbol_exists(dl_c18n_is_tramp link.h LIBUNWIND_HAS_CHERI_LIB_C18N)
5 changes: 0 additions & 5 deletions libunwind/include/__libunwind_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,4 @@
# define _LIBUNWIND_HIGHEST_DWARF_REGISTER 287
#endif // _LIBUNWIND_IS_NATIVE_ONLY

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

#endif // ____LIBUNWIND_CONFIG_H__
24 changes: 0 additions & 24 deletions libunwind/src/AddressSpace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,6 @@ 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 @@ -415,24 +409,6 @@ 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();
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
59 changes: 45 additions & 14 deletions libunwind/src/CompartmentInfo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,52 @@
#ifndef __COMPARTMENT_INFO_HPP__
#define __COMPARTMENT_INFO_HPP__

#include <link.h>

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 kCalleeSavedOffset = 2 * sizeof(void *);
static const uint32_t kCalleeSavedCount = 10;
static const uint32_t kReturnAddressOffset = 15 * sizeof(void *) + 8;
static const uint32_t kPCOffset = sizeof(void *);
#endif // _LIBUNWIND_TARGET_AARCH64
#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_CHERI_C18N_SUPPORT

// A wrapper for RTLD APIs related to library-based compartmentalisation (c18n).
template <typename A, typename R>
struct CompartmentInfo {
typedef typename A::pint_t pint_t;

static int unwindIfAtBoundary(R &registers) {
#ifdef _LIBUNWIND_HAS_CHERI_LIB_C18N
#ifdef _LIBUNWIND_TARGET_AARCH64
struct dl_c18n_compart_state state;
pint_t pc = registers.getIP();
pint_t tf = registers.getTrustedStack();

if (!dl_c18n_is_tramp(pc, (void *)tf))
return UNW_STEP_SUCCESS;

CHERI_DBG("COMPARTMENT BOUNDARY %#p\n", (void *)pc);

tf = (pint_t)dl_c18n_pop_trusted_stk(&state, (void *)tf);

registers.setTrustedStack(tf);
CHERI_DBG("C18N: SET TRUSTED STACK %#p\n", (void *)tf);

registers.setFP((pint_t)state.fp);
CHERI_DBG("C18N: SET FP %#p\n", state.fp);

registers.setSP((pint_t)state.sp);
CHERI_DBG("C18N: SET SP: %#p\n", state.sp);

registers.setIP((pint_t)state.pc);
CHERI_DBG("C18N: SET IP: %#p\n", state.pc);

for (size_t i = 0; i < sizeof(state.regs) / sizeof(*state.regs); ++i) {
registers.setCapabilityRegister(UNW_ARM64_C19 + i, (pint_t)state.regs[i]);
CHERI_DBG("C18N: SET REGISTER: %lu (%s): %#p\n",
UNW_ARM64_C19 + i,
registers.getRegisterName(UNW_ARM64_C19 + i),
state.regs[i]);
}
#endif
#endif // _LIBUNWIND_HAS_CHERI_LIB_C18N
return UNW_STEP_SUCCESS;
}
};
} // namespace libunwind
#endif // __COMPARTMENT_INFO_HPP__
100 changes: 0 additions & 100 deletions libunwind/src/DwarfInstructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#include "Registers.hpp"
#include "DwarfParser.hpp"
#include "config.h"
#include "CompartmentInfo.hpp"


namespace libunwind {
Expand Down Expand Up @@ -55,14 +54,6 @@ 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 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 Down Expand Up @@ -255,75 +246,6 @@ 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>
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.getP(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.getP(csp + CI.kNewSPOffset);
newRegisters.setSP(nextRCSP);
CHERI_DBG("SANDBOX: SETTING RESTRICTED CSP: %#p\n",
(void *)newRegisters.getSP());
// Restore callee-saved registers
// Restore: c19-c28
for (size_t i = 0, offset = CI.kCalleeSavedOffset; i < CI.kCalleeSavedCount;
++i, offset += sizeof(void *)) {
pint_t regValue = addressSpace.getP(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);
}
// Restore the frame pointer
pint_t newFP = addressSpace.getP(csp);
CHERI_DBG("SANDBOX: SETTING CFP %#p\n", (void *)newFP);
newRegisters.setFP(newFP);
// Get the new return address.
return addressSpace.getP(csp + CI.kPCOffset);
}

template <typename A, typename R>
bool DwarfInstructions<A, R>::isCompartmentTransitionTrampoline(
pint_t ecsp, A &addressSpace, CompartmentInfo &CI, pint_t returnAddress) {
ptraddr_t expectedReturnAddress =
addressSpace.template get<ptraddr_t>(ecsp + CI.kReturnAddressOffset);
CHERI_DBG(
"isCompartmentTransitionTrampoline(): expectedReturnAddress: 0x%lx\n",
expectedReturnAddress);
return expectedReturnAddress == returnAddress;
}
#else // _LIBUNWIND_TARGET_AARCH64
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 @@ -483,28 +405,6 @@ 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 without
// 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.getTrustedStack();
if (__builtin_cheri_sealed_get(csp))
csp = __builtin_cheri_unseal(csp, 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);
Expand Down
4 changes: 2 additions & 2 deletions libunwind/src/Registers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1874,10 +1874,10 @@ class _LIBUNWIND_HIDDEN Registers_arm64 {
void setIP(uintptr_t value) { _registers.__pc = value; }
uintptr_t getFP() const { return _registers.__fp; }
void setFP(uintptr_t value) { _registers.__fp = value; }
#if defined(__CHERI_PURE_CAPABILITY__) && defined(_LIBUNWIND_CHERI_C18N_SUPPORT)
#ifdef __CHERI_PURE_CAPABILITY__
uintptr_t getTrustedStack() const { return _registers.__ecsp; }
void setTrustedStack(uintptr_t value) { _registers.__ecsp = value; }
#endif // __CHERI_PURE_CAPABILITY__ && _LIBUNWIND_CHERI_C18N_SUPPORT
#endif

private:
struct GPRs {
Expand Down
10 changes: 10 additions & 0 deletions libunwind/src/UnwindCursor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ extern "C" _Unwind_Reason_Code __libunwind_seh_personality(
#include "Registers.hpp"
#include "RWMutex.hpp"
#include "Unwind-EHABI.h"
#include "CompartmentInfo.hpp"

namespace libunwind {

Expand Down Expand Up @@ -1294,6 +1295,10 @@ class UnwindCursor : public AbstractUnwindCursor{
}
#endif // defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND)

int stepThroughIfAtC18NBoundary() {
return CompartmentInfo<A, R>::unwindIfAtBoundary(_registers);
}

A &_addressSpace;
R _registers;
unw_proc_info_t _info;
Expand Down Expand Up @@ -2867,6 +2872,11 @@ int UnwindCursor<A, R>::step() {
#endif
}

// If we are at a compartment boundary, step through it by asking RTLD to
// restore registers from the trusted stack.
if (result == UNW_STEP_SUCCESS)
result = stepThroughIfAtC18NBoundary();

// update info based on new PC
if (result == UNW_STEP_SUCCESS) {
this->setInfoBasedOnIPRegister(true);
Expand Down
46 changes: 17 additions & 29 deletions libunwind/src/UnwindRegistersRestore.S
Original file line number Diff line number Diff line change
Expand Up @@ -703,25 +703,6 @@ Lnovec:

#elif defined(__aarch64__)

//
// extern "C" void __rtld_unw_setcontext(void *c0, void *c1,
// void *rcsp, void **sealed_ecsp);
//
#if defined(__CHERI_PURE_CAPABILITY__)
DEFINE_LIBUNWIND_FUNCTION(__rtld_unw_setcontext)
mov c16, c2
ldp c2, c3, [c3, #(-0x210 + 0x20)]
mov csp, c16
#ifdef __ARM_MORELLO_PURECAP_BENCHMARK_ABI
and x30, x30, #~1
ret x30
#else
ret
#endif
END_LIBUNWIND_FUNCTION(__rtld_unw_setcontext)
WEAK_ALIAS(__rtld_unw_setcontext, _rtld_unw_setcontext)
#endif

//
// extern "C" void __libunwind_Registers_arm64_jumpto(Registers_arm64 *);
//
Expand All @@ -731,8 +712,18 @@ WEAK_ALIAS(__rtld_unw_setcontext, _rtld_unw_setcontext)
.p2align 2
DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto)
#ifdef __CHERI_PURE_CAPABILITY__
#ifdef _LIBUNWIND_HAS_CHERI_LIB_C18N
// Preserve the argument in a callee-saved register instead of pushing it onto
// the stack because stack unwinding will switch the stack.
mov c19, c0
ldr c0, [c19, #0x1f0] // Pass the target untrusted stack pointer
ldr c1, [c19, #0x210] // Pass the target trusted stack pointer
bl dl_c18n_unwind_trusted_stk
mov c0, c19
#endif

// skip restore of c0,c1 for now
// also skip restoring c2 and c3 because they will get clobbered later on
ldp c2, c3, [c0, #0x020]
ldp c4, c5, [c0, #0x040]
ldp c6, c7, [c0, #0x060]
ldp c8, c9, [c0, #0x080]
Expand Down Expand Up @@ -772,17 +763,14 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto)
// context struct, because it is allocated on the stack, and an exception
// could clobber the de-allocated portion of the stack after csp has been
// restored.
ldr c2, [c0, #0x1f0]
add c3, c0, #0x210
ldp c0, c1, [c0, #0x000]
// XXX: variant PCS is not yet supported by rtld, work around it
// using a function pointer.
adrp c16, :got:_rtld_unw_setcontext
ldr c16, [c16, :got_lo12:_rtld_unw_setcontext]
ldr c16, [c0, #0x1f0]
ldp c0, c1, [c0, #0x000] // restore c0,c1
mov csp,c16 // restore csp
#ifdef __ARM_MORELLO_PURECAP_BENCHMARK_ABI
br x16
and x30, x30, #~1
ret x30 // jump to pc
#else
br c16
ret // jump to pcc
#endif
#else
// skip restore of x0,x1 for now
Expand Down
Loading

0 comments on commit 4c97ca9

Please sign in to comment.