Title: Unpickler BUILD can traverse a concurrently mutated shared state dictionary · Issue #157124 · python/cpython · GitHub
Open Graph Title: Unpickler BUILD can traverse a concurrently mutated shared state dictionary · Issue #157124 · python/cpython
X Title: Unpickler BUILD can traverse a concurrently mutated shared state dictionary · Issue #157124 · python/cpython
Description: Description The ordinary-state and slot-state loops in _pickle.load_build() traverse their state dictionary with PyDict_Next() without a critical section or owned snapshot. A reduction can supply an existing dictionary shared with other ...
Open Graph Description: Description The ordinary-state and slot-state loops in _pickle.load_build() traverse their state dictionary with PyDict_Next() without a critical section or owned snapshot. A reduction can supply a...
X Description: Description The ordinary-state and slot-state loops in _pickle.load_build() traverse their state dictionary with PyDict_Next() without a critical section or owned snapshot. A reduction can supply a...
Opengraph URL: https://github.com/python/cpython/issues/157124
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Unpickler BUILD can traverse a concurrently mutated shared state dictionary","articleBody":"**Description**\n\nThe ordinary-state and slot-state loops in `_pickle.load_build()` traverse their state dictionary with `PyDict_Next()` without a critical section or owned snapshot. A reduction can supply an existing dictionary shared with other threads, so key-set changes can invalidate traversal and replacements can reclaim borrowed keys or values before assignment.\n\n**Observed Behavior**\n\nOn a free-threaded build, ordinary-state mode terminates with SIGSEGV in about 0.23 seconds and slot-state mode in about 0.15 seconds. Both compatibility-GIL controls complete normally.\n\n**Affected Version**\n\nCPython 3.14.7 at commit `823f0323ee6ec1402088b73bce1a38473cac36dc`, using the free-threaded build with `PYTHON_GIL=0`.\n\n**Reproduction**\n\nRun both modes:\n\n```sh\nPYTHON_GIL=0 python3.14t poc/reproduce.py 10 state\nPYTHON_GIL=0 python3.14t poc/reproduce.py 10 slot\n```\n\n**PoC Source Code**\n\n`poc/reproduce.py`:\n\n```python\n#!/usr/bin/env python3\n\"\"\"Race Unpickler BUILD iteration with mutation of its shared state dict.\"\"\"\n\nimport pickle\nimport sys\nimport threading\nimport time\n\n\nMODE = sys.argv[2] if len(sys.argv) > 2 else \"state\"\nif MODE not in {\"state\", \"slot\"}:\n raise ValueError(\"mode must be 'state' or 'slot'\")\n\nSHARED = {f\"field_{i}\": object() for i in range(4096)}\nKEYS = tuple(SHARED)\nSTOP = threading.Event()\nFAILURES = []\n\n\ndef get_shared():\n return SHARED\n\n\nclass SharedState(dict):\n def __reduce__(self):\n return get_shared, ()\n\n\nclass Victim:\n def __reduce__(self):\n state = SharedState()\n if MODE == \"slot\":\n state = (None, state)\n return Victim, (), state\n\n\nPAYLOAD = pickle.dumps(Victim(), protocol=2)\n\n\ndef mutate():\n while not STOP.is_set():\n SHARED.clear()\n SHARED.update({key: object() for key in KEYS})\n\n\ndef load_worker(deadline):\n count = 0\n try:\n while time.monotonic() \u003c deadline and not STOP.is_set():\n obj = pickle.loads(PAYLOAD)\n count += 1\n except BaseException as exc:\n FAILURES.append((type(exc).__name__, repr(exc), count))\n STOP.set()\n\n\ndef main():\n seconds = float(sys.argv[1]) if len(sys.argv) > 1 else 10.0\n deadline = time.monotonic() + seconds\n threads = [threading.Thread(target=mutate) for _ in range(2)]\n threads += [threading.Thread(target=load_worker, args=(deadline,)) for _ in range(8)]\n for thread in threads:\n thread.start()\n for thread in threads[2:]:\n thread.join()\n STOP.set()\n for thread in threads[:2]:\n thread.join()\n print(\"mode=\", MODE, \"gil=\", sys._is_gil_enabled(), \"failures=\", FAILURES)\n return bool(FAILURES)\n\n\nraise SystemExit(main())\n```\n","author":{"url":"https://github.com/Nievesjyl","@type":"Person","name":"Nievesjyl"},"datePublished":"2026-09-07T18:48:53Z","url":"https://github.com/python/cpython/issues/157124"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:46256297-fcc8-51ca-acd8-dcf2c8ffcbdd |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | EDB6:241D9D:5C9EC6:81592F:6AF85B0C |
| html-safe-nonce | f5b24bb0cf33b721ab39d8665e0256423116b97c268e6e45f05bfb4de20ca518 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFREI2OjI0MUQ5RDo1QzlFQzY6ODE1OTJGOjZBRjg1QjBDIiwidmlzaXRvcl9pZCI6IjEyNDk2MzQxMTUzODg5ODgxNzIiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | bf7413b515a21728315a1051063d6e5f4dc63232b3ed09a220fc99920cb6b1d5 |
| hovercard-subject-tag | issue:5378103837 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/python/cpython/157124/issue_layout |
| twitter:image | https://opengraph.githubassets.com/63c49d7699adf958c4b43c4543d325a6d6c5135443bd85872d8ba59892f0eaf8/python/cpython/issues/157124 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/63c49d7699adf958c4b43c4543d325a6d6c5135443bd85872d8ba59892f0eaf8/python/cpython/issues/157124 |
| og:image:alt | Description The ordinary-state and slot-state loops in _pickle.load_build() traverse their state dictionary with PyDict_Next() without a critical section or owned snapshot. A reduction can supply a... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | Nievesjyl |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5713ecd83001b2f55c3509fb7a292bf7d66d0f167c7ae87a9f7eaec5a52bf8db |
| turbo-cache-control | no-preview |
| go-import | github.com/python/cpython git https://github.com/python/cpython.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 81598961 |
| octolytics-dimension-repository_nwo | python/cpython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 81598961 |
| octolytics-dimension-repository_network_root_nwo | python/cpython |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 653be32ff5c755082a4db662491bfbbcfc9112c1 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width