Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libunwind: Use new unwinding APIs under c18n #740

Merged
merged 2 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
dpgao marked this conversation as resolved.
Show resolved Hide resolved
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_trampoline 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>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the declarations for dl_c18n_is_tramp and dl_c18n_pop_trusted_stk are now in link_elf.h, I include link.h to avoid having to re-declare both functions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but then you need to detect them in CMake, because otherwise you'll break the build on Linux or current/old versions of CheriBSD.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, why isn't the struct declared by system headers then?..

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm maybe it's easier to just hard-code everything here in libunwind then.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, test it? See if you can build against old and new CheriBSD.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And make sure to test against CHERI-RISC-V too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I've successfully built the latest commit with CTSRD-CHERI/cheribsd#2123 for both Morello and CHERI-RISC-V, and it also builds on an older CheriBSD installation through CMake.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And when built for Morello with 2123 it detected c18n support?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, CMake detects c18n support on 2123.


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 {
dpgao marked this conversation as resolved.
Show resolved Hide resolved
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_trampoline(pc, (void *)tf))
return UNW_STEP_SUCCESS;

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

tf = (pint_t)dl_c18n_pop_trusted_stack(&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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whilst using a saved register is better anyway, this justification seems undesirable, shouldn't it leave us on the same stack? Then when this function / longjmp returns it can restore the real stack that corresponds to what the compartment should have.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can leave the stack unchanged, but then there would be a small window between after dl_c18n_unwind_trusted_stk returns and the new stack is installed where the trusted stack says we're in the target compartment but the stack pointer still contains that of the source compartment. This doesn't seem desirable.

Under the current implementation, after dl_c18n_unwind_trusted_stk returns, we are for all purposes already in the target compartment (i.e. not even in libunwind's compartment anymore). And the final ret resumes execution of the target compartment without going through a trampoline. (Because unw_getcontext is a trusted function, it has access to the raw return capability to the target compartment.)

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_stack
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
Loading