diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 7d5c6e224789be..5689ecbffc4b30 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -221,6 +221,11 @@ The following standard library modules have been updated to accept :func:`eval` and :func:`exec` accept :class:`!frozendict` for *globals*, and :func:`type` and :meth:`str.maketrans` accept :class:`!frozendict` for *dict*. +Code checking for :class:`dict` type using ``isinstance(arg, dict)`` can be +updated to ``isinstance(arg, (dict, frozendict))`` to accept also the +:class:`!frozendict` type, or to ``isinstance(arg, collections.abc.Mapping)`` +to accept also other mapping types such as :class:`~types.MappingProxyType`. + .. seealso:: :pep:`814` for the full specification and rationale. (Contributed by Victor Stinner and Donghee Na in :gh:`141510`.) 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/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 8ff9c99435bb1a..6507a7b5b0f695 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -665,12 +665,10 @@ def get_platform(): For other non-POSIX platforms, currently just returns :data:`sys.platform`.""" if os.name == 'nt': - if 'amd64' in sys.version.lower(): - return 'win-amd64' - if '(arm)' in sys.version.lower(): - return 'win-arm32' - if '(arm64)' in sys.version.lower(): - return 'win-arm64' + import _sysconfig + platform = _sysconfig.get_platform() + if platform: + return platform return sys.platform if os.name != "posix" or not hasattr(os, 'uname'): diff --git a/Lib/test/test_pkgutil.py b/Lib/test/test_pkgutil.py index d4faaaeca00457..948afb8c18cf67 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, 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/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 502103ce629358..e43f91eb9238f9 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -7,6 +7,7 @@ import shutil import json import textwrap +from unittest.mock import patch from copy import copy from test import support @@ -247,19 +248,15 @@ def test_get_platform(self): self.assertIsInstance(actual_platform, str) self.assertTrue(actual_platform) - # windows XP, 32bits + # Windows os.name = 'nt' - sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) ' - '[MSC v.1310 32 bit (Intel)]') - sys.platform = 'win32' - self.assertEqual(get_platform(), 'win32') - - # windows XP, amd64 - os.name = 'nt' - sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) ' - '[MSC v.1310 32 bit (Amd64)]') - sys.platform = 'win32' - self.assertEqual(get_platform(), 'win-amd64') + with patch('_sysconfig.get_platform', create=True, return_value='win32'): + self.assertEqual(get_platform(), 'win32') + with patch('_sysconfig.get_platform', create=True, return_value='win-amd64'): + self.assertEqual(get_platform(), 'win-amd64') + sys.platform = 'test-plaform' + with patch('_sysconfig.get_platform', create=True, return_value=None): + self.assertEqual(get_platform(), 'test-plaform') # macbook os.name = 'posix' diff --git a/Misc/NEWS.d/next/Library/2026-03-18-23-54-36.gh-issue-145410.NvLWj5.rst b/Misc/NEWS.d/next/Library/2026-03-18-23-54-36.gh-issue-145410.NvLWj5.rst new file mode 100644 index 00000000000000..8d84b70097d3e2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-18-23-54-36.gh-issue-145410.NvLWj5.rst @@ -0,0 +1,3 @@ +On Windows, :func:`sysconfig.get_platform` now gets the platform from the +``_sysconfig`` module instead of parsing :data:`sys.version` string. Patch +by Victor Stinner. 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..c0ee07dcf60a4b --- /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`. diff --git a/Modules/_sysconfig.c b/Modules/_sysconfig.c index 345498e670dd6a..bcb9d108174f43 100644 --- a/Modules/_sysconfig.c +++ b/Modules/_sysconfig.c @@ -81,11 +81,48 @@ _sysconfig_config_vars_impl(PyObject *module) return config; } +#ifdef MS_WINDOWS +/*[clinic input] +_sysconfig.get_platform + +Return a string that identifies the current platform. +[clinic start generated code]*/ + +static PyObject * +_sysconfig_get_platform_impl(PyObject *module) +/*[clinic end generated code: output=4ecbbe2b77633f3e input=c0b43abda44f9a01]*/ +{ +#ifdef MS_WIN64 +# if defined(_M_X64) || defined(_M_AMD64) +# define SYSCONFIG_PLATFORM "win-amd64" +# elif defined(_M_ARM64) +# define SYSCONFIG_PLATFORM "win-arm64" +# endif +#endif + +#if defined(MS_WIN32) && !defined(MS_WIN64) +# if defined(_M_IX86) +# define SYSCONFIG_PLATFORM "win32" +# elif defined(_M_ARM) +# define SYSCONFIG_PLATFORM "win-arm32" +# endif +#endif + +#ifdef SYSCONFIG_PLATFORM + return PyUnicode_FromString(SYSCONFIG_PLATFORM); +#else + Py_RETURN_NONE; +#endif +} +#endif // MS_WINDOWS + + PyDoc_STRVAR(sysconfig__doc__, "A helper for the sysconfig module."); static struct PyMethodDef sysconfig_methods[] = { _SYSCONFIG_CONFIG_VARS_METHODDEF + _SYSCONFIG_GET_PLATFORM_METHODDEF {NULL, NULL} }; diff --git a/Modules/clinic/_sysconfig.c.h b/Modules/clinic/_sysconfig.c.h index eb3d396298bb21..031a738cf47c7b 100644 --- a/Modules/clinic/_sysconfig.c.h +++ b/Modules/clinic/_sysconfig.c.h @@ -19,4 +19,30 @@ _sysconfig_config_vars(PyObject *module, PyObject *Py_UNUSED(ignored)) { return _sysconfig_config_vars_impl(module); } -/*[clinic end generated code: output=25d395cf02eced1f input=a9049054013a1b77]*/ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_sysconfig_get_platform__doc__, +"get_platform($module, /)\n" +"--\n" +"\n" +"Return a string that identifies the current platform."); + +#define _SYSCONFIG_GET_PLATFORM_METHODDEF \ + {"get_platform", (PyCFunction)_sysconfig_get_platform, METH_NOARGS, _sysconfig_get_platform__doc__}, + +static PyObject * +_sysconfig_get_platform_impl(PyObject *module); + +static PyObject * +_sysconfig_get_platform(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _sysconfig_get_platform_impl(module); +} + +#endif /* defined(MS_WINDOWS) */ + +#ifndef _SYSCONFIG_GET_PLATFORM_METHODDEF + #define _SYSCONFIG_GET_PLATFORM_METHODDEF +#endif /* !defined(_SYSCONFIG_GET_PLATFORM_METHODDEF) */ +/*[clinic end generated code: output=558531e148f92320 input=a9049054013a1b77]*/