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