Title: Deadlock when `array.fromfile` re-enters `append` via `__index__` · Issue #6549 · RustPython/RustPython · GitHub
Open Graph Title: Deadlock when `array.fromfile` re-enters `append` via `__index__` · Issue #6549 · RustPython/RustPython
X Title: Deadlock when `array.fromfile` re-enters `append` via `__index__` · Issue #6549 · RustPython/RustPython
Description: What happened? array.fromfile calls the user-provided f.read(), and that callback can append objects to the same array while it is being resized. The append path grabs the array's write lock before converting the element, then calls the ...
Open Graph Description: What happened? array.fromfile calls the user-provided f.read(), and that callback can append objects to the same array while it is being resized. The append path grabs the array's write lock before...
X Description: What happened? array.fromfile calls the user-provided f.read(), and that callback can append objects to the same array while it is being resized. The append path grabs the array's write lock be...
Opengraph URL: https://github.com/RustPython/RustPython/issues/6549
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Deadlock when `array.fromfile` re-enters `append` via `__index__`","articleBody":"### What happened?\n\n`array.fromfile` calls the user-provided `f.read()`, and that callback can append objects to the same array while it is being resized. The append path grabs the array's write lock before converting the element, then calls the object's `__index__`. If `__index__` performs another `array.append` (as in the PoC), the second append blocks forever on the already-held `PyRwLock`, causing a deadlock instead of raising an error.\n\n**Proof of Concept:**\n```python\nimport array\n\na = array.array(\"b\")\n\nclass X:\n def __index__(self):\n a.append(0)\n return 0\n\nclass R:\n def read(self, n):\n a.append(X())\n return b\"\\0\" * n\n\na.fromfile(R(), 1)\n```\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eAffected Versions\u003c/strong\u003e\u003c/summary\u003e\n\n| RustPython Version | Status | Exit Code |\n|---|---|---|\n| `Python 3.13.0alpha (heads/main-dirty:21300f689, Dec 13 2025, 22:16:49) [RustPython 0.4.0 with rustc 1.90.0-nightly (11ad40bb8 2025-06-28)]` | Deadlock | 124 |\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eVulnerable Code\u003c/strong\u003e\u003c/summary\u003e\n\n```c\n#[pymethod]\nfn fromfile(\u0026self, f: PyObjectRef, n: isize, vm: \u0026VirtualMachine) -\u003e PyResult\u003c()\u003e {\n let b = vm.call_method(\u0026f, \"read\", (n_bytes,))?; // user f.read runs arbitrary code while the array can still be mutated\n ...\n}\n\n#[pymethod]\nfn append(zelf: \u0026Py\u003cSelf\u003e, x: PyObjectRef, vm: \u0026VirtualMachine) -\u003e PyResult\u003c()\u003e {\n zelf.try_resizable(vm)?.push(x, vm) // takes a non-reentrant PyRwLock write guard before converting x\n}\n\nimpl ArrayElement for i8 {\n fn try_into_from_object(vm: \u0026VirtualMachine, obj: PyObjectRef) -\u003e PyResult\u003cSelf\u003e {\n obj.try_index(vm)?.try_to_primitive(vm) // invokes __index__ while the write lock is held; reentrant array.append inside __index__ deadlocks\n }\n}\n\nimpl BufferResizeGuard for PyArray {\n fn try_resizable_opt(\u0026self) -\u003e Option\u003cSelf::Resizable\u003c'_\u003e\u003e {\n let w = self.write(); // second append blocks here waiting on the same PyRwLock, hanging the interpreter\n (self.exports.load(atomic::Ordering::SeqCst) == 0).then_some(w)\n }\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eRust Output\u003c/strong\u003e\u003c/summary\u003e\n\n```\nProgram hangs forever\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eCPython Output\u003c/strong\u003e\u003c/summary\u003e\n\n```\n(No output)\n```\n\u003c/details\u003e\n\n","author":{"url":"https://github.com/jackfromeast","@type":"Person","name":"jackfromeast"},"datePublished":"2025-12-27T04:53:28.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":2},"url":"https://github.com/6549/RustPython/issues/6549"}
| 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:7a0d43c7-442b-998f-7d8d-8c15141ae0bd |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | EDB6:29E881:A06DCA:E02CEA:696A559C |
| html-safe-nonce | 68be20cf31ba3849f1db71834c0818cebdf1dff25c7ec5d97b598345204c9f81 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFREI2OjI5RTg4MTpBMDZEQ0E6RTAyQ0VBOjY5NkE1NTlDIiwidmlzaXRvcl9pZCI6IjQxNDAzOTQzOTI2MDc5Mzc5NDgiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 23e46692eaa6abae28fb505e7a6fafa740cadea0ee126407898bce8cfac9e42f |
| hovercard-subject-tag | issue:3764354946 |
| 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/RustPython/RustPython/6549/issue_layout |
| twitter:image | https://opengraph.githubassets.com/815ba18e3f5f6caab174b76a47e273a4eb593d1eae0d0072858d71b6d1c775c9/RustPython/RustPython/issues/6549 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/815ba18e3f5f6caab174b76a47e273a4eb593d1eae0d0072858d71b6d1c775c9/RustPython/RustPython/issues/6549 |
| og:image:alt | What happened? array.fromfile calls the user-provided f.read(), and that callback can append objects to the same array while it is being resized. The append path grabs the array's write lock before... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | jackfromeast |
| hostname | github.com |
| expected-hostname | github.com |
| None | 3f871c8e07f0ae1886fa8dac284166d28b09ad5bada6476fc10b674e489788ef |
| turbo-cache-control | no-preview |
| go-import | github.com/RustPython/RustPython git https://github.com/RustPython/RustPython.git |
| octolytics-dimension-user_id | 39710557 |
| octolytics-dimension-user_login | RustPython |
| octolytics-dimension-repository_id | 135201145 |
| octolytics-dimension-repository_nwo | RustPython/RustPython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 135201145 |
| octolytics-dimension-repository_network_root_nwo | RustPython/RustPython |
| 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 | 63c426b30d262aba269ef14c40e3c817b384cd61 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width