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
27volatile char *stack_top_ptr;
28volatile unsigned long stack_top_sp;
29volatile char c;
30
31enum 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))
42int 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
75static 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
114int child(unsigned int stack_used, int delta, enum access_type type)
115{
116 unsigned long low, stack_high;
117
118 assert(search_proc_maps(needle: "[stack]", low: &low, high: &stack_high) == 0);
119
120 assert(consume_stack(target_sp: 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
129static 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
157static 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, delta: rlim_cur, type) == 0);
166
167 // But if we go past the rlimit it should fail
168 assert(test_one(DEFAULT_SIZE, delta: rlim_cur + 1, type) != 0);
169}
170
171static 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(type: LOAD, page_size, rlim_cur: rlimit.rlim_cur);
182 printf("Testing stores ...\n");
183 test_one_type(type: STORE, page_size, rlim_cur: rlimit.rlim_cur);
184
185 printf("All OK\n");
186
187 return 0;
188}
189
190#ifdef __powerpc__
191#include "utils.h"
192
193int main(void)
194{
195 return test_harness(test, "stack_expansion_ldst");
196}
197#else
198int main(void)
199{
200 return test();
201}
202#endif
203

source code of linux/tools/testing/selftests/powerpc/mm/stack_expansion_ldst.c