1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Test that loads/stores expand the stack segment, or trigger a SEGV, in
4  * various conditions.
5  *
6  * Based on test code by Tom Lane.
7  */
8 
9 #undef NDEBUG
10 #include <assert.h>
11 
12 #include <err.h>
13 #include <errno.h>
14 #include <stdio.h>
15 #include <signal.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/resource.h>
19 #include <sys/time.h>
20 #include <sys/types.h>
21 #include <sys/wait.h>
22 #include <unistd.h>
23 
24 #define _KB (1024)
25 #define _MB (1024 * 1024)
26 
27 volatile char *stack_top_ptr;
28 volatile unsigned long stack_top_sp;
29 volatile char c;
30 
31 enum access_type {
32 	LOAD,
33 	STORE,
34 };
35 
36 /*
37  * Consume stack until the stack pointer is below @target_sp, then do an access
38  * (load or store) at offset @delta from either the base of the stack or the
39  * current stack pointer.
40  */
41 __attribute__ ((noinline))
consume_stack(unsigned long target_sp,unsigned long stack_high,int delta,enum access_type type)42 int consume_stack(unsigned long target_sp, unsigned long stack_high, int delta, enum access_type type)
43 {
44 	unsigned long target;
45 	char stack_cur;
46 
47 	if ((unsigned long)&stack_cur > target_sp)
48 		return consume_stack(target_sp, stack_high, delta, type);
49 	else {
50 		// We don't really need this, but without it GCC might not
51 		// generate a recursive call above.
52 		stack_top_ptr = &stack_cur;
53 
54 #ifdef __powerpc__
55 		asm volatile ("mr %[sp], %%r1" : [sp] "=r" (stack_top_sp));
56 #else
57 		asm volatile ("mov %%rsp, %[sp]" : [sp] "=r" (stack_top_sp));
58 #endif
59 		target = stack_high - delta + 1;
60 		volatile char *p = (char *)target;
61 
62 		if (type == STORE)
63 			*p = c;
64 		else
65 			c = *p;
66 
67 		// Do something to prevent the stack frame being popped prior to
68 		// our access above.
69 		getpid();
70 	}
71 
72 	return 0;
73 }
74 
search_proc_maps(char * needle,unsigned long * low,unsigned long * high)75 static int search_proc_maps(char *needle, unsigned long *low, unsigned long *high)
76 {
77 	unsigned long start, end;
78 	static char buf[4096];
79 	char name[128];
80 	FILE *f;
81 	int rc;
82 
83 	f = fopen("/proc/self/maps", "r");
84 	if (!f) {
85 		perror("fopen");
86 		return -1;
87 	}
88 
89 	while (fgets(buf, sizeof(buf), f)) {
90 		rc = sscanf(buf, "%lx-%lx %*c%*c%*c%*c %*x %*d:%*d %*d %127s\n",
91 			    &start, &end, name);
92 		if (rc == 2)
93 			continue;
94 
95 		if (rc != 3) {
96 			printf("sscanf errored\n");
97 			rc = -1;
98 			break;
99 		}
100 
101 		if (strstr(name, needle)) {
102 			*low = start;
103 			*high = end - 1;
104 			rc = 0;
105 			break;
106 		}
107 	}
108 
109 	fclose(f);
110 
111 	return rc;
112 }
113 
child(unsigned int stack_used,int delta,enum access_type type)114 int child(unsigned int stack_used, int delta, enum access_type type)
115 {
116 	unsigned long low, stack_high;
117 
118 	assert(search_proc_maps("[stack]", &low, &stack_high) == 0);
119 
120 	assert(consume_stack(stack_high - stack_used, stack_high, delta, type) == 0);
121 
122 	printf("Access OK: %s delta %-7d used size 0x%06x stack high 0x%lx top_ptr %p top sp 0x%lx actual used 0x%lx\n",
123 	       type == LOAD ? "load" : "store", delta, stack_used, stack_high,
124 	       stack_top_ptr, stack_top_sp, stack_high - stack_top_sp + 1);
125 
126 	return 0;
127 }
128 
test_one(unsigned int stack_used,int delta,enum access_type type)129 static int test_one(unsigned int stack_used, int delta, enum access_type type)
130 {
131 	pid_t pid;
132 	int rc;
133 
134 	pid = fork();
135 	if (pid == 0)
136 		exit(child(stack_used, delta, type));
137 
138 	assert(waitpid(pid, &rc, 0) != -1);
139 
140 	if (WIFEXITED(rc) && WEXITSTATUS(rc) == 0)
141 		return 0;
142 
143 	// We don't expect a non-zero exit that's not a signal
144 	assert(!WIFEXITED(rc));
145 
146 	printf("Faulted:   %s delta %-7d used size 0x%06x signal %d\n",
147 	       type == LOAD ? "load" : "store", delta, stack_used,
148 	       WTERMSIG(rc));
149 
150 	return 1;
151 }
152 
153 // This is fairly arbitrary but is well below any of the targets below,
154 // so that the delta between the stack pointer and the target is large.
155 #define DEFAULT_SIZE	(32 * _KB)
156 
test_one_type(enum access_type type,unsigned long page_size,unsigned long rlim_cur)157 static void test_one_type(enum access_type type, unsigned long page_size, unsigned long rlim_cur)
158 {
159 	unsigned long delta;
160 
161 	// We should be able to access anywhere within the rlimit
162 	for (delta = page_size; delta <= rlim_cur; delta += page_size)
163 		assert(test_one(DEFAULT_SIZE, delta, type) == 0);
164 
165 	assert(test_one(DEFAULT_SIZE, rlim_cur, type) == 0);
166 
167 	// But if we go past the rlimit it should fail
168 	assert(test_one(DEFAULT_SIZE, rlim_cur + 1, type) != 0);
169 }
170 
test(void)171 static int test(void)
172 {
173 	unsigned long page_size;
174 	struct rlimit rlimit;
175 
176 	page_size = getpagesize();
177 	getrlimit(RLIMIT_STACK, &rlimit);
178 	printf("Stack rlimit is 0x%lx\n", rlimit.rlim_cur);
179 
180 	printf("Testing loads ...\n");
181 	test_one_type(LOAD, page_size, rlimit.rlim_cur);
182 	printf("Testing stores ...\n");
183 	test_one_type(STORE, page_size, rlimit.rlim_cur);
184 
185 	printf("All OK\n");
186 
187 	return 0;
188 }
189 
190 #ifdef __powerpc__
191 #include "utils.h"
192 
main(void)193 int main(void)
194 {
195 	return test_harness(test, "stack_expansion_ldst");
196 }
197 #else
main(void)198 int main(void)
199 {
200 	return test();
201 }
202 #endif
203