Revisiting #async / #await in #POSIX C, trying to "add some #security"
Recap: Consider a classic #reactor-style service in C with a #threadpool attached to run the individual request handlers. When such a handler needs to do some I/O, it'll have to wait for its completion, and doing so is kind of straight forward by just blocking the worker thread executing the job until whatever I/O was needed completes.
Now, blocking a thread is never a great thing to do and I recently tooted about an interesting alternative I found: Make use of the (unfortunately deprecated) POSIX user context switching to enable releasing the worker thread while waiting. In a nutshell, you create a context with #makecontext that has its own private #stack, and then you can use #swapcontext to get off the thread, and later again to get back on the thread. A minor issue is: It must be the *same* thread ... so you might have to wait until it completes something else before you can resume your job. But then, that's probably okayish, you can make sure in your job scheduling to only use worker threads with awaited tasks attached when no other thread is available.
In my first implementation, I just used #malloc to create a 64kiB private stack for each thread job. That's perfectly fine if you can guarantee your job will never consume more stack space, AND it won't have any vulnerabilities allowing some attacker to mess with the stack. But in practice, especially for a library offering this async/await implementation, it's nothing but a wild #CVE generator.
So, I now improved on that:
* Allocate a much larger stack of now 2MiB. That alone makes issues at least less likely. And on a sane modern OS, we can still assume pages will only be mapped "on demand".
* Only allocate the stack directly before running the thread job, and delegate allocation to some internal "stack manager" that keeps track of all allocated stacks and reuses them, only freeing them on exit. This should avoid most of the allocation overhead.
* If MAP_ANON / MAP_ANONYMOUS is available, use #mmap for allocating the stack. That at least gives a chance to stay away from other allocations ....
* But finally, if MAP_STACK is available, use this flag! From my research, #FreeBSD, #OpenBSD and #NetBSD will for example make sure there's at least one "guard page" below a stack mapped with this flag, so a stack overflow consistently takes the SIGSEGV emergency exit
. #Linux knows this flag as well, but doesn't seem to implement such protection at this time ...