summary refs log tree commit diff
path: root/string
diff options
context:
space:
mode:
Diffstat (limited to 'string')
-rw-r--r--string/Makefile7
-rw-r--r--string/Versions3
-rw-r--r--string/bits/string3.h9
-rw-r--r--string/explicit_bzero.c38
-rw-r--r--string/string.h4
-rw-r--r--string/test-explicit_bzero.c20
-rw-r--r--string/test-memset.c10
-rw-r--r--string/tst-xbzero-opt.c288
8 files changed, 375 insertions, 4 deletions
diff --git a/string/Makefile b/string/Makefile
index 69d3f802fb..9a8b46d1bf 100644
--- a/string/Makefile
+++ b/string/Makefile
@@ -41,20 +41,21 @@ routines	:= strcat strchr strcmp strcoll strcpy strcspn		\
 				     addsep replace)			\
 		   envz basename					\
 		   strcoll_l strxfrm_l string-inlines memrchr		\
-		   xpg-strerror strerror_l
+		   xpg-strerror strerror_l explicit_bzero
 
 strop-tests	:= memchr memcmp memcpy memmove mempcpy memset memccpy	\
 		   stpcpy stpncpy strcat strchr strcmp strcpy strcspn	\
 		   strlen strncmp strncpy strpbrk strrchr strspn memmem	\
 		   strstr strcasestr strnlen strcasecmp strncasecmp	\
-		   strncat rawmemchr strchrnul bcopy bzero memrchr
+		   strncat rawmemchr strchrnul bcopy bzero memrchr	\
+		   explicit_bzero
 tests		:= tester inl-tester noinl-tester testcopy test-ffs	\
 		   tst-strlen stratcliff tst-svc tst-inlcall		\
 		   bug-strncat1 bug-strspn1 bug-strpbrk1 tst-bswap	\
 		   tst-strtok tst-strxfrm bug-strcoll1 tst-strfry	\
 		   bug-strtok1 $(addprefix test-,$(strop-tests))	\
 		   bug-envz1 tst-strxfrm2 tst-endian tst-svc2		\
-		   tst-strtok_r bug-strcoll2 tst-cmp
+		   tst-strtok_r bug-strcoll2 tst-cmp tst-xbzero-opt
 
 xtests = tst-strcoll-overflow
 
diff --git a/string/Versions b/string/Versions
index 475c1fdb64..9b709d12a9 100644
--- a/string/Versions
+++ b/string/Versions
@@ -82,4 +82,7 @@ libc {
   }
   GLIBC_2.24 {
   }
+  GLIBC_2.25 {
+    explicit_bzero;
+  }
 }
diff --git a/string/bits/string3.h b/string/bits/string3.h
index 8f13b65fa6..940c8d8e24 100644
--- a/string/bits/string3.h
+++ b/string/bits/string3.h
@@ -102,6 +102,15 @@ __NTH (bzero (void *__dest, size_t __len))
 {
   (void) __builtin___memset_chk (__dest, '\0', __len, __bos0 (__dest));
 }
+
+void __explicit_bzero_chk (void *__dest, size_t __len, size_t __destlen)
+  __THROW __nonnull ((1));
+
+__fortify_function void
+__NTH (explicit_bzero (void *__dest, size_t __len))
+{
+  __explicit_bzero_chk (__dest, __len, __bos0 (__dest));
+}
 #endif
 
 __fortify_function char *
