2022-11-27 Paul Eggert <eggert@cs.ucla.edu>
+ memset_explicit: new module
+ * doc/posix-functions/memset_explicit.texi, lib/memset_explicit.c:
+ * m4/memset_explicit.m4, modules/memset_explicit:
+ * modules/memset_explicit-tests, tests/test-memset_explicit.c:
+ New files.
+ * lib/string.in.h (memset_explict): New decl.
+ * m4/string_h.m4 (gl_STRING_H, gl_STRING_H_REQUIRE_DEFAULTS)
+ (gl_STRING_H_DEFAULTS):
+ * modules/string (string.h):
+ Support memset_explicit.
+
explicit_bzero: add poison
* m4/string_h.m4 (gl_STRING_H): Poison explicit_bzero.
This was inadvertently omitted when explicit_bzero was added.
func_module stdckdint
func_end_table
+ element="String handling <string.h>"
+ element=`printf "%s" "$element" | sed -e "$sed_lt" -e "$sed_gt"`
+ func_section_wrap c23_ext_string
+ func_wrap H3
+ func_echo "$element"
+
+ func_module memset_explicit
+
element="Support for GNU multiple precision arithmetic"
func_section_wrap gmp
func_wrap H2
* memcpy::
* memmove::
* memset::
+* memset_explicit::
* mkdir::
* mkdirat::
* mkdtemp::
@include posix-functions/memcpy.texi
@include posix-functions/memmove.texi
@include posix-functions/memset.texi
+@include posix-functions/memset_explicit.texi
@include posix-functions/mkdir.texi
@include posix-functions/mkdirat.texi
@include posix-functions/mkdtemp.texi
--- /dev/null
+@node memset_explicit
+@subsection @code{memset_explicit}
+@findex memset_explicit
+
+Documentation:
+@itemize
+@item
+@ifinfo
+@ref{Erasing Sensitive Data,,Erasing Sensitive Data,libc},
+@end ifinfo
+@ifnotinfo
+@url{https://www.gnu.org/software/libc/manual/html_node/Erasing-Sensitive-Data.html},
+@end ifnotinfo
+@c Not yet present:
+@c @item
+@c @uref{https://www.kernel.org/doc/man-pages/online/pages/man3/memset_explicit.3.html,,man memset_explicit}.
+@end itemize
+
+Gnulib module: memset_explicit
+
+The @code{memset_explicit} function is an approximation to what is
+needed, and does not suffice in general to erase information.
+Although calling @code{memset_explicit} should clear the memory in
+question, the information that was in memory may still be available
+elsewhere on the machine. Proper implementation of information
+erasure requires support from levels below C code.
+
+Portability problems fixed by Gnulib:
+@itemize
+@item
+This function is missing on some platforms:
+glibc 2.36, FreeBSD 13.1, NetBSD 9.3, OpenBSD 7.2, macOS 13, Solaris 11.4, Android 13,
+and many other systems.
+@end itemize
+
+Portability problems not fixed by Gnulib:
+@itemize
+@item
+Although the module's implementation should set the memory on
+platforms compatible with GCC and on platforms using traditional
+linkers, it may not set the memory on non-GCC platforms that use
+whole-program optimization.
+@end itemize
--- /dev/null
+/* Erase sensitive data from memory.
+ Copyright 2022 Free Software Foundation, Inc.
+
+ This file 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.
+
+ This file 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 this program. If not, see <https://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+/* memset_s need this define */
+#if HAVE_MEMSET_S
+# define __STDC_WANT_LIB_EXT1__ 1
+#endif
+
+#include <string.h>
+
+#if defined _WIN32 && !defined __CYGWIN__
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+#endif
+
+/* Set S's bytes to C, where S has LEN bytes. The compiler will not
+ optimize effects away, even if S is dead after the call. */
+void *
+memset_explicit (void *s, int c, size_t len)
+{
+#if defined _WIN32 && !defined __CYGWIN__
+ if (!c)
+ return SecureZeroMemory (s, len);
+#endif
+#if HAVE_EXPLICIT_MEMSET
+ return explicit_memset (s, c, len);
+#elif HAVE_MEMSET_S
+ (void) memset_s (s, len, c, len);
+ return s;
+#elif defined __GNUC__ && !defined __clang__
+ memset (s, c, len);
+ /* Compiler barrier. */
+ __asm__ volatile ("" ::: "memory");
+ return s;
+#elif defined __clang__
+ memset (s, c, len);
+ /* Compiler barrier. */
+ /* With asm ("" ::: "memory") LLVM analyzes uses of 's' and finds that the
+ whole thing is dead and eliminates it. Use 'g' to work around this
+ problem. See <https://bugs.llvm.org/show_bug.cgi?id=15495#c11>. */
+ __asm__ volatile ("" : : "g"(s) : "memory");
+ return s;
+#else
+ /* Invoke memset through a volatile function pointer. This defeats compiler
+ optimizations. */
+ void * (* const volatile volatile_memset) (void *, int, size_t) = memset;
+ return volatile_memset (s, c, len);
+#endif
+}
# endif
#endif
+/* Overwrite a block of memory. The compiler will not optimize
+ effects away, even if the block is dead after the call. */
+#if @GNULIB_MEMSET_EXPLICIT@
+# if ! @HAVE_MEMSET_EXPLICIT@
+_GL_FUNCDECL_SYS (memset_explicit, void *,
+ (void *__dest, int __c, size_t __n) _GL_ARG_NONNULL ((1)));
+# endif
+_GL_CXXALIAS_SYS (memset_explicit, void *, (void *__dest, int __c, size_t __n));
+_GL_CXXALIASWARN (memset_explicit);
+#elif defined GNULIB_POSIXCHECK
+# undef memset_explicit
+# if HAVE_RAW_DECL_MEMSET_EXPLICIT
+_GL_WARN_ON_USE (memset_explicit, "memset_explicit is unportable - "
+ "use gnulib module memset_explicit for portability");
+# endif
+#endif
+
/* Find the first occurrence of C in S. More efficient than
memchr(S,C,N), at the expense of undefined behavior if C does not
occur within N bytes. */
--- /dev/null
+dnl Copyright 2022 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_MEMSET_EXPLICIT],
+[
+ AC_REQUIRE([gl_STRING_H_DEFAULTS])
+
+ AC_CHECK_FUNCS_ONCE([memset_explicit])
+ if test $ac_cv_func_memset_explicit = no; then
+ HAVE_MEMSET_EXPLICIT=0
+ fi
+])
+
+AC_DEFUN([gl_PREREQ_MEMSET_EXPLICIT],
+[
+ AC_CHECK_FUNCS([explicit_memset])
+ AC_CHECK_FUNCS_ONCE([memset_s])
+])
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
-# serial 34
+# serial 35
# Written by Paul Eggert.
dnl guaranteed by C89.
gl_WARN_ON_USE_PREPARE([[#include <string.h>
]],
- [explicit_bzero ffsl ffsll memmem mempcpy memrchr
+ [explicit_bzero ffsl ffsll memmem mempcpy memrchr memset_explicit
rawmemchr stpcpy stpncpy strchrnul
strdup strncat strndup strnlen strpbrk strsep strcasestr strtok_r
strerror_r strerrorname_np sigabbrev_np sigdescr_np strsignal strverscmp])
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MEMMEM])
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MEMPCPY])
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MEMRCHR])
+ gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MEMSET_EXPLICIT])
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_RAWMEMCHR])
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STPCPY])
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STPNCPY])
HAVE_FFSLL=1; AC_SUBST([HAVE_FFSLL])
HAVE_DECL_MEMMEM=1; AC_SUBST([HAVE_DECL_MEMMEM])
HAVE_MEMPCPY=1; AC_SUBST([HAVE_MEMPCPY])
+ HAVE_MEMSET_EXPLICIT=1; AC_SUBST([HAVE_MEMSET_EXPLICIT])
HAVE_DECL_MEMRCHR=1; AC_SUBST([HAVE_DECL_MEMRCHR])
HAVE_RAWMEMCHR=1; AC_SUBST([HAVE_RAWMEMCHR])
HAVE_STPCPY=1; AC_SUBST([HAVE_STPCPY])
--- /dev/null
+Description:
+Erase sensitive data from memory.
+
+Files:
+lib/memset_explicit.c
+m4/memset_explicit.m4
+
+Depends-on:
+string
+
+configure.ac:
+gl_FUNC_MEMSET_EXPLICIT
+gl_CONDITIONAL([GL_COND_OBJ_MEMSET_EXPLICIT], [test $HAVE_MEMSET_EXPLICIT = 0])
+AM_COND_IF([GL_COND_OBJ_MEMSET_EXPLICIT], [
+ gl_PREREQ_MEMSET_EXPLICIT
+])
+gl_STRING_MODULE_INDICATOR([memset_explicit])
+
+Makefile.am:
+if GL_COND_OBJ_MEMSET_EXPLICIT
+lib_SOURCES += memset_explicit.c
+endif
+
+Include:
+<string.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
--- /dev/null
+Files:
+tests/test-memset_explicit.c
+tests/signature.h
+tests/macros.h
+
+Depends-on:
+stdint
+vma-iter
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-memset_explicit
+check_PROGRAMS += test-memset_explicit
-e 's/@''GNULIB_MEMMEM''@/$(GNULIB_MEMMEM)/g' \
-e 's/@''GNULIB_MEMPCPY''@/$(GNULIB_MEMPCPY)/g' \
-e 's/@''GNULIB_MEMRCHR''@/$(GNULIB_MEMRCHR)/g' \
+ -e 's/@''GNULIB_MEMSET_EXPLICIT''@/$(GNULIB_MEMSET_EXPLICIT)/g' \
-e 's/@''GNULIB_RAWMEMCHR''@/$(GNULIB_RAWMEMCHR)/g' \
-e 's/@''GNULIB_STPCPY''@/$(GNULIB_STPCPY)/g' \
-e 's/@''GNULIB_STPNCPY''@/$(GNULIB_STPNCPY)/g' \
-e 's|@''HAVE_DECL_MEMMEM''@|$(HAVE_DECL_MEMMEM)|g' \
-e 's|@''HAVE_MEMPCPY''@|$(HAVE_MEMPCPY)|g' \
-e 's|@''HAVE_DECL_MEMRCHR''@|$(HAVE_DECL_MEMRCHR)|g' \
+ -e 's|@''HAVE_MEMSET_EXPLICIT''@|$(HAVE_MEMSET_EXPLICIT)|g' \
-e 's|@''HAVE_RAWMEMCHR''@|$(HAVE_RAWMEMCHR)|g' \
-e 's|@''HAVE_STPCPY''@|$(HAVE_STPCPY)|g' \
-e 's|@''HAVE_STPNCPY''@|$(HAVE_STPNCPY)|g' \
--- /dev/null
+/* Test memset_explicit.
+ Copyright 2020-2022 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2020. */
+/* Adapted for memset_explicit by Paul Eggert <eggert@cs.ucla.edu>, 2022. */
+
+#include <config.h>
+
+/* Specification. */
+#include <string.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (memset_explicit, void, (void *, int, size_t));
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "vma-iter.h"
+#include "macros.h"
+
+#define SECRET "xyzzy1729"
+#define SECRET_SIZE 9
+
+static char zero[SECRET_SIZE] = { 0 };
+
+/* Enable this to verify that the test is effective. */
+#if 0
+# define memset_explicit(a, c, n) memset (a, c, n)
+#endif
+
+/* =================== Verify operation on static memory =================== */
+
+static char stbuf[SECRET_SIZE];
+
+static void
+test_static (void)
+{
+ memcpy (stbuf, SECRET, SECRET_SIZE);
+ memset_explicit (stbuf, 0, SECRET_SIZE);
+ ASSERT (memcmp (zero, stbuf, SECRET_SIZE) == 0);
+ for (int i = 1; i <= UCHAR_MAX; i++)
+ {
+ char checkbuf[SECRET_SIZE];
+ memset (checkbuf, i, SECRET_SIZE);
+ memcpy (stbuf, SECRET, SECRET_SIZE);
+ memset_explicit (stbuf, i, SECRET_SIZE);
+ ASSERT (memcmp (checkbuf, stbuf, SECRET_SIZE) == 0);
+ }
+}
+
+/* =============== Verify operation on heap-allocated memory =============== */
+
+/* Test whether an address range is mapped in memory. */
+#if VMA_ITERATE_SUPPORTED
+
+struct locals
+{
+ uintptr_t range_start;
+ uintptr_t range_end;
+};
+
+static int
+vma_iterate_callback (void *data, uintptr_t start, uintptr_t end,
+ unsigned int flags)
+{
+ struct locals *lp = (struct locals *) data;
+
+ /* Remove from [range_start, range_end) the part at the beginning or at the
+ end that is covered by [start, end). */
+ if (start <= lp->range_start && end > lp->range_start)
+ lp->range_start = (end < lp->range_end ? end : lp->range_end);
+ if (start < lp->range_end && end >= lp->range_end)
+ lp->range_end = (start > lp->range_start ? start : lp->range_start);
+
+ return 0;
+}
+
+static bool
+is_range_mapped (uintptr_t range_start, uintptr_t range_end)
+{
+ struct locals l;
+
+ l.range_start = range_start;
+ l.range_end = range_end;
+ vma_iterate (vma_iterate_callback, &l);
+ return l.range_start == l.range_end;
+}
+
+#else
+
+static bool
+is_range_mapped (uintptr_t range_start, uintptr_t range_end)
+{
+ return true;
+}
+
+#endif
+
+static void
+test_heap (void)
+{
+ char *heapbuf = (char *) malloc (SECRET_SIZE);
+ ASSERT (heapbuf);
+ uintptr_t volatile addr = (uintptr_t) heapbuf;
+ memcpy (heapbuf, SECRET, SECRET_SIZE);
+ memset_explicit (heapbuf, 0, SECRET_SIZE);
+ free (heapbuf);
+ heapbuf = (char *) addr;
+ if (is_range_mapped (addr, addr + SECRET_SIZE))
+ {
+ /* some implementation could override freed memory by canaries so
+ compare against secret */
+ ASSERT (memcmp (heapbuf, SECRET, SECRET_SIZE) != 0);
+ printf ("test_heap: address range is still mapped after free().\n");
+ }
+ else
+ printf ("test_heap: address range is unmapped after free().\n");
+}
+
+/* =============== Verify operation on stack-allocated memory =============== */
+
+/* There are two passes:
+ 1. Put a secret in memory and invoke memset_explicit on it.
+ 2. Verify that the memory has been erased.
+ Implement them in the same function, so that they access the same memory
+ range on the stack. Declare the local scalars to be volatile so they
+ are not optimized away. That way, the test verifies that the compiler
+ does not eliminate a call to memset_explicit, even if data flow analysis
+ reveals that the stack area is dead at the end of the function. */
+static bool _GL_ATTRIBUTE_NOINLINE
+do_secret_stuff (int volatile pass, char *volatile *volatile last_stackbuf)
+{
+ char stackbuf[SECRET_SIZE];
+ if (pass == 1)
+ {
+ memcpy (stackbuf, SECRET, SECRET_SIZE);
+ memset_explicit (stackbuf, 0, SECRET_SIZE);
+ *last_stackbuf = stackbuf;
+ return false;
+ }
+ else /* pass == 2 */
+ {
+ /* Use *last_stackbuf here, because stackbuf may be allocated at a
+ different address than *last_stackbuf. This can happen
+ when the compiler splits this function into different functions,
+ one for pass == 1 and one for pass != 1. */
+ return memcmp (zero, *last_stackbuf, SECRET_SIZE) != 0;
+ }
+}
+
+static void
+test_stack (void)
+{
+ int count = 0;
+ int repeat;
+ char *volatile last_stackbuf;
+
+ for (repeat = 2 * 1000; repeat > 0; repeat--)
+ {
+ /* This odd way of writing two consecutive statements
+ do_secret_stuff (1, &last_stackbuf);
+ count += do_secret_stuff (2, &last_stackbuf);
+ ensures that the two do_secret_stuff calls are performed with the same
+ stack pointer value, on m68k. */
+ if ((repeat % 2) == 0)
+ do_secret_stuff (1, &last_stackbuf);
+ else
+ count += do_secret_stuff (2, &last_stackbuf);
+ }
+ /* If memset_explicit works, count is near 0. (It may be > 0 if there were
+ some asynchronous signal invocations between the two calls of
+ do_secret_stuff.)
+ If memset_explicit is optimized away by the compiler, count comes out as
+ approximately 1000. */
+ printf ("test_stack: count = %d\n", count);
+ ASSERT (count < 50);
+}
+
+/* ========================================================================== */
+
+int
+main ()
+{
+ test_static ();
+ test_heap ();
+ test_stack ();
+
+ return 0;
+}