Repository navigation
gh-159244: Fix crash in dict() on a concurrently mutated key/value pair - #159245
Conversation
…lue pair In the free-threaded build, dict() and dict.update() checked that a list key/value pair had two items and then read them with borrowed references without holding its lock, so another thread could empty the list in between and the items were read from a freed or NULL array. Read the size and both items under Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST, as bytes.join() and str.join() already do.
…t-pair-race # Conflicts: # Lib/test/test_free_threading/test_dict.py
|
@methane, I merged main to resolve a conflict in the tests, the fix itself is unchanged. Could you merge this when you have a moment? Should it also be backported to 3.15 and 3.14? |
|
Thanks @christianaurichzm for the PR, and @methane for merging it 🌮🎉.. I'm working now to backport this PR to: 3.14, 3.15. |
|
Sorry, @christianaurichzm and @methane, I could not cleanly backport this to Please backport manually with cherry_picker, see the devguide for more information. |
|
Sorry, @christianaurichzm and @methane, I could not cleanly backport this to Please backport manually with cherry_picker, see the devguide for more information. |
|
GH-160585 is a backport of this pull request to the 3.15 branch. |
|
GH-160587 is a backport of this pull request to the 3.14 branch. |
…ir (GH-159245) In the free-threaded build, dict() and dict.update() checked that a list key/value pair had two items and then read them with borrowed references without holding its lock, so another thread could empty the list in between and the items were read from a freed or NULL array. (cherry picked from commit eaab974) Co-authored-by: Christian Aurich Zanettini Martins <[email protected]>
…ir (GH-159245) In the free-threaded build, dict() and dict.update() checked that a list key/value pair had two items and then read them with borrowed references without holding its lock, so another thread could empty the list in between and the items were read from a freed or NULL array. (cherry picked from commit eaab974) Co-authored-by: Christian Aurich Zanettini Martins <[email protected]>
Fixes #159244
In the free-threaded build,
dict(),dict.update()andPyDict_MergeFromSeq2()(all throughmerge_from_seq2_lock_held()) check that a list key/value pair has two items, then read them withPySequence_Fast_GET_ITEM(), which returns borrowed references, without holding the list's lock. If another thread empties the list in between, the items are read from a freed or NULL array.This change reads the size and both items under
Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST, asstr.join()andbytes.join()already do. Both items are increfed before the lock is released, so the rest of the loop works on strong references as before.The list's critical section is nested inside the dict's. No Python code runs while the list is locked.
In the default build the macros expand to an empty block.
Verification
test_free_threading.test_dictcrashed an unpatched free-threaded ASan debug build in 5 out of 5 runs and passed 28 out of 28 runs with the fix. A longer stress script (2 mutating threads, 4 readers, 15 seconds) segfaults without the fix and runs cleanly with it.test_dict,test_capi.test_dictandtest_free_threading.test_dictpass on the free-threaded build, andtest_dictandtest_free_threading.test_dictpass with-R 3:3.test_dictpasses on the default build.