only load a stdlib-named C extension from the standard library - #3193
only load a stdlib-named C extension from the standard library#3193kali834x wants to merge 3 commits into
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3193 +/- ##
=======================================
Coverage 93.65% 93.65%
=======================================
Files 93 93
Lines 11613 11618 +5
=======================================
+ Hits 10876 10881 +5
Misses 737 737
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
DanielNoord
left a comment
There was a problem hiding this comment.
I like it!
@jacobtylerwalls generally is quite knowledgeable about our handling of imports. Do you have time for a review as well?
yangfan-yf-yf
left a comment
There was a problem hiding this comment.
The location check is the right boundary, but the regression currently only exercises a temporary directory outside both library roots. It does not cover the security-relevant branch that rejects EXT_LIB_DIRS before accepting a path under STD_LIB_DIRS.
In a virtual environment, site-packages can sit below the standard-library directory, which is exactly why the early EXT_LIB_DIRS rejection is needed. Please add a focused regression for a location under EXT_LIB_DIRS (for example, an extension-suffixed colorsys path) and assert that it is not accepted as a standard-library location. That both guards the virtualenv case and covers the currently missed branch.
I verified locally that the proposed predicate rejects such a path, and the existing temporary-directory regression passes.
|
Good point, that branch was untested. Added Confirmed it fails if the |
|
Thanks for adding both requested boundary tests. I rechecked the current head: the focused modutils/manager selection passes ( The branch is currently behind |
7dca334 to
44962b3
Compare
|
Rebased onto main and resolved the ChangeLog conflict. The new entry now sits under the 4.4.0 section alongside the data-descriptor fix that landed there. Product and test files were unchanged by the rebase, and the focused modutils/manager tests still pass. |
I can try over the next few days, sorry! |
jacobtylerwalls
left a comment
There was a problem hiding this comment.
Thanks for working on this.
| # Only trust a stdlib name that resolves to the stdlib itself. | ||
| if ( | ||
| is_stdlib_module(modname) | ||
| and location is not None |
There was a problem hiding this comment.
Nothing fails when I remove and location is not None, so we must be missing a test case for when location is None, e.g. for a stdlib c module like time.
There was a problem hiding this comment.
Right, that branch was uncovered. The reason nothing fails with a real module is that time is a statically linked builtin (C_BUILTIN, location None), so it never reaches _can_load_extension, which is only called for C_EXTENSION. The guard is there because found_spec.location is typed str | None and is_stdlib_path calls os.path.realpath, which raises on None.
Pushed a focused test that calls _can_load_extension("colorsys", None) directly and asserts False. Drop the guard and it errors with TypeError instead of returning False, so the branch is pinned now.
|
|
||
| def test_ast_from_module_name_extension_shadowing_stdlib(self) -> None: | ||
| """An extension outside the stdlib is stubbed, not imported.""" | ||
| modname = "colorsys" |
There was a problem hiding this comment.
This test fails when I change this to time, although it's not clear to me if if the shadowing file is the one that's interpreted by astroid or not.
Can you walk me through this?
There was a problem hiding this comment.
Good question, and the difference is the point of picking colorsys here. colorsys is a pure-Python stdlib module, so a colorsys.<ext>.so planted at the front of sys.path resolves ahead of the real colorsys.py. astroid sees a C_EXTENSION whose location is the planted file, and that is the file it would import, which is the bug. The fix stubs it instead.
time behaves differently because it is a statically linked builtin. BuiltinImporter sits ahead of the path-based finders, so it resolves time before sys.path is ever scanned, and the planted time.so is ignored. astroid gets a C_BUILTIN with location None, never reaches the extension gate, and imports the real builtin, so nothing is stubbed and the assertion fails. The shadowing file is not what astroid interprets for time, which is why the test uses a pure-Python stdlib name.
Pierre-Sassoulas
left a comment
There was a problem hiding this comment.
Let's fix pre-commit and then merge :)
efc250b to
c9124da
Compare
|
The pre-commit failure was the merge conflict: main moved the changelog to towncrier since the last rebase, so pre-commit.ci couldn't compute a merge. Rebased again and moved the entry from ChangeLog into doc/whatsnew/fragments/3193.bugfix. pre-commit passes locally on the diff, no other changes. |
Type of Changes
Description
_can_load_extensiononly receives the module name, andis_stdlib_moduleis aplain name test, so any C extension whose name matches a stdlib module is
imported no matter where it came from.
ImportlibFinder.find_modulewalkssys.pathin order and tries the extension suffixes before.py, and theanalysed project's directory comes first, so a
colorsys.cpython-313-x86_64-linux-gnu.socommitted to a repository resolves ahead of the real module and
ast_from_module_namehands it toload_module_from_name, which runs itsmodule init. That happens with
always_load_extensionsoff and an emptyextension-pkg-allow-list, so the opt-in that guards extension loading isskipped entirely and linting a checkout executes code out of it. Pass the
resolved location down and require it to be inside
STD_LIB_DIRSbefore thestdlib name is trusted, excluding
EXT_LIB_DIRSbecause site-packages sitsbelow the stdlib directory in a virtualenv. Extensions shipped with the
interpreter still load from
lib-dynload, and an explicit allow-list entrystill wins.