Browse Source

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 <david@gibson.dropbear.id.au>
David Gibson 9 years ago
parent
commit
b4f0767d38

+ 73 - 3
ccan/coroutine/coroutine.c

@@ -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;
 

+ 25 - 2
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

+ 12 - 6
ccan/coroutine/test/api-1.c

@@ -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 */

+ 3 - 14
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);
 

+ 10 - 8
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);