diff --git a/Lib/pkgutil.py b/Lib/pkgutil.py index 56731de64af494..90eb24dc65d0d4 100644 --- a/Lib/pkgutil.py +++ b/Lib/pkgutil.py @@ -635,6 +635,9 @@ 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 0cc99e0cc22763..5b660ebc95d88a 100644 --- a/Lib/test/test_pkgutil.py +++ b/Lib/test/test_pkgutil.py @@ -57,6 +57,25 @@ 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 new file mode 100644 index 00000000000000..148a9f9f92945e --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-03-16-18-07-00.gh-issue-146121.vRbdro.rst @@ -0,0 +1,3 @@ +: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 `_.