| /* Test that explicit_bzero block clears are not optimized out. |
| Copyright (C) 2016-2018 Free Software Foundation, Inc. |
| This file is part of the GNU C Library. |
| |
| The GNU C Library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| The GNU C Library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with the GNU C Library; if not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* This test is conceptually based on a test designed by Matthew |
| Dempsky for the OpenBSD regression suite: |
| <openbsd>/src/regress/lib/libc/explicit_bzero/explicit_bzero.c. |
| The basic idea is, we have a function that contains a |
| block-clearing operation (not necessarily explicit_bzero), after |
| which the block is dead, in the compiler-jargon sense. Execute |
| that function while running on a user-allocated alternative |
| stack. Then we have another pointer to the memory region affected |
| by the block clear -- namely, the original allocation for the |
| alternative stack -- and can find out whether it actually happened. |
| |
| The OpenBSD test uses sigaltstack and SIGUSR1 to get onto an |
| alternative stack. This causes a number of awkward problems; some |
| operating systems (e.g. Solaris and OSX) wipe the signal stack upon |
| returning to the normal stack, there's no way to be sure that other |
| processes running on the same system will not interfere, and the |
| signal stack is very small so it's not safe to call printf there. |
| This implementation instead uses the <ucontext.h> coroutine |
| interface. The coroutine stack is still too small to safely use |
| printf, but we know the OS won't erase it, so we can do all the |
| checks and printing from the normal stack. */ |
| |
| #define _GNU_SOURCE 1 |
| |
| #include <errno.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ucontext.h> |
| #include <unistd.h> |
| |
| /* A byte pattern that is unlikely to occur by chance: the first 16 |
| prime numbers (OEIS A000040). */ |
| static const unsigned char test_pattern[16] = |
| { |
| 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 |
| }; |
| |
| /* Immediately after each subtest returns, we call swapcontext to get |
| back onto the main stack. That call might itself overwrite the |
| test pattern, so we fill a modest-sized buffer with copies of it |
| and check whether any of them survived. */ |
| |
| #define PATTERN_SIZE (sizeof test_pattern) |
| #define PATTERN_REPS 32 |
| #define TEST_BUFFER_SIZE (PATTERN_SIZE * PATTERN_REPS) |
| |
| /* There are three subtests, two of which are sanity checks. |
| Each test follows this sequence: |
| |
| main coroutine |
| ---- -------- |
| advance cur_subtest |
| swap |
| call setup function |
| prepare test buffer |
| swap |
| verify that buffer |
| was filled in |
| swap |
| possibly clear buffer |
| return |
| swap |
| check buffer again, |
| according to test |
| expectation |
| |
| In the "no_clear" case, we don't do anything to the test buffer |
| between preparing it and letting it go out of scope, and we expect |
| to find it. This confirms that the test buffer does get filled in |
| and we can find it from the stack buffer. In the "ordinary_clear" |
| case, we clear it using memset. Depending on the target, the |
| compiler may not be able to apply dead store elimination to the |
| memset call, so the test does not fail if the memset is not |
| eliminated. Finally, the "explicit_clear" case uses explicit_bzero |
| and expects _not_ to find the test buffer, which is the real |
| test. */ |
| |
| static ucontext_t uc_main, uc_co; |
| |
| /* Always check the test buffer immediately after filling it; this |
| makes externally visible side effects depend on the buffer existing |
| and having been filled in. */ |
| static inline __attribute__ ((always_inline)) void |
| prepare_test_buffer (unsigned char *buf) |
| { |
| for (unsigned int i = 0; i < PATTERN_REPS; i++) |
| memcpy (buf + i*PATTERN_SIZE, test_pattern, PATTERN_SIZE); |
| |
| if (swapcontext (&uc_co, &uc_main)) |
| abort (); |
| } |
| |
| /* Use a volatile global to ensure that aggressive compilers don't |
| decide that the test buffer is unused and evaporate it. */ |
| |
| volatile unsigned char *vol_glob; |
| |
| static void |
| setup_no_clear (void) |
| { |
| unsigned char buf[TEST_BUFFER_SIZE]; |
| prepare_test_buffer (buf); |
| vol_glob = buf; |
| } |
| |
| static void |
| setup_ordinary_clear (void) |
| { |
| unsigned char buf[TEST_BUFFER_SIZE]; |
| prepare_test_buffer (buf); |
| memset (buf, 0, TEST_BUFFER_SIZE); |
| vol_glob = buf; |
| } |
| |
| static void |
| setup_explicit_clear (void) |
| { |
| unsigned char buf[TEST_BUFFER_SIZE]; |
| prepare_test_buffer (buf); |
| explicit_bzero (buf, TEST_BUFFER_SIZE); |
| } |
| |
| enum test_expectation |
| { |
| EXPECT_NONE, EXPECT_SOME, EXPECT_ALL, NO_EXPECTATIONS |
| }; |
| struct subtest |
| { |
| void (*setup_subtest) (void); |
| const char *label; |
| enum test_expectation expected; |
| }; |
| static const struct subtest *cur_subtest; |
| |
| static const struct subtest subtests[] = |
| { |
| { setup_no_clear, "no clear", EXPECT_SOME }, |
| /* The memset may happen or not, depending on compiler |
| optimizations. */ |
| { setup_ordinary_clear, "ordinary clear", NO_EXPECTATIONS }, |
| { setup_explicit_clear, "explicit clear", EXPECT_NONE }, |
| { 0, 0, -1 } |
| }; |
| |
| static void |
| test_coroutine (void) |
| { |
| while (cur_subtest->setup_subtest) |
| { |
| cur_subtest->setup_subtest (); |
| if (swapcontext (&uc_co, &uc_main)) |
| abort (); |
| } |
| } |
| |
| /* All the code above this point runs on the coroutine stack. |
| All the code below this point runs on the main stack. */ |
| |
| static int test_status; |
| static unsigned char *co_stack_buffer; |
| static size_t co_stack_size; |
| |
| static unsigned int |
| count_test_patterns (unsigned char *buf, size_t bufsiz) |
| { |
| unsigned char *first = memmem (buf, bufsiz, test_pattern, PATTERN_SIZE); |
| if (!first) |
| return 0; |
| unsigned int cnt = 0; |
| for (unsigned int i = 0; i < PATTERN_REPS; i++) |
| { |
| unsigned char *p = first + i*PATTERN_SIZE; |
| if (p + PATTERN_SIZE - buf > bufsiz) |
| break; |
| if (memcmp (p, test_pattern, PATTERN_SIZE) == 0) |
| cnt++; |
| } |
| return cnt; |
| } |
| |
| static void |
| check_test_buffer (enum test_expectation expected, |
| const char *label, const char *stage) |
| { |
| unsigned int cnt = count_test_patterns (co_stack_buffer, co_stack_size); |
| switch (expected) |
| { |
| case EXPECT_NONE: |
| if (cnt == 0) |
| printf ("PASS: %s/%s: expected 0 got %d\n", label, stage, cnt); |
| else |
| { |
| printf ("FAIL: %s/%s: expected 0 got %d\n", label, stage, cnt); |
| test_status = 1; |
| } |
| break; |
| |
| case EXPECT_SOME: |
| if (cnt > 0) |
| printf ("PASS: %s/%s: expected some got %d\n", label, stage, cnt); |
| else |
| { |
| printf ("FAIL: %s/%s: expected some got 0\n", label, stage); |
| test_status = 1; |
| } |
| break; |
| |
| case EXPECT_ALL: |
| if (cnt == PATTERN_REPS) |
| printf ("PASS: %s/%s: expected %d got %d\n", label, stage, |
| PATTERN_REPS, cnt); |
| else |
| { |
| printf ("FAIL: %s/%s: expected %d got %d\n", label, stage, |
| PATTERN_REPS, cnt); |
| test_status = 1; |
| } |
| break; |
| |
| case NO_EXPECTATIONS: |
| printf ("INFO: %s/%s: found %d patterns%s\n", label, stage, cnt, |
| cnt == 0 ? " (memset not eliminated)" : ""); |
| break; |
| |
| default: |
| printf ("ERROR: %s/%s: invalid value for 'expected' = %d\n", |
| label, stage, (int)expected); |
| test_status = 1; |
| } |
| } |
| |
| static void |
| test_loop (void) |
| { |
| cur_subtest = subtests; |
| while (cur_subtest->setup_subtest) |
| { |
| if (swapcontext (&uc_main, &uc_co)) |
| abort (); |
| check_test_buffer (EXPECT_ALL, cur_subtest->label, "prepare"); |
| if (swapcontext (&uc_main, &uc_co)) |
| abort (); |
| check_test_buffer (cur_subtest->expected, cur_subtest->label, "test"); |
| cur_subtest++; |
| } |
| /* Terminate the coroutine. */ |
| if (swapcontext (&uc_main, &uc_co)) |
| abort (); |
| } |
| |
| int |
| do_test (void) |
| { |
| size_t page_alignment = sysconf (_SC_PAGESIZE); |
| if (page_alignment < sizeof (void *)) |
| page_alignment = sizeof (void *); |
| |
| co_stack_size = SIGSTKSZ + TEST_BUFFER_SIZE; |
| if (co_stack_size < page_alignment * 4) |
| co_stack_size = page_alignment * 4; |
| |
| void *p; |
| int err = posix_memalign (&p, page_alignment, co_stack_size); |
| if (err || !p) |
| { |
| printf ("ERROR: allocating alt stack: %s\n", strerror (err)); |
| return 2; |
| } |
| co_stack_buffer = p; |
| |
| if (getcontext (&uc_co)) |
| { |
| printf ("ERROR: allocating coroutine context: %s\n", strerror (err)); |
| return 2; |
| } |
| uc_co.uc_stack.ss_sp = co_stack_buffer; |
| uc_co.uc_stack.ss_size = co_stack_size; |
| uc_co.uc_link = &uc_main; |
| makecontext (&uc_co, test_coroutine, 0); |
| |
| test_loop (); |
| return test_status; |
| } |
| |
| #include <support/test-driver.c> |