1/* $OpenBSD: rthread_stack.c,v 1.20 2021/09/17 15:20:21 deraadt Exp $ */
2
3/* PUBLIC DOMAIN: No Rights Reserved. Marco S Hyman <marc@snafu.org> */
4
5#include <sys/types.h>
6#include <sys/mman.h>
7
8#include <errno.h>
9#include <pthread.h>
10#include <stdint.h>
11#include <stdlib.h>
12#include <unistd.h>
13
14#include "rthread.h"
15
16/*
17 * Follow uthread's example and keep around stacks that have default
18 * attributes for possible reuse.
19 */
20static SLIST_HEAD(, stack) def_stacks = SLIST_HEAD_INITIALIZER(head);
21static _atomic_lock_t def_stacks_lock = _SPINLOCK_UNLOCKED;
22
23struct stack *
24_rthread_alloc_stack(pthread_t thread)
25{
26	struct stack *stack;
27	u_int32_t rnd;
28	caddr_t base;
29	caddr_t guard;
30	size_t size;
31	size_t guardsize;
32
33	/* if the request uses the defaults, try to reuse one */
34	if (thread->attr.stack_addr == NULL &&
35	    thread->attr.stack_size == RTHREAD_STACK_SIZE_DEF &&
36	    thread->attr.guard_size == _thread_pagesize) {
37		_spinlock(&def_stacks_lock);
38		stack = SLIST_FIRST(&def_stacks);
39		if (stack != NULL) {
40			SLIST_REMOVE_HEAD(&def_stacks, link);
41			_spinunlock(&def_stacks_lock);
42			return (stack);
43		}
44		_spinunlock(&def_stacks_lock);
45	}
46
47	/* allocate the stack struct that we'll return */
48	stack = malloc(sizeof(*stack));
49	if (stack == NULL)
50		return (NULL);
51
52	/* Smaller the stack, smaller the random bias */
53	if (thread->attr.stack_size > _thread_pagesize)
54		rnd = arc4random() & (_thread_pagesize - 1);
55	else if (thread->attr.stack_size == _thread_pagesize)
56		rnd = arc4random() & (_thread_pagesize / 16 - 1);
57	else
58		rnd = 0;
59	rnd &= ~_STACKALIGNBYTES;
60
61	/* If a stack address was provided, just fill in the details */
62	if (thread->attr.stack_addr != NULL) {
63		stack->base = base = thread->attr.stack_addr;
64		stack->len  = thread->attr.stack_size;
65#ifdef MACHINE_STACK_GROWS_UP
66		stack->sp = base + rnd;
67#else
68		stack->sp = base + thread->attr.stack_size - (_STACKALIGNBYTES+1) - rnd;
69#endif
70		/*
71		 * This impossible guardsize marks this stack as
72		 * application allocated so it won't be freed or
73		 * cached by _rthread_free_stack()
74		 */
75		stack->guardsize = 1;
76		return (stack);
77	}
78
79	/* round up the requested sizes up to full pages */
80	size = ROUND_TO_PAGE(thread->attr.stack_size);
81	guardsize = ROUND_TO_PAGE(thread->attr.guard_size);
82
83	/* check for overflow */
84	if (size < thread->attr.stack_size ||
85	    guardsize < thread->attr.guard_size ||
86	    SIZE_MAX - size < guardsize) {
87		free(stack);
88		errno = EINVAL;
89		return (NULL);
90	}
91	size += guardsize;
92
93	/* actually allocate the real stack */
94	base = mmap(NULL, size, PROT_READ | PROT_WRITE,
95	    MAP_PRIVATE | MAP_ANON | MAP_STACK, -1, 0);
96	if (base == MAP_FAILED) {
97		free(stack);
98		return (NULL);
99	}
100
101#ifdef MACHINE_STACK_GROWS_UP
102	guard = base + size - guardsize;
103	stack->sp = base + rnd;
104#else
105	guard = base;
106	stack->sp = base + size - (_STACKALIGNBYTES+1) - rnd;
107#endif
108
109	/* memory protect the guard region */
110	if (guardsize != 0 && mprotect(guard, guardsize, PROT_NONE) == -1) {
111		munmap(base, size);
112		free(stack);
113		return (NULL);
114	}
115
116	stack->base = base;
117	stack->guardsize = guardsize;
118	stack->len = size;
119	return (stack);
120}
121
122void
123_rthread_free_stack(struct stack *stack)
124{
125	if (stack->len == RTHREAD_STACK_SIZE_DEF + stack->guardsize &&
126	    stack->guardsize == _thread_pagesize) {
127		_spinlock(&def_stacks_lock);
128		SLIST_INSERT_HEAD(&def_stacks, stack, link);
129		_spinunlock(&def_stacks_lock);
130	} else {
131		/* unmap the storage unless it was application allocated */
132		if (stack->guardsize != 1)
133			munmap(stack->base, stack->len);
134		free(stack);
135	}
136}
137