coroutine: Stack allocation
authorDavid Gibson <david@gibson.dropbear.id.au>
Fri, 20 Jan 2017 12:49:43 +0000 (23:49 +1100)
committerDavid Gibson <david@gibson.dropbear.id.au>
Tue, 24 Jan 2017 10:22:27 +0000 (21:22 +1100)
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 <david@gibson.dropbear.id.au>
ccan/coroutine/coroutine.c
ccan/coroutine/coroutine.h
ccan/coroutine/test/api-1.c
ccan/coroutine/test/api-2.c
ccan/coroutine/test/api-3.c

index fa321ab85a6cf55a2fccb3113cc222352b26bc8d..f77b7790009037ab49f9ad308f6ddd79a277d5a6 100644 (file)
@@ -5,6 +5,9 @@
 #include <inttypes.h>
 #include <stdlib.h>
 
+#include <unistd.h>
+#include <sys/mman.h>
+
 #include <ccan/ptrint/ptrint.h>
 #include <ccan/compiler/compiler.h>
 #include <ccan/build_assert/build_assert.h>
@@ -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;
 
index 7a8c88f112eccc2b021037c40c9bd04faa8465ad..54b5198d1f6e597194b9ae2b22c0490ea4883fdb 100644 (file)
@@ -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
index ea59dc02fb3f9ad7df753b5cc84a8f91972af0c5..c59793a0542fe3be3018fe9f3d990eb7c5bb1826 100644 (file)
@@ -3,6 +3,8 @@
 #include <ccan/coroutine/coroutine.h>
 #include <ccan/tap/tap.h>
 
+#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 */
index 3564e0df790d8c84bcdc03f9dab43fafd722e9a8..4da0292f20284d995ce6786742de4a738c72bda0 100644 (file)
@@ -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);
 
index 4b90b46310b2952ba2a780ac72089db842670496..013e60a54a99f4879b8b3f31b08d898a324aaf6f 100644 (file)
@@ -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);