diff --git a/Include/Python.h b/Include/Python.h --- a/Include/Python.h +++ b/Include/Python.h @@ -123,6 +123,7 @@ #include "eval.h" #include "pyctype.h" +#include "pymemsets.h" #include "pystrtod.h" #include "pystrcmp.h" #include "dtoa.h" diff --git a/Include/pymemsets.h b/Include/pymemsets.h new file mode 100644 --- /dev/null +++ b/Include/pymemsets.h @@ -0,0 +1,35 @@ +#ifndef Py_MEMSET_S_H +#define Py_MEMSET_S_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#ifdef HAVE_STDINT_H +# include +#endif + +#ifndef errno_t +typedef int errno_t; +#define errno_t errno_t +#endif + +#ifndef rsize_t +typedef size_t rsize_t; +#define rsize_t rsize_t +#endif + +#ifndef RSIZE_MAX +/* Reasonable large value for bound check, 1 Gibibyte minus 1 Byte */ +#define RSIZE_MAX ((rsize_t)1073741823U) +#endif + +PyAPI_FUNC(errno_t) _Py_memset_s(void *, rsize_t, int, rsize_t); + +#ifdef __cplusplus +} +#endif + +#endif /* Py_MEMSET_S_H */ diff --git a/Makefile.pre.in b/Makefile.pre.in --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -349,6 +349,7 @@ Python/importdl.o \ Python/marshal.o \ Python/modsupport.o \ + Python/mymemsets.o \ Python/mystrtoul.o \ Python/mysnprintf.o \ Python/peephole.o \ @@ -812,6 +813,7 @@ $(srcdir)/Include/pyerrors.h \ $(srcdir)/Include/pyfpe.h \ $(srcdir)/Include/pymath.h \ + $(srcdir)/Include/pymemsets.h \ $(srcdir)/Include/pygetopt.h \ $(srcdir)/Include/pymacro.h \ $(srcdir)/Include/pymem.h \ diff --git a/Modules/_sha3/sha3module.c b/Modules/_sha3/sha3module.c --- a/Modules/_sha3/sha3module.c +++ b/Modules/_sha3/sha3module.c @@ -150,7 +150,8 @@ #define SHA3_process Update #define SHA3_done Final #define SHA3_copystate(dest, src) memcpy(&(dest), &(src), sizeof(SHA3_state)) -#define SHA3_clearstate(state) memset(&(state), 0, sizeof(SHA3_state)) +#define SHA3_clearstate(state) _Py_memset_s(&(state), sizeof(SHA3_state),\ + 0, sizeof(SHA3_state)) /* The structure for storing SHA3 info */ diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -425,6 +425,7 @@ + @@ -637,6 +638,7 @@ + diff --git a/Python/mymemsets.c b/Python/mymemsets.c new file mode 100644 --- /dev/null +++ b/Python/mymemsets.c @@ -0,0 +1,94 @@ +#include "Python.h" + +/* Windows doesn't provide EOVERFLOW. */ +#ifndef EOVERFLOW +# define EOVERFLOW E2BIG +#endif + + +/* ISO C11 memset_s() function + * + * The function implements the best effort to overwrite a memory location + * with data in order to wipe sensitive information. memset() isn't + * sufficient because compilers often optimize a call to memset() away, when + * the memory segment is not accessed anymore, e.g. at the end of a function + * body. The functions follows recommendation MSC06-C of the `CERT Secure + * Coding Standards`. + * + * _Py_memset_s() comes WITHOUT warranty and does NOT guarantee any security. + * The page holding your data might already been swapped to disk or shared + * with a forked child process. It's still better than no wiping ... + * + * Section K.3.7.4.1, paragraph 4 [ISO/IEC 9899:2011] states: + * + * The memset_s function copies the value of c (converted to an unsigned + * char) into each of the first n characters of the object pointed to by s. + * Unlike memset, any call to the memset_s function shall be evaluated + * strictly according to the rules of the abstract machine as described + * in (5.1.2.3). That is, any call to the memset_s function shall assume + * that the memory indicated by s and n may be accessible in the future + * and thus must contain the values indicated by c. + */ +errno_t +_Py_memset_s(void *s, rsize_t smax, int c, rsize_t n) +{ + errno_t errval = 0; + volatile unsigned char *p = s; + + /* The 1st and 2nd runtime-constraint violation abort the function. */ + if (s == NULL) { + errval = EINVAL; + goto err; + } + if (smax > RSIZE_MAX) { + errval = E2BIG; + goto err; + } + /* The 3rd and 4th runtime-constraint violation limit n to smax. */ + if (n > RSIZE_MAX) { + errval = E2BIG; + n = smax; + } + if (n > smax) { + errval = EOVERFLOW; + n = smax; + } + + while (n--) { + *p++ = (unsigned char)c; + } + + if (errval == 0) { + return 0; + } + else { + err: + errno = errval; + return errval; + } +} + +/* I have seen other tricks to accomplish the same task faster with less CPU + * instructions. + * + * A patch for NetBSD creates a const volatile function + * pointer to memset() and calls memset() by dereferencing the volatile + * function pointer. I'm not sure if this works with all compilers. + * + * static void * (* const volatile __memset_vp)(void *, int, size_t) = (memset); + * (*__memset_vp)(s, c, n); + * + * Source: http://ftp.netbsd.org/pub/NetBSD/misc/apb/memset_s.20120224.diff + * + * Another trick for GCC and LLVM is inline assembly to prevent dead code + * elimination: + * + * memset(s, c, n); + * asm volatile("" : : "r"(s) : "memory"); + * no code : no write : reads s : clobbers memory + * + * Source: http://llvm.org/bugs/show_bug.cgi?id=15495 + * + * Windows has SecureZeroMemory(void*, size_t). + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa366877%28v=vs.85%29.aspx + */ diff --git a/Python/random.c b/Python/random.c --- a/Python/random.c +++ b/Python/random.c @@ -249,7 +249,7 @@ } if (seed == 0) { /* disable the randomized hash */ - memset(secret, 0, secret_size); + _Py_memset_s(secret, secret_size, 0, secret_size); } else { lcg_urandom(seed, (unsigned char*)secret, secret_size); diff --git a/configure b/configure --- a/configure +++ b/configure @@ -10428,6 +10428,32 @@ fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for memset_s" >&5 +$as_echo_n "checking for memset_s... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +char buf[10]; memset_s(buf, sizeof(buf), 0, sizeof(buf)); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + +$as_echo "#define HAVE_MEMSET_S 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking for epoll" >&5 $as_echo_n "checking for epoll... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext diff --git a/configure.ac b/configure.ac --- a/configure.ac +++ b/configure.ac @@ -2845,6 +2845,13 @@ AC_MSG_RESULT(yes)], [AC_MSG_RESULT(no) ]) +AC_MSG_CHECKING(for memset_s) +AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[char buf[10]; memset_s(buf, sizeof(buf), 0, sizeof(buf))]])], + [AC_DEFINE(HAVE_MEMSET_S, 1, Define if you have the C11 'memset_s' function.) + AC_MSG_RESULT(yes)], + [AC_MSG_RESULT(no) +]) AC_MSG_CHECKING(for epoll) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[void *x=epoll_create]])], [AC_DEFINE(HAVE_EPOLL, 1, Define if you have the 'epoll' functions.) diff --git a/pyconfig.h.in b/pyconfig.h.in --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -552,6 +552,9 @@ /* Define to 1 if you have the `memrchr' function. */ #undef HAVE_MEMRCHR +/* Define if you have the C11 'memset_s' function. */ +#undef HAVE_MEMSET_S + /* Define to 1 if you have the `mkdirat' function. */ #undef HAVE_MKDIRAT