(root)/
glibc-2.38/
string/
tst-xbzero-opt.c
       1  /* Test that explicit_bzero block clears are not optimized out.
       2     Copyright (C) 2016-2023 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
     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
     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
     137  setup_no_clear (void)
     138  {
     139    unsigned char buf[TEST_BUFFER_SIZE];
     140    prepare_test_buffer (buf);
     141  }
     142  
     143  static 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
     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
     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
     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
     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
     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
     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>