Skip to content

gh-149449: Fix use-after-free in _PyUnicode_GetNameCAPI#150323

Merged
kumaraditya303 merged 3 commits into
python:mainfrom
eendebakpt:fix-ucnhash-capi-uaf
May 24, 2026
Merged

gh-149449: Fix use-after-free in _PyUnicode_GetNameCAPI#150323
kumaraditya303 merged 3 commits into
python:mainfrom
eendebakpt:fix-ucnhash-capi-uaf

Conversation

@eendebakpt
Copy link
Copy Markdown
Contributor

@eendebakpt eendebakpt commented May 23, 2026

The cache stored the raw struct pointer extracted from unicodedata's _ucnhash_CAPI capsule without keeping a reference to the owning capsule. Dropping the module from sys.modules and running gc.collect() then freed the struct while \N{...} decoding and the namereplace codec handler were still using it. Hold a strong reference to the capsule on the interpreter state and Py_CLEAR it in _PyUnicode_Fini.

This needs backports I think, as it also affects python versions 3.10 to 3.15.

Claude was used to construct a PR.

Comment thread Objects/unicodeobject.c Outdated
@kumaraditya303
Copy link
Copy Markdown
Contributor

I think the simpler alternative to this is to just statically allocate _PyUnicode_Name_CAPI in unicodedata_create_capi.

The _PyUnicode_Name_CAPI struct was malloc'd per import and freed by
the capsule destructor, leaving the per-interpreter cached pointer
dangling once unicodedata was removed from sys.modules and gc'd. The
\N{...} parser path and the namereplace codec handler then crashed.

Allocate the struct in static storage and drop the destructor; the
contents are immutable function pointers shared across imports.
@eendebakpt eendebakpt force-pushed the fix-ucnhash-capi-uaf branch from b942917 to ca33d17 Compare May 24, 2026 14:31
Comment thread Modules/unicodedata.c Outdated
@kumaraditya303 kumaraditya303 added needs backport to 3.13 bugs and security fixes needs backport to 3.14 bugs and security fixes needs backport to 3.15 pre-release feature fixes, bugs and security fixes labels May 24, 2026
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
@kumaraditya303 kumaraditya303 enabled auto-merge (squash) May 24, 2026 15:50
@kumaraditya303 kumaraditya303 merged commit 43c60ec into python:main May 24, 2026
60 checks passed
@miss-islington-app
Copy link
Copy Markdown

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

@bedevere-app
Copy link
Copy Markdown

bedevere-app Bot commented May 24, 2026

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

@bedevere-app bedevere-app Bot removed the needs backport to 3.15 pre-release feature fixes, bugs and security fixes label May 24, 2026
@bedevere-app
Copy link
Copy Markdown

bedevere-app Bot commented May 24, 2026

GH-150353 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 May 24, 2026
@bedevere-app
Copy link
Copy Markdown

bedevere-app Bot commented May 24, 2026

GH-150354 is a backport of this pull request to the 3.13 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.13 bugs and security fixes label May 24, 2026
@bedevere-bot
Copy link
Copy Markdown

⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️

Hi! The buildbot ARM64 macOS 3.x (tier-2) has failed when building commit 43c60ec.

What do you need to do:

  1. Don't panic.
  2. Check the buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/#/builders/725/builds/13430) and take a look at the build logs.
  4. Check if the failure is related to this commit (43c60ec) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/#/builders/725/builds/13430

Failed tests:

  • test_ssl

Summary of the results of the build (if available):

==

Click to see traceback logs
remote: Enumerating objects: 16, done.        
remote: Counting objects:   9% (1/11)        
remote: Counting objects:  18% (2/11)        
remote: Counting objects:  27% (3/11)        
remote: Counting objects:  36% (4/11)        
remote: Counting objects:  45% (5/11)        
remote: Counting objects:  54% (6/11)        
remote: Counting objects:  63% (7/11)        
remote: Counting objects:  72% (8/11)        
remote: Counting objects:  81% (9/11)        
remote: Counting objects:  90% (10/11)        
remote: Counting objects: 100% (11/11)        
remote: Counting objects: 100% (11/11), done.        
remote: Compressing objects:  14% (1/7)        
remote: Compressing objects:  28% (2/7)        
remote: Compressing objects:  42% (3/7)        
remote: Compressing objects:  57% (4/7)        
remote: Compressing objects:  71% (5/7)        
remote: Compressing objects:  85% (6/7)        
remote: Compressing objects: 100% (7/7)        
remote: Compressing objects: 100% (7/7), done.        
remote: Total 16 (delta 4), reused 4 (delta 4), pack-reused 5 (from 1)        
From https://github.com/python/cpython
 * branch                    main       -> FETCH_HEAD
Note: switching to '43c60ec2fddd316a4a6b7b6c68eae7cb66df0850'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 43c60ec2fdd gh-149449: Fix use-after-free in `_PyUnicode_GetNameCAPI` (#150323)
Switched to and reset branch 'main'

ranlib: warning: 'libpython3.16d.a(brc.o)' has no symbols
ranlib: warning: 'libpython3.16d.a(dynamic_annotations.o)' has no symbols
ranlib: warning: 'libpython3.16d.a(gc_free_threading.o)' has no symbols
ranlib: warning: 'libpython3.16d.a(index_pool.o)' has no symbols
ranlib: warning: 'libpython3.16d.a(jit.o)' has no symbols
ranlib: warning: 'libpython3.16d.a(jit_publish.o)' has no symbols
ranlib: warning: 'libpython3.16d.a(optimizer_analysis.o)' has no symbols
ranlib: warning: 'libpython3.16d.a(optimizer_symbols.o)' has no symbols
ranlib: warning: 'libpython3.16d.a(pymath.o)' has no symbols
ranlib: warning: 'libpython3.16d.a(pystats.o)' has no symbols
ranlib: warning: 'libpython3.16d.a(stackrefs.o)' has no symbols
ranlib: warning: 'libpython3.16d.a(uniqueid.o)' has no symbols

make: *** [buildbottest] Error 2

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants