]>
Commit | Line | Data |
---|---|---|
68448be2 | 1 | /* Test for i386 sigaction sa_restorer handling (BZ#21269) |
581c785b | 2 | Copyright (C) 2017-2022 Free Software Foundation, Inc. |
68448be2 AZ |
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 | |
5a82c748 | 17 | <https://www.gnu.org/licenses/>. */ |
68448be2 AZ |
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 (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 (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 (jmpbuf, 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, page_size, PROT_READ | PROT_WRITE, | |
95 | MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -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, 2 * sizeof (struct user_desc), | |
105 | PROT_READ | PROT_WRITE, | |
106 | MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -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 (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 ((int *) &ftx, FUTEX_WAIT, 1, NULL, NULL, 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 (1, &desc, 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, sigsegv_handler, 0); | |
178 | /* 32-bit kernels send SIGILL instead of SIGSEGV on IRET faults. */ | |
179 | xsethandler (SIGILL, sigsegv_handler, 0); | |
4d76d3e5 FW |
180 | /* Some kernels send SIGBUS instead. */ |
181 | xsethandler (SIGBUS, sigsegv_handler, 0); | |
68448be2 AZ |
182 | |
183 | thread = xpthread_create (0, threadproc, 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 (0x11, &desc, sizeof (desc)); | |
209 | ||
210 | /* Arm the thread. */ | |
211 | ftx = 1; | |
212 | futex ((int*) &ftx, FUTEX_WAKE, 0, NULL, NULL, 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 ((int*) &ftx, FUTEX_WAKE, 0, NULL, NULL, 0); | |
229 | ||
230 | xpthread_join (thread); | |
231 | ||
232 | return 0; | |
233 | } | |
234 | ||
235 | #include <support/test-driver.c> |