bpo-35214: Initial clang MemorySanitizer support (GH-10479)

Adds configure flags for msan and ubsan builds to make it easier to enable.
These also encode the detail that address sanitizer and memory sanitizer
should disable pymalloc.

Define MEMORY_SANITIZER when appropriate at build time and adds workarounds
to existing code to mark things as initialized where the sanitizer is otherwise unable to
determine that.  This lets our build succeed under the memory sanitizer.  not all tests
pass without sanitizer failures yet but we're in pretty good shape after this.
This commit is contained in:
Gregory P. Smith 2018-11-12 12:07:14 -08:00 committed by GitHub
parent a9655b7f71
commit 1584a00815
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 115 additions and 4 deletions

View File

@ -53,6 +53,15 @@
#include "pyport.h" #include "pyport.h"
#include "pymacro.h" #include "pymacro.h"
/* A convenient way for code to know if clang's memory sanitizer is enabled. */
#if defined(__has_feature)
# if __has_feature(memory_sanitizer)
# if !defined(MEMORY_SANITIZER)
# define MEMORY_SANITIZER
# endif
# endif
#endif
/* Debug-mode build with pymalloc implies PYMALLOC_DEBUG. /* Debug-mode build with pymalloc implies PYMALLOC_DEBUG.
* PYMALLOC_DEBUG is in error if pymalloc is not in use. * PYMALLOC_DEBUG is in error if pymalloc is not in use.
*/ */

View File

@ -0,0 +1,4 @@
The interpreter and extension modules have had annotations added so that
they work properly under clang's Memory Sanitizer. A new configure flag
--with-memory-sanitizer has been added to make test builds of this nature
easier to perform.

View File

