Skip to content

gh-161407: Allocate audit hook entries with malloc() - #161409

Open
maikschulze wants to merge 2 commits into
python:mainfrom
maikschulze:gh-161407-audit-hook-malloc
Open

maikschulze wants to merge 2 commits into
python:mainfrom
maikschulze:gh-161407-audit-hook-malloc

Conversation

@maikschulze

@maikschulze maikschulze commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

PySys_AddAuditHook() can be called before Py_Initialize(), but the hook's list entry was allocated with PyMem_RawMalloc() and freed with PyMem_RawFree() at finalization. When the startup configuration changes the raw allocator in between, the entry is freed with a different allocator than it was allocated with, and the process crashes at exit. That happens with PYTHONMALLOC=mimalloc on builds with the GIL since 3.15, and whenever the debug hooks are installed (-X dev, the *_debug allocators).

This allocates and frees the entries with plain malloc() / free(), which nothing at startup replaces.

I first used _PyMem_DefaultRawMalloc() / _PyMem_DefaultRawFree(), like other pre-initialization data. In debug builds those go through the debug hooks, whose underlying allocator PYTHONMALLOC=mimalloc_debug replaces, so a Py_DEBUG build still crashed.

Tested on macOS (release with the GIL, free-threaded, Py_DEBUG) and Windows (release with and without the GIL): _testembed test_audit, which adds a hook before Py_Initialize(), under each PYTHONMALLOC value and in development mode, and test_audit, test_embed, test_sys and test_capi.test_mem. The full test suite passes on macOS (release build with the GIL). On a Windows machine with a popular endpoint security agent, which adds such a hook to every python.exe, python -c pass crashed at exit under every setting that changes the raw allocator and now exits cleanly.

Affected since 3.13 (debug hooks) and 3.15 (PYTHONMALLOC=mimalloc).

I used an AI assistant to investigate this and draft the patch; I reviewed and tested the changes myself.

A hook added with PySys_AddAuditHook() before Py_Initialize() was
allocated with PyMem_RawMalloc() before the startup configuration was
applied, and freed with PyMem_RawFree() at finalization. When
PYTHONMALLOC=mimalloc, -X dev or a debug allocator changed the raw
allocator in between, the entry was freed with a different allocator
and the process crashed at exit.

Allocate and free the entries with plain malloc() and free(), which
nothing replaces at startup, and test _testembed test_audit under the
affected settings.
@python-cla-bot

python-cla-bot Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

Comment thread Python/sysmodule.c

_Py_AuditHookEntry *e = (_Py_AuditHookEntry*)PyMem_RawMalloc(
sizeof(_Py_AuditHookEntry));
/* Use malloc(), not PyMem_RawMalloc(): a hook can be added before

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.

Honestly, I don't think that it's worth it to bother with _PyMem_RawMalloc() wrapper (and pass NULL as context). I would prefer to use malloc() / free() directly.

The "new" PyInitConfig C API also uses directly malloc() / free() to avoid troubles with changing the memory allocator.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in e5d45db, thanks: plain malloc() / free() now, like PyInitConfig.

Comment thread Lib/test/test_embed.py Outdated
def test_audit(self):
self.run_embedded_interpreter("test_audit")

def test_audit_hook_survives_allocator_change(self):

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.

I don't think that adding a test is useful, since Python/sysmodule.c no longer depends on the Python memory allocator (PYTHONMALLOC) with this change. I suggest removing the test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed in e5d45db.

As suggested in review: the _PyMem_RawMalloc() wrapper adds nothing here, and
with malloc() the audit hook entries no longer depend on PYTHONMALLOC.
@maikschulze

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Also confirmed on a Windows machine with a popular endpoint security agent (which adds an audit hook before Py_Initialize()): python -c pass crashed at exit with PYTHONMALLOC=mimalloc, the debug allocators and -X dev, and exits cleanly with this change.

@maikschulze
maikschulze requested a review from vstinner November 7, 2026 07:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants