From 9b6e6a4b8469ead1bbd546d723de49fcaa83e798 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Mon, 16 Mar 2026 18:11:01 +0000 Subject: [PATCH 1/4] `pkgutil.get_data()` reject invalid resource arguments --- Lib/pkgutil.py | 3 +++ Lib/test/test_pkgutil.py | 19 +++++++++++++++++++ ...-03-16-18-07-00.gh-issue-111111.vRbdro.rst | 3 +++ 3 files changed, 25 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-03-16-18-07-00.gh-issue-111111.vRbdro.rst diff --git a/Lib/pkgutil.py b/Lib/pkgutil.py index 8772a66791a3c9..c3109a3a4cd414 100644 --- a/Lib/pkgutil.py +++ b/Lib/pkgutil.py @@ -393,6 +393,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 d4faaaeca00457..50373ead6a071e 100644 --- a/Lib/test/test_pkgutil.py +++ b/Lib/test/test_pkgutil.py @@ -61,6 +61,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, '/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/Library/2026-03-16-18-07-00.gh-issue-111111.vRbdro.rst b/Misc/NEWS.d/next/Library/2026-03-16-18-07-00.gh-issue-111111.vRbdro.rst new file mode 100644 index 00000000000000..c0ee07dcf60a4b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-16-18-07-00.gh-issue-111111.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`. From f3c66b954fc48b822cf5e01ec97661789954fde8 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 18 Mar 2026 16:15:32 +0000 Subject: [PATCH 2/4] Update news entry --- .../2026-03-16-18-07-00.gh-issue-146121.vRbdro.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Library/2026-03-16-18-07-00.gh-issue-111111.vRbdro.rst => Security/2026-03-16-18-07-00.gh-issue-146121.vRbdro.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2026-03-16-18-07-00.gh-issue-111111.vRbdro.rst b/Misc/NEWS.d/next/Security/2026-03-16-18-07-00.gh-issue-146121.vRbdro.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2026-03-16-18-07-00.gh-issue-111111.vRbdro.rst rename to Misc/NEWS.d/next/Security/2026-03-16-18-07-00.gh-issue-146121.vRbdro.rst From 531e7c3c1495f0bd13dd144c97955f9e1fd2c173 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 18 Mar 2026 16:39:22 +0000 Subject: [PATCH 3/4] Fix test on windows, use `os.sep` --- Lib/test/test_pkgutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pkgutil.py b/Lib/test/test_pkgutil.py index 50373ead6a071e..daf9e87746325e 100644 --- a/Lib/test/test_pkgutil.py +++ b/Lib/test/test_pkgutil.py @@ -76,7 +76,7 @@ def test_getdata_path_traversal(self): with self.assertRaises(ValueError): pkgutil.get_data(pkg, 'sub/../../../etc/passwd') with self.assertRaises(ValueError): - pkgutil.get_data(pkg, '/etc/passwd') + pkgutil.get_data(pkg, os.sep + 'etc' + os.sep + 'passwd') del sys.modules[pkg] From 7d4d60116c91f9f00c3856bd169975da4f5cdf8b Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 18 Mar 2026 17:05:33 +0000 Subject: [PATCH 4/4] Use `os.path.abspath` instead --- Lib/test/test_pkgutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pkgutil.py b/Lib/test/test_pkgutil.py index daf9e87746325e..948afb8c18cf67 100644 --- a/Lib/test/test_pkgutil.py +++ b/Lib/test/test_pkgutil.py @@ -76,7 +76,7 @@ def test_getdata_path_traversal(self): with self.assertRaises(ValueError): pkgutil.get_data(pkg, 'sub/../../../etc/passwd') with self.assertRaises(ValueError): - pkgutil.get_data(pkg, os.sep + 'etc' + os.sep + 'passwd') + pkgutil.get_data(pkg, os.path.abspath('/etc/passwd')) del sys.modules[pkg]