@ -75,6 +75,10 @@
#include <alloca.h> #include <alloca.h>
#endif #endif
#ifdef MEMORY_SANITIZER
#include <sanitizer/msan_interface.h>
#endif
#if defined(_DEBUG) || defined(__MINGW32__) #if defined(_DEBUG) || defined(__MINGW32__)
/* Don't use structured exception handling on Windows if this is defined. /* Don't use structured exception handling on Windows if this is defined.
MingW, AFAIK, doesn't support it. MingW, AFAIK, doesn't support it.
@ -1125,6 +1129,13 @@ PyObject *_ctypes_callproc(PPROC pProc,
rtype = _ctypes_get_ffi_type(restype); rtype = _ctypes_get_ffi_type(restype);
resbuf = alloca(max(rtype->size, sizeof(ffi_arg))); resbuf = alloca(max(rtype->size, sizeof(ffi_arg)));
#ifdef MEMORY_SANITIZER
/* ffi_call actually initializes resbuf, but from asm, which
* MemorySanitizer can't detect. Avoid false positives from MSan. */
if (resbuf != NULL) {
__msan_unpoison(resbuf, max(rtype->size, sizeof(ffi_arg)));
}
#endif
avalues = (void **)alloca(sizeof(void *) * argcount); avalues = (void **)alloca(sizeof(void *) * argcount);
atypes = (ffi_type **)alloca(sizeof(ffi_type *) * argcount); atypes = (ffi_type **)alloca(sizeof(ffi_type *) * argcount);
if (!resbuf || !avalues || !atypes) { if (!resbuf || !avalues || !atypes) {

View File

@ -21,6 +21,10 @@
#include <dirent.h> #include <dirent.h>
#endif #endif
#ifdef MEMORY_SANITIZER
# include <sanitizer/msan_interface.h>
#endif
#if defined(__ANDROID__) && __ANDROID_API__ < 21 && !defined(SYS_getdents64) #if defined(__ANDROID__) && __ANDROID_API__ < 21 && !defined(SYS_getdents64)
# include <sys/linux-syscalls.h> # include <sys/linux-syscalls.h>
# define SYS_getdents64 __NR_getdents64 # define SYS_getdents64 __NR_getdents64
@ -287,6 +291,9 @@ _close_open_fds_safe(int start_fd, PyObject* py_fds_to_keep)
sizeof(buffer))) > 0) { sizeof(buffer))) > 0) {
struct linux_dirent64 *entry; struct linux_dirent64 *entry;
int offset; int offset;
#ifdef MEMORY_SANITIZER
__msan_unpoison(buffer, bytes);
#endif
for (offset = 0; offset < bytes; offset += entry->d_reclen) { for (offset = 0; offset < bytes; offset += entry->d_reclen) {
int fd; int fd;
entry = (struct linux_dirent64 *)(buffer + offset); entry = (struct linux_dirent64 *)(buffer + offset);

View File

@ -1370,7 +1370,7 @@ void _PyFaulthandler_Fini(void)
#ifdef HAVE_SIGALTSTACK #ifdef HAVE_SIGALTSTACK
if (stack.ss_sp != NULL) { if (stack.ss_sp != NULL) {
/* Fetch the current alt stack */ /* Fetch the current alt stack */
stack_t current_stack; stack_t current_stack = {};
if (sigaltstack(NULL, &current_stack) == 0) { if (sigaltstack(NULL, &current_stack) == 0) {
if (current_stack.ss_sp == stack.ss_sp) { if (current_stack.ss_sp == stack.ss_sp) {
/* The current alt stack is the one that we installed. /* The current alt stack is the one that we installed.

View File

@ -20,6 +20,10 @@
# endif # endif
#endif #endif
#ifdef MEMORY_SANITIZER
# include <sanitizer/msan_interface.h>
#endif
#ifdef Py_DEBUG #ifdef Py_DEBUG
int _Py_HashSecret_Initialized = 0; int _Py_HashSecret_Initialized = 0;
#else #else
@ -143,6 +147,11 @@ py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise)
else { else {
n = syscall(SYS_getrandom, dest, n, flags); n = syscall(SYS_getrandom, dest, n, flags);
} }
# ifdef MEMORY_SANITIZER
if (n > 0) {
__msan_unpoison(dest, n);
}
# endif
#endif #endif
if (n < 0) { if (n < 0) {

View File

@ -17,7 +17,9 @@ double _Py_force_double(double x)
/* inline assembly for getting and setting the 387 FPU control word on /* inline assembly for getting and setting the 387 FPU control word on
gcc/x86 */ gcc/x86 */
#ifdef MEMORY_SANITIZER
__attribute__((no_sanitize_memory))
#endif
unsigned short _Py_get_387controlword(void) { unsigned short _Py_get_387controlword(void) {
unsigned short cw; unsigned short cw;
__asm__ __volatile__ ("fnstcw %0" : "=m" (cw)); __asm__ __volatile__ ("fnstcw %0" : "=m" (cw));

45
configure vendored
View File

@ -819,6 +819,8 @@ enable_optimizations
with_lto with_lto
with_hash_algorithm with_hash_algorithm
with_address_sanitizer with_address_sanitizer
with_memory_sanitizer
with_undefined_behavior_sanitizer
with_libs with_libs
with_system_expat with_system_expat
with_system_ffi with_system_ffi
@ -1515,7 +1517,10 @@ Optional Packages:
--with-hash-algorithm=[fnv|siphash24] --with-hash-algorithm=[fnv|siphash24]
select hash algorithm select hash algorithm
--with-address-sanitizer --with-address-sanitizer
enable AddressSanitizer enable AddressSanitizer (asan)
--with-memory-sanitizer enable MemorySanitizer (msan)
--with-undefined-behavior-sanitizer
enable UndefinedBehaviorSanitizer (ubsan)
--with-libs='lib1 ...' link against additional libs --with-libs='lib1 ...' link against additional libs
--with-system-expat build pyexpat module using an installed expat --with-system-expat build pyexpat module using an installed expat
library library
@ -10038,6 +10043,44 @@ if test "${with_address_sanitizer+set}" = set; then :
$as_echo "$withval" >&6; } $as_echo "$withval" >&6; }
BASECFLAGS="-fsanitize=address -fno-omit-frame-pointer $BASECFLAGS" BASECFLAGS="-fsanitize=address -fno-omit-frame-pointer $BASECFLAGS"
LDFLAGS="-fsanitize=address $LDFLAGS" LDFLAGS="-fsanitize=address $LDFLAGS"
# ASan works by controlling memory allocation, our own malloc interferes.
with_pymalloc="no"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-memory-sanitizer" >&5
$as_echo_n "checking for --with-memory-sanitizer... " >&6; }
# Check whether --with-memory_sanitizer was given.
if test "${with_memory_sanitizer+set}" = set; then :
withval=$with_memory_sanitizer;
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $withval" >&5
$as_echo "$withval" >&6; }
BASECFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer $BASECFLAGS"
LDFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 $LDFLAGS"
# MSan works by controlling memory allocation, our own malloc interferes.
with_pymalloc="no"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-undefined-behavior-sanitizer" >&5
$as_echo_n "checking for --with-undefined-behavior-sanitizer... " >&6; }
# Check whether --with-undefined_behavior_sanitizer was given.
if test "${with_undefined_behavior_sanitizer+set}" = set; then :
withval=$with_undefined_behavior_sanitizer;
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $withval" >&5
$as_echo "$withval" >&6; }
BASECFLAGS="-fsanitize=undefined $BASECFLAGS"
LDFLAGS="-fsanitize=undefined $LDFLAGS"
else else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5

View File

@ -2854,11 +2854,37 @@ esac
AC_MSG_CHECKING(for --with-address-sanitizer) AC_MSG_CHECKING(for --with-address-sanitizer)
AC_ARG_WITH(address_sanitizer, AC_ARG_WITH(address_sanitizer,
AS_HELP_STRING([--with-address-sanitizer], AS_HELP_STRING([--with-address-sanitizer],
[enable AddressSanitizer]), [enable AddressSanitizer (asan)]),
[ [
AC_MSG_RESULT($withval) AC_MSG_RESULT($withval)
BASECFLAGS="-fsanitize=address -fno-omit-frame-pointer $BASECFLAGS" BASECFLAGS="-fsanitize=address -fno-omit-frame-pointer $BASECFLAGS"
LDFLAGS="-fsanitize=address $LDFLAGS" LDFLAGS="-fsanitize=address $LDFLAGS"
# ASan works by controlling memory allocation, our own malloc interferes.
with_pymalloc="no"
],
[AC_MSG_RESULT(no)])
AC_MSG_CHECKING(for --with-memory-sanitizer)
AC_ARG_WITH(memory_sanitizer,
AS_HELP_STRING([--with-memory-sanitizer],
[enable MemorySanitizer (msan)]),
[
AC_MSG_RESULT($withval)
BASECFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer $BASECFLAGS"
LDFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 $LDFLAGS"
# MSan works by controlling memory allocation, our own malloc interferes.
with_pymalloc="no"
],
[AC_MSG_RESULT(no)])
AC_MSG_CHECKING(for --with-undefined-behavior-sanitizer)
AC_ARG_WITH(undefined_behavior_sanitizer,
AS_HELP_STRING([--with-undefined-behavior-sanitizer],
[enable UndefinedBehaviorSanitizer (ubsan)]),
[
AC_MSG_RESULT($withval)
BASECFLAGS="-fsanitize=undefined $BASECFLAGS"
LDFLAGS="-fsanitize=undefined $LDFLAGS"
], ],
[AC_MSG_RESULT(no)]) [AC_MSG_RESULT(no)])