1f08c3bdfSopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
2f08c3bdfSopenharmony_ci/*
3f08c3bdfSopenharmony_ci * Copyright (c) Zilogic Systems Pvt. Ltd., 2020
4f08c3bdfSopenharmony_ci * Email: code@zilogic.com
5f08c3bdfSopenharmony_ci */
6f08c3bdfSopenharmony_ci
7f08c3bdfSopenharmony_ci/*
8f08c3bdfSopenharmony_ci * Test mmap() MAP_GROWSDOWN flag
9f08c3bdfSopenharmony_ci *
10f08c3bdfSopenharmony_ci * # Test1:
11f08c3bdfSopenharmony_ci *
12f08c3bdfSopenharmony_ci *   We assign the memory region partially allocated with MAP_GROWSDOWN flag to
13f08c3bdfSopenharmony_ci *   a thread as a stack and expect the mapping to grow when we touch the
14f08c3bdfSopenharmony_ci *   guard page by calling a recusive function in the thread that uses the
15f08c3bdfSopenharmony_ci *   growable mapping as a stack.
16f08c3bdfSopenharmony_ci *
17f08c3bdfSopenharmony_ci *   The kernel only grows the memory region when the stack pointer is within
18f08c3bdfSopenharmony_ci *   guard page when the guard page is touched so simply faulting the guard
19f08c3bdfSopenharmony_ci *   page will not cause the mapping to grow.
20f08c3bdfSopenharmony_ci *
21f08c3bdfSopenharmony_ci *   Newer kernels does not allow a MAP_GROWSDOWN mapping to grow closer than
22f08c3bdfSopenharmony_ci *   'stack_guard_gap' pages to an existing mapping. So when we map the stack we
23f08c3bdfSopenharmony_ci *   make sure there is enough of free address space before the lowest stack
24f08c3bdfSopenharmony_ci *   address.
25f08c3bdfSopenharmony_ci *
26f08c3bdfSopenharmony_ci *   Kernel default 'stack_guard_gap' size is '256 * getpagesize()'.
27f08c3bdfSopenharmony_ci *
28f08c3bdfSopenharmony_ci *   The stack memory map would look like:
29f08c3bdfSopenharmony_ci *
30f08c3bdfSopenharmony_ci *   |  -  -  -   reserved  size   -  -  -  |
31f08c3bdfSopenharmony_ci *
32f08c3bdfSopenharmony_ci *   +-- - - - --+------------+-------------+
33f08c3bdfSopenharmony_ci *   | 256 pages |  unmapped  |   mapped    |
34f08c3bdfSopenharmony_ci *   +-- - - - --+------------+-------------+
35f08c3bdfSopenharmony_ci *                            | mapped size |
36f08c3bdfSopenharmony_ci *   ^           |  -  -  stack size  -  -  |
37f08c3bdfSopenharmony_ci *   start
38f08c3bdfSopenharmony_ci *               ^                          ^
39f08c3bdfSopenharmony_ci *               stack bottom       stack top
40f08c3bdfSopenharmony_ci *
41f08c3bdfSopenharmony_ci * # Test2:
42f08c3bdfSopenharmony_ci *
43f08c3bdfSopenharmony_ci *   We allocate stack as we do in the first test but we mmap a page in the
44f08c3bdfSopenharmony_ci *   space the stack is supposed to grow into and we expect the thread to
45f08c3bdfSopenharmony_ci *   segfault when the guard page is faulted.
46f08c3bdfSopenharmony_ci */
47f08c3bdfSopenharmony_ci
48f08c3bdfSopenharmony_ci#include <unistd.h>
49f08c3bdfSopenharmony_ci#include <pthread.h>
50f08c3bdfSopenharmony_ci#include <sys/mman.h>
51f08c3bdfSopenharmony_ci#include <sys/wait.h>
52f08c3bdfSopenharmony_ci#include <sys/types.h>
53f08c3bdfSopenharmony_ci#include <stdlib.h>
54f08c3bdfSopenharmony_ci#include <stdbool.h>
55f08c3bdfSopenharmony_ci
56f08c3bdfSopenharmony_ci#include "tst_test.h"
57f08c3bdfSopenharmony_ci#include "tst_safe_pthread.h"
58f08c3bdfSopenharmony_ci
59f08c3bdfSopenharmony_cistatic long page_size;
60f08c3bdfSopenharmony_ci
61f08c3bdfSopenharmony_cistatic bool __attribute__((noinline)) check_stackgrow_up(void)
62f08c3bdfSopenharmony_ci{
63f08c3bdfSopenharmony_ci	char local_var;
64f08c3bdfSopenharmony_ci	static char *addr;
65f08c3bdfSopenharmony_ci
66f08c3bdfSopenharmony_ci       if (!addr) {
67f08c3bdfSopenharmony_ci               addr = &local_var;
68f08c3bdfSopenharmony_ci               return check_stackgrow_up();
69f08c3bdfSopenharmony_ci       }
70f08c3bdfSopenharmony_ci
71f08c3bdfSopenharmony_ci       return (addr < &local_var);
72f08c3bdfSopenharmony_ci}
73f08c3bdfSopenharmony_ci
74f08c3bdfSopenharmony_cistatic void setup(void)
75f08c3bdfSopenharmony_ci{
76f08c3bdfSopenharmony_ci	if (check_stackgrow_up())
77f08c3bdfSopenharmony_ci		tst_brk(TCONF, "Test can't be performed with stack grows up architecture");
78f08c3bdfSopenharmony_ci
79f08c3bdfSopenharmony_ci	page_size = getpagesize();
80f08c3bdfSopenharmony_ci}
81f08c3bdfSopenharmony_ci
82f08c3bdfSopenharmony_ci/*
83f08c3bdfSopenharmony_ci * Returns stack lowest address. Note that the address is not mapped and will
84f08c3bdfSopenharmony_ci * be mapped on page fault when we grow the stack to the lowest address possible.
85f08c3bdfSopenharmony_ci */
86f08c3bdfSopenharmony_cistatic void *allocate_stack(size_t stack_size, size_t mapped_size)
87f08c3bdfSopenharmony_ci{
88f08c3bdfSopenharmony_ci	void *start, *stack_top, *stack_bottom;
89f08c3bdfSopenharmony_ci
90f08c3bdfSopenharmony_ci	long reserved_size = 256 * page_size + stack_size;
91f08c3bdfSopenharmony_ci
92f08c3bdfSopenharmony_ci	start = SAFE_MMAP(NULL, reserved_size, PROT_READ | PROT_WRITE,
93f08c3bdfSopenharmony_ci	                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
94f08c3bdfSopenharmony_ci	SAFE_MUNMAP(start, reserved_size);
95f08c3bdfSopenharmony_ci
96f08c3bdfSopenharmony_ci	SAFE_MMAP((start + reserved_size - mapped_size), mapped_size, PROT_READ | PROT_WRITE,
97f08c3bdfSopenharmony_ci		  MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN,
98f08c3bdfSopenharmony_ci		  -1, 0);
99f08c3bdfSopenharmony_ci
100f08c3bdfSopenharmony_ci	stack_top = start + reserved_size;
101f08c3bdfSopenharmony_ci	stack_bottom = start + reserved_size - stack_size;
102f08c3bdfSopenharmony_ci
103f08c3bdfSopenharmony_ci	tst_res(TINFO, "start = %p, stack_top = %p, stack bottom = %p",
104f08c3bdfSopenharmony_ci		start, stack_top, stack_bottom);
105f08c3bdfSopenharmony_ci	tst_res(TINFO, "mapped pages %zu, stack pages %zu",
106f08c3bdfSopenharmony_ci	        mapped_size/page_size, stack_size/page_size);
107f08c3bdfSopenharmony_ci
108f08c3bdfSopenharmony_ci	return stack_bottom;
109f08c3bdfSopenharmony_ci}
110f08c3bdfSopenharmony_ci
111f08c3bdfSopenharmony_cistatic __attribute__((noinline)) void *check_depth_recursive(void *limit)
112f08c3bdfSopenharmony_ci{
113f08c3bdfSopenharmony_ci	if ((off_t) &limit < (off_t) limit) {
114f08c3bdfSopenharmony_ci		tst_res(TINFO, "&limit = %p, limit = %p", &limit, limit);
115f08c3bdfSopenharmony_ci		return NULL;
116f08c3bdfSopenharmony_ci	}
117f08c3bdfSopenharmony_ci
118f08c3bdfSopenharmony_ci	return check_depth_recursive(limit);
119f08c3bdfSopenharmony_ci}
120f08c3bdfSopenharmony_ci
121f08c3bdfSopenharmony_ci/*
122f08c3bdfSopenharmony_ci * We set the limit one page above the stack bottom to make sure that the stack
123f08c3bdfSopenharmony_ci * frame will not overflow to the next page, which would potentially cause
124f08c3bdfSopenharmony_ci * segfault if we are unlucky and there is a mapping right after the guard gap.
125f08c3bdfSopenharmony_ci *
126f08c3bdfSopenharmony_ci * Generally the stack frame would be much smaller than page_size so moving the
127f08c3bdfSopenharmony_ci * pointer by a few bytes would probably be enough, but we do not want to take
128f08c3bdfSopenharmony_ci * any chances.
129f08c3bdfSopenharmony_ci */
130f08c3bdfSopenharmony_cistatic void grow_stack(void *stack, size_t size)
131f08c3bdfSopenharmony_ci{
132f08c3bdfSopenharmony_ci	pthread_t test_thread;
133f08c3bdfSopenharmony_ci	pthread_attr_t attr;
134f08c3bdfSopenharmony_ci	int ret;
135f08c3bdfSopenharmony_ci	void *limit = stack + page_size;
136f08c3bdfSopenharmony_ci
137f08c3bdfSopenharmony_ci	ret = pthread_attr_init(&attr);
138f08c3bdfSopenharmony_ci	if (ret)
139f08c3bdfSopenharmony_ci		tst_brk(TBROK, "pthread_attr_init failed during setup");
140f08c3bdfSopenharmony_ci
141f08c3bdfSopenharmony_ci	ret = pthread_attr_setstack(&attr, stack, size);
142f08c3bdfSopenharmony_ci	if (ret)
143f08c3bdfSopenharmony_ci		tst_brk(TBROK, "pthread_attr_setstack failed during setup");
144f08c3bdfSopenharmony_ci
145f08c3bdfSopenharmony_ci	SAFE_PTHREAD_CREATE(&test_thread, &attr, check_depth_recursive, limit);
146f08c3bdfSopenharmony_ci	SAFE_PTHREAD_JOIN(test_thread, NULL);
147f08c3bdfSopenharmony_ci
148f08c3bdfSopenharmony_ci	exit(0);
149f08c3bdfSopenharmony_ci}
150f08c3bdfSopenharmony_ci
151f08c3bdfSopenharmony_cistatic void grow_stack_success(size_t stack_size, size_t mapped_size)
152f08c3bdfSopenharmony_ci{
153f08c3bdfSopenharmony_ci	pid_t child_pid;
154f08c3bdfSopenharmony_ci	int wstatus;
155f08c3bdfSopenharmony_ci	void *stack;
156f08c3bdfSopenharmony_ci
157f08c3bdfSopenharmony_ci	child_pid = SAFE_FORK();
158f08c3bdfSopenharmony_ci	if (!child_pid) {
159f08c3bdfSopenharmony_ci		stack = allocate_stack(stack_size, mapped_size);
160f08c3bdfSopenharmony_ci		grow_stack(stack, stack_size);
161f08c3bdfSopenharmony_ci	}
162f08c3bdfSopenharmony_ci
163f08c3bdfSopenharmony_ci	SAFE_WAIT(&wstatus);
164f08c3bdfSopenharmony_ci	if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0)
165f08c3bdfSopenharmony_ci		tst_res(TPASS, "Stack grows in unmapped region");
166f08c3bdfSopenharmony_ci	else
167f08c3bdfSopenharmony_ci		tst_res(TFAIL, "Child: %s", tst_strstatus(wstatus));
168f08c3bdfSopenharmony_ci
169f08c3bdfSopenharmony_ci}
170f08c3bdfSopenharmony_ci
171f08c3bdfSopenharmony_ci/*
172f08c3bdfSopenharmony_ci * We map a page at the bottom of the stack which will cause the thread to be
173f08c3bdfSopenharmony_ci * killed with SIGSEGV on faulting the guard page.
174f08c3bdfSopenharmony_ci */
175f08c3bdfSopenharmony_cistatic void grow_stack_fail(size_t stack_size, size_t mapped_size)
176f08c3bdfSopenharmony_ci{
177f08c3bdfSopenharmony_ci	pid_t child_pid;
178f08c3bdfSopenharmony_ci	int wstatus;
179f08c3bdfSopenharmony_ci	void *stack;
180f08c3bdfSopenharmony_ci
181f08c3bdfSopenharmony_ci	child_pid = SAFE_FORK();
182f08c3bdfSopenharmony_ci	if (!child_pid) {
183f08c3bdfSopenharmony_ci		tst_no_corefile(0);
184f08c3bdfSopenharmony_ci		stack = allocate_stack(stack_size, mapped_size);
185f08c3bdfSopenharmony_ci
186f08c3bdfSopenharmony_ci		SAFE_MMAP(stack, page_size, PROT_READ | PROT_WRITE,
187f08c3bdfSopenharmony_ci			  MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
188f08c3bdfSopenharmony_ci
189f08c3bdfSopenharmony_ci		tst_res(TINFO, "mapped page at %p", stack);
190f08c3bdfSopenharmony_ci
191f08c3bdfSopenharmony_ci		grow_stack(stack, stack_size);
192f08c3bdfSopenharmony_ci	}
193f08c3bdfSopenharmony_ci
194f08c3bdfSopenharmony_ci	SAFE_WAIT(&wstatus);
195f08c3bdfSopenharmony_ci        if (WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGSEGV)
196f08c3bdfSopenharmony_ci		tst_res(TPASS, "Child killed by %s as expected", tst_strsig(SIGSEGV));
197f08c3bdfSopenharmony_ci        else
198f08c3bdfSopenharmony_ci                tst_res(TFAIL, "Child: %s", tst_strstatus(wstatus));
199f08c3bdfSopenharmony_ci}
200f08c3bdfSopenharmony_ci
201f08c3bdfSopenharmony_cistatic void run_test(void)
202f08c3bdfSopenharmony_ci{
203f08c3bdfSopenharmony_ci	size_t pthread_stack = LTP_ALIGN(PTHREAD_STACK_MIN, getpagesize());
204f08c3bdfSopenharmony_ci	size_t stack_size = 8 * pthread_stack;
205f08c3bdfSopenharmony_ci
206f08c3bdfSopenharmony_ci	grow_stack_success(stack_size, pthread_stack);
207f08c3bdfSopenharmony_ci	grow_stack_success(stack_size, stack_size/2);
208f08c3bdfSopenharmony_ci	grow_stack_fail(stack_size, pthread_stack);
209f08c3bdfSopenharmony_ci	grow_stack_fail(stack_size, stack_size/2);
210f08c3bdfSopenharmony_ci}
211f08c3bdfSopenharmony_ci
212f08c3bdfSopenharmony_cistatic struct tst_test test = {
213f08c3bdfSopenharmony_ci	.setup = setup,
214f08c3bdfSopenharmony_ci	.test_all = run_test,
215f08c3bdfSopenharmony_ci	.forks_child = 1,
216f08c3bdfSopenharmony_ci};
217