1 | /* Test for i386 sigaction sa_restorer handling (BZ#21269) |
2 | Copyright (C) 2017-2024 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 | long ret = syscall (sysno: SYS_modify_ldt, func, ptr, bytecount); |
56 | |
57 | if (ret == -1) |
58 | { |
59 | if (errno == ENOSYS) |
60 | FAIL_UNSUPPORTED ("modify_ldt not supported" ); |
61 | FAIL_EXIT1 ("modify_ldt failed (errno=%d)" , errno); |
62 | } |
63 | } |
64 | |
65 | static int |
66 | futex (int *uaddr, int futex_op, int val, void *timeout, int *uaddr2, |
67 | int val3) |
68 | { |
69 | return syscall (sysno: SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3); |
70 | } |
71 | |
72 | static void |
73 | xsethandler (int sig, void (*handler)(int, siginfo_t *, void *), int flags) |
74 | { |
75 | struct sigaction sa = { 0 }; |
76 | sa.sa_sigaction = handler; |
77 | sa.sa_flags = SA_SIGINFO | flags; |
78 | TEST_VERIFY_EXIT (sigemptyset (&sa.sa_mask) == 0); |
79 | TEST_VERIFY_EXIT (sigaction (sig, &sa, 0) == 0); |
80 | } |
81 | |
82 | static jmp_buf jmpbuf; |
83 | |
84 | static void |
85 | sigsegv_handler (int sig, siginfo_t *info, void *ctx_void) |
86 | { |
87 | siglongjmp (jmpbuf, 1); |
88 | } |
89 | |
90 | /* Points to an array of 1024 ints, each holding its own index. */ |
91 | static const unsigned int *counter_page; |
92 | static struct user_desc *low_user_desc; |
93 | static struct user_desc *low_user_desc_clear; /* Used to delete GDT entry. */ |
94 | static int gdt_entry_num; |
95 | |
96 | static void |
97 | setup_counter_page (void) |
98 | { |
99 | long page_size = sysconf (_SC_PAGE_SIZE); |
100 | TEST_VERIFY_EXIT (page_size > 0); |
101 | unsigned int *page = xmmap (NULL, length: page_size, PROT_READ | PROT_WRITE, |
102 | MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, fd: -1); |
103 | for (int i = 0; i < (page_size / sizeof (unsigned int)); i++) |
104 | page[i] = i; |
105 | counter_page = page; |
106 | } |
107 | |
108 | static void |
109 | setup_low_user_desc (void) |
110 | { |
111 | low_user_desc = xmmap (NULL, length: 2 * sizeof (struct user_desc), |
112 | PROT_READ | PROT_WRITE, |
113 | MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, fd: -1); |
114 | |
115 | low_user_desc->entry_number = -1; |
116 | low_user_desc->base_addr = (unsigned long) &counter_page[1]; |
117 | low_user_desc->limit = 0xffff; |
118 | low_user_desc->seg_32bit = 1; |
119 | low_user_desc->contents = 0; |
120 | low_user_desc->read_exec_only = 0; |
121 | low_user_desc->limit_in_pages = 1; |
122 | low_user_desc->seg_not_present = 0; |
123 | low_user_desc->useable = 0; |
124 | |
125 | xset_thread_area (u_info: low_user_desc); |
126 | |
127 | low_user_desc_clear = low_user_desc + 1; |
128 | low_user_desc_clear->entry_number = gdt_entry_num; |
129 | low_user_desc_clear->read_exec_only = 1; |
130 | low_user_desc_clear->seg_not_present = 1; |
131 | } |
132 | |
133 | /* Possible values of futex: |
134 | 0: thread is idle. |
135 | 1: thread armed. |
136 | 2: thread should clear LDT entry 0. |
137 | 3: thread should exit. */ |
138 | static atomic_uint ftx; |
139 | |
140 | static void * |
141 | threadproc (void *ctx) |
142 | { |
143 | while (1) |
144 | { |
145 | /* Continue to wait here until we've successfully waited, unless |
146 | we're supposed to be clearing the LDT already. */ |
147 | while (futex (uaddr: (int *) &ftx, FUTEX_WAIT, val: 1, NULL, NULL, val3: 0) < 0) |
148 | if (atomic_load (&ftx) >= 2) |
149 | break; |
150 | |
151 | /* Normally there's time to hit this busy loop and wait for ftx |
152 | to be set to 2. */ |
153 | while (atomic_load (&ftx) != 2) |
154 | { |
155 | if (atomic_load (&ftx) >= 3) |
156 | return NULL; |
157 | } |
158 | |
159 | /* clear LDT entry 0. */ |
160 | const struct user_desc desc = { 0 }; |
161 | xmodify_ldt (func: 1, ptr: &desc, bytecount: sizeof (desc)); |
162 | |
163 | /* If ftx == 2, set it to zero, If ftx == 100, quit. */ |
164 | if (atomic_fetch_add (&ftx, -2) != 2) |
165 | return NULL; |
166 | } |
167 | } |
168 | |
169 | |
170 | /* As described in testcase, for historical reasons x86_32 Linux (and compat |
171 | on x86_64) interprets SA_RESTORER clear with nonzero sa_restorer as a |
172 | request for stack switching if the SS segment is 'funny' (this is default |
173 | scenario for vDSO system). This means that anything that tries to mix |
174 | signal handling with segmentation should explicit clear the sa_restorer. |
175 | |
176 | This testcase check if sigaction in fact does it by changing the local |
177 | descriptor table (LDT) through the modify_ldt syscall and triggering |
178 | a synchronous segfault on iret fault by trying to install an invalid |
179 | segment. With a correct zeroed sa_restorer it should not trigger an |
180 | 'real' SEGSEGV and allows the siglongjmp in signal handler. */ |
181 | |
182 | static int |
183 | do_test (void) |
184 | { |
185 | setup_counter_page (); |
186 | setup_low_user_desc (); |
187 | |
188 | pthread_t thread; |
189 | unsigned short orig_ss; |
190 | |
191 | xsethandler (SIGSEGV, handler: sigsegv_handler, flags: 0); |
192 | /* 32-bit kernels send SIGILL instead of SIGSEGV on IRET faults. */ |
193 | xsethandler (SIGILL, handler: sigsegv_handler, flags: 0); |
194 | /* Some kernels send SIGBUS instead. */ |
195 | xsethandler (SIGBUS, handler: sigsegv_handler, flags: 0); |
196 | |
197 | thread = xpthread_create (attr: 0, thread_func: threadproc, closure: 0); |
198 | |
199 | asm volatile ("mov %%ss, %0" : "=rm" (orig_ss)); |
200 | |
201 | for (int i = 0; i < 5; i++) |
202 | { |
203 | if (sigsetjmp (jmpbuf, 1) != 0) |
204 | continue; |
205 | |
206 | /* We may have longjmp'd before triggering the thread. If so, |
207 | trigger the thread now and wait for it. */ |
208 | if (atomic_load (&ftx) == 1) |
209 | atomic_store (&ftx, 2); |
210 | |
211 | /* Make sure the thread is ready after the last test. FTX is |
212 | initially zero for the first loop, and set to zero each time |
213 | the thread clears the LDT. */ |
214 | while (atomic_load (&ftx) != 0) |
215 | ; |
216 | |
217 | struct user_desc desc = { |
218 | .entry_number = 0, |
219 | .base_addr = 0, |
220 | .limit = 0xffff, |
221 | .seg_32bit = 1, |
222 | .contents = 0, |
223 | .read_exec_only = 0, |
224 | .limit_in_pages = 1, |
225 | .seg_not_present = 0, |
226 | .useable = 0 |
227 | }; |
228 | |
229 | xmodify_ldt (func: 0x11, ptr: &desc, bytecount: sizeof (desc)); |
230 | |
231 | /* Arm the thread. We loop here until we've woken up one thread. */ |
232 | atomic_store (&ftx, 1); |
233 | while (futex (uaddr: (int*) &ftx, FUTEX_WAKE, val: 1, NULL, NULL, val3: 0) < 1) |
234 | ; |
235 | |
236 | /* Give the thread a chance to get into it's busy loop. */ |
237 | usleep (useconds: 5); |
238 | |
239 | /* At *ANY* point after this instruction, we may segfault and |
240 | longjump back to the top of the loop. The intention is to |
241 | have this happen when the thread clears the LDT, but it could |
242 | happen elsewhen. */ |
243 | asm volatile ("mov %0, %%ss" : : "r" (0x7)); |
244 | |
245 | /* Fire up thread modify_ldt call. */ |
246 | atomic_store (&ftx, 2); |
247 | |
248 | /* And wait for it. */ |
249 | while (atomic_load (&ftx) != 0) |
250 | ; |
251 | |
252 | /* On success, modify_ldt will segfault us synchronously and we will |
253 | escape via siglongjmp. */ |
254 | support_record_failure (); |
255 | } |
256 | |
257 | atomic_store (&ftx, 100); |
258 | futex (uaddr: (int*) &ftx, FUTEX_WAKE, val: 0, NULL, NULL, val3: 0); |
259 | |
260 | xpthread_join (thr: thread); |
261 | |
262 | return 0; |
263 | } |
264 | |
265 | #include <support/test-driver.c> |
266 | |