Skip to content

gh-156049: Add support for building with HWASAN - #156721

Merged
StanFromIreland merged 6 commits into
python:mainfrom
StanFromIreland:hwasan-support
Oct 6, 2026
Merged

StanFromIreland merged 6 commits into
python:mainfrom
StanFromIreland:hwasan-support

Conversation

@StanFromIreland

@StanFromIreland StanFromIreland commented Aug 31, 2026 •

Copy link
Copy Markdown
Member

This supersedes GH-149166 and GH-156050, pulling in their changes and adding a --with-hwaddress-sanitizer configure option as well as a few little miscellaneous changes for better support. Note that x86-64 has to use clang's page aliasing
mode, which shares the heap across fork(), so the test suite is only reliable
on AArch64.

@AnnaAr321 and @fmayer I'd appreciate your reviews :-)

Co-authored-by: Florian Mayer <fmayer@google.com>
Co-authored-by: Anna <araslanova.anna.a@gmail.com>
@read-the-docs-community

read-the-docs-community Bot commented Aug 31, 2026 •

Copy link
Copy Markdown

Comment thread Doc/using/configure.rst
@fmayer

fmayer commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

lgtm

@vstinner

Copy link
Copy Markdown
Member

Hi. You asked me to review this change, but I don't know how to test it manually. Can you provide a way to introduce a bug on purpose and explain how to test that HWASAN detects it properly?

Comment thread Lib/test/support/__init__.py Outdated
Comment thread Doc/using/configure.rst Outdated
Comment thread Doc/using/configure.rst
Comment thread configure.ac Outdated
Comment thread Doc/using/configure.rst
Note that on x86-64 this uses `page aliasing
<https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html#supported-architectures>`_,
which only tags heap allocations and is unsafe for programs that ``fork()``,
including much of the test suite.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know which GCC and clang versions added support for this sanitizer?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LLVM (the reference implementation) supports it on AArch64, and additionally on x86-64 since LLVM 13 with page aliasing. GCC supports it only on AArch64, since GCC 11.

@StanFromIreland

StanFromIreland commented Sep 22, 2026 •

Copy link
Copy Markdown
Member Author

Can you provide a way to introduce a bug on purpose and explain how to test that HWASAN detects it properly?

Hi, thanks for the review! If you mean just a trivial example, try for example applying this patch which adds a little heap-use-after-free:

diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c
index a7fefb1fec5..e7fe3131052 100644
--- a/Modules/zlibmodule.c
+++ b/Modules/zlibmodule.c
@@ -1987,6 +1987,9 @@ static unsigned int
 zlib_crc32_impl(PyObject *module, Py_buffer *data, unsigned int value)
 /*[clinic end generated code: output=b217562e4fe6d6a6 input=1229cb2fb5ea948a]*/
 {
+    volatile char *probe = PyMem_RawMalloc(1);
+    PyMem_RawFree((void *)probe);
+    *probe = '\0';
     /* Releasing the GIL for very small buffers is inefficient
        and may lower performance */
     if (data->len > 1024*5) {

Then compile Python with the flag:

./configure --with-hwaddress-sanitizer
make -j16

And run:

$ ./python -c 'import zlib; zlib.crc32(b"x")'

Which for me results in:

==129189==ERROR: HWAddressSanitizer: tag-mismatch on address 0x6802000005c0 at pc 0x000000404d5a
WRITE of size 1 at 0x6802000005c0 tags: 05/1f (ptr/mem) in thread T0
    #0 0x000000404d5a in __hwasan_store1 (/home/stan/dev/cpython/python+0x404d5a) (BuildId: 0e0c51a83d94ef2780e0f8f6b3eb3cebacd90b1d)
    #1 0x7fdd1072f973 in zlib_crc32_impl /home/stan/dev/cpython/./Modules/zlibmodule.c:1992:12
    #2 0x7fdd1072f973 in zlib_crc32 /home/stan/dev/cpython/./Modules/clinic/zlibmodule.c.h:1285:21
    #3 0x0000004e3573 in _PyObject_VectorcallTstate /home/stan/dev/cpython/./Include/internal/pycore_call.h:144:11
    #4 0x0000004e3573 in PyObject_Vectorcall /home/stan/dev/cpython/Objects/call.c:327:12
    #5 0x0000006a51ff in _Py_VectorCallInstrumentation_StackRefSteal /home/stan/dev/cpython/Python/ceval.c:768:11
    #6 0x0000006aa214 in _PyEval_EvalFrameDefault /home/stan/dev/cpython/Python/generated_cases.c.h:1906:35
    #7 0x0000006a4248 in _PyEval_EvalFrame /home/stan/dev/cpython/./Include/internal/pycore_ceval.h:122:16
    #8 0x0000006a4248 in _PyEval_Vector /home/stan/dev/cpython/Python/ceval.c:2172:12
    #9 0x0000006a4248 in PyEval_EvalCode /home/stan/dev/cpython/Python/ceval.c:679:21
    #10 0x0000007642ac in run_mod /home/stan/dev/cpython/Python/pythonrun.c:1509:19
    #11 0x000000762316 in _PyRun_String /home/stan/dev/cpython/Python/pythonrun.c:1292:15
    #12 0x0000007621a6 in _PyRun_SimpleString /home/stan/dev/cpython/Python/pythonrun.c:590:21
    #13 0x00000079a3d9 in pymain_run_command /home/stan/dev/cpython/Modules/main.c:308:18
    #14 0x00000079a3d9 in pymain_run_python /home/stan/dev/cpython/Modules/main.c:795:21
    #15 0x00000079a3d9 in Py_RunMain /home/stan/dev/cpython/Modules/main.c:891:5
    #16 0x00000079a7cc in pymain_main /home/stan/dev/cpython/Modules/main.c:921:12
    #17 0x00000079a8fb in Py_BytesMain /home/stan/dev/cpython/Modules/main.c:945:12
    #18 0x7fdd10bfb5b4 in __libc_start_call_main (/lib64/libc.so.6+0x35b4) (BuildId: 4586895ddf125800fcd0df1f77ad693eac681450)
    #19 0x7fdd10bfb667 in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x3667) (BuildId: 4586895ddf125800fcd0df1f77ad693eac681450)
    #20 0x000000402c54 in _start (/home/stan/dev/cpython/python+0x402c54) (BuildId: 0e0c51a83d94ef2780e0f8f6b3eb3cebacd90b1d)

[0x6802000005c0,0x6802000005d0) is a small unallocated heap chunk; size: 16 offset: 0

Cause: use-after-free
0x6802000005c0 is located 0 bytes inside a 1-byte region [0x6802000005c0,0x6802000005c1)
freed by thread T0 here:
    #0 0x00000040c16b in free (/home/stan/dev/cpython/python+0x40c16b) (BuildId: 0e0c51a83d94ef2780e0f8f6b3eb3cebacd90b1d)
    #1 0x7fdd1072f96b in zlib_crc32_impl /home/stan/dev/cpython/./Modules/zlibmodule.c:1991:5
    #2 0x7fdd1072f96b in zlib_crc32 /home/stan/dev/cpython/./Modules/clinic/zlibmodule.c.h:1285:21
    #3 0x0000004e3573 in _PyObject_VectorcallTstate /home/stan/dev/cpython/./Include/internal/pycore_call.h:144:11
    #4 0x0000004e3573 in PyObject_Vectorcall /home/stan/dev/cpython/Objects/call.c:327:12
    #5 0x0000006a51ff in _Py_VectorCallInstrumentation_StackRefSteal /home/stan/dev/cpython/Python/ceval.c:768:11
    #6 0x0000006aa214 in _PyEval_EvalFrameDefault /home/stan/dev/cpython/Python/generated_cases.c.h:1906:35
    #7 0x0000006a4248 in _PyEval_EvalFrame /home/stan/dev/cpython/./Include/internal/pycore_ceval.h:122:16
    #8 0x0000006a4248 in _PyEval_Vector /home/stan/dev/cpython/Python/ceval.c:2172:12
    #9 0x0000006a4248 in PyEval_EvalCode /home/stan/dev/cpython/Python/ceval.c:679:21
    #10 0x0000007642ac in run_mod /home/stan/dev/cpython/Python/pythonrun.c:1509:19
    #11 0x000000762316 in _PyRun_String /home/stan/dev/cpython/Python/pythonrun.c:1292:15
    #12 0x0000007621a6 in _PyRun_SimpleString /home/stan/dev/cpython/Python/pythonrun.c:590:21
    #13 0x00000079a3d9 in pymain_run_command /home/stan/dev/cpython/Modules/main.c:308:18
    #14 0x00000079a3d9 in pymain_run_python /home/stan/dev/cpython/Modules/main.c:795:21
    #15 0x00000079a3d9 in Py_RunMain /home/stan/dev/cpython/Modules/main.c:891:5
    #16 0x00000079a7cc in pymain_main /home/stan/dev/cpython/Modules/main.c:921:12
    #17 0x00000079a8fb in Py_BytesMain /home/stan/dev/cpython/Modules/main.c:945:12
    #18 0x7fdd10bfb5b4 in __libc_start_call_main (/lib64/libc.so.6+0x35b4) (BuildId: 4586895ddf125800fcd0df1f77ad693eac681450)
    #19 0x7fdd10bfb667 in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x3667) (BuildId: 4586895ddf125800fcd0df1f77ad693eac681450)
    #20 0x000000402c54 in _start (/home/stan/dev/cpython/python+0x402c54) (BuildId: 0e0c51a83d94ef2780e0f8f6b3eb3cebacd90b1d)

previously allocated by thread T0 here:
    #0 0x00000040c888 in malloc (/home/stan/dev/cpython/python+0x40c888) (BuildId: 0e0c51a83d94ef2780e0f8f6b3eb3cebacd90b1d)
    #1 0x7fdd1072f960 in zlib_crc32_impl /home/stan/dev/cpython/./Modules/zlibmodule.c:1990:28
    #2 0x7fdd1072f960 in zlib_crc32 /home/stan/dev/cpython/./Modules/clinic/zlibmodule.c.h:1285:21
    #3 0x0000004e3573 in _PyObject_VectorcallTstate /home/stan/dev/cpython/./Include/internal/pycore_call.h:144:11
    #4 0x0000004e3573 in PyObject_Vectorcall /home/stan/dev/cpython/Objects/call.c:327:12
    #5 0x0000006a51ff in _Py_VectorCallInstrumentation_StackRefSteal /home/stan/dev/cpython/Python/ceval.c:768:11
    #6 0x0000006aa214 in _PyEval_EvalFrameDefault /home/stan/dev/cpython/Python/generated_cases.c.h:1906:35
    #7 0x0000006a4248 in _PyEval_EvalFrame /home/stan/dev/cpython/./Include/internal/pycore_ceval.h:122:16
    #8 0x0000006a4248 in _PyEval_Vector /home/stan/dev/cpython/Python/ceval.c:2172:12
    #9 0x0000006a4248 in PyEval_EvalCode /home/stan/dev/cpython/Python/ceval.c:679:21
    #10 0x0000007642ac in run_mod /home/stan/dev/cpython/Python/pythonrun.c:1509:19
    #11 0x000000762316 in _PyRun_String /home/stan/dev/cpython/Python/pythonrun.c:1292:15
    #12 0x0000007621a6 in _PyRun_SimpleString /home/stan/dev/cpython/Python/pythonrun.c:590:21
    #13 0x00000079a3d9 in pymain_run_command /home/stan/dev/cpython/Modules/main.c:308:18
    #14 0x00000079a3d9 in pymain_run_python /home/stan/dev/cpython/Modules/main.c:795:21
    #15 0x00000079a3d9 in Py_RunMain /home/stan/dev/cpython/Modules/main.c:891:5
    #16 0x00000079a7cc in pymain_main /home/stan/dev/cpython/Modules/main.c:921:12
    #17 0x00000079a8fb in Py_BytesMain /home/stan/dev/cpython/Modules/main.c:945:12
    #18 0x7fdd10bfb5b4 in __libc_start_call_main (/lib64/libc.so.6+0x35b4) (BuildId: 4586895ddf125800fcd0df1f77ad693eac681450)
    #19 0x7fdd10bfb667 in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x3667) (BuildId: 4586895ddf125800fcd0df1f77ad693eac681450)
    #20 0x000000402c54 in _start (/home/stan/dev/cpython/python+0x402c54) (BuildId: 0e0c51a83d94ef2780e0f8f6b3eb3cebacd90b1d)

hwasan_dev_note_heap_rb_distance: 1 1023
hwasan_dev_note_num_matching_addrs: 0
hwasan_dev_note_num_matching_addrs_4b: 0
Thread: T0 0x5fff00002000 stack: [0x7ffc35499000,0x7ffc35c99000) sz: 8388608 tls: [0x7fdd10bf6c00,0x7fdd10bf7d00)


Memory tags around the buggy address (one tag corresponds to 16 bytes):
  0x6801fffffd00: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 
  0x6801fffffe00: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 
  0x6801ffffff00: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 
  0x680200000000: 01  0c  06  06  0c  0c  01  08  08  0c  0f  0f  08  07  01  0d 
  0x680200000100: 0e  0f  01  01  0f  07  0c  0c  0c  0b  0d  01  01  0f  0f  08 
  0x680200000200: 08  04  0f  04  02  08  08  05  06  09  05  05  06  04  04  03 
  0x680200000300: 04  04  05  08  04  04  04  07  07  06  07  07  09  07  04  05 
  0x680200000400: 05  03  06  03  04  03  04  03  06  03  06  01  08  03  01  06 
=>0x680200000500: 05  01  03  0f  01  04  01  04  07  77  04  0e [1f] 00  00  00 
  0x680200000600: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 
  0x680200000700: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 
  0x680200000800: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 
  0x680200000900: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 
  0x680200000a00: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 
  0x680200000b00: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 
  0x680200000c00: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 
  0x680200000d00: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 
Tags for short granules around the buggy address (one tag corresponds to 16 bytes):
  0x680200000400: 03  01  06  07  07  06  01  07  02  02  05  02  06  00  05  04 
=>0x680200000500: 00  03  00  04  01  00  06  00  00  ..  01  06 [..] ..  ..  .. 
  0x680200000600: ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  .. 
See https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html#short-granules for a description of short granule tags
SUMMARY: HWAddressSanitizer: tag-mismatch (/home/stan/dev/cpython/python+0x404d5a) (BuildId: 0e0c51a83d94ef2780e0f8f6b3eb3cebacd90b1d) in __hwasan_store1

Co-authored-by: Victor Stinner <vstinner@python.org>
@StanFromIreland

Copy link
Copy Markdown
Member Author

I pushed a commit which incorporates the fix in #157934 for MSan/ASan.

@StanFromIreland

Copy link
Copy Markdown
Member Author

@emmatyping can you please take a look?

@vstinner

vstinner commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Would it be crazy to add an unit test for the test? Add a function in _testcapi which triggers HWASAN (as you shown), and make sure that it fails if HWASAN is used.

Comment thread configure.ac
[x86_64|amd64], [hwasan_flags="$hwasan_flags -fsanitize-hwaddress-experimental-aliasing"]
)
AX_CHECK_COMPILE_FLAG([$hwasan_flags],[
BASECFLAGS="$hwasan_flags -fno-omit-frame-pointer $BASECFLAGS"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the rationale for adding -fno-omit-frame-pointer? Is it required by -fsanitize=hwaddress? Why not putting it in hwasan_flags?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't required, it's just there for usable stack traces in the reports as the sanitizer runtime uses its frame-pointer-based unwinder for the allocation/free stacks. The --with-address-sanitizer and --with-memory-sanitizer blocks add it for the same reason.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ok, I didn't notice that it's also used by other sanitizers.

Since PEP 831 in Python 3.15, Python is built with -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer by default, so it should not make a big difference to add -fno-omit-frame-pointer anyway :-)

@bedevere-app bedevere-app Bot added the type-feature A feature request or enhancement label Oct 5, 2026
@StanFromIreland

Copy link
Copy Markdown
Member Author

Would it be crazy to add an unit test for the test? Add a function in _testcapi which triggers HWASAN (as you shown), and make sure that it fails if HWASAN is used.

Sure, I added such a test. It also works under ASan.

@vstinner vstinner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The change looks complete and it's well documented. Thanks to updating libregrtest ;-)

def check_sanitizer(*, address=False, memory=False, ub=False, thread=False,
function=True):
def check_sanitizer(*, address=False, hwaddress=False, memory=False, ub=False,
thread=False, function=True):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a long list of sanitizers! 😃 It's cool that we supported all of them!

@vstinner

vstinner commented Oct 5, 2026

Copy link
Copy Markdown
Member

Oh, multiple GitHub Action jobs failed with "Cancelled after 15m". I'm not sure what happened. Anyway, I clicked on re-run failed jobs.

@StanFromIreland

Copy link
Copy Markdown
Member Author

There was a GitHub incident, thank you!

@StanFromIreland
StanFromIreland merged commit 197607e into python:main Oct 6, 2026
65 checks passed
@StanFromIreland
StanFromIreland deleted the hwasan-support branch October 6, 2026 18:34
@bedevere-bot

Copy link
Copy Markdown

⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️

Hi! The buildbot x86-64 MacOS Intel ASAN NoGIL 3.x (tier-2) has failed when building commit 197607e.

What do you need to do:

  1. Don't panic.
  2. Check the buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/#/builders/1366/builds/8195) and take a look at the build logs.
  4. Check if the failure is related to this commit (197607e) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/#/builders/1366/builds/8195

Failed tests:

  • test_capi

Failed subtests:

  • test_buffer_overflow - test.test_capi.test_mem.AddressSanitizerTests.test_buffer_overflow

Summary of the results of the build (if available):

==

Click to see traceback logs
Traceback (most recent call last):
  File "/Users/buildbot/buildarea/3.x.itamaro-macos-intel-aws.macos-with-brew.asan.nogil/build/Lib/test/test_capi/test_mem.py", line 195, in test_buffer_overflow
    self.assertIn(b'AddressSanitizer', out.err)
    ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: b'AddressSanitizer' not found in b'Fatal Python error: preconfig_init_allocator: PYTHONMALLOC: unknown allocator\nPython runtime state: preinitializing\n\nStack (most recent call first):\n  <freed thread state>\n'


Traceback (most recent call last):
  File "<string>", line 10, in <module>
  File "<frozen importlib._bootstrap>", line 838, in module_from_spec
  File "<frozen importlib._bootstrap_external>", line 1032, in create_module
  File "<frozen importlib._bootstrap>", line 543, in _call_with_frames_removed
ImportError: module _test_module_state_shared does not support loading in subinterpreters
xpected failure

@StanFromIreland

Copy link
Copy Markdown
Member Author

Aargh, I wrote #158932 to fix the test.

@vstinner

vstinner commented Oct 6, 2026

Copy link
Copy Markdown
Member

@StanFromIreland: Ah, test_capi.AddressSanitizerTests.test_buffer_overflow() failed on x86-64 MacOS Intel ASAN NoGIL 3.x because PYTHONMALLOC=malloc is not supported on Free Threading.

FAIL: test_buffer_overflow (test.test_capi.test_mem.AddressSanitizerTests.test_buffer_overflow)
AssertionError: b'AddressSanitizer' not found in b'Fatal Python error: preconfig_init_allocator: PYTHONMALLOC: unknown allocator\nPython runtime state: preinitializing\n\nStack (most recent call first):\n  <freed thread state>\n'
Re-running test_capi in verbose mode (matching: test_buffer_overflow)
FAIL: test_buffer_overflow (test.test_capi.test_mem.AddressSanitizerTe

Problem: the buffer overflow is not detected using mimalloc: `PYTHONMALLOC=mimalloc ./python -c 'import _testcapi; _testcapi.pymem_buffer_overflow()' exits successfully.

I suppose that skipping the test on Free Threading is an acceptable trade-off for now.

Note: the overflow is detected using PYTHONMALLOC=mimalloc_debug (or PYTHONMALLOC=debug which means the same).

$ PYTHONMALLOC=debug ./python -c 'import _testcapi; _testcapi.pymem_buffer_overflow()'
Debug memory block at address p=0x7b04fa0c0ee0: API 'm'
    16 bytes originally requested
    The 7 pad bytes at p-7 are FORBIDDENBYTE, as expected.
    The 8 pad bytes at tail=0x7b04fa0c0ef0 are not all FORBIDDENBYTE (0xfd):
        at tail+0: 0x78 *** OUCH
        at tail+1: 0xfd
        at tail+2: 0xfd
        at tail+3: 0xfd
        at tail+4: 0xfd
        at tail+5: 0xfd
        at tail+6: 0xfd
        at tail+7: 0xfd
    Data at p: cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd

Enable tracemalloc to get the memory block allocation traceback

Fatal Python error: _PyMem_DebugRawFree: bad trailing pad byte
Python runtime state: initialized

Stack (most recent call first):
  File "<string>", line 1 in <module>

Extension modules: _testcapi (total: 1)
Aborted                    PYTHONMALLOC=debug ./python -c 'import _testcapi; _testcapi.pymem_buffer_overflow()'

@StanFromIreland

Copy link
Copy Markdown
Member Author

Note: the overflow is detected using PYTHONMALLOC=mimalloc_debug (or PYTHONMALLOC=debug which means the same).

However, that is CPython's own debug-hooks checker, not {HW}ASan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type-feature A feature request or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants