Skip to content

Commit 5f4d05d

Browse files
authored
[3.7] bpo-35214: Initial clang MemorySanitizer support (GH-10479) (GH-10492)
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. (cherry picked from commit 1584a00) Co-authored-by: Gregory P. Smith <[email protected]> [Google LLC]
1 parent f3b0b91 commit 5f4d05d

File tree

9 files changed

+115
-4
lines changed

9 files changed

+115
-4
lines changed

Include/Python.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@
5353
#include "pyport.h"
5454
#include "pymacro.h"
5555

56+
/* A convenient way for code to know if clang's memory sanitizer is enabled. */
57+
#if defined(__has_feature)
58+
# if __has_feature(memory_sanitizer)
59+
# if !defined(MEMORY_SANITIZER)
60+
# define MEMORY_SANITIZER
61+
# endif
62+
# endif
63+
#endif
64+
5665
#include "pyatomic.h"
5766

5867
/* Debug-mode build with pymalloc implies PYMALLOC_DEBUG.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The interpreter and extension modules have had annotations added so that
2+
they work properly under clang's Memory Sanitizer. A new configure flag
3+
--with-memory-sanitizer has been added to make test builds of this nature
4+
easier to perform.

Modules/_ctypes/callproc.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@
7575
#include <alloca.h>
7676
#endif
7777

78+
#ifdef MEMORY_SANITIZER
79+
#include <sanitizer/msan_interface.h>
80+
#endif
81+
7882
#if defined(_DEBUG) || defined(__MINGW32__)
7983
/* Don't use structured exception handling on Windows if this is defined.
8084
MingW, AFAIK, doesn't support it.
@@ -1125,6 +1129,13 @@ PyObject *_ctypes_callproc(PPROC pProc,
11251129
rtype = _ctypes_get_ffi_type(restype);
11261130
resbuf = alloca(max(rtype->size, sizeof(ffi_arg)));
11271131

1132+
#ifdef MEMORY_SANITIZER
1133+
/* ffi_call actually initializes resbuf, but from asm, which
1134+
* MemorySanitizer can't detect. Avoid false positives from MSan. */
1135+
if (resbuf != NULL) {
1136+
__msan_unpoison(resbuf, max(rtype->size, sizeof(ffi_arg)));
1137+
}
1138+
#endif
11281139
avalues = (void **)alloca(sizeof(void *) * argcount);
11291140
atypes = (ffi_type **)alloca(sizeof(ffi_type *) * argcount);
11301141
if (!resbuf || !avalues || !atypes) {

Modules/_posixsubprocess.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
#include <dirent.h>
2222
#endif
2323

24+
#ifdef MEMORY_SANITIZER
25+
# include <sanitizer/msan_interface.h>
26+
#endif
27+
2428
#if defined(__ANDROID__) && __ANDROID_API__ < 21 && !defined(SYS_getdents64)
2529
# include <sys/linux-syscalls.h>
2630
# define SYS_getdents64 __NR_getdents64
@@ -287,6 +291,9 @@ _close_open_fds_safe(int start_fd, PyObject* py_fds_to_keep)
287291
sizeof(buffer))) > 0) {
288292
struct linux_dirent64 *entry;
289293
int offset;
294+
#ifdef MEMORY_SANITIZER
295+
__msan_unpoison(buffer, bytes);
296+
#endif
290297
for (offset = 0; offset < bytes; offset += entry->d_reclen) {
291298
int fd;
292299
entry = (struct linux_dirent64 *)(buffer + offset);

Modules/faulthandler.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1369,7 +1369,7 @@ void _PyFaulthandler_Fini(void)
13691369
#ifdef HAVE_SIGALTSTACK
13701370
if (stack.ss_sp != NULL) {
13711371
/* Fetch the current alt stack */
1372-
stack_t current_stack;
1372+
stack_t current_stack = {};
13731373
if (sigaltstack(NULL, &current_stack) == 0) {
13741374
if (current_stack.ss_sp == stack.ss_sp) {
13751375
/* The current alt stack is the one that we installed.

Python/bootstrap_hash.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
# endif
2121
#endif
2222

23+
#ifdef MEMORY_SANITIZER
24+
# include <sanitizer/msan_interface.h>
25+
#endif
26+
2327
#ifdef Py_DEBUG
2428
int _Py_HashSecret_Initialized = 0;
2529
#else
@@ -143,6 +147,11 @@ py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise)
143147
else {
144148
n = syscall(SYS_getrandom, dest, n, flags);
145149
}
150+
# ifdef MEMORY_SANITIZER
151+
if (n > 0) {
152+
__msan_unpoison(dest, n);
153+
}
154+
# endif
146155
#endif
147156

148157
if (n < 0) {

Python/pymath.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ double _Py_force_double(double x)
1717

1818
/* inline assembly for getting and setting the 387 FPU control word on
1919
gcc/x86 */
20-
20+
#ifdef MEMORY_SANITIZER
21+
__attribute__((no_sanitize_memory))
22+
#endif
2123
unsigned short _Py_get_387controlword(void) {
2224
unsigned short cw;
2325
__asm__ __volatile__ ("fnstcw %0" : "=m" (cw));

configure

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,8 @@ enable_optimizations
821821
with_lto
822822
with_hash_algorithm
823823
with_address_sanitizer
824+
with_memory_sanitizer
825+
with_undefined_behavior_sanitizer
824826
with_libs
825827
with_system_expat
826828
with_system_ffi
@@ -1508,7 +1510,10 @@ Optional Packages:
15081510
--with-hash-algorithm=[fnv|siphash24]
15091511
select hash algorithm
15101512
--with-address-sanitizer
1511-
enable AddressSanitizer
1513+
enable AddressSanitizer (asan)
1514+
--with-memory-sanitizer enable MemorySanitizer (msan)
1515+
--with-undefined-behavior-sanitizer
1516+
enable UndefinedBehaviorSanitizer (ubsan)
15121517
--with-libs='lib1 ...' link against additional libs
15131518
--with-system-expat build pyexpat module using an installed expat
15141519
library
@@ -10087,6 +10092,44 @@ if test "${with_address_sanitizer+set}" = set; then :
1008710092
$as_echo "$withval" >&6; }
1008810093
BASECFLAGS="-fsanitize=address -fno-omit-frame-pointer $BASECFLAGS"
1008910094
LDFLAGS="-fsanitize=address $LDFLAGS"
10095+
# ASan works by controlling memory allocation, our own malloc interferes.
10096+
with_pymalloc="no"
10097+
10098+
else
10099+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
10100+
$as_echo "no" >&6; }
10101+
fi
10102+
10103+
10104+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-memory-sanitizer" >&5
10105+
$as_echo_n "checking for --with-memory-sanitizer... " >&6; }
10106+
10107+
# Check whether --with-memory_sanitizer was given.
10108+
if test "${with_memory_sanitizer+set}" = set; then :
10109+
withval=$with_memory_sanitizer;
10110+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $withval" >&5
10111+
$as_echo "$withval" >&6; }
10112+
BASECFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer $BASECFLAGS"
10113+
LDFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 $LDFLAGS"
10114+
# MSan works by controlling memory allocation, our own malloc interferes.
10115+
with_pymalloc="no"
10116+
10117+
else
10118+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
10119+
$as_echo "no" >&6; }
10120+
fi
10121+
10122+
10123+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-undefined-behavior-sanitizer" >&5
10124+
$as_echo_n "checking for --with-undefined-behavior-sanitizer... " >&6; }
10125+
10126+
# Check whether --with-undefined_behavior_sanitizer was given.
10127+
if test "${with_undefined_behavior_sanitizer+set}" = set; then :
10128+
withval=$with_undefined_behavior_sanitizer;
10129+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $withval" >&5
10130+
$as_echo "$withval" >&6; }
10131+
BASECFLAGS="-fsanitize=undefined $BASECFLAGS"
10132+
LDFLAGS="-fsanitize=undefined $LDFLAGS"
1009010133

