Skip to content

gh-159244: Fix crash in dict() on a concurrently mutated key/value pair - #159245

Merged
methane merged 2 commits into
python:mainfrom
christianaurichzm:gh-159244-dict-pair-race
Oct 24, 2026
Merged

methane merged 2 commits into
python:mainfrom
christianaurichzm:gh-159244-dict-pair-race

Conversation

@christianaurichzm

Copy link
Copy Markdown
Contributor

Fixes #159244

In the free-threaded build, dict(), dict.update() and PyDict_MergeFromSeq2() (all through merge_from_seq2_lock_held()) check that a list key/value pair has two items, then read them with PySequence_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, as str.join() and bytes.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

  • The new test in test_free_threading.test_dict crashed 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_dict and test_free_threading.test_dict pass on the free-threaded build, and test_dict and test_free_threading.test_dict pass with -R 3:3. test_dict passes on the default build.

…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
@christianaurichzm

Copy link
Copy Markdown
Contributor Author

@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?

@methane methane added topic-free-threading needs backport to 3.14 bugs and security fixes needs backport to 3.15 bugs and security fixes type-bug An unexpected behavior, bug, or error interpreter-core (Objects, Python, Grammar, and Parser dirs) labels Oct 24, 2026
@methane
methane merged commit eaab974 into python:main Oct 24, 2026
66 checks passed
@miss-islington-app

Copy link
Copy Markdown

Thanks @christianaurichzm for the PR, and @methane for merging it 🌮🎉.. I'm working now to backport this PR to: 3.14, 3.15.
🐍🍒⛏🤖

@miss-islington-app

Copy link
Copy Markdown

Sorry, @christianaurichzm and @methane, I could not cleanly backport this to 3.15 due to a conflict.

Please backport manually with cherry_picker, see the devguide for more information.

cherry_picker eaab974e8a40a58c2207afd69df0147982fab29d 3.15

@miss-islington-app

Copy link
Copy Markdown

Sorry, @christianaurichzm and @methane, I could not cleanly backport this to 3.14 due to a conflict.

Please backport manually with cherry_picker, see the devguide for more information.

cherry_picker eaab974e8a40a58c2207afd69df0147982fab29d 3.14

@bedevere-app

bedevere-app Bot commented Oct 24, 2026

Copy link
Copy Markdown

GH-160585 is a backport of this pull request to the 3.15 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.15 bugs and security fixes label Oct 24, 2026
@bedevere-app

bedevere-app Bot commented Oct 24, 2026

Copy link
Copy Markdown

GH-160587 is a backport of this pull request to the 3.14 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.14 bugs and security fixes label Oct 24, 2026
methane added a commit that referenced this pull request Oct 24, 2026
…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]>
methane added a commit that referenced this pull request Oct 24, 2026
…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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

interpreter-core (Objects, Python, Grammar, and Parser dirs) topic-free-threading type-bug An unexpected behavior, bug, or error

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Free-threading: dict() crashes when a key/value pair list is mutated concurrently

2 participants