Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-114763: Protect lazy loading modules from attribute access race #114781

Merged
merged 12 commits into from Feb 24, 2024

Conversation

effigies
Copy link
Contributor

@effigies effigies commented Jan 31, 2024

As described in #114763, setting the __class__ attribute of a lazy-loading module to ModuleType enables other threads to attempt to access attributes before the loading is complete. This PR moves self.__class__ = types.ModuleType to be the final act.

This requires two additional pieces to work:

  1. A lock, followed by a __class__ check to prevent threads that arrive while the module is being loaded from attempting to double-load the module.
  2. An Event to indicate that the load is in progress to the loading thread, so it can permit dunder attribute access to the exec_module() call. This path also requires that the lock be reentrant.

The Event needs to be tied to the specific module, so I also tied the lock to the module, as opposed to a lock scoped to the importlib.util module. I used object.__getattribute__() for __spec__ and __dict__, which were previously accessed directly after self.__class__ was reset.

Otherwise, I tried to keep things as close as possible to the original. I will try to write a unit test fitting with the module style, but the minimal reproduction in the issue is resolved by these changes.

Closes #114763

Lib/importlib/util.py Show resolved Hide resolved
Lib/importlib/util.py Outdated Show resolved Hide resolved
Lib/importlib/util.py Outdated Show resolved Hide resolved
Lib/test/test_importlib/test_lazy.py Show resolved Hide resolved
@effigies
Copy link
Contributor Author

@brettcannon Just checking in if you're waiting on me for anything. (No rush from my end.)

@brettcannon
Copy link
Member

@effigies Nope, I'm just swamped right now, so I haven't had time to do another review yet.

@brettcannon brettcannon self-requested a review February 13, 2024 00:29
@effigies
Copy link
Contributor Author

No worries! I appreciate you taking the time, whenever you get it.

Copy link
Member

@brettcannon brettcannon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor tweaks, but otherwise LGTM!

Lib/importlib/util.py Outdated Show resolved Hide resolved
Lib/importlib/util.py Outdated Show resolved Hide resolved
Lib/importlib/util.py Outdated Show resolved Hide resolved
Lib/importlib/util.py Outdated Show resolved Hide resolved
Lib/test/test_importlib/test_lazy.py Show resolved Hide resolved
@bedevere-app
Copy link

bedevere-app bot commented Feb 16, 2024

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@effigies
Copy link
Contributor Author

effigies commented Feb 17, 2024

@bedevere-bot I have made the requested changes; please review again.

@@ -244,5 +263,7 @@ def exec_module(self, module):
loader_state = {}
loader_state['__dict__'] = module.__dict__.copy()
loader_state['__class__'] = module.__class__
loader_state['lock'] = threading.RLock()
loader_state['is_loading'] = threading.Event()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, can we use a bool flag instead of threading.Event? I see that access and modification to loader_state['is_loading'] is protected by the loader_state['lock'], so there is no thread safety issue.

Using an additional threading.Event would introduce unnecessary resource costs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, good catch. I will switch to a bool and push later today.

@brettcannon brettcannon self-requested a review February 23, 2024 19:34
@brettcannon
Copy link
Member

!buildbot wasi

@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @brettcannon for commit 023d65d 🤖

The command will test the builders whose names match following regular expression: wasi

The builders matched are:

  • wasm32-wasi Non-Debug PR
  • wasm32 WASI 8Core PR
  • wasm32-wasi PR

@brettcannon brettcannon added needs backport to 3.11 only security fixes needs backport to 3.12 bug and security fixes type-bug An unexpected behavior, bug, or error topic-importlib labels Feb 23, 2024
@brettcannon brettcannon merged commit 200271c into python:main Feb 24, 2024
35 of 40 checks passed
@miss-islington-app
Copy link

Thanks @effigies for the PR, and @brettcannon for merging it 🌮🎉.. I'm working now to backport this PR to: 3.11, 3.12.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Feb 24, 2024
…aces (pythonGH-114781)

Setting the __class__ attribute of a lazy-loading module to ModuleType enables other threads to attempt to access attributes before the loading is complete. Now that is protected by a lock.
(cherry picked from commit 200271c)

Co-authored-by: Chris Markiewicz <effigies@gmail.com>
@bedevere-app
Copy link

bedevere-app bot commented Feb 24, 2024

GH-115870 is a backport of this pull request to the 3.12 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.12 bug and security fixes label Feb 24, 2024
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Feb 24, 2024
…aces (pythonGH-114781)

Setting the __class__ attribute of a lazy-loading module to ModuleType enables other threads to attempt to access attributes before the loading is complete. Now that is protected by a lock.
(cherry picked from commit 200271c)

Co-authored-by: Chris Markiewicz <effigies@gmail.com>
@bedevere-app
Copy link

bedevere-app bot commented Feb 24, 2024

GH-115871 is a backport of this pull request to the 3.11 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.11 only security fixes label Feb 24, 2024
@effigies effigies deleted the fix-issue-114763 branch February 24, 2024 00:03
brettcannon pushed a commit that referenced this pull request Feb 26, 2024
…races (GH-114781) (GH-115870)

gh-114763: Protect lazy loading modules from attribute access races (GH-114781)

Setting the __class__ attribute of a lazy-loading module to ModuleType enables other threads to attempt to access attributes before the loading is complete. Now that is protected by a lock.
(cherry picked from commit 200271c)

Co-authored-by: Chris Markiewicz <effigies@gmail.com>
brettcannon pushed a commit that referenced this pull request Feb 26, 2024
…races (GH-114781) (GH-115871)

gh-114763: Protect lazy loading modules from attribute access races (GH-114781)

Setting the __class__ attribute of a lazy-loading module to ModuleType enables other threads to attempt to access attributes before the loading is complete. Now that is protected by a lock.
(cherry picked from commit 200271c)

Co-authored-by: Chris Markiewicz <effigies@gmail.com>
woodruffw pushed a commit to woodruffw-forks/cpython that referenced this pull request Mar 4, 2024
…aces (pythonGH-114781)

Setting the __class__ attribute of a lazy-loading module to ModuleType enables other threads to attempt to access attributes before the loading is complete. Now that is protected by a lock.
@hugovk
Copy link
Member

hugovk commented Mar 23, 2024

Please see #117178 for a potential regression in 3.11 - 3.13.

diegorusso pushed a commit to diegorusso/cpython that referenced this pull request Apr 17, 2024
…aces (pythonGH-114781)

Setting the __class__ attribute of a lazy-loading module to ModuleType enables other threads to attempt to access attributes before the loading is complete. Now that is protected by a lock.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic-importlib type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Accessing attributes of a lazily-loaded module is not thread-safe
5 participants