1 | /* Test for i386 sigaction sa_restorer handling (BZ#21269) |
2 | Copyright (C) 2017-2022 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | /* This is based on Linux test tools/testing/selftests/x86/ldt_gdt.c, |
20 | more specifically in do_multicpu_tests function. The main changes |
21 | are: |
22 | |
23 | - C11 atomics instead of plain access. |
24 | - Remove x86_64 support which simplifies the syscall handling |
25 | and fallbacks. |
26 | - Replicate only the test required to trigger the issue for the |
27 | BZ#21269. */ |
28 | |
29 | #include <stdatomic.h> |
30 | |
31 | #include <asm/ldt.h> |
32 | #include <linux/futex.h> |
33 | |
34 | #include <setjmp.h> |
35 | #include <signal.h> |
36 | #include <errno.h> |
37 | #include <sys/syscall.h> |
38 | #include <sys/mman.h> |
39 | |
40 | #include <support/xunistd.h> |
41 | #include <support/check.h> |
42 | #include <support/xthread.h> |
43 | |
44 | static int |
45 | xset_thread_area (struct user_desc *u_info) |
46 | { |
47 | long ret = syscall (sysno: SYS_set_thread_area, u_info); |
48 | TEST_VERIFY_EXIT (ret == 0); |
49 | return ret; |
50 | } |
51 | |
52 | static void |
53 | xmodify_ldt (int func, const void *ptr, unsigned long bytecount) |
54 | { |
55 | TEST_VERIFY_EXIT (syscall (SYS_modify_ldt, 1, ptr, bytecount) == 0); |
56 | } |
57 | |
58 | static int |
59 | futex (int *uaddr, int futex_op, int val, void *timeout, int *uaddr2, |
60 | int val3) |
61 | { |
62 | return syscall (sysno: SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3); |
63 | } |
64 | |
65 | static void |
66 | xsethandler (int sig, void (*handler)(int, siginfo_t *, void *), int flags) |
67 | { |
68 | struct sigaction sa = { 0 }; |
69 | sa.sa_sigaction = handler; |
70 | sa.sa_flags = SA_SIGINFO | flags; |
71 | TEST_VERIFY_EXIT (sigemptyset (&sa.sa_mask) == 0); |
72 | TEST_VERIFY_EXIT (sigaction (sig, &sa, 0) == 0); |
73 | } |
74 | |
75 | static jmp_buf jmpbuf; |
76 | |
77 | static void |
78 | sigsegv_handler (int sig, siginfo_t *info, void *ctx_void) |
79 | { |
80 | siglongjmp (env: jmpbuf, val: 1); |
81 | } |
82 | |
83 | /* Points to an array of 1024 ints, each holding its own index. */ |
84 | static const unsigned int *counter_page; |
85 | static struct user_desc *low_user_desc; |
86 | static struct user_desc *low_user_desc_clear; /* Used to delete GDT entry. */ |
87 | static int gdt_entry_num; |
88 | |
89 | static void |
90 | setup_counter_page (void) |
91 | { |
92 | long page_size = sysconf (_SC_PAGE_SIZE); |
93 | TEST_VERIFY_EXIT (page_size > 0); |
94 | unsigned int *page = xmmap (NULL, length: page_size, PROT_READ | PROT_WRITE, |
95 | MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, fd: -1); |
96 | for (int i = 0; i < (page_size / sizeof (unsigned int)); i++) |
97 | page[i] = i; |
98 | counter_page = page; |
99 | } |
100 | |
101 | static void |
102 | setup_low_user_desc (void) |
103 | { |
104 | low_user_desc = xmmap (NULL, length: 2 * sizeof (struct user_desc), |
105 | PROT_READ | PROT_WRITE, |
106 | MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, fd: -1); |
107 | |
108 | low_user_desc->entry_number = -1; |
109 | low_user_desc->base_addr = (unsigned long) &counter_page[1]; |
110 | low_user_desc->limit = 0xffff; |
111 | low_user_desc->seg_32bit = 1; |
112 | low_user_desc->contents = 0; |
113 | low_user_desc->read_exec_only = 0; |
114 | low_user_desc->limit_in_pages = 1; |
115 | low_user_desc->seg_not_present = 0; |
116 | low_user_desc->useable = 0; |
117 | |
118 | xset_thread_area (u_info: low_user_desc); |
119 | |
120 | low_user_desc_clear = low_user_desc + 1; |
121 | low_user_desc_clear->entry_number = gdt_entry_num; |
122 | low_user_desc_clear->read_exec_only = 1; |
123 | low_user_desc_clear->seg_not_present = 1; |
124 | } |
125 | |
126 | /* Possible values of futex: |
127 | 0: thread is idle. |
128 | 1: thread armed. |
129 | 2: thread should clear LDT entry 0. |
130 | 3: thread should exit. */ |
131 | static atomic_uint ftx; |
132 | |
133 | static void * |
134 | threadproc (void *ctx) |
135 | { |
136 | while (1) |
137 | { |
138 | futex (uaddr: (int *) &ftx, FUTEX_WAIT, val: 1, NULL, NULL, val3: 0); |
139 | while (atomic_load (&ftx) != 2) |
140 | { |
141 | if (atomic_load (&ftx) >= 3) |
142 | return NULL; |
143 | } |
144 | |
145 | /* clear LDT entry 0. */ |
146 | const struct user_desc desc = { 0 }; |
147 | xmodify_ldt (func: 1, ptr: &desc, bytecount: sizeof (desc)); |
148 | |
149 | /* If ftx == 2, set it to zero, If ftx == 100, quit. */ |
150 | if (atomic_fetch_add (&ftx, -2) != 2) |
151 | return NULL; |
152 | } |
153 | } |
154 | |
155 | |
156 | /* As described in testcase, for historical reasons x86_32 Linux (and compat |
157 | on x86_64) interprets SA_RESTORER clear with nonzero sa_restorer as a |
158 | request for stack switching if the SS segment is 'funny' (this is default |
159 | scenario for vDSO system). This means that anything that tries to mix |
160 | signal handling with segmentation should explicit clear the sa_restorer. |
161 | |
162 | This testcase check if sigaction in fact does it by changing the local |
163 | descriptor table (LDT) through the modify_ldt syscall and triggering |
164 | a synchronous segfault on iret fault by trying to install an invalid |
165 | segment. With a correct zeroed sa_restorer it should not trigger an |
166 | 'real' SEGSEGV and allows the siglongjmp in signal handler. */ |
167 | |
168 | static int |
169 | do_test (void) |
170 | { |
171 | setup_counter_page (); |
172 | setup_low_user_desc (); |
173 | |
174 | pthread_t thread; |
175 | unsigned short orig_ss; |
176 | |
177 | xsethandler (SIGSEGV, handler: sigsegv_handler, flags: 0); |
178 | /* 32-bit kernels send SIGILL instead of SIGSEGV on IRET faults. */ |
179 | xsethandler (SIGILL, handler: sigsegv_handler, flags: 0); |
180 | /* Some kernels send SIGBUS instead. */ |
181 | xsethandler (SIGBUS, handler: sigsegv_handler, flags: 0); |
182 | |
183 | thread = xpthread_create (attr: 0, thread_func: threadproc, closure: 0); |
184 | |
185 | asm volatile ("mov %%ss, %0" : "=rm" (orig_ss)); |
186 | |
187 | for (int i = 0; i < 5; i++) |
188 | { |
189 | if (sigsetjmp (jmpbuf, 1) != 0) |
190 | continue; |
191 | |
192 | /* Make sure the thread is ready after the last test. */ |
193 | while (atomic_load (&ftx) != 0) |
194 | ; |
195 | |
196 | struct user_desc desc = { |
197 | .entry_number = 0, |
198 | .base_addr = 0, |
199 | .limit = 0xffff, |
200 | .seg_32bit = 1, |
201 | .contents = 0, |
202 | .read_exec_only = 0, |
203 | .limit_in_pages = 1, |
204 | .seg_not_present = 0, |
205 | .useable = 0 |
206 | }; |
207 | |
208 | xmodify_ldt (func: 0x11, ptr: &desc, bytecount: sizeof (desc)); |
209 | |
210 | /* Arm the thread. */ |
211 | ftx = 1; |
212 | futex (uaddr: (int*) &ftx, FUTEX_WAKE, val: 0, NULL, NULL, val3: 0); |
213 | |
214 | asm volatile ("mov %0, %%ss" : : "r" (0x7)); |
215 | |
216 | /* Fire up thread modify_ldt call. */ |
217 | atomic_store (&ftx, 2); |
218 | |
219 | while (atomic_load (&ftx) != 0) |
220 | ; |
221 | |
222 | /* On success, modify_ldt will segfault us synchronously and we will |
223 | escape via siglongjmp. */ |
224 | support_record_failure (); |
225 | } |
226 | |
227 | atomic_store (&ftx, 100); |
228 | futex (uaddr: (int*) &ftx, FUTEX_WAKE, val: 0, NULL, NULL, val3: 0); |
229 | |
230 | xpthread_join (thr: thread); |
231 | |
232 | return 0; |
233 | } |
234 | |
235 | #include <support/test-driver.c> |
236 | |