Skip to content

gh-149509: Fix posix_spawn heap corruption when environ is mutated during the call#149724

Open
1fanwang wants to merge 1 commit into
python:mainfrom
1fanwang:fix/posix-spawn-environ-mutated
Open

gh-149509: Fix posix_spawn heap corruption when environ is mutated during the call#149724
1fanwang wants to merge 1 commit into
python:mainfrom
1fanwang:fix/posix-spawn-environ-mutated

Conversation

@1fanwang
Copy link
Copy Markdown

@1fanwang 1fanwang commented May 12, 2026

Closes #149509.

When os.posix_spawn(..., env=None) is called and the global C environ is replaced between the argv parsing and the cleanup branch — by an LD_PRELOAD interposer (the original report came from gprofng), an audit hook, or anything else — the if (envlist != environ) cleanup test misfires. The original environ pointer was captured by parse_envlist, the new environ doesn't match, so the cleanup treats the borrowed envlist as an owned heap allocation. _Py_FREELIST_FREE then walks past envc (which the borrow path leaves uninitialised), corrupting the heap and crashing the interpreter.

The fix tracks ownership explicitly. An envlist_owned flag is set only on the path that calls parse_envlist (which mallocs). The cleanup runs only when that flag is set, and the matching envc initialisation is moved up so the cleanup loop always has a defined upper bound.

Tests

Lib/test/test_os/test_posix.py::_PosixSpawnMixin::test_env_none_with_environ_mutated_during_call reproduces the bug in an assert_python_ok subprocess so the environ swap stays isolated. The subprocess installs a sys.addaudithook that — when os.posix_spawn fires — uses ctypes.c_void_p.in_dll(libc, 'environ') to point environ at an empty replacement array, then calls the spawn, then restores. Because _PosixSpawnMixin is inherited by both PosixSpawnTests and PosixSpawnPTests, the test covers posix_spawn and posix_spawnp.

The test aborts the interpreter on main with Debug memory block ... pad bytes are not all FORBIDDENBYTE (exit 139). With this PR it passes.

NEWS

Misc/NEWS.d/next/Library/2026-05-12-07-01-29.gh-issue-149509.WwKxjE.rst.

…-call

When env is None, py_posix_spawn aliases the global C environ array
into envlist without copying it, but envc is left uninitialised. The
cleanup block decided whether to free envlist by comparing it against
environ. If something running during the call (an LD_PRELOAD
interposer such as gprofng, or an audit hook that ends up modifying
the environment) replaces environ with a different pointer, the
identity check would mis-classify the borrowed pointer as owned and
free it with the uninitialised envc, corrupting the process
environment and typically crashing.

Track ownership explicitly with an envlist_owned flag instead of
inferring it from a pointer identity check, and initialise envc to 0.

The regression test simulates the interposer using a sys audit hook
that swaps environ via ctypes between argument parsing and the
cleanup path.
@python-cla-bot
Copy link
Copy Markdown

python-cla-bot Bot commented May 12, 2026

All commit authors signed the Contributor License Agreement.

CLA signed

Comment thread Modules/posixmodule.c
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.

posix_spawn crashes when python is run with gprofng

2 participants