Skip to content

Commit 802e87c

Browse files
Alexey DobriyanIngo Molnar
authored andcommitted
selftests/x86/mm: Add new test that userspace stack is in fact NX
Here is how it works: * fault and fill the stack from RSP with INT3 down until rlimit allows, * fill upwards with INT3 too, overwrite libc stuff, argv, envp, * try to exec INT3 on each page and catch it in either SIGSEGV or SIGTRAP handler. Note: trying to execute _every_ INT3 on a 8 MiB stack takes 30-40 seconds even on fast machine which is too much for kernel selftesting (not for LTP!) so only 1 INT3 per page is tried. Tested on F37 kernel and on a custom kernel which does: vm_flags |= VM_EXEC; to stack VMA. Report from the buggy kernel: $ ./nx_stack_32 stack min ff007000 stack max ff807000 FAIL executable page on the stack: eip ff806001 $ ./nx_stack_64 stack min 7ffe65bb0000 stack max 7ffe663b0000 FAIL executable page on the stack: rip 7ffe663af001 Signed-off-by: Alexey Dobriyan <[email protected]> Signed-off-by: Ingo Molnar <[email protected]> Link: https://lore.kernel.org/r/4cef8266-ad6d-48af-a5f1-fc2b6a8eb422@p183
1 parent bfb32e2 commit 802e87c

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

tools/testing/selftests/x86/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt test_mremap
1414
check_initial_reg_state sigreturn iopl ioperm \
1515
test_vsyscall mov_ss_trap \
1616
syscall_arg_fault fsgsbase_restore sigaltstack
17+
TARGETS_C_BOTHBITS += nx_stack
1718
TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
1819
test_FCMOV test_FCOMI test_FISTTP \
1920
vdso_restorer
@@ -109,3 +110,6 @@ $(OUTPUT)/test_syscall_vdso_32: thunks_32.S
109110
# state.
110111
$(OUTPUT)/check_initial_reg_state_32: CFLAGS += -Wl,-ereal_start -static
111112
$(OUTPUT)/check_initial_reg_state_64: CFLAGS += -Wl,-ereal_start -static
113+
114+
$(OUTPUT)/nx_stack_32: CFLAGS += -Wl,-z,noexecstack
115+
$(OUTPUT)/nx_stack_64: CFLAGS += -Wl,-z,noexecstack
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/*
2+
* Copyright (c) 2023 Alexey Dobriyan <[email protected]>
3+
*
4+
* Permission to use, copy, modify, and distribute this software for any
5+
* purpose with or without fee is hereby granted, provided that the above
6+
* copyright notice and this permission notice appear in all copies.
7+
*
8+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15+
*/
16+
/*
17+
* Test that userspace stack is NX. Requires linking with -Wl,-z,noexecstack
18+
* because I don't want to bother with PT_GNU_STACK detection.
19+
*
20+
* Fill the stack with INT3's and then try to execute some of them:
21+
* SIGSEGV -- good, SIGTRAP -- bad.
22+
*
23+
* Regular stack is completely overwritten before testing.
24+
* Test doesn't exit SIGSEGV handler after first fault at INT3.
25+
*/
26+
#undef _GNU_SOURCE
27+
#define _GNU_SOURCE
28+
#undef NDEBUG
29+
#include <assert.h>
30+
#include <signal.h>
31+
#include <stdint.h>
32+
#include <stdio.h>
33+
#include <stdlib.h>
34+
#include <sys/mman.h>
35+
#include <sys/resource.h>
36+
#include <unistd.h>
37+
38+
#define PAGE_SIZE 4096
39+
40+
/*
41+
* This is memset(rsp, 0xcc, -1); but down.
42+
* It will SIGSEGV when bottom of the stack is reached.
43+
* Byte-size access is important! (see rdi tweak in the signal handler).
44+
*/
45+
void make_stack1(void);
46+
asm(
47+
".pushsection .text\n"
48+
".globl make_stack1\n"
49+
".align 16\n"
50+
"make_stack1:\n"
51+
"mov $0xcc, %al\n"
52+
#if defined __amd64__
53+
"mov %rsp, %rdi\n"
54+
"mov $-1, %rcx\n"
55+
#elif defined __i386__
56+
"mov %esp, %edi\n"
57+
"mov $-1, %ecx\n"
58+
#else
59+
#error
60+
#endif
61+
"std\n"
62+
"rep stosb\n"
63+
/* unreachable */
64+
"hlt\n"
65+
".type make_stack1,@function\n"
66+
".size make_stack1,.-make_stack1\n"
67+
".popsection\n"
68+
);
69+
70+
/*
71+
* memset(p, 0xcc, -1);
72+
* It will SIGSEGV when top of the stack is reached.
73+
*/
74+
void make_stack2(uint64_t p);
75+
asm(
76+
".pushsection .text\n"
77+
".globl make_stack2\n"
78+
".align 16\n"
79+
"make_stack2:\n"
80+
"mov $0xcc, %al\n"
81+
#if defined __amd64__
82+
"mov $-1, %rcx\n"
83+
#elif defined __i386__
84+
"mov $-1, %ecx\n"
85+
#else
86+
#error
87+
#endif
88+
"cld\n"
89+
"rep stosb\n"
90+
/* unreachable */
91+
"hlt\n"
92+
".type make_stack2,@function\n"
93+
".size make_stack2,.-make_stack2\n"
94+
".popsection\n"
95+
);
96+
97+
static volatile int test_state = 0;
98+
static volatile unsigned long stack_min_addr;
99+
100+
#if defined __amd64__
101+
#define RDI REG_RDI
102+
#define RIP REG_RIP
103+
#define RIP_STRING "rip"
104+
#elif defined __i386__
105+
#define RDI REG_EDI
106+
#define RIP REG_EIP
107+
#define RIP_STRING "eip"
108+
#else
109+
#error
110+
#endif
111+
112+
static void sigsegv(int _, siginfo_t *__, void *uc_)
113+
{
114+
/*
115+
* Some Linux versions didn't clear DF before entering signal
116+
* handler. make_stack1() doesn't have a chance to clear DF
117+
* either so we clear it by hand here.
118+
*/
119+
asm volatile ("cld" ::: "memory");
120+
121+
ucontext_t *uc = uc_;
122+
123+
if (test_state == 0) {
124+
/* Stack is faulted and cleared from RSP to the lowest address. */
125+
stack_min_addr = ++uc->uc_mcontext.gregs[RDI];
126+
if (1) {
127+
printf("stack min %lx\n", stack_min_addr);
128+
}
129+
uc->uc_mcontext.gregs[RIP] = (uintptr_t)&make_stack2;
130+
test_state = 1;
131+
} else if (test_state == 1) {
132+
/* Stack has been cleared from top to bottom. */
133+
unsigned long stack_max_addr = uc->uc_mcontext.gregs[RDI];
134+
if (1) {
135+
printf("stack max %lx\n", stack_max_addr);
136+
}
137+
/* Start faulting pages on stack and see what happens. */
138+
uc->uc_mcontext.gregs[RIP] = stack_max_addr - PAGE_SIZE;
139+
test_state = 2;
140+
} else if (test_state == 2) {
141+
/* Stack page is NX -- good, test next page. */
142+
uc->uc_mcontext.gregs[RIP] -= PAGE_SIZE;
143+
if (uc->uc_mcontext.gregs[RIP] == stack_min_addr) {
144+
/* One more SIGSEGV and test ends. */
145+
test_state = 3;
146+
}
147+
} else {
148+
printf("PASS\tAll stack pages are NX\n");
149+
_exit(EXIT_SUCCESS);
150+
}
151+
}
152+
153+
static void sigtrap(int _, siginfo_t *__, void *uc_)
154+
{
155+
const ucontext_t *uc = uc_;
156+
unsigned long rip = uc->uc_mcontext.gregs[RIP];
157+
printf("FAIL\texecutable page on the stack: " RIP_STRING " %lx\n", rip);
158+
_exit(EXIT_FAILURE);
159+
}
160+
161+
int main(void)
162+
{
163+
{
164+
struct sigaction act = {};
165+
sigemptyset(&act.sa_mask);
166+
act.sa_flags = SA_SIGINFO;
167+
act.sa_sigaction = &sigsegv;
168+
int rv = sigaction(SIGSEGV, &act, NULL);
169+
assert(rv == 0);
170+
}
171+
{
172+
struct sigaction act = {};
173+
sigemptyset(&act.sa_mask);
174+
act.sa_flags = SA_SIGINFO;
175+
act.sa_sigaction = &sigtrap;
176+
int rv = sigaction(SIGTRAP, &act, NULL);
177+
assert(rv == 0);
178+
}
179+
{
180+
struct rlimit rlim;
181+
int rv = getrlimit(RLIMIT_STACK, &rlim);
182+
assert(rv == 0);
183+
/* Cap stack at time-honored 8 MiB value. */
184+
rlim.rlim_max = rlim.rlim_cur;
185+
if (rlim.rlim_max > 8 * 1024 * 1024) {
186+
rlim.rlim_max = 8 * 1024 * 1024;
187+
}
188+
rv = setrlimit(RLIMIT_STACK, &rlim);
189+
assert(rv == 0);
190+
}
191+
{
192+
/*
193+
* We don't know now much stack SIGSEGV handler uses.
194+
* Bump this by 1 page every time someone complains,
195+
* or rewrite it in assembly.
196+
*/
197+
const size_t len = SIGSTKSZ;
198+
void *p = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
199+
assert(p != MAP_FAILED);
200+
stack_t ss = {};
201+
ss.ss_sp = p;
202+
ss.ss_size = len;
203+
int rv = sigaltstack(&ss, NULL);
204+
assert(rv == 0);
205+
}
206+
make_stack1();
207+
/*
208+
* Unreachable, but if _this_ INT3 is ever reached, it's a bug somewhere.
209+
* Fold it into main SIGTRAP pathway.
210+
*/
211+
__builtin_trap();
212+
}

0 commit comments

Comments
 (0)