Skip to content

gh-142782: Fix use-after-free in the zoneinfo strong cache - #159441

Open
tonghuaroot wants to merge 3 commits into
python:mainfrom
tonghuaroot:zoneinfo-strong-cache-reentrant-uaf
Open

tonghuaroot wants to merge 3 commits into
python:mainfrom
tonghuaroot:zoneinfo-strong-cache-reentrant-uaf

Conversation

@tonghuaroot

Copy link
Copy Markdown
Contributor

find_in_strong_cache() compares the lookup key against each cached key with PyObject_RichCompareBool(). A str subclass whose __eq__ calls ZoneInfo.clear_cache() frees the node being compared, and the traversal then dereferences the freed node (gh-142782).

This keeps a reference to the node key during the comparison, and bumps a strong_cache_version counter on every strong-cache mutation; if the version changes during a comparison, the lookup returns a miss instead of touching a possibly-freed node. This also addresses the false-negative / duplicate concern raised on the issue.

Verified under ./configure --with-address-sanitizer: the reproducer and the new test_strong_cache_reentrant_clear crash without the fix and are clean with it; the full test_zoneinfo suite passes.

Comparing a lookup key against a cached key runs the key's __eq__, which
can re-enter and call ZoneInfo.clear_cache(), freeing the node being
compared; find_in_strong_cache() then dereferenced the freed node.

Keep a reference to the node key during the comparison and bump a cache
version counter on every mutation, so a lookup that observes a mutation
during a comparison returns a miss instead of using a freed node.
Comment thread Lib/test/test_zoneinfo/test_zoneinfo.py Outdated
Comment on lines +1684 to +1685
klass(Stringy("America/Los_Angeles"))
klass(Stringy("America/Los_Angeles"))

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.

Do we need assertions here?

Suggested change
klass(Stringy("America/Los_Angeles"))
klass(Stringy("America/Los_Angeles"))
zone1 = klass(Stringy("America/Los_Angeles"))
zone2 = klass(Stringy("America/Los_Angeles"))
self.assertIsNot(zone2, zone1)
self.assertEqual(str(zone2), "America/Los_Angeles")

@tonghuaroot

Copy link
Copy Markdown
Contributor Author

good point, added the str() checks. i left out the identity assertion though: this test runs against both the C and pure-python ZoneInfo, and they differ there. the C version has the strong cache, so clearing it mid-lookup makes the second lookup a miss and returns a fresh object; the pure-python version only has the weak cache, so the second lookup returns the same object. an assertIsNot would pass for C but fail for pure-python.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants