Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Lib/pkgutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions Lib/test/test_pkgutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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`.
Loading