From 8376908a9373d2242fe00a204f5199b42e17cd03 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 7 Apr 2026 08:17:39 +0200 Subject: [PATCH 1/3] Revert "gh-146121: `pkgutil.get_data()` reject invalid resource arguments (#146122)" This reverts commit bcdf231946b1da8bdfbab4c05539bb0cc964a1c7. --- Lib/pkgutil.py | 3 --- Lib/test/test_pkgutil.py | 19 ------------------- ...-03-16-18-07-00.gh-issue-146121.vRbdro.rst | 3 --- 3 files changed, 25 deletions(-) delete mode 100644 Misc/NEWS.d/next/Security/2026-03-16-18-07-00.gh-issue-146121.vRbdro.rst diff --git a/Lib/pkgutil.py b/Lib/pkgutil.py index c3109a3a4cd414..8772a66791a3c9 100644 --- a/Lib/pkgutil.py +++ b/Lib/pkgutil.py @@ -393,9 +393,6 @@ def get_data(package, resource): # signature - an os.path format "filename" starting with the dirname of # the package's __file__ parts = resource.split('/') - if os.path.isabs(resource) or '..' in parts: - raise ValueError("resource must be a relative path with no " - "parent directory components") parts.insert(0, os.path.dirname(mod.__file__)) resource_name = os.path.join(*parts) return loader.get_data(resource_name) diff --git a/Lib/test/test_pkgutil.py b/Lib/test/test_pkgutil.py index 948afb8c18cf67..d4faaaeca00457 100644 --- a/Lib/test/test_pkgutil.py +++ b/Lib/test/test_pkgutil.py @@ -61,25 +61,6 @@ def test_getdata_filesys(self): del sys.modules[pkg] - def test_getdata_path_traversal(self): - pkg = 'test_getdata_traversal' - - # Make a package with some resources - package_dir = os.path.join(self.dirname, pkg) - os.mkdir(package_dir) - # Empty init.py - f = open(os.path.join(package_dir, '__init__.py'), "wb") - f.close() - - with self.assertRaises(ValueError): - pkgutil.get_data(pkg, '../../../etc/passwd') - with self.assertRaises(ValueError): - pkgutil.get_data(pkg, 'sub/../../../etc/passwd') - with self.assertRaises(ValueError): - pkgutil.get_data(pkg, os.path.abspath('/etc/passwd')) - - del sys.modules[pkg] - def test_getdata_zipfile(self): zip = 'test_getdata_zipfile.zip' pkg = 'test_getdata_zipfile' diff --git a/Misc/NEWS.d/next/Security/2026-03-16-18-07-00.gh-issue-146121.vRbdro.rst b/Misc/NEWS.d/next/Security/2026-03-16-18-07-00.gh-issue-146121.vRbdro.rst deleted file mode 100644 index c0ee07dcf60a4b..00000000000000 --- a/Misc/NEWS.d/next/Security/2026-03-16-18-07-00.gh-issue-146121.vRbdro.rst +++ /dev/null @@ -1,3 +0,0 @@ -:func:`pkgutil.get_data` now raises rejects *resource* arguments containing the -parent directory components or that is an absolute path. -This addresses :cve:`2026-3479`. From f78d896f4ba583ca6bf7c0b1125e00f4791b574a Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 7 Apr 2026 08:43:31 +0200 Subject: [PATCH 2/3] doc: get_data has the same security model as open(); limitations are for alternate loaders --- Doc/library/pkgutil.rst | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Doc/library/pkgutil.rst b/Doc/library/pkgutil.rst index 47d24b6f7d06bb..17a4a36489946b 100644 --- a/Doc/library/pkgutil.rst +++ b/Doc/library/pkgutil.rst @@ -151,24 +151,48 @@ support. :meth:`get_data ` API. The *package* argument should be the name of a package, in standard module format (``foo.bar``). The *resource* argument should be in the form of a relative - filename, using ``/`` as the path separator. The parent directory name - ``..`` is not allowed, and nor is a rooted name (starting with a ``/``). + filename, using ``/`` as the path separator. The function returns a binary string that is the contents of the specified resource. + This function uses the :term:`loader` method + :func:`~importlib.abc.FileLoader.get_data` + to support modules installed in the filesystem, but also in zip files, + databases, or elsewhere. + For packages located in the filesystem, which have already been imported, this is the rough equivalent of:: d = os.path.dirname(sys.modules[package].__file__) data = open(os.path.join(d, resource), 'rb').read() + Like the :func:`open` function, :func:`!get_data` can follow parent + directories (``../``) and absolute paths (starting with ``/`` or ``C:/``, + for example). + It can open compilation/installation artifacts like ``.py`` and ``.pyc`` + files or files with :func:`reserved filenames `. + To be compatible with non-filesystem loaders, avoid using these features. + + .. warning:: + + This function is intended for trusted input. + It does not verify that *resource* "belongs" to *package*. + + If you use a user-provided *resource* path, consider verifying it. + For example, require an alphanumeric filename with a known extension, or + install and check a list of known resources. + If the package cannot be located or loaded, or it uses a :term:`loader` which does not support :meth:`get_data `, then ``None`` is returned. In particular, the :term:`loader` for :term:`namespace packages ` does not support :meth:`get_data `. + .. seealso:: + + The :mod:`importlib.resources` module provides structures access to + module resources. .. function:: resolve_name(name) From 5532786dbc36c572eb8b2593f0691a1018cad80e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 7 Apr 2026 09:55:38 +0200 Subject: [PATCH 3/3] Apply suggestion from @StanFromIreland Co-authored-by: Stan Ulbrych --- Doc/library/pkgutil.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/pkgutil.rst b/Doc/library/pkgutil.rst index 17a4a36489946b..aa7dd71c1329df 100644 --- a/Doc/library/pkgutil.rst +++ b/Doc/library/pkgutil.rst @@ -191,7 +191,7 @@ support. .. seealso:: - The :mod:`importlib.resources` module provides structures access to + The :mod:`importlib.resources` module provides structured access to module resources. .. function:: resolve_name(name)