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). */
53static 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
98static ucontext_t uc_main, uc_co;
99
100static __attribute__ ((noinline, noclone)) int
101use_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
122static inline ALWAYS_INLINE void
123prepare_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 (oucp: &uc_co, ucp: &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
136static void
137setup_no_clear (void)
138{
139 unsigned char buf[TEST_BUFFER_SIZE];
140 prepare_test_buffer (buf);
141}
142
143static void
144setup_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
151static void
152setup_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
159enum test_expectation
160 {
161 EXPECT_NONE, EXPECT_SOME, EXPECT_ALL, NO_EXPECTATIONS
162 };
163struct subtest
164{
165 void (*setup_subtest) (void);
166 const char *label;
167 enum test_expectation expected;
168};
169static const struct subtest *cur_subtest;
170
171static 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
181static void
182test_coroutine (void)
183{
184 while (cur_subtest->setup_subtest)
185 {
186 cur_subtest->setup_subtest ();
187 if (swapcontext (oucp: &uc_co, ucp: &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
195static int test_status;
196static unsigned char *co_stack_buffer;
197static size_t co_stack_size;
198
199static unsigned int
200count_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
217static void
218check_test_buffer (enum test_expectation expected,
219 const char *label, const char *stage)
220{
221 unsigned int cnt = count_test_patterns (buf: co_stack_buffer, bufsiz: co_stack_size);
222 switch (expected)
223 {
224 case EXPECT_NONE:
225 if (cnt == 0)
226 printf (format: "PASS: %s/%s: expected 0 got %d\n", label, stage, cnt);
227 else
228 {
229 printf (format: "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 (format: "PASS: %s/%s: expected some got %d\n", label, stage, cnt);
237 else
238 {
239 printf (format: "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 (format: "PASS: %s/%s: expected %d got %d\n", label, stage,
247 PATTERN_REPS, cnt);
248 else
249 {
250 printf (format: "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 (format: "INFO: %s/%s: found %d patterns%s\n", label, stage, cnt,
258 cnt == 0 ? " (memset not eliminated)" : "");
259 break;
260
261 default:
262 printf (format: "ERROR: %s/%s: invalid value for 'expected' = %d\n",
263 label, stage, (int)expected);
264 test_status = 1;
265 }
266}
267
268static void
269test_loop (void)
270{
271 cur_subtest = subtests;
272 while (cur_subtest->setup_subtest)
273 {
274 if (swapcontext (oucp: &uc_main, ucp: &uc_co))
275 abort ();
276 check_test_buffer (expected: EXPECT_ALL, label: cur_subtest->label, stage: "prepare");
277 if (swapcontext (oucp: &uc_main, ucp: &uc_co))
278 abort ();
279 check_test_buffer (expected: cur_subtest->expected, label: cur_subtest->label, stage: "test");
280 cur_subtest++;
281 }
282 /* Terminate the coroutine. */
283 if (swapcontext (oucp: &uc_main, ucp: &uc_co))
284 abort ();
285}
286
287int
288do_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 (memptr: &p, alignment: page_alignment, size: co_stack_size);
300 if (err || !p)
301 {
302 printf (format: "ERROR: allocating alt stack: %s\n", strerror (errnum: err));
303 return 2;
304 }
305 co_stack_buffer = p;
306
307 if (getcontext (ucp: &uc_co))
308 {
309 printf (format: "ERROR: allocating coroutine context: %s\n", strerror (errnum: 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 (ucp: &uc_co, func: test_coroutine, argc: 0);
316
317 test_loop ();
318 return test_status;
319}
320
321#include <support/test-driver.c>
322

source code of glibc/string/tst-xbzero-opt.c