Skip to content

Commit

Permalink
Support the clone system call.
Browse files Browse the repository at this point in the history
This implements two new LKL hooks. The first one to create an lthread
with a specific initial register state (to capture the returns-twice
behaviour of clone, along with the caller's ability to define the stack
and TLS addresses).  The new thread is immediately associated with the
Linux task structure (normally, lthreads are associated with Linux tasks
lazily when they perform a system call).

The second hook destroys a thread.  This is done in response to an exit
system call.  This is somewhat complicated, because LKL never returns to
this thread and the thread's stack may be deallocated by the time we
exit it.

The lthread scheduler does not have an easy way of adding a mechanism to
kill a thread without that thread running.  We can add one eventually,
but for now create a temporary stack that lthreads can use during
teardown and make them run the teardown from there.

Disable access02 test.  It is spuriously passing and this makes it fail.
See #277 for more information.

Fixes #155
  • Loading branch information
davidchisnall committed May 20, 2020
1 parent 60d2249 commit 24b7865
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 16 deletions.
2 changes: 1 addition & 1 deletion sgx-lkl-musl
50 changes: 48 additions & 2 deletions src/include/enclave/lthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,23 @@ extern "C"
size_t sleeptime_ns,
size_t futex_wake_spins);

/**
* Create a new thread where the caller manages the initial thread state.
* The newly created thread is returned via `new_lt`. The newly created
* thread will begin executing from `pc`, with its stack pointer set to
* `sp` and its TLS area set to `tls`. It is the caller's responsibility
* to ensure that the stack and TLS area allocated by this thread are
* cleaned up.
*
* The thread is not scheduled by this call and must be explicitly
* scheduled by the caller.
*/
int lthread_create_primitive(
struct lthread** new_lt,
void* pc,
void* sp,
void* tls);

