Repository navigation
_posixsubprocess.fork_exec(): use-after-free if an argv item's __fspath__ mutates args #151403
Description
Crash report
_posixsubprocess.fork_exec() converts each item of the args sequence with
PyUnicode_FSConverter(), which calls the item's __fspath__(). The item is
only borrowed from the (incref'd) args sequence. If __fspath__() runs Python
that drops the sequence's last reference to the item and then returns a
non-str/bytes object, the error path in PyOS_FSPath() reads the type of the
now-freed item — a use-after-free.
// Modules/posixmodule.c, PyOS_FSPath():
func = _PyObject_LookupSpecial(path, &_Py_ID(__fspath__)); // bound method: only surviving strong ref to `path`
path_repr = _PyObject_CallNoArgs(func); // runs the attacker __fspath__()
Py_DECREF(func); // frees `path` once args dropped its last ref
...
PyErr_Format(..., _PyType_Name(Py_TYPE(path)) ...); // <-- use-after-free read of freed `path`The between-iteration length recheck at Modules/_posixsubprocess.c:1088 does not
help: the free happens inside the current iteration's PyUnicode_FSConverter()
call, before the next iteration's recheck runs.
Reproducer
On a build configured with --with-address-sanitizer, run with
PYTHONMALLOC=malloc:
import _posixsubprocess
class Evil:
def __fspath__(self):
args.clear() # drop the args list's last reference to self
return 12345 # non-str/bytes -> takes the error path in PyOS_FSPath
args = [Evil()]
_posixsubprocess.fork_exec(
args, [b"false"],
True, (), None, [b"env"],
-1, -1, -1, -1,
1, 2, 3, 4,
True, True, 0,
None, None, None, -1,
None)AddressSanitizer:
==ERROR: AddressSanitizer: heap-use-after-free ... READ of size 8
#0 PyOS_FSPath posixmodule.c:17189
#1 PyUnicode_FSConverter unicodeobject.c:4013
#2 subprocess_fork_exec_impl _posixsubprocess.c:1093
freed by thread T0 here:
#3 method_dealloc classobject.c:219 // Py_XDECREF(im->im_self)
#5 PyOS_FSPath posixmodule.c:17181 // Py_DECREF(func)
A non-debug build does not fault deterministically, but the read is into freed
memory.
Notes
Same family as gh-151295 (bytes.join) and gh-151370 (marshal.dumps): a
borrowed reference used across a callout that can run arbitrary Python. Triggering
requires a custom __fspath__, so this is a robustness / crash-hardening issue,
not a security vulnerability.
Linked PRs
Linked PRs
- gh-151403: Fix use-after-free when an argv item's __fspath__ mutates args #151404
- [3.15] gh-151403: Fix use-after-free when an argv item's __fspath__ mutates args (GH-151404) #151445
- [3.14] gh-151403: Fix use-after-free when an argv item's __fspath__ mutates args (GH-151404) #151446
- [3.13] gh-151403: Fix use-after-free when an argv item's __fspath__ mutates args (GH-151404) #151447
Activity
pythongh-151403: Fix use-after-free when an argv item's __fspath__ mu…
pythongh-151403: Use os.PathLike.__fspath__ reference in NEWS to fix …
gh-151403: Fix use-after-free when an argv item's __fspath__ mutates …
[3.15] gh-151403: Fix use-after-free when an argv item's __fspath__ m…
[3.14] gh-151403: Fix use-after-free when an argv item's __fspath__ m…
[3.13] gh-151403: Fix use-after-free when an argv item's __fspath__ m…
gpshead commentedon Jun 13, 2026
thanks!
pythongh-151403: Fix use-after-free when an argv item's __fspath__ mu…