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
xset_thread_area(struct user_desc * u_info)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
xmodify_ldt(int func,const void * ptr,unsigned long bytecount)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
futex(int * uaddr,int futex_op,int val,void * timeout,int * uaddr2,int val3)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
xsethandler(int sig,void (* handler)(int,siginfo_t *,void *),int flags)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
sigsegv_handler(int sig,siginfo_t * info,void * ctx_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
setup_counter_page(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
setup_low_user_desc(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 *
threadproc(void * ctx)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
do_test(void)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);
180   /* Some kernels send SIGBUS instead.  */
181   xsethandler (SIGBUS, sigsegv_handler, 0);
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>
236