1 /* Test that explicit_bzero block clears are not optimized out.
2    Copyright (C) 2016-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 test is conceptually based on a test designed by Matthew
20    Dempsky for the OpenBSD regression suite:
21    <openbsd>/src/regress/lib/libc/explicit_bzero/explicit_bzero.c.
22    The basic idea is, we have a function that contains a
23    block-clearing operation (not necessarily explicit_bzero), after
24    which the block is dead, in the compiler-jargon sense.  Execute
25    that function while running on a user-allocated alternative
26    stack. Then we have another pointer to the memory region affected
27    by the block clear -- namely, the original allocation for the
28    alternative stack -- and can find out whether it actually happened.
29 
30    The OpenBSD test uses sigaltstack and SIGUSR1 to get onto an
31    alternative stack.  This causes a number of awkward problems; some
32    operating systems (e.g. Solaris and OSX) wipe the signal stack upon
33    returning to the normal stack, there's no way to be sure that other
34    processes running on the same system will not interfere, and the
35    signal stack is very small so it's not safe to call printf there.
36    This implementation instead uses the <ucontext.h> coroutine
37    interface.  The coroutine stack is still too small to safely use
38    printf, but we know the OS won't erase it, so we can do all the
39    checks and printing from the normal stack.  */
40 
41 #define _GNU_SOURCE 1
42 
43 #include <errno.h>
44 #include <signal.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <ucontext.h>
49 #include <unistd.h>
50 
51 /* A byte pattern that is unlikely to occur by chance: the first 16
52    prime numbers (OEIS A000040).  */
53 static const unsigned char test_pattern[16] =
54 {
55   2, 3, 5, 7,  11, 13, 17, 19,  23, 29, 31, 37,  41, 43, 47, 53
56 };
57 
58 /* Immediately after each subtest returns, we call swapcontext to get
59    back onto the main stack.  That call might itself overwrite the
60    test pattern, so we fill a modest-sized buffer with copies of it
61    and check whether any of them survived.  */
62 
63 #define PATTERN_SIZE (sizeof test_pattern)
64 #define PATTERN_REPS 32
65 #define TEST_BUFFER_SIZE (PATTERN_SIZE * PATTERN_REPS)
66 
67 /* There are three subtests, two of which are sanity checks.
68    Each test follows this sequence:
69 
70      main                      coroutine
71      ----                      --------
72      advance cur_subtest
73      swap
74                                call setup function
75                                  prepare test buffer
76                                  swap
77      verify that buffer
78      was filled in
79      swap
80                                  possibly clear buffer
81                                  return
82                                swap
83      check buffer again,
84      according to test
85      expectation
86 
87    In the "no_clear" case, we don't do anything to the test buffer
88    between preparing it and letting it go out of scope, and we expect
89    to find it.  This confirms that the test buffer does get filled in
90    and we can find it from the stack buffer.  In the "ordinary_clear"
91    case, we clear it using memset.  Depending on the target, the
92    compiler may not be able to apply dead store elimination to the
93    memset call, so the test does not fail if the memset is not
94    eliminated.  Finally, the "explicit_clear" case uses explicit_bzero
95    and expects _not_ to find the test buffer, which is the real
96    test.  */
97 
98 static ucontext_t uc_main, uc_co;
99 
100 static __attribute__ ((noinline, noclone)) int
use_test_buffer(unsigned char * buf)101 use_test_buffer (unsigned char *buf)
102 {
103   unsigned int sum = 0;
104 
105   for (unsigned int i = 0; i < PATTERN_REPS; i++)
106     sum += buf[i * PATTERN_SIZE];
107 
108   return (sum == 2 * PATTERN_REPS) ? 0 : 1;
109 }
110 
111 /* Always check the test buffer immediately after filling it; this
112    makes externally visible side effects depend on the buffer existing
113    and having been filled in.  */
114 #if defined __CET__ && !__glibc_has_attribute (__indirect_return__)
115 /* Note: swapcontext returns via indirect branch when SHSTK is enabled.
116    Without indirect_return attribute, swapcontext is marked with
117    returns_twice attribute, which prevents always_inline to work.  */
118 # define ALWAYS_INLINE
119 #else
120 # define ALWAYS_INLINE	__attribute__ ((always_inline))
121 #endif
122 static inline ALWAYS_INLINE void
prepare_test_buffer(unsigned char * buf)123 prepare_test_buffer (unsigned char *buf)
124 {
125   for (unsigned int i = 0; i < PATTERN_REPS; i++)
126     memcpy (buf + i*PATTERN_SIZE, test_pattern, PATTERN_SIZE);
127 
128   if (swapcontext (&uc_co, &uc_main))
129     abort ();
130 
131   /* Force the compiler to really copy the pattern to buf.  */
132   if (use_test_buffer (buf))
133     abort ();
134 }
135 
136 static void
setup_no_clear(void)137 setup_no_clear (void)
138 {
139   unsigned char buf[TEST_BUFFER_SIZE];
140   prepare_test_buffer (buf);
141 }
142 
143 static void
setup_ordinary_clear(void)144 setup_ordinary_clear (void)
145 {
146   unsigned char buf[TEST_BUFFER_SIZE];
147   prepare_test_buffer (buf);
148   memset (buf, 0, TEST_BUFFER_SIZE);
149 }
150 
151 static void
setup_explicit_clear(void)152 setup_explicit_clear (void)
153 {
154   unsigned char buf[TEST_BUFFER_SIZE];
155   prepare_test_buffer (buf);
156   explicit_bzero (buf, TEST_BUFFER_SIZE);
157 }
158 
159 enum test_expectation
160   {
161     EXPECT_NONE, EXPECT_SOME, EXPECT_ALL, NO_EXPECTATIONS
162   };
163 struct subtest
164 {
165   void (*setup_subtest) (void);
166   const char *label;
167   enum test_expectation expected;
168 };
169 static const struct subtest *cur_subtest;
170 
171 static const struct subtest subtests[] =
172 {
173   { setup_no_clear,       "no clear",       EXPECT_SOME },
174   /* The memset may happen or not, depending on compiler
175      optimizations.  */
176   { setup_ordinary_clear, "ordinary clear", NO_EXPECTATIONS },
177   { setup_explicit_clear, "explicit clear", EXPECT_NONE },
178   { 0,                    0,                -1          }
179 };
180 
181 static void
test_coroutine(void)182 test_coroutine (void)
183 {
184   while (cur_subtest->setup_subtest)
185     {
186       cur_subtest->setup_subtest ();
187       if (swapcontext (&uc_co, &uc_main))
188 	abort ();
189     }
190 }
191 
192 /* All the code above this point runs on the coroutine stack.
193    All the code below this point runs on the main stack.  */
194 
195 static int test_status;
196 static unsigned char *co_stack_buffer;
197 static size_t co_stack_size;
198 
199 static unsigned int
count_test_patterns(unsigned char * buf,size_t bufsiz)200 count_test_patterns (unsigned char *buf, size_t bufsiz)
201 {
202   unsigned char *first = memmem (buf, bufsiz, test_pattern, PATTERN_SIZE);
203   if (!first)
204     return 0;
205   unsigned int cnt = 0;
206   for (unsigned int i = 0; i < PATTERN_REPS; i++)
207     {
208       unsigned char *p = first + i*PATTERN_SIZE;
209       if (p + PATTERN_SIZE - buf > bufsiz)
210 	break;
211       if (memcmp (p, test_pattern, PATTERN_SIZE) == 0)
212 	cnt++;
213     }
214   return cnt;
215 }
216 
217 static void
check_test_buffer(enum test_expectation expected,const char * label,const char * stage)218 check_test_buffer (enum test_expectation expected,
219 		   const char *label, const char *stage)
220 {
221   unsigned int cnt = count_test_patterns (co_stack_buffer, co_stack_size);
222   switch (expected)
223     {
224     case EXPECT_NONE:
225       if (cnt == 0)
226 	printf ("PASS: %s/%s: expected 0 got %d\n", label, stage, cnt);
227       else
228 	{
229 	  printf ("FAIL: %s/%s: expected 0 got %d\n", label, stage, cnt);
230 	  test_status = 1;
231 	}
232       break;
233 
234     case EXPECT_SOME:
235       if (cnt > 0)
236 	printf ("PASS: %s/%s: expected some got %d\n", label, stage, cnt);
237       else
238 	{
239 	  printf ("FAIL: %s/%s: expected some got 0\n", label, stage);
240 	  test_status = 1;
241 	}
242       break;
243 
244      case EXPECT_ALL:
245       if (cnt == PATTERN_REPS)
246 	printf ("PASS: %s/%s: expected %d got %d\n", label, stage,
247 		PATTERN_REPS, cnt);
248       else
249 	{
250 	  printf ("FAIL: %s/%s: expected %d got %d\n", label, stage,
251 		  PATTERN_REPS, cnt);
252 	  test_status = 1;
253 	}
254       break;
255 
256     case NO_EXPECTATIONS:
257       printf ("INFO: %s/%s: found %d patterns%s\n", label, stage, cnt,
258 	      cnt == 0 ? " (memset not eliminated)" : "");
259       break;
260 
261     default:
262       printf ("ERROR: %s/%s: invalid value for 'expected' = %d\n",
263 	      label, stage, (int)expected);
264       test_status = 1;
265     }
266 }
267 
268 static void
test_loop(void)269 test_loop (void)
270 {
271   cur_subtest = subtests;
272   while (cur_subtest->setup_subtest)
273     {
274       if (swapcontext (&uc_main, &uc_co))
275 	abort ();
276       check_test_buffer (EXPECT_ALL, cur_subtest->label, "prepare");
277       if (swapcontext (&uc_main, &uc_co))
278 	abort ();
279       check_test_buffer (cur_subtest->expected, cur_subtest->label, "test");
280       cur_subtest++;
281     }
282   /* Terminate the coroutine.  */
283   if (swapcontext (&uc_main, &uc_co))
284     abort ();
285 }
286 
287 int
do_test(void)288 do_test (void)
289 {
290   size_t page_alignment = sysconf (_SC_PAGESIZE);
291   if (page_alignment < sizeof (void *))
292     page_alignment = sizeof (void *);
293 
294   co_stack_size = SIGSTKSZ + TEST_BUFFER_SIZE;
295   if (co_stack_size < page_alignment * 4)
296     co_stack_size = page_alignment * 4;
297 
298   void *p;
299   int err = posix_memalign (&p, page_alignment, co_stack_size);
300   if (err || !p)
301     {
302       printf ("ERROR: allocating alt stack: %s\n", strerror (err));
303       return 2;
304     }
305   co_stack_buffer = p;
306 
307   if (getcontext (&uc_co))
308     {
309       printf ("ERROR: allocating coroutine context: %s\n", strerror (err));
310       return 2;
311     }
312   uc_co.uc_stack.ss_sp   = co_stack_buffer;
313   uc_co.uc_stack.ss_size = co_stack_size;
314   uc_co.uc_link          = &uc_main;
315   makecontext (&uc_co, test_coroutine, 0);
316 
317   test_loop ();
318   return test_status;
319 }
320 
321 #include <support/test-driver.c>
322