Title: import_run_extension() can return on the wrong interpreter after _PyUnicode_Copy() failure · Issue #150454 · python/cpython · GitHub
Open Graph Title: import_run_extension() can return on the wrong interpreter after _PyUnicode_Copy() failure · Issue #150454 · python/cpython
X Title: import_run_extension() can return on the wrong interpreter after _PyUnicode_Copy() failure · Issue #150454 · python/cpython
Description: Bug report Bug description: Summary import_run_extension() can return NULL while the current thread is still switched to the main interpreter. This happens in the subinterpreter extension import path when copying info->filename with _PyU...
Open Graph Description: Bug report Bug description: Summary import_run_extension() can return NULL while the current thread is still switched to the main interpreter. This happens in the subinterpreter extension import pa...
X Description: Bug report Bug description: Summary import_run_extension() can return NULL while the current thread is still switched to the main interpreter. This happens in the subinterpreter extension import pa...
Opengraph URL: https://github.com/python/cpython/issues/150454
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"import_run_extension() can return on the wrong interpreter after _PyUnicode_Copy() failure","articleBody":"# Bug report\n\n### Bug description:\n\n## Summary\n\n`import_run_extension()` can return `NULL` while the current thread is still switched to the main interpreter. This happens in the subinterpreter extension import path when copying `info-\u003efilename` with `_PyUnicode_Copy()` fails.\n\nThe function switches from the importing subinterpreter to the main interpreter before calling the extension module init function. For single-phase init modules, it then copies the original filename because the original object may have been allocated by the subinterpreter's obmalloc:\n\n```c\nfilename = _PyUnicode_Copy(info-\u003efilename);\nif (filename == NULL) {\n return NULL;\n}\n```\n\nThat direct `return NULL` bypasses the `main_finally` cleanup path, including `switch_back_from_main_interpreter()`. The caller resumes with the wrong interpreter current for the thread, which can lead to a crash or interpreter state corruption.\n\nThis is a sub-issue of https://github.com/python/cpython/issues/146102 with [gist details](https://gist.github.com/devdanzin/432d9af1a8833bef53bc9ae342310622)\n\n## Affected area\n\n`Python/import.c`, in `import_run_extension()`, around the single-phase extension import handling after switching to the main interpreter.\n\n\n## Reproducer\n\nThe following script runs each attempt in a child process so that a vulnerable build can crash without taking down the driver process. It requires a CPython build tree with `_interpreters`, `_testcapi`, and `_testsinglephase` available.\n\n```python\nfrom __future__ import annotations\n\nimport argparse\nimport subprocess\nimport sys\nimport textwrap\n\n\nCHILD = r\"\"\"\nimport _interpreters\nimport _testsinglephase\n\nfilename = _testsinglephase.__file__\nscript = f'''\nimport _testcapi\nfrom test.test_import import import_extension_from_file\n\n_testcapi.set_nomemory(START, STOP)\ntry:\n import_extension_from_file('_testsinglephase_basic_copy', {filename!r})\nfinally:\n _testcapi.remove_mem_hooks()\n'''\n\ninterp = _interpreters.create()\ntry:\n exc = _interpreters.run_string(interp, script)\nfinally:\n _interpreters.destroy(interp)\n\nif exc is None:\n print(\"import unexpectedly succeeded\")\nelse:\n print(f\"{exc.type.__name__}: {exc.msg}\")\n\"\"\"\n\n\ndef run_one(python: str, start: int) -\u003e subprocess.CompletedProcess[bytes]:\n code = CHILD.replace(\"START\", str(start)).replace(\"STOP\", str(start + 1))\n return subprocess.run(\n [python, \"-X\", \"faulthandler\", \"-c\", code],\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n\n\ndef main() -\u003e int:\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--python\", default=sys.executable)\n parser.add_argument(\"--start\", type=int, default=1)\n parser.add_argument(\"--stop\", type=int, default=50)\n args = parser.parse_args()\n\n for start in range(args.start, args.stop + 1):\n proc = run_one(args.python, start)\n out = proc.stdout.decode(\"utf-8\", \"replace\").strip()\n err = proc.stderr.decode(\"utf-8\", \"replace\").strip()\n if proc.returncode:\n print(f\"CRASH start={start} returncode={proc.returncode}\")\n if out:\n print(\"stdout:\")\n print(textwrap.indent(out, \" \"))\n if err:\n print(\"stderr:\")\n print(textwrap.indent(err, \" \"))\n return 1\n print(f\"ok start={start}: {out}\")\n\n print(\"No child process crashed in the scanned range.\")\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n```\n\nExample command on Windows:\n\n```powershell\n.\\PCbuild\\amd64\\python.exe repro_import_run_extension_copy_failure.py --start 1 --stop 50\n```\n\nOn a vulnerable build, one of the child processes can terminate with an access violation or another abnormal return code.\n\nOn a fixed build, allocation failures in this area should be converted into a controlled subinterpreter-side `ImportError`, for example:\n\n```text\nImportError: failed to import from subinterpreter due to exception\n```\n\n## Expected behavior\n\nAny error after switching to the main interpreter must go through the cleanup path that switches back to the original interpreter before returning to the caller.\n\n\n\n\n### CPython versions tested on:\n\nCPython main branch\n\n### Operating systems tested on:\n\nWindows\n\n\u003c!-- gh-linked-prs --\u003e\n### Linked PRs\n* gh-150455\n\u003c!-- /gh-linked-prs --\u003e\n","author":{"url":"https://github.com/lpyu001","@type":"Person","name":"lpyu001"},"datePublished":"2026-05-26T07:15:38.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/150454/cpython/issues/150454"}
| 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:586b69d7-f397-4e45-10d8-b801f9093ac2 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 9F8A:2380C5:AF5ACE0:B2B75D0:6A50B97F |
| html-safe-nonce | d3b6a362d0be868440d71e15f981a09b9eacd07675f29fffb14f841162251520 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5RjhBOjIzODBDNTpBRjVBQ0UwOkIyQjc1RDA6NkE1MEI5N0YiLCJ2aXNpdG9yX2lkIjoiMjY1MTg2NzM0NzU1MjM1MjYzOSIsInJlZ2lvbl9lZGdlIjoic2VhIiwicmVnaW9uX3JlbmRlciI6InNlYSJ9 |
| visitor-hmac | ccf909b37e3c612b2ddcc457762d90f536b78fed4b7aede7a69378de112131ec |
| hovercard-subject-tag | issue:4522214937 |
| 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/150454/issue_layout |
| twitter:image | https://opengraph.githubassets.com/63c929b2c1a259f6ff53248fc76ab15dc0c011168686bfc5a9eb845b474fb64a/python/cpython/issues/150454 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/63c929b2c1a259f6ff53248fc76ab15dc0c011168686bfc5a9eb845b474fb64a/python/cpython/issues/150454 |
| og:image:alt | Bug report Bug description: Summary import_run_extension() can return NULL while the current thread is still switched to the main interpreter. This happens in the subinterpreter extension import pa... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | lpyu001 |
| hostname | github.com |
| expected-hostname | github.com |
| None | 63772747707af4093f0d5fc7aa1bb3bb55f412a9865207c4f421aa34c64cc404 |
| 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 | ded53fe94e3ff1bb4dbf8cc77e285396c92580fb |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width