int lthread_create(
struct lthread** new_lt,
struct lthread_attr* attrp,
Expand Down Expand Up @@ -238,9 +255,38 @@ extern "C"

int lthread_key_delete(long key);

void* lthread_getspecific(long key);
/**
* Access a thread-local variable corresponding to the key given by `key`,
* in the thread specified by `lt`. This function is not safe to call
* while `lt` is running or concurrently with a call to
* `lthread_setspecific_remote` on the same lthread. It is the caller's
* responsibility to ensure that this does not happen, for example after
* the thread has been removed from the scheduler during tear-down or by
* explicitly descheduling it.
*/
void* lthread_getspecific_remote(struct lthread* lt, long key);

int lthread_setspecific(long key, const void* value);
/**
* Sets a thread-local variable corresponding to the key given by `key` to
* `value`, in the thread specified by `lt`. This function is not safe to
* call while `lt` is running or concurrently with a call to
* `lthread_setspecific_remote` on the same lthread. It is the caller's
* responsibility to ensure that this does not happen. The most common use
* for this is between a call to `lthread_create_primitive` and
* `__scheduler_enqueue`, to initialise a thread-local variable before a
* thread starts.
*/
int lthread_setspecific_remote(struct lthread* lt, long key, const void* value);

static void* lthread_getspecific(long key)
{
return lthread_getspecific_remote(lthread_current(), key);
}

static int lthread_setspecific(long key, const void* value)
{
return lthread_setspecific_remote(lthread_current(), key, value);
}

static inline void __scheduler_enqueue(struct lthread* lt)
{
Expand Down
66 changes: 66 additions & 0 deletions src/lkl/posix-host.c
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,70 @@ static lkl_thread_t thread_create(void (*fn)(void*), void* arg)
return (lkl_thread_t)thread;
}

/**
* Create an lthread to back a Linux task, created with a clone-family call
* into the kernel.
*/
static lkl_thread_t thread_create_host(void* pc, void* sp, void* tls, struct lkl_tls_key* task_key, void* task_value)
{
struct lthread* thread;
// Create the thread. The lthread layer will set up the threading data
// structures and prepare the lthread to run with the specified instruction
// and stack addresses.
int ret = lthread_create_primitive(&thread, pc, sp, tls);
if (ret)
{
sgxlkl_fail("lthread_create failed\n");
}
// Store the host task pointer. LKL normally sets this lazily the first
// time that a thread calls into the LKL. Threads created via this
// mechanism begin life in the kernel and so need to be associated with the
// kernel task that created them.
lthread_setspecific_remote(thread, task_key->key, task_value);
// Mark the thread as runnable. This must be done *after* the
// `lthread_setspecific_remote` call, to ensure that the thread does not
// run while we are modifying its TLS.
__scheduler_enqueue(thread);
return (lkl_thread_t)thread;
}

static void host_thread_exit(void)
{
LKL_TRACE("enter");
lthread_exit(0);
}

/**
* Destroy the lthread backing a host task created with a clone-family call.
* This is called after an `exit` system call. The system call does not return
* and the lthread backing the LKL thread that issued the task will not be
* invoked again.
*/
static void thread_destroy_host(lkl_thread_t tid, struct lkl_tls_key* task_key)
{
// Somewhat arbitrary size of the stack for teardown. This must be big
// enough that `host_thread_exit` can run and call any remaining TLS
// destructors. This can be removed once there is a clean mechanism for
// destroying a not-running lthread without scheduling it.
static const size_t teardown_stack_size = 8192;
struct lthread *thr = (struct lthread*)tid;
// The thread is currently blocking on the LKL scheduler semaphore, remove
// it from the sleeping list.
_lthread_desched_sleep(thr);
// Delete its task reference in TLS. Without this, the thread's destructor
// will call back into LKL and deadlock.
lthread_setspecific_remote(thr, task_key->key, NULL);
// Give the thread a stack to use during lthread teardown.
thr->attr.stack_size = teardown_stack_size;
thr->attr.stack = enclave_mmap(0, teardown_stack_size, 0, PROT_READ | PROT_WRITE, 1);
// Set up the state so that this will call into the host_thread_exit function and
thr->ctx.eip = host_thread_exit;
thr->ctx.esp = thr->attr.stack + teardown_stack_size;
// Schedule the thread again. It will exit when it is next scheduled.
__scheduler_enqueue(thr);
}


static void thread_detach(void)
{
LKL_TRACE("enter\n");
Expand Down Expand Up @@ -585,6 +649,8 @@ struct lkl_host_operations sgxlkl_host_ops = {
.panic = panic,
.terminate = terminate,
.thread_create = thread_create,
.thread_create_host = thread_create_host,
.thread_destroy_host = thread_destroy_host,
.thread_detach = thread_detach,
.thread_exit = thread_exit,
.thread_join = thread_join,
Expand Down
125 changes: 114 additions & 11 deletions src/sched/lthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,9 @@ __asm__(" .text \n"
" movq 24(%rdi), %rbx \n"
" movq 8(%rdi), %rbp # restore frame_pointer \n"
" movq 0(%rdi), %rsp # restore stack_pointer \n"
" movq 16(%rdi), %rax # restore insn_pointer \n"
" movq %rax, (%rsp) \n"
" movq 16(%rdi), %rdx # restore insn_pointer \n"
" xor %rax, %rax # Clear return register 1\n"
" movq %rdx, (%rsp) \n"
" ret \n");
#endif

Expand Down Expand Up @@ -627,6 +628,98 @@ static void init_file_lock(FILE* f)
f->lock = 0;
}

int lthread_create_primitive(
struct lthread** new_lt,
void* pc,
void* sp,
void* tls)
{
struct lthread* lt;

// FIXME: Remove when we no longer have lthread / libc layering issues.
if (!libc.threaded && libc.threads_minus_1 >= 0)
{
for (FILE* f = *__ofl_lock(); f; f = f->next)
init_file_lock(f);
__ofl_unlock();
init_file_lock(__stdin_used);
init_file_lock(__stdout_used);
init_file_lock(__stderr_used);
libc.threaded = 1;
}

if ((lt = oe_calloc(1, sizeof(struct lthread))) == NULL)
{
return -1;
}

// FIXME: Once lthread / pthread layering is fixed, just use the tls
// argument as gs base. We can't do that now because _lthread_free
// attempts to unmap this area.
lt->itlssz = libc.tls_size;
if (libc.tls_size)
{
if ((lt->itls = (uint8_t*)enclave_mmap(
0,
lt->itlssz,
0, /* map_fixed */
PROT_READ | PROT_WRITE,
1 /* zero_pages */)) == MAP_FAILED)
{
oe_free(lt);
return -1;
}
if (__init_utp(__copy_utls(lt, lt->itls, lt->itlssz), 0))
{
oe_free(lt);
return -1;
}
}
LIST_INIT(&lt->tls);
lt->locale = &libc.global_locale;

lt->attr.state = BIT(LT_ST_READY);
lt->tid = a_fetch_add(&spawned_lthreads, 1);
lt->robust_list.head = &lt->robust_list.head;

lthread_set_funcname(lt, "cloned host task");

if (new_lt)
{
*new_lt = lt;
}

a_inc(&libc.threads_minus_1);

SGXLKL_TRACE_THREAD(
"[tid=%-3d] create: thread_count=%d\n", lt->tid, thread_count);

#if DEBUG
struct lthread_queue* new_ltq =
(struct lthread_queue*)oe_malloc(sizeof(struct lthread_queue));
new_ltq->lt = lt;
new_ltq->next = NULL;
if (__active_lthreads_tail)
{
__active_lthreads_tail->next = new_ltq;
}
else
{
__active_lthreads = new_ltq;
}
__active_lthreads_tail = new_ltq;
#endif /* DEBUG */

// Set up the lthread initial PC and stack pointer.
lt->ctx.eip = pc;
// Reserve space on the stack for the return address. `_switch` will pop
// this off.
lt->ctx.esp = ((char*)sp) - sizeof(void*);
(void)tls;

return 0;
}

int lthread_create(
struct lthread** new_lt,
struct lthread_attr* attrp,
Expand Down Expand Up @@ -899,10 +992,15 @@ int lthread_setcancelstate(int new, int* old)
return 0;
}

static struct lthread_tls* lthread_findtlsslot(long key)
/**
* Find the TLS slot for a specified lthread. It is the caller's
* responsibility to ensure that the specified lthread is not concurrently
* accessed. `lthread_current()` is always safe to use here as is any lthread
* that has not yet been scheduled.
*/
static struct lthread_tls* lthread_findtlsslot(struct lthread *lt, long key)
{
struct lthread_tls *d, *d_tmp;
struct lthread* lt = lthread_current();
LIST_FOREACH_SAFE(d, &lt->tls, tls_next, d_tmp)
{
if (d->key == key)
Expand All @@ -913,10 +1011,15 @@ static struct lthread_tls* lthread_findtlsslot(long key)
return NULL;
}

static int lthread_addtlsslot(long key, void* data)
/**
* Add a TLS slot for a specified lthread. It is the caller's responsibility
* to ensure that the specified lthread is not concurrently accessed.
* `lthread_current()` is always safe to use here as is any lthread that has
* not yet been scheduled.
*/
static int lthread_addtlsslot(struct lthread* lt, long key, void* data)
{
struct lthread_tls* d;
struct lthread* lt = lthread_current();
d = oe_calloc(1, sizeof(struct lthread_tls));
if (d == NULL)
{
Expand All @@ -928,27 +1031,27 @@ static int lthread_addtlsslot(long key, void* data)
return 0;
}

void* lthread_getspecific(long key)
void* lthread_getspecific_remote(struct lthread* lt, long key)
{
struct lthread_tls* d;
if ((d = lthread_findtlsslot(key)) == NULL)
if ((d = lthread_findtlsslot(lt, key)) == NULL)
{
return NULL;
}
return d->data;
}

int lthread_setspecific(long key, const void* value)
int lthread_setspecific_remote(struct lthread* lt, long key, const void* value)
{
struct lthread_tls* d;
if ((d = lthread_findtlsslot(key)) != NULL)
if ((d = lthread_findtlsslot(lt, key)) != NULL)
{
d->data = (void*)value;
return 0;
}
else
{
return lthread_addtlsslot(key, (void*)value);
return lthread_addtlsslot(lt, key, (void*)value);
}
}

Expand Down
10 changes: 10 additions & 0 deletions tests/basic/clone/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM alpine:3.6 AS builder

RUN apk add --no-cache gcc musl-dev

ADD *.c /
RUN gcc -fPIE -pie -o clone clone.c -g

FROM alpine:3.6

COPY --from=builder clone .
41 changes: 41 additions & 0 deletions tests/basic/clone/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
include ../../common.mk

PROG=clone
PROG_SRC=$(PROG).c
IMAGE_SIZE=5M

EXECUTION_TIMEOUT=60

SGXLKL_ENV=SGXLKL_ETHREADS=8 SGXLKL_VERBOSE=1 SGXLKL_KERNEL_VERBOSE=1
SGXLKL_HW_PARAMS=--hw-debug
SGXLKL_SW_PARAMS=--sw-debug

SGXLKL_ROOTFS=sgx-lkl-rootfs.img

.DELETE_ON_ERROR:
.PHONY: all clean

$(SGXLKL_ROOTFS): $(PROG_SRC)
${SGXLKL_DISK_TOOL} create --size=${IMAGE_SIZE} --docker=./Dockerfile ${SGXLKL_ROOTFS}

gettimeout:
@echo ${EXECUTION_TIMEOUT}

run: run-hw run-sw

run-gdb: run-hw-gdb

run-hw: ${SGXLKL_ROOTFS}
$(SGXLKL_ENV) $(SGXLKL_STARTER) $(SGXLKL_HW_PARAMS) $(SGXLKL_ROOTFS) $(PROG)

run-sw: ${SGXLKL_ROOTFS}
$(SGXLKL_ENV) $(SGXLKL_STARTER) $(SGXLKL_SW_PARAMS) $(SGXLKL_ROOTFS) $(PROG)

run-hw-gdb: ${SGXLKL_ROOTFS}
$(SGXLKL_ENV) $(SGXLKL_GDB) --args $(SGXLKL_STARTER) $(SGXLKL_HW_PARAMS) $(SGXLKL_ROOTFS) $(PROG)

run-sw-gdb: ${SGXLKL_ROOTFS}
$(SGXLKL_ENV) $(SGXLKL_GDB) --args $(SGXLKL_STARTER) $(SGXLKL_SW_PARAMS) $(SGXLKL_ROOTFS) $(PROG)

clean:
rm -f $(SGXLKL_ROOTFS) $(PROG)
Loading

0 comments on commit 24b7865

Please sign in to comment.