stack_clash.c revision c3b43efc8174e236c02ffeb27434bbc816f2ee43
1/*
2 * Copyright (c) 2017 Pavel Boldin <pboldin@cloudlinux.com>
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Adapted from code by Michal Hocko.
18 */
19
20/* This is a regression test of the Stack Clash [1] vulnerability. This tests
21 * that there is at least 256 PAGE_SIZE of stack guard gap which is considered
22 * hard to hop above. Code adapted from the Novell's bugzilla [2].
23 *
24 * The code `mmap(2)`s region close to the stack end. The code then allocates
25 * memory on stack until it hits guard page and SIGSEGV or SIGBUS is generated
26 * by the kernel. The signal handler checks that fault address is further than
27 * THRESHOLD from the mmapped area.
28 *
29 * We read /proc/self/maps to examine exact top of the stack and `mmap(2)`
30 * our region exactly GAP_PAGES * PAGE_SIZE away. We read /proc/cmdline to
31 * see if a different stack_guard_gap size is configured. We set stack limit
32 * to infinity and preallocate REQ_STACK_SIZE bytes of stack so that no calls
33 * after `mmap` are moving stack further.
34 *
35 * [1] https://blog.qualys.com/securitylabs/2017/06/19/the-stack-clash
36 * [2] https://bugzilla.novell.com/show_bug.cgi?id=CVE-2017-1000364
37 */
38
39#include <sys/mman.h>
40#include <sys/wait.h>
41#include <stdio.h>
42#include <unistd.h>
43#include <alloca.h>
44#include <signal.h>
45#include <stdlib.h>
46
47#include "tst_test.h"
48#include "tst_safe_stdio.h"
49
50static unsigned long page_size;
51static unsigned long page_mask;
52static unsigned long GAP_PAGES = 256;
53static unsigned long THRESHOLD;
54static int STACK_GROWSDOWN;
55
56#define SIGNAL_STACK_SIZE	(1UL<<20)
57#define FRAME_SIZE		1024
58#define REQ_STACK_SIZE		(1024 * 1024)
59
60#define EXIT_TESTBROKE		TBROK
61
62void exhaust_stack_into_sigsegv(void)
63{
64	volatile char * ptr = alloca(FRAME_SIZE - sizeof(long));
65	*ptr = '\0';
66	exhaust_stack_into_sigsegv();
67}
68
69#define MAPPED_LEN page_size
70static unsigned long mapped_addr;
71
72void segv_handler(int sig, siginfo_t *info, void *data LTP_ATTRIBUTE_UNUSED)
73{
74	unsigned long fault_addr = (unsigned long)info->si_addr;
75	unsigned long mmap_end = mapped_addr + MAPPED_LEN;
76	ssize_t diff;
77
78	if (sig != SIGSEGV && sig != SIGBUS)
79		return;
80
81	if (STACK_GROWSDOWN)
82		diff = fault_addr - mmap_end;
83	else
84		diff = mapped_addr - fault_addr;
85
86	tst_res(TINFO,
87		"mmap = [%lx, %lx), addr = %lx, diff = %lx, THRESHOLD = %lx",
88		mapped_addr, mmap_end, fault_addr, diff, THRESHOLD);
89	if (diff < 0 || (unsigned long)diff < THRESHOLD)
90		_exit(EXIT_FAILURE);
91	else
92		_exit(EXIT_SUCCESS);
93}
94
95unsigned long read_stack_addr_from_proc(unsigned long *stack_size)
96{
97	FILE *fh;
98	char buf[1024];
99	unsigned long stack_top = -1UL, start, end;
100
101	fh = SAFE_FOPEN("/proc/self/maps", "r");
102
103	while (!feof(fh)) {
104		if (fgets(buf, sizeof(buf), fh) == NULL) {
105			tst_brk(TBROK | TERRNO, "fgets");
106			goto out;
107		}
108
109		if (!strstr(buf, "[stack"))
110			continue;
111
112		if (sscanf(buf, "%lx-%lx", &start, &end) != 2) {
113			tst_brk(TBROK | TERRNO, "sscanf");
114			goto out;
115		}
116
117		*stack_size = end - start;
118
119		if (STACK_GROWSDOWN)
120			stack_top = start;
121		else
122			stack_top = end;
123		break;
124	}
125
126out:
127	SAFE_FCLOSE(fh);
128	return stack_top;
129}
130
131void dump_proc_self_maps(void)
132{
133	static char buf[64];
134	static const char *cmd[] = {"cat", buf, NULL};
135	sprintf(buf, "/proc/%d/maps", getpid());
136	tst_run_cmd(cmd, NULL, NULL, 0);
137}
138
139void preallocate_stack(unsigned long required)
140{
141	volatile char *garbage;
142
143	garbage = alloca(required);
144	garbage[0] = garbage[required - 1] = '\0';
145}
146
147void do_child(void)
148{
149	unsigned long stack_addr, stack_size;
150	stack_t signal_stack;
151	struct sigaction segv_sig = {.sa_sigaction = segv_handler, .sa_flags = SA_ONSTACK|SA_SIGINFO};
152	void *map;
153	unsigned long gap = GAP_PAGES * page_size;
154	struct rlimit rlimit;
155
156	rlimit.rlim_cur = rlimit.rlim_max = RLIM_INFINITY;
157	SAFE_SETRLIMIT(RLIMIT_STACK, &rlimit);
158
159	preallocate_stack(REQ_STACK_SIZE);
160
161	stack_addr = read_stack_addr_from_proc(&stack_size);
162	if (stack_addr == -1UL) {
163		tst_brk(TBROK, "can't read stack top from /proc/self/maps");
164		return;
165	}
166
167	if (STACK_GROWSDOWN)
168		mapped_addr = stack_addr - gap - MAPPED_LEN;
169	else
170		mapped_addr = stack_addr + gap;
171
172	mapped_addr &= page_mask;
173	map = SAFE_MMAP((void *)mapped_addr, MAPPED_LEN,
174			PROT_READ|PROT_WRITE,
175			MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 0);
176	tst_res(TINFO, "Stack:0x%lx+0x%lx mmap:%p+0x%lx",
177		stack_addr, stack_size, map, MAPPED_LEN);
178
179	signal_stack.ss_sp = SAFE_MALLOC(SIGNAL_STACK_SIZE);
180	signal_stack.ss_size = SIGNAL_STACK_SIZE;
181	signal_stack.ss_flags = 0;
182	if (sigaltstack(&signal_stack, NULL) == -1) {
183		tst_brk(TBROK | TERRNO, "sigaltstack");
184		return;
185	}
186	if (sigaction(SIGSEGV, &segv_sig, NULL) == -1 ||
187	    sigaction(SIGBUS,  &segv_sig, NULL) == -1) {
188		tst_brk(TBROK | TERRNO, "sigaction");
189		return;
190	}
191
192#ifdef DEBUG
193	dump_proc_self_maps();
194#endif
195
196	exhaust_stack_into_sigsegv();
197}
198
199void setup(void)
200{
201	char buf[4096], *p;
202
203	page_size = sysconf(_SC_PAGESIZE);
204	page_mask = ~(page_size - 1);
205
206	buf[4095] = '\0';
207	SAFE_FILE_SCANF("/proc/cmdline", "%4095[^\n]", buf);
208
209	if ((p = strstr(buf, "stack_guard_gap=")) != NULL) {
210		if (sscanf(p, "stack_guard_gap=%ld", &GAP_PAGES) != 1) {
211			tst_brk(TBROK | TERRNO, "sscanf");
212			return;
213		}
214		tst_res(TINFO, "stack_guard_gap = %ld", GAP_PAGES);
215	}
216
217	THRESHOLD = (GAP_PAGES - 1) * page_size;
218
219	{
220		volatile int *a = alloca(128);
221
222		{
223			volatile int *b = alloca(128);
224
225			STACK_GROWSDOWN = a > b;
226			tst_res(TINFO, "STACK_GROWSDOWN = %d == %p > %p", STACK_GROWSDOWN, a, b);
227		}
228	}
229}
230
231void stack_clash_test(void)
232{
233	int status;
234	pid_t pid;
235
236	pid = SAFE_FORK();
237	if (!pid) {
238		do_child();
239		exit(EXIT_TESTBROKE);
240		return;
241	}
242
243	SAFE_WAITPID(pid, &status, 0);
244
245	if (WIFEXITED(status)) {
246		switch (WEXITSTATUS(status)) {
247		case EXIT_FAILURE:
248			tst_res(TFAIL, "stack is too close to the mmaped area");
249			return;
250		case EXIT_SUCCESS:
251			tst_res(TPASS, "stack is far enough from mmaped area");
252			return;
253		default:
254		case EXIT_TESTBROKE:
255			break;
256		}
257	}
258
259	tst_brk(TBROK, "child did not exit gracefully");
260}
261
262static struct tst_test test = {
263	.forks_child = 1,
264	.needs_root = 1,
265	.setup = setup,
266	.test_all = stack_clash_test,
267};
268