gh-149816: Fix UAF in Modules/_pickle.c#150024
Conversation
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
|
Is it possible to add a test for this? |
Get a strong reference atomically for list item instead of 2 operations.
Initially I thought no, but I found a way, thanks for asking |
| @@ -3210,8 +3212,11 @@ batch_list_exact(PickleState *state, PicklerObject *self, PyObject *obj) | |||
| if (_Pickler_Write(self, &mark_op, 1) < 0) | |||
| return -1; | |||
| while (total < PyList_GET_SIZE(obj)) { | |||
There was a problem hiding this comment.
philosophy question... true even before this change: We serialize a list that could be mutated during serialization. We don't detect that this has happened, so the serialized value can be inconsistent as far as what values are picked up in the serialized form. Do we care? should we just document that? asking because batch_dict_exact does detect size change on dicts and raises a RuntimError. We could at least detect a size change and do similar here. should we? it'd be consistent.
Get a strong reference atomically for list item instead of 2 operations.
Original description of the problem from 91.md:
Vulnerability #91
Title: Racy list item borrow causes UAF
Category: Memory Safety Violations
Tags: write,race,env,dos
CWEs: CWE-416, CWE-367
CVSS: CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:H/SC:N/SI:N/SA:N
Severity: Medium (5.8)
Location:
cpython/Modules/_pickle.c:3213:3214in functionbatch_list_exactDescription
batch_list_exactreads list elements usingPyList_GET_ITEMand only then increments the reference count (Py_INCREF) without holding the list lock or using a safe strong-ref getter (cpython/Modules/_pickle.c:3213-3214, alsocpython/Modules/_pickle.c:3197-3198). In free-threaded mode,_pickleruns without the GIL (cpython/Modules/_pickle.c:8242), so concurrent list mutation is possible while pickling. A mutator thread can remove/replace the same element and decref it to zero (cpython/Objects/listobject.c:1145-1156) between size check/access inbatch_list_exact(cpython/Modules/_pickle.c:3212-3214), leading toPy_INCREFon freed memory (use-after-free write).Trigger Conditions
Pre-conditions:
_pickleis used (it declares no-GIL operation atcpython/Modules/_pickle.c:8242).listand protocol is greater than 0, sosave_listusesbatch_list_exact(cpython/Modules/_pickle.c:3266-3269).Data flow:
pickle.dumps(shared_list, protocol=5);save_listdispatches tobatch_list_exact(cpython/Modules/_pickle.c:3266-3269).batch_list_exact, Thread A evaluates loop/size and then fetches a borrowed element viaPyList_GET_ITEM(cpython/Modules/_pickle.c:3212-3213).del shared_list[i]orshared_list[i] = other), and list code decrefs the old element (cpython/Objects/listobject.c:1145-1156), potentially freeing it.Py_INCREF(item)on the stale pointer (cpython/Modules/_pickle.c:3214), triggering UAF memory corruption/crash.Impact
This can cause native memory corruption in the interpreter (use-after-free with refcount write), typically leading to process crash (denial of service) and potentially enabling arbitrary code execution in worst case under favorable heap/layout conditions. Exploitability is constrained by requiring a free-threaded build and a concurrent mutation race on the same list object, but the code path is reachable from normal Python APIs (
pickle.dumps) and does not enforce safety against such concurrent access.Remediation
batch_list_exact()(cpython/Modules/_pickle.c), replace both unsafe borrowed-ref reads (PyList_GET_ITEM+Py_INCREF) with a strong-reference API (PyList_GetItemRef/ internal equivalent) so element lifetime is acquired atomically for free-threaded builds.save_list()and passed intobatch_list_exact().RuntimeError(e.g., “list changed size during iteration”) instead of continuing.pickle.dumps()+ list mutation to ensure no UAF/crash, and verify behavior is clean exception-only under race.Reproduction
--with-pydebug).listused by two concurrent threads:> 0(so_pickletakes the fast exact-list path),_picklelist batching /Py_INCREFon a list item.Code Context