diff --git a/string/explicit_bzero.c b/string/explicit_bzero.c
new file mode 100644
index 0000000000..6712cad5a6
--- /dev/null
+++ b/string/explicit_bzero.c
@@ -0,0 +1,38 @@
+/* Erasure of sensitive data, generic implementation.
+   Copyright (C) 2016 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/>.  */
+
+/* An assembler implementation of explicit_bzero can be created as an
+   assembler alias of an optimized bzero implementation.
+   Architecture-specific implementations also need to define
+   __explicit_bzero_chk.  */
+
+#include <string.h>
+
+/* glibc-internal users use __explicit_bzero_chk, and explicit_bzero
+   redirects to that.  */
+#undef explicit_bzero
+
+/* Set LEN bytes of S to 0.  The compiler will not delete a call to
+   this function, even if S is dead after the call.  */
+void
+explicit_bzero (void *s, size_t len)
+{
+  memset (s, '\0', len);
+  /* Compiler barrier.  */
+  asm volatile ("" ::: "memory");
+}
diff --git a/string/string.h b/string/string.h
index b103e64912..c398569c15 100644
--- a/string/string.h
+++ b/string/string.h
@@ -453,6 +453,10 @@ extern void bcopy (const void *__src, void *__dest, size_t __n)
 /* Set N bytes of S to 0.  */
 extern void bzero (void *__s, size_t __n) __THROW __nonnull ((1));
 
+/* As bzero, but the compiler will not delete a call to this
+   function, even if S is dead after the call.  */
+extern void explicit_bzero (void *__s, size_t __n) __THROW __nonnull ((1));
+
 /* Compare N bytes of S1 and S2 (same as memcmp).  */
 extern int bcmp (const void *__s1, const void *__s2, size_t __n)
      __THROW __attribute_pure__ __nonnull ((1, 2));
diff --git a/string/test-explicit_bzero.c b/string/test-explicit_bzero.c
new file mode 100644
index 0000000000..5a4543b41a
--- /dev/null
+++ b/string/test-explicit_bzero.c
@@ -0,0 +1,20 @@
+/* Test and measure explicit_bzero.
+   Copyright (C) 2016 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/>.  */
+#define TEST_EXPLICIT_BZERO
+#define TEST_BZERO
+#include "test-memset.c"
diff --git a/string/test-memset.c b/string/test-memset.c
index fee3bdf37f..7ca4f2076c 100644
--- a/string/test-memset.c
+++ b/string/test-memset.c
@@ -19,7 +19,11 @@
 
 #define TEST_MAIN
 #ifdef TEST_BZERO
-# define TEST_NAME "bzero"
+# ifdef TEST_EXPLICIT_BZERO
+#  define TEST_NAME "explicit_bzero"
+# else
+#  define TEST_NAME "bzero"
+# endif
 #else
 # ifndef WIDE
 #  define TEST_NAME "memset"
@@ -56,7 +60,11 @@ void builtin_bzero (char *, size_t);
 
 IMPL (simple_bzero, 0)
 IMPL (builtin_bzero, 0)
+#ifdef TEST_EXPLICIT_BZERO
+IMPL (explicit_bzero, 1)
+#else
 IMPL (bzero, 1)
+#endif
 
 void
 simple_bzero (char *s, size_t n)
diff --git a/string/tst-xbzero-opt.c b/string/tst-xbzero-opt.c
new file mode 100644
index 0000000000..312857e056
--- /dev/null
+++ b/string/tst-xbzero-opt.c
@@ -0,0 +1,288 @@
+/* Test that explicit_bzero block clears are not optimized out.
+   Copyright (C) 2016 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, and we expect to find it.  This
+   confirms that the compiler can optimize out block clears in this
+   context; if it can't, the real test might be succeeding for the
+   wrong reason.  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 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 ();
+}
+
+static void
+setup_no_clear (void)
+{
+  unsigned char buf[TEST_BUFFER_SIZE];
+  prepare_test_buffer (buf);
+}
+
+static void
+setup_ordinary_clear (void)
+{
+  unsigned char buf[TEST_BUFFER_SIZE];
+  prepare_test_buffer (buf);
+  memset (buf, 0, TEST_BUFFER_SIZE);
+}
+
+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 };
+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 },
+  { setup_ordinary_clear, "ordinary clear", EXPECT_SOME },
+  { 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;
+
+    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>