libcheri Stack

file: libcheri_stack.c

This file implements the “trusted stacks for the libcheri compartmentalization model”. “Each pthread has its own trusted stack that tracks calls between libcheri objects; frames contain the information required to recover control safely to the caller context (in another protection domain) both in the event of a CRetrun from the callee, and in the event of a trusted-stack unwind due to an exception termination execution in a object permaturely.”

Implement Stack as TLS: This implementation uses linker/compiler-provided thread-local storage for the stack, to avoid the need for explicit pthreads-based initialization and management that would otherwise need to incur inline in libcheri_invoke(). Although we might want to change this functional simplification for performance reasons in the future.

TODO: Reentrancy and signal: XXX: Today we access this field lock-free, since it won’t be accessed by other threads (at least currently). However, there is a question about reentrancy and signal handers, where some more care may be required, as signal handlers may wish to rewrite the trusted stack – and must be carefull in the event that the signal is delivered while the trusted stack is already being used/manipulated. One example would be if a domain transition or other manipulation is taking place when a timer interrupt fires. This suggests the notion of a critical section protecting the trusted stack, but we’d like to avoid the need for this in invocation path as our goal is to avoid system calls there. Some more thinking is required here – e.g., do we want to do a soft-interrupt-style thing along the lines if interrupts taken during spl()s in the kernel, with the singal handler ‘scheduling’ the change to take place once the preemted code returns..?

Stack Struct & init

/*
 * Currently, we have a maximum invocation depth that is low, and encoded in
 * the ABI.  We might want to revisit these choices to hide any limit from the
 * library consumer, to raise it, and perhaps to allow flexible per-thread
 * limits.  A lot will depend on eventual common usage patterns.
 */
#define	LIBCHERI_STACK_DEPTH	8	/* XXXRW: 8 is a nice round number. */
struct libcheri_stack {
	register_t	lcs_tsp;	/* Byte offset into lcs_frames,
					 * not frame index. */
	register_t	lcs_tsize;	/* Stack size, in bytes. */
	register_t	_lcs_pad0;
	register_t	_lcs_pad1;
	struct libcheri_stack_frame	lcs_frames[LIBCHERI_STACK_DEPTH];
} __aligned(CHERICAP_SIZE);

Questions

TLS model

XXXAR: we use local-exec tls model here to ensure that we can load the values without using the captable.

__thread struct libcheri_stack __libcheri_stack_tls_storage
    __attribute__((__aligned__(32), tls_model("local-exec"))) = {
	.lcs_tsize = LIBCHERI_STACK_SIZE,
	.lcs_tsp = LIBCHERI_STACK_SIZE,
};

Stack init – align check

// file:
//  lib/libcheri/libcheri_stack.c
         
void
libcheri_stack_init(void)
{

    /*
    * Ensure thread-local storage for the first thread's trusted stack is
    * suitably aligned.
    */
    assert(((vaddr_t)&__libcheri_stack_tls_storage % CHERICAP_SIZE) == 0);
}

stack get/set/unwind

APIs to get and set the trusted stack, which currently encode the internal stack structure, and assume a fixed-size trusted stack.

int libcheri_stack_get(struct libcheri_stack *lcsp): Return success/failure on “get to allow for the possibility that trusted stacks might be allocated on-demand in the future, and hence might not be present if libcheri hasn’t been used from a thread previously.

// file:
//  lib/libcheri/libcheri_stack.c: libcheri_stack_get(struct libcheri_stack *lcsp):

memcpy(lcsp, &__libcheri_stack_tls_storage, sizeof(*lcsp));

int libcheri_stack_numframes(int *numframesp): Return the number of frames on the trusted stack; similarly, retain a return value in case in the future we need to make this conditional on a trusted stack being allocated.

int libcheri_stack_set(struct libcheri_stack *lcsp): Allow the trusted stack to be set, subject to various safety constraints.

// file:
//  lib/libcheri/libcheri_stack.c

int
libcheri_stack_set(struct libcheri_stack *lcsp)
{

	if (lcsp->lcs_tsize != __libcheri_stack_tls_storage.lcs_tsize) {
		errno = EINVAL;
		return (-1);
	}
	if (lcsp->lcs_tsp < 0 || lcsp->lcs_tsp > LIBCHERI_STACK_SIZE ||
	    (lcsp->lcs_tsp % LIBCHERI_STACKFRAME_SIZE) != 0) {
		errno = EINVAL;
		return (-1);
	}
	memcpy(&__libcheri_stack_tls_storage, lcsp,
	    sizeof(__libcheri_stack_tls_storage));
	return (0);
}

int libcheri_stack_unwind(ucontext_t *uap, register_t ret, u_int op, u_int num_frames): Unwind the trust stack the specified number of frames (or all) – machine-independent portion.

Created Sep 3, 2019 // Last Updated Sep 4, 2019

If you could revise
the fundmental principles of
computer system design
to improve security...

... what would you change?