From b4f0767d388c7f3629666400f48ede1a7b4be7f5 Mon Sep 17 00:00:00 2001 From: David Gibson Date: Fri, 20 Jan 2017 23:49:43 +1100 Subject: [PATCH] coroutine: Stack allocation At present, coroutine stacks must be allocated explicitly by the user, then initialized with coroutine_stack_init(). This adds a new coroutine_stack_alloc() function which allocates a stack, making life easier for users. coroutine_stack_release() will automatically determine if the given stack was set up with _init() or alloc() and act accordingly. The stacks are allocate with mmap() rather than a plain malloc(), and a guard page is added, so an overflow of the stack should result in a relatively debuggable SEGV instead of random data corruption. Signed-off-by: David Gibson --- ccan/coroutine/coroutine.c | 76 +++++++++++++++++++++++++++++++++++-- ccan/coroutine/coroutine.h | 27 ++++++++++++- ccan/coroutine/test/api-1.c | 18 ++++++--- ccan/coroutine/test/api-2.c | 17 ++------- ccan/coroutine/test/api-3.c | 18 +++++---- 5 files changed, 123 insertions(+), 33 deletions(-) diff --git a/ccan/coroutine/coroutine.c b/ccan/coroutine/coroutine.c index fa321ab8..f77b7790 100644 --- a/ccan/coroutine/coroutine.c +++ b/ccan/coroutine/coroutine.c @@ -5,6 +5,9 @@ #include #include +#include +#include + #include #include #include @@ -70,23 +73,90 @@ struct coroutine_stack *coroutine_stack_init(void *buf, size_t bufsize, ((char *)buf + bufsize - metasize) - 1; #endif - stack->magic = COROUTINE_STACK_MAGIC; + stack->magic = COROUTINE_STACK_MAGIC_BUF; stack->size = size; vg_register_stack(stack); return stack; } +struct coroutine_stack *coroutine_stack_alloc(size_t totalsize, size_t metasize) +{ + struct coroutine_stack *stack; + size_t pgsz = getpagesize(); + size_t mapsize; + char *map, *guard; + int rc; + + mapsize = ((totalsize + (pgsz - 1)) & ~(pgsz - 1)) + pgsz; + + map = mmap(NULL, mapsize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (map == MAP_FAILED) + return NULL; + +#if HAVE_STACK_GROWS_UPWARDS + guard = map + mapsize - pgsz; + stack = (struct coroutine_stack *)(guard - totalsize + metasize); +#else + guard = map; + stack = (struct coroutine_stack *)(map + pgsz + totalsize - metasize) + - 1; +#endif + + rc = mprotect(guard, pgsz, PROT_NONE); + if (rc != 0) { + munmap(map, mapsize); + return NULL; + } + + stack->magic = COROUTINE_STACK_MAGIC_ALLOC; + stack->size = totalsize - sizeof(*stack) - metasize; + + vg_register_stack(stack); + + return stack; +} + +static void coroutine_stack_free(struct coroutine_stack *stack, size_t metasize) +{ + void *map; + size_t pgsz = getpagesize(); + size_t totalsize = stack->size + sizeof(*stack) + metasize; + size_t mapsize = ((totalsize + (pgsz - 1)) & ~(pgsz - 1)) + pgsz; + +#if HAVE_STACK_GROWS_UPWARDS + map = (char *)(stack + 1) + stack->size + pgsz - mapsize; +#else + map = (char *)stack - stack->size - pgsz; +#endif + + munmap(map, mapsize); +} + void coroutine_stack_release(struct coroutine_stack *stack, size_t metasize) { vg_deregister_stack(stack); - memset(stack, 0, sizeof(*stack)); + + switch (stack->magic) { + case COROUTINE_STACK_MAGIC_BUF: + memset(stack, 0, sizeof(*stack)); + break; + + case COROUTINE_STACK_MAGIC_ALLOC: + coroutine_stack_free(stack, metasize); + break; + + default: + abort(); + } } struct coroutine_stack *coroutine_stack_check(struct coroutine_stack *stack, const char *abortstr) { if (stack && vg_addressable(stack, sizeof(*stack)) - && (stack->magic == COROUTINE_STACK_MAGIC) + && ((stack->magic == COROUTINE_STACK_MAGIC_BUF) + || (stack->magic == COROUTINE_STACK_MAGIC_ALLOC)) && (stack->size >= COROUTINE_MIN_STKSZ)) return stack; diff --git a/ccan/coroutine/coroutine.h b/ccan/coroutine/coroutine.h index 7a8c88f1..54b5198d 100644 --- a/ccan/coroutine/coroutine.h +++ b/ccan/coroutine/coroutine.h @@ -53,10 +53,16 @@ struct coroutine_state; #define COROUTINE_MIN_STKSZ 2048 /** - * COROUTINE_STACK_MAGIC - Magic number for coroutine stacks + * COROUTINE_STACK_MAGIC_BUF - Magic number for coroutine stacks in a user + * supplied buffer */ -#define COROUTINE_STACK_MAGIC 0xc040c040574c574c +#define COROUTINE_STACK_MAGIC_BUF 0xc040c040574cb00f +/** + * COROUTINE_STACK_MAGIC_ALLOC - Magic number for coroutine stacks + * allocated by this module + */ +#define COROUTINE_STACK_MAGIC_ALLOC 0xc040c040574ca110 /** * coroutine_stack_init - Prepare a coroutine stack in an existing buffer @@ -75,6 +81,23 @@ struct coroutine_state; struct coroutine_stack *coroutine_stack_init(void *buf, size_t bufsize, size_t metasize); +/** + * coroutine_stack_alloc - Allocate a coroutine stack + * @totalsize: total size to allocate + * @metasize: size of metadata to add to the stack (not including + * coroutine internal overhead) + * + * Allocates a coroutine stack of size @totalsize, including both + * internal overhead (COROUTINE_STK_OVERHEAD) and metadata of size + * @metasize. Where available this will also create a guard page, so + * that overruning the stack will result in an immediate crash, rather + * than data corruption. + * + * This will fail if the totalsize < (COROUTINE_MIN_STKSZ + + * COROUTINE_STK_OVERHEAD + metasize). + */ +struct coroutine_stack *coroutine_stack_alloc(size_t bufsize, size_t metasize); + /** * coroutine_stack_release - Stop using a coroutine stack * @stack: coroutine stack to release diff --git a/ccan/coroutine/test/api-1.c b/ccan/coroutine/test/api-1.c index ea59dc02..c59793a0 100644 --- a/ccan/coroutine/test/api-1.c +++ b/ccan/coroutine/test/api-1.c @@ -3,6 +3,8 @@ #include #include +#define STKSZ (COROUTINE_MIN_STKSZ + COROUTINE_STK_OVERHEAD) + static int global = 0; static void trivial_fn(void *p) @@ -18,6 +20,10 @@ static void test_trivial(struct coroutine_stack *stack) { struct coroutine_state t, master; + ok1(stack != NULL); + ok1(coroutine_stack_check(stack, NULL) == stack); + ok1(coroutine_stack_size(stack) == COROUTINE_MIN_STKSZ); + if (!COROUTINE_AVAILABLE) { skip(1, "Coroutines not available"); return; @@ -30,22 +36,22 @@ static void test_trivial(struct coroutine_stack *stack) } -static char buf[COROUTINE_MIN_STKSZ + COROUTINE_STK_OVERHEAD]; +static char buf[STKSZ]; int main(void) { struct coroutine_stack *stack; /* This is how many tests you plan to run */ - plan_tests(4); + plan_tests(2 * 4 + 1); stack = coroutine_stack_init(buf, sizeof(buf), 0); - ok1(stack != NULL); - ok1(coroutine_stack_check(stack, NULL) == stack); - ok1(coroutine_stack_size(stack) == COROUTINE_MIN_STKSZ); - test_trivial(stack); + coroutine_stack_release(stack, 0); + ok1(!coroutine_stack_check(stack, NULL)); + stack = coroutine_stack_alloc(STKSZ, 0); + test_trivial(stack); coroutine_stack_release(stack, 0); /* This exits depending on whether all tests passed */ diff --git a/ccan/coroutine/test/api-2.c b/ccan/coroutine/test/api-2.c index 3564e0df..4da0292f 100644 --- a/ccan/coroutine/test/api-2.c +++ b/ccan/coroutine/test/api-2.c @@ -59,20 +59,13 @@ static void f2(void *p) static void test1(size_t bufsz) { - void *buf1, *buf2; struct coroutine_stack *stack1, *stack2; - buf1 = malloc(bufsz); - ok1(buf1 != NULL); - stack1 = coroutine_stack_init(buf1, bufsz, 0); - diag("buf1=%p stack1=%p bufsz=0x%zx overhead=0x%zx\n", - buf1, stack1, bufsz, COROUTINE_STK_OVERHEAD); + stack1 = coroutine_stack_alloc(bufsz, 0); ok1(coroutine_stack_check(stack1, NULL) == stack1); ok1(coroutine_stack_size(stack1) == bufsz - COROUTINE_STK_OVERHEAD); - buf2 = malloc(bufsz); - ok1(buf2 != NULL); - stack2 = coroutine_stack_init(buf2, bufsz, 0); + stack2 = coroutine_stack_alloc(bufsz, 0); ok1(coroutine_stack_check(stack2, NULL) == stack2); ok1(coroutine_stack_size(stack2) == bufsz - COROUTINE_STK_OVERHEAD); @@ -90,18 +83,14 @@ static void test1(size_t bufsz) ok(1, "Completed test1"); coroutine_stack_release(stack1, 0); - ok1(coroutine_stack_check(stack1, NULL) == NULL); - free(buf1); coroutine_stack_release(stack2, 0); - ok1(coroutine_stack_check(stack2, NULL) == NULL); - free(buf2); } int main(void) { /* This is how many tests you plan to run */ - plan_tests(14); + plan_tests(10); test1(8192); diff --git a/ccan/coroutine/test/api-3.c b/ccan/coroutine/test/api-3.c index 4b90b463..013e60a5 100644 --- a/ccan/coroutine/test/api-3.c +++ b/ccan/coroutine/test/api-3.c @@ -38,6 +38,11 @@ static void test_metadata(struct coroutine_stack *stack) { struct metadata *meta; + ok1(stack != NULL); + ok1(coroutine_stack_check(stack, NULL) == stack); + ok1(coroutine_stack_size(stack) + == BUFSIZE - COROUTINE_STK_OVERHEAD - sizeof(struct metadata)); + meta = coroutine_stack_to_metadata(stack, sizeof(*meta)); ok1(coroutine_stack_from_metadata(meta, sizeof(*meta)) == stack); @@ -68,22 +73,19 @@ int main(void) struct coroutine_stack *stack; /* This is how many tests you plan to run */ - plan_tests(10); + plan_tests(1 + 2 * 9); /* Fix seed so we get consistent, though pseudo-random results */ srandom(0); + stack = coroutine_stack_alloc(BUFSIZE, sizeof(struct metadata)); + test_metadata(stack); + coroutine_stack_release(stack, sizeof(struct metadata)); + buf = malloc(BUFSIZE); ok1(buf != NULL); - stack = coroutine_stack_init(buf, BUFSIZE, sizeof(struct metadata)); - ok1(stack != NULL); - ok1(coroutine_stack_check(stack, NULL) == stack); - ok1(coroutine_stack_size(stack) - == BUFSIZE - COROUTINE_STK_OVERHEAD - sizeof(struct metadata)); - test_metadata(stack); - coroutine_stack_release(stack, sizeof(struct metadata)); free(buf); -- 2.39.2