Skip to content

Commit 1584a00

Browse files
authored
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.
1 parent a9655b7 commit 1584a00

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
/* Debug-mode build with pymalloc implies PYMALLOC_DEBUG.
5766
* PYMALLOC_DEBUG is in error if pymalloc is not in use.
5867
*/
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
@@ -1370,7 +1370,7 @@ void _PyFaulthandler_Fini(void)
13701370
#ifdef HAVE_SIGALTSTACK
13711371
if (stack.ss_sp != NULL) {
13721372
/* Fetch the current alt stack */
1373-
stack_t current_stack;
1373+
stack_t current_stack = {};
13741374
if (sigaltstack(NULL, &current_stack) == 0) {
13751375
if (current_stack.ss_sp == stack.ss_sp) {
13761376
/* 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
@@ -819,6 +819,8 @@ enable_optimizations
819819
with_lto
820820
with_hash_algorithm
821821
with_address_sanitizer
822+
with_memory_sanitizer
823+
with_undefined_behavior_sanitizer
822824
with_libs
823825
with_system_expat
824826
with_system_ffi
@@ -1515,7 +1517,10 @@ Optional Packages:
15151517
--with-hash-algorithm=[fnv|siphash24]
15161518
select hash algorithm
15171519
--with-address-sanitizer
1518-
enable AddressSanitizer
1520+
enable AddressSanitizer (asan)
1521+
--with-memory-sanitizer enable MemorySanitizer (msan)
1522+
--with-undefined-behavior-sanitizer
1523+
enable UndefinedBehaviorSanitizer (ubsan)
15191524
--with-libs='lib1 ...' link against additional libs
15201525
--with-system-expat build pyexpat module using an installed expat
15211526
library
@@ -10038,6 +10043,44 @@ if test "${with_address_sanitizer+set}" = set; then :
1003810043
$as_echo "$withval" >&6; }
1003910044
BASECFLAGS="-fsanitize=address -fno-omit-frame-pointer $BASECFLAGS"
1004010045
LDFLAGS="-fsanitize=address $LDFLAGS"
10046+
# ASan works by controlling memory allocation, our own malloc interferes.
10047+
with_pymalloc="no"
10048+
10049+
else
10050+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
10051+
$as_echo "no" >&6; }
10052+
fi
10053+
10054+
10055+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-memory-sanitizer" >&5
10056+
$as_echo_n "checking for --with-memory-sanitizer... " >&6; }
10057+
10058+
# Check whether --with-memory_sanitizer was given.
10059+
if test "${with_memory_sanitizer+set}" = set; then :
10060+
withval=$with_memory_sanitizer;
10061+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $withval" >&5
10062+
$as_echo "$withval" >&6; }
10063+
BASECFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer $BASECFLAGS"
10064+
LDFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 $LDFLAGS"
10065+
# MSan works by controlling memory allocation, our own malloc interferes.
10066+
with_pymalloc="no"
10067+
10068+
else
10069+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
10070+
$as_echo "no" >&6; }
10071+
fi
10072+
10073+
10074+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-undefined-behavior-sanitizer" >&5
10075+
$as_echo_n "checking for --with-undefined-behavior-sanitizer... " >&6; }
10076+
10077+
# Check whether --with-undefined_behavior_sanitizer was given.
10078+
if test "${with_undefined_behavior_sanitizer+set}" = set; then :
10079+
withval=$with_undefined_behavior_sanitizer;
10080+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $withval" >&5
10081+
$as_echo "$withval" >&6; }
10082+
BASECFLAGS="-fsanitize=undefined $BASECFLAGS"
10083+
LDFLAGS="-fsanitize=undefined $LDFLAGS"
1004110084

1004210085
else
1004310086
{ $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
@@ -2854,11 +2854,37 @@ esac
28542854
AC_MSG_CHECKING(for --with-address-sanitizer)
28552855
AC_ARG_WITH(address_sanitizer,
28562856
AS_HELP_STRING([--with-address-sanitizer],
2857-
[enable AddressSanitizer]),
2857+
[enable AddressSanitizer (asan)]),
28582858
[
28592859
AC_MSG_RESULT($withval)
28602860
BASECFLAGS="-fsanitize=address -fno-omit-frame-pointer $BASECFLAGS"
28612861
LDFLAGS="-fsanitize=address $LDFLAGS"
2862+
# ASan works by controlling memory allocation, our own malloc interferes.
2863+
with_pymalloc="no"
2864+
],
2865+
[AC_MSG_RESULT(no)])
2866+
2867+
AC_MSG_CHECKING(for --with-memory-sanitizer)
2868+
AC_ARG_WITH(memory_sanitizer,
2869+
AS_HELP_STRING([--with-memory-sanitizer],
2870+
[enable MemorySanitizer (msan)]),
2871+
[
2872+
AC_MSG_RESULT($withval)
2873+
BASECFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer $BASECFLAGS"
2874+
LDFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 $LDFLAGS"
2875+
# MSan works by controlling memory allocation, our own malloc interferes.
2876+
with_pymalloc="no"
2877+
],
2878+
[AC_MSG_RESULT(no)])
2879+
2880+
AC_MSG_CHECKING(for --with-undefined-behavior-sanitizer)
2881+
AC_ARG_WITH(undefined_behavior_sanitizer,
2882+
AS_HELP_STRING([--with-undefined-behavior-sanitizer],
2883+
[enable UndefinedBehaviorSanitizer (ubsan)]),
2884+
[
2885+
AC_MSG_RESULT($withval)
2886+
BASECFLAGS="-fsanitize=undefined $BASECFLAGS"
2887+
LDFLAGS="-fsanitize=undefined $LDFLAGS"
28622888
],
28632889
[AC_MSG_RESULT(no)])
28642890

0 commit comments

Comments
 (0)