Skip to content

gh-158803: Fix crash in bytes.join() on a concurrently mutated list - #158910

Open
christianaurichzm wants to merge 2 commits into
python:mainfrom
christianaurichzm:gh-158803-bytes-join-race
Open

christianaurichzm wants to merge 2 commits into
python:mainfrom
christianaurichzm:gh-158803-bytes-join-race

Conversation

@christianaurichzm

@christianaurichzm christianaurichzm commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #158803

In the free-threaded build, bytes.join(), bytearray.join() and PyBytes_Join() (which share Objects/stringlib/join.h) read items from a list with PySequence_Fast_GET_ITEM(), which returns a borrowed reference, without holding the list's lock. If another thread replaces an item in the meantime, the old item can be freed before join() takes its own reference to it.

str.join() had the same problem and was fixed in gh-119247, which added Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST. This change uses the same pattern: the existing body becomes bytes_join_lock_held(), and bytes_join() calls it inside the critical section, as PyUnicode_Join() does with _PyUnicode_JoinArray().

The critical section can be suspended while an item's __buffer__() runs, or while a large result is copied with the thread state detached. Every item is increfed while the lock is held, and the existing size check still raises RuntimeError if the list changes size, so both cases stay safe.

In the default build the macros expand to an empty block.

Verification

  • The new test in test_free_threading.test_bytes_object crashes an unpatched free-threaded debug build in most runs (6 to 8 out of 10) and passed 30 out of 30 runs with the fix. The reproducer from the issue segfaults without the fix and runs cleanly for 10 seconds with it.
  • Under ThreadSanitizer (clang 21, free-threaded, same configure options and suppressions as CI), test_free_threading, test_bytes and test_capi.test_bytes pass with no reports.
  • test_bytes, test_capi.test_bytes and test_free_threading.test_bytes_object pass with -R 3:3 on both builds.
  • No new failures in the full test suite on either build.

…list

In the free-threaded build, bytes.join() and bytearray.join() read
items from the list with borrowed references and without holding its
lock, so another thread could replace and free an item before it was
increfed.

Run the join under Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST, as
PyUnicode_Join() already does.
@christianaurichzm

Copy link
Copy Markdown
Contributor Author

The macOS (free-threading) failure is unrelated to this change: the test run was stopped by a SIGINT that escaped test_idle's InterruptTest.test_interrupt_blocking_call. The same failure happened on #158828 and on the Windows NoGIL buildbot (reported in gh-74112), and it is fixed by #158914.

eendebakpt added a commit to eendebakpt/cpython that referenced this pull request Oct 6, 2026
…-join-exact-fast

The fast path holds no reference to the items, so in the free-threaded
build it relies on the sequence being locked by the caller.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@eendebakpt eendebakpt added needs backport to 3.14 bugs and security fixes needs backport to 3.15 pre-release feature fixes, bugs and security fixes labels Oct 6, 2026

@eendebakpt eendebakpt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! The failing CI indeed seems unrelated. Can you merge with main to trigger the CI again?

@christianaurichzm

Copy link
Copy Markdown
Contributor Author

@eendebakpt Thanks! Merged main. The Windows failure is the known test_sslproto flake (gh-158646). Could you re-run it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting merge needs backport to 3.14 bugs and security fixes needs backport to 3.15 pre-release feature fixes, bugs and security fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Segfault in bytes.join for mutating list

2 participants