1009110134
else
1009210135
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5

configure.ac

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2910,11 +2910,37 @@ esac
29102910
AC_MSG_CHECKING(for --with-address-sanitizer)
29112911
AC_ARG_WITH(address_sanitizer,
29122912
AS_HELP_STRING([--with-address-sanitizer],
2913-
[enable AddressSanitizer]),
2913+
[enable AddressSanitizer (asan)]),
29142914
[
29152915
AC_MSG_RESULT($withval)
29162916
BASECFLAGS="-fsanitize=address -fno-omit-frame-pointer $BASECFLAGS"
29172917
LDFLAGS="-fsanitize=address $LDFLAGS"
2918+
# ASan works by controlling memory allocation, our own malloc interferes.
2919+
with_pymalloc="no"
2920+
],
2921+
[AC_MSG_RESULT(no)])
2922+
2923+
AC_MSG_CHECKING(for --with-memory-sanitizer)
2924+
AC_ARG_WITH(memory_sanitizer,
2925+
AS_HELP_STRING([--with-memory-sanitizer],
2926+
[enable MemorySanitizer (msan)]),
2927+
[
2928+
AC_MSG_RESULT($withval)
2929+
BASECFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer $BASECFLAGS"
2930+
LDFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 $LDFLAGS"
2931+
# MSan works by controlling memory allocation, our own malloc interferes.
2932+
with_pymalloc="no"
2933+
],
2934+
[AC_MSG_RESULT(no)])
2935+
2936+
AC_MSG_CHECKING(for --with-undefined-behavior-sanitizer)
2937+
AC_ARG_WITH(undefined_behavior_sanitizer,
2938+
AS_HELP_STRING([--with-undefined-behavior-sanitizer],
2939+
[enable UndefinedBehaviorSanitizer (ubsan)]),
2940+
[
2941+
AC_MSG_RESULT($withval)
2942+
BASECFLAGS="-fsanitize=undefined $BASECFLAGS"
2943+
LDFLAGS="-fsanitize=undefined $LDFLAGS"
29182944
],
29192945
[AC_MSG_RESULT(no)])
29202946

0 commit comments

Comments
 (0)