diff --git a/backport-3.9-bpo-37013-Fix-the-error-handling-in-socket.if_in.patch b/backport-3.9-bpo-37013-Fix-the-error-handling-in-socket.if_in.patch new file mode 100644 index 0000000000000000000000000000000000000000..ed2ef894e3b1eb78ec2f02ab0b8f210da4c61a68 --- /dev/null +++ b/backport-3.9-bpo-37013-Fix-the-error-handling-in-socket.if_in.patch @@ -0,0 +1,89 @@ +From a6f73f61147048187908299ae911c5ad498d813a Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Wed, 17 Jan 2024 14:47:26 +0100 +Subject: [PATCH] [3.9] bpo-37013: Fix the error handling in + socket.if_indextoname() (GH-13503) (GH-112600) + +* Fix a crash when pass UINT_MAX. +* Fix an integer overflow on 64-bit non-Windows platforms. +(cherry picked from commit 0daf555c6fb3feba77989382135a58215e1d70a5) + +Co-authored-by: Zackery Spytz +--- + Lib/test/test_socket.py | 13 +++++++++++++ + ...2023-12-01-16-09-59.gh-issue-81194.FFad1c.rst | 3 +++ + Modules/socketmodule.c | 16 +++++++++++----- + 3 files changed, 27 insertions(+), 5 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2023-12-01-16-09-59.gh-issue-81194.FFad1c.rst + +diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py +index 127d61cb6a..043e554388 100755 +--- a/Lib/test/test_socket.py ++++ b/Lib/test/test_socket.py +@@ -1070,7 +1070,20 @@ def testInterfaceNameIndex(self): + 'socket.if_indextoname() not available.') + def testInvalidInterfaceIndexToName(self): + self.assertRaises(OSError, socket.if_indextoname, 0) ++ self.assertRaises(OverflowError, socket.if_indextoname, -1) ++ self.assertRaises(OverflowError, socket.if_indextoname, 2**1000) + self.assertRaises(TypeError, socket.if_indextoname, '_DEADBEEF') ++ if hasattr(socket, 'if_nameindex'): ++ indices = dict(socket.if_nameindex()) ++ for index in indices: ++ index2 = index + 2**32 ++ if index2 not in indices: ++ with self.assertRaises((OverflowError, OSError)): ++ socket.if_indextoname(index2) ++ for index in 2**32-1, 2**64-1: ++ if index not in indices: ++ with self.assertRaises((OverflowError, OSError)): ++ socket.if_indextoname(index) + + @unittest.skipUnless(hasattr(socket, 'if_nametoindex'), + 'socket.if_nametoindex() not available.') +diff --git a/Misc/NEWS.d/next/Library/2023-12-01-16-09-59.gh-issue-81194.FFad1c.rst b/Misc/NEWS.d/next/Library/2023-12-01-16-09-59.gh-issue-81194.FFad1c.rst +new file mode 100644 +index 0000000000..feb7a8643b +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2023-12-01-16-09-59.gh-issue-81194.FFad1c.rst +@@ -0,0 +1,3 @@ ++Fix a crash in :func:`socket.if_indextoname` with specific value (UINT_MAX). ++Fix an integer overflow in :func:`socket.if_indextoname` on 64-bit ++non-Windows platforms. +diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c +index 133470f9b8..9e0223b127 100644 +--- a/Modules/socketmodule.c ++++ b/Modules/socketmodule.c +@@ -6890,17 +6890,23 @@ Returns the interface index corresponding to the interface name if_name."); + static PyObject * + socket_if_indextoname(PyObject *self, PyObject *arg) + { ++ unsigned long index_long = PyLong_AsUnsignedLong(arg); ++ if (index_long == (unsigned long) -1 && PyErr_Occurred()) { ++ return NULL; ++ } ++ + #ifdef MS_WINDOWS +- NET_IFINDEX index; ++ NET_IFINDEX index = (NET_IFINDEX)index_long; + #else +- unsigned long index; ++ unsigned int index = (unsigned int)index_long; + #endif +- char name[IF_NAMESIZE + 1]; + +- index = PyLong_AsUnsignedLong(arg); +- if (index == (unsigned long) -1) ++ if ((unsigned long)index != index_long) { ++ PyErr_SetString(PyExc_OverflowError, "index is too large"); + return NULL; ++ } + ++ char name[IF_NAMESIZE + 1]; + if (if_indextoname(index, name) == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +-- +2.34.1.windows.1 + diff --git a/backport-3.9-gh-109858-Protect-zipfile-from-quoted-overlap-zi.patch b/backport-3.9-gh-109858-Protect-zipfile-from-quoted-overlap-zi.patch new file mode 100644 index 0000000000000000000000000000000000000000..91fcdb57abd8dca8141fc3667c159a6bd997f9ba --- /dev/null +++ b/backport-3.9-gh-109858-Protect-zipfile-from-quoted-overlap-zi.patch @@ -0,0 +1,146 @@ +From a2c59992e9e8d35baba9695eb186ad6c6ff85c51 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Wed, 17 Jan 2024 14:48:06 +0100 +Subject: [PATCH] [3.9] gh-109858: Protect zipfile from "quoted-overlap" + zipbomb (GH-110016) (GH-113915) + +Raise BadZipFile when try to read an entry that overlaps with other entry or +central directory. +(cherry picked from commit 66363b9a7b9fe7c99eba3a185b74c5fdbf842eba) + +Co-authored-by: Serhiy Storchaka +--- + Lib/test/test_zipfile.py | 60 +++++++++++++++++++ + Lib/zipfile.py | 12 ++++ + ...-09-28-13-15-51.gh-issue-109858.43e2dg.rst | 3 + + 3 files changed, 75 insertions(+) + create mode 100644 Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst + +diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py +index bd383d3f68..17e95eb862 100644 +--- a/Lib/test/test_zipfile.py ++++ b/Lib/test/test_zipfile.py +@@ -2045,6 +2045,66 @@ def test_decompress_without_3rd_party_library(self): + with zipfile.ZipFile(zip_file) as zf: + self.assertRaises(RuntimeError, zf.extract, 'a.txt') + ++ @requires_zlib() ++ def test_full_overlap(self): ++ data = ( ++ b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e' ++ b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00a\xed' ++ b'\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\d\x0b`P' ++ b'K\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2' ++ b'\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00aPK' ++ b'\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e' ++ b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00bPK\x05' ++ b'\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00\x00/\x00\x00' ++ b'\x00\x00\x00' ++ ) ++ with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: ++ self.assertEqual(zipf.namelist(), ['a', 'b']) ++ zi = zipf.getinfo('a') ++ self.assertEqual(zi.header_offset, 0) ++ self.assertEqual(zi.compress_size, 16) ++ self.assertEqual(zi.file_size, 1033) ++ zi = zipf.getinfo('b') ++ self.assertEqual(zi.header_offset, 0) ++ self.assertEqual(zi.compress_size, 16) ++ self.assertEqual(zi.file_size, 1033) ++ self.assertEqual(len(zipf.read('a')), 1033) ++ with self.assertRaisesRegex(zipfile.BadZipFile, 'File name.*differ'): ++ zipf.read('b') ++ ++ @requires_zlib() ++ def test_quoted_overlap(self): ++ data = ( ++ b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05Y\xfc' ++ b'8\x044\x00\x00\x00(\x04\x00\x00\x01\x00\x00\x00a\x00' ++ b'\x1f\x00\xe0\xffPK\x03\x04\x14\x00\x00\x00\x08\x00\xa0l' ++ b'H\x05\xe2\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00' ++ b'\x00\x00b\xed\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\' ++ b'd\x0b`PK\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0' ++ b'lH\x05Y\xfc8\x044\x00\x00\x00(\x04\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00aPK\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0l' ++ b'H\x05\xe2\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x00\x00' ++ b'bPK\x05\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00' ++ b'\x00S\x00\x00\x00\x00\x00' ++ ) ++ with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: ++ self.assertEqual(zipf.namelist(), ['a', 'b']) ++ zi = zipf.getinfo('a') ++ self.assertEqual(zi.header_offset, 0) ++ self.assertEqual(zi.compress_size, 52) ++ self.assertEqual(zi.file_size, 1064) ++ zi = zipf.getinfo('b') ++ self.assertEqual(zi.header_offset, 36) ++ self.assertEqual(zi.compress_size, 16) ++ self.assertEqual(zi.file_size, 1033) ++ with self.assertRaisesRegex(zipfile.BadZipFile, 'Overlapped entries'): ++ zipf.read('a') ++ self.assertEqual(len(zipf.read('b')), 1033) ++ + def tearDown(self): + unlink(TESTFN) + unlink(TESTFN2) +diff --git a/Lib/zipfile.py b/Lib/zipfile.py +index 1e942a503e..95f95ee112 100644 +--- a/Lib/zipfile.py ++++ b/Lib/zipfile.py +@@ -338,6 +338,7 @@ class ZipInfo (object): + 'compress_size', + 'file_size', + '_raw_time', ++ '_end_offset', + ) + + def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): +@@ -379,6 +380,7 @@ def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): + self.external_attr = 0 # External file attributes + self.compress_size = 0 # Size of the compressed file + self.file_size = 0 # Size of the uncompressed file ++ self._end_offset = None # Start of the next local header or central directory + # Other attributes are set by class ZipFile: + # header_offset Byte offset to the file header + # CRC CRC-32 of the uncompressed file +@@ -1399,6 +1401,12 @@ def _RealGetContents(self): + if self.debug > 2: + print("total", total) + ++ end_offset = self.start_dir ++ for zinfo in sorted(self.filelist, ++ key=lambda zinfo: zinfo.header_offset, ++ reverse=True): ++ zinfo._end_offset = end_offset ++ end_offset = zinfo.header_offset + + def namelist(self): + """Return a list of file names in the archive.""" +@@ -1554,6 +1562,10 @@ def open(self, name, mode="r", pwd=None, *, force_zip64=False): + 'File name in directory %r and header %r differ.' + % (zinfo.orig_filename, fname)) + ++ if (zinfo._end_offset is not None and ++ zef_file.tell() + zinfo.compress_size > zinfo._end_offset): ++ raise BadZipFile(f"Overlapped entries: {zinfo.orig_filename!r} (possible zip bomb)") ++ + # check for encrypted flag & handle password + is_encrypted = zinfo.flag_bits & 0x1 + if is_encrypted: +diff --git a/Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst b/Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst +new file mode 100644 +index 0000000000..be279caffc +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst +@@ -0,0 +1,3 @@ ++Protect :mod:`zipfile` from "quoted-overlap" zipbomb. It now raises ++BadZipFile when try to read an entry that overlaps with other entry or ++central directory. +-- +2.34.1.windows.1 + diff --git a/backport-3.9-gh-113659-Skip-hidden-.pth-files-GH-113660-GH-11.patch b/backport-3.9-gh-113659-Skip-hidden-.pth-files-GH-113660-GH-11.patch new file mode 100644 index 0000000000000000000000000000000000000000..475501f2b767588126c6f0cfa2b720d0f5914a59 --- /dev/null +++ b/backport-3.9-gh-113659-Skip-hidden-.pth-files-GH-113660-GH-11.patch @@ -0,0 +1,122 @@ +From 8fc8c45b6717be58ad927def1bf3ea05c83cab8c Mon Sep 17 00:00:00 2001 +From: Serhiy Storchaka +Date: Wed, 17 Jan 2024 16:28:17 +0200 +Subject: [PATCH] [3.9] gh-113659: Skip hidden .pth files (GH-113660) + (GH-114146) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +(cherry picked from commit 74208ed0c440244fb809d8acc97cb9ef51e888e3) + +Co-authored-by: Łukasz Langa +--- + Lib/site.py | 11 +++++- + Lib/test/test_site.py | 39 +++++++++++++++++++ + ...-01-02-19-52-23.gh-issue-113659.DkmnQc.rst | 1 + + 3 files changed, 50 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Security/2024-01-02-19-52-23.gh-issue-113659.DkmnQc.rst + +diff --git a/Lib/site.py b/Lib/site.py +index 9e617afb00..54ffc4fdc0 100644 +--- a/Lib/site.py ++++ b/Lib/site.py +@@ -74,6 +74,7 @@ + import builtins + import _sitebuiltins + import io ++import stat + + # Prefixes for site-packages; add additional prefixes like /usr/local here + PREFIXES = [sys.prefix, sys.exec_prefix] +@@ -156,6 +157,13 @@ def addpackage(sitedir, name, known_paths): + else: + reset = False + fullname = os.path.join(sitedir, name) ++ try: ++ st = os.lstat(fullname) ++ except OSError: ++ return ++ if ((getattr(st, 'st_flags', 0) & stat.UF_HIDDEN) or ++ (getattr(st, 'st_file_attributes', 0) & stat.FILE_ATTRIBUTE_HIDDEN)): ++ return + try: + f = io.TextIOWrapper(io.open_code(fullname)) + except OSError: +@@ -203,7 +211,8 @@ def addsitedir(sitedir, known_paths=None): + names = os.listdir(sitedir) + except OSError: + return +- names = [name for name in names if name.endswith(".pth")] ++ names = [name for name in names ++ if name.endswith(".pth") and not name.startswith(".")] + for name in sorted(names): + addpackage(sitedir, name, known_paths) + if reset: +diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py +index 3d25d7e473..e578cd7db3 100644 +--- a/Lib/test/test_site.py ++++ b/Lib/test/test_site.py +@@ -16,6 +16,7 @@ + import os + import re + import shutil ++import stat + import subprocess + import sys + import sysconfig +@@ -185,6 +186,44 @@ def test_addsitedir(self): + finally: + pth_file.cleanup() + ++ def test_addsitedir_dotfile(self): ++ pth_file = PthFile('.dotfile') ++ pth_file.cleanup(prep=True) ++ try: ++ pth_file.create() ++ site.addsitedir(pth_file.base_dir, set()) ++ self.assertNotIn(site.makepath(pth_file.good_dir_path)[0], sys.path) ++ self.assertIn(pth_file.base_dir, sys.path) ++ finally: ++ pth_file.cleanup() ++ ++ @unittest.skipUnless(hasattr(os, 'chflags'), 'test needs os.chflags()') ++ def test_addsitedir_hidden_flags(self): ++ pth_file = PthFile() ++ pth_file.cleanup(prep=True) ++ try: ++ pth_file.create() ++ st = os.stat(pth_file.file_path) ++ os.chflags(pth_file.file_path, st.st_flags | stat.UF_HIDDEN) ++ site.addsitedir(pth_file.base_dir, set()) ++ self.assertNotIn(site.makepath(pth_file.good_dir_path)[0], sys.path) ++ self.assertIn(pth_file.base_dir, sys.path) ++ finally: ++ pth_file.cleanup() ++ ++ @unittest.skipUnless(sys.platform == 'win32', 'test needs Windows') ++ def test_addsitedir_hidden_file_attribute(self): ++ pth_file = PthFile() ++ pth_file.cleanup(prep=True) ++ try: ++ pth_file.create() ++ subprocess.check_call(['attrib', '+H', pth_file.file_path]) ++ site.addsitedir(pth_file.base_dir, set()) ++ self.assertNotIn(site.makepath(pth_file.good_dir_path)[0], sys.path) ++ self.assertIn(pth_file.base_dir, sys.path) ++ finally: ++ pth_file.cleanup() ++ + # This tests _getuserbase, hence the double underline + # to distinguish from a test for getuserbase + def test__getuserbase(self): +diff --git a/Misc/NEWS.d/next/Security/2024-01-02-19-52-23.gh-issue-113659.DkmnQc.rst b/Misc/NEWS.d/next/Security/2024-01-02-19-52-23.gh-issue-113659.DkmnQc.rst +new file mode 100644 +index 0000000000..744687e723 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2024-01-02-19-52-23.gh-issue-113659.DkmnQc.rst +@@ -0,0 +1 @@ ++Skip ``.pth`` files with names starting with a dot or hidden file attribute. +-- +2.34.1.windows.1 + diff --git a/backport-3.9-gh-91133-tempfile.TemporaryDirectory-fix-symlink.patch b/backport-3.9-gh-91133-tempfile.TemporaryDirectory-fix-symlink.patch new file mode 100644 index 0000000000000000000000000000000000000000..2dcb430173845248918d6ecc0098384b8ca2d0a3 --- /dev/null +++ b/backport-3.9-gh-91133-tempfile.TemporaryDirectory-fix-symlink.patch @@ -0,0 +1,214 @@ +From d54e22a669ae6e987199bb5d2c69bb5a46b0083b Mon Sep 17 00:00:00 2001 +From: Serhiy Storchaka +Date: Wed, 17 Jan 2024 15:47:47 +0200 +Subject: [PATCH] [3.9] gh-91133: tempfile.TemporaryDirectory: fix symlink bug + in cleanup (GH-99930) (GH-112842) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +(cherry picked from commit 81c16cd94ec38d61aa478b9a452436dc3b1b524d) + +Co-authored-by: Søren Løvborg +--- + Lib/tempfile.py | 27 ++-- + Lib/test/test_tempfile.py | 117 +++++++++++++++++- + ...2-12-01-16-57-44.gh-issue-91133.LKMVCV.rst | 2 + + 3 files changed, 136 insertions(+), 10 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst + +diff --git a/Lib/tempfile.py b/Lib/tempfile.py +index eafce6f25b..59a628a174 100644 +--- a/Lib/tempfile.py ++++ b/Lib/tempfile.py +@@ -268,6 +268,22 @@ def _mkstemp_inner(dir, pre, suf, flags, output_type): + raise FileExistsError(_errno.EEXIST, + "No usable temporary file name found") + ++def _dont_follow_symlinks(func, path, *args): ++ # Pass follow_symlinks=False, unless not supported on this platform. ++ if func in _os.supports_follow_symlinks: ++ func(path, *args, follow_symlinks=False) ++ elif _os.name == 'nt' or not _os.path.islink(path): ++ func(path, *args) ++ ++def _resetperms(path): ++ try: ++ chflags = _os.chflags ++ except AttributeError: ++ pass ++ else: ++ _dont_follow_symlinks(chflags, path, 0) ++ _dont_follow_symlinks(_os.chmod, path, 0o700) ++ + + # User visible interfaces. + +@@ -789,17 +805,10 @@ def __init__(self, suffix=None, prefix=None, dir=None): + def _rmtree(cls, name): + def onerror(func, path, exc_info): + if issubclass(exc_info[0], PermissionError): +- def resetperms(path): +- try: +- _os.chflags(path, 0) +- except AttributeError: +- pass +- _os.chmod(path, 0o700) +- + try: + if path != name: +- resetperms(_os.path.dirname(path)) +- resetperms(path) ++ _resetperms(_os.path.dirname(path)) ++ _resetperms(path) + + try: + _os.unlink(path) +diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py +index 8ad1bb98e8..571263d9c9 100644 +--- a/Lib/test/test_tempfile.py ++++ b/Lib/test/test_tempfile.py +@@ -1394,6 +1394,103 @@ def test_cleanup_with_symlink_to_a_directory(self): + "were deleted") + d2.cleanup() + ++ @support.skip_unless_symlink ++ def test_cleanup_with_symlink_modes(self): ++ # cleanup() should not follow symlinks when fixing mode bits (#91133) ++ with self.do_create(recurse=0) as d2: ++ file1 = os.path.join(d2, 'file1') ++ open(file1, 'wb').close() ++ dir1 = os.path.join(d2, 'dir1') ++ os.mkdir(dir1) ++ for mode in range(8): ++ mode <<= 6 ++ with self.subTest(mode=format(mode, '03o')): ++ def test(target, target_is_directory): ++ d1 = self.do_create(recurse=0) ++ symlink = os.path.join(d1.name, 'symlink') ++ os.symlink(target, symlink, ++ target_is_directory=target_is_directory) ++ try: ++ os.chmod(symlink, mode, follow_symlinks=False) ++ except NotImplementedError: ++ pass ++ try: ++ os.chmod(symlink, mode) ++ except FileNotFoundError: ++ pass ++ os.chmod(d1.name, mode) ++ d1.cleanup() ++ self.assertFalse(os.path.exists(d1.name)) ++ ++ with self.subTest('nonexisting file'): ++ test('nonexisting', target_is_directory=False) ++ with self.subTest('nonexisting dir'): ++ test('nonexisting', target_is_directory=True) ++ ++ with self.subTest('existing file'): ++ os.chmod(file1, mode) ++ old_mode = os.stat(file1).st_mode ++ test(file1, target_is_directory=False) ++ new_mode = os.stat(file1).st_mode ++ self.assertEqual(new_mode, old_mode, ++ '%03o != %03o' % (new_mode, old_mode)) ++ ++ with self.subTest('existing dir'): ++ os.chmod(dir1, mode) ++ old_mode = os.stat(dir1).st_mode ++ test(dir1, target_is_directory=True) ++ new_mode = os.stat(dir1).st_mode ++ self.assertEqual(new_mode, old_mode, ++ '%03o != %03o' % (new_mode, old_mode)) ++ ++ @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags') ++ @support.skip_unless_symlink ++ def test_cleanup_with_symlink_flags(self): ++ # cleanup() should not follow symlinks when fixing flags (#91133) ++ flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK ++ self.check_flags(flags) ++ ++ with self.do_create(recurse=0) as d2: ++ file1 = os.path.join(d2, 'file1') ++ open(file1, 'wb').close() ++ dir1 = os.path.join(d2, 'dir1') ++ os.mkdir(dir1) ++ def test(target, target_is_directory): ++ d1 = self.do_create(recurse=0) ++ symlink = os.path.join(d1.name, 'symlink') ++ os.symlink(target, symlink, ++ target_is_directory=target_is_directory) ++ try: ++ os.chflags(symlink, flags, follow_symlinks=False) ++ except NotImplementedError: ++ pass ++ try: ++ os.chflags(symlink, flags) ++ except FileNotFoundError: ++ pass ++ os.chflags(d1.name, flags) ++ d1.cleanup() ++ self.assertFalse(os.path.exists(d1.name)) ++ ++ with self.subTest('nonexisting file'): ++ test('nonexisting', target_is_directory=False) ++ with self.subTest('nonexisting dir'): ++ test('nonexisting', target_is_directory=True) ++ ++ with self.subTest('existing file'): ++ os.chflags(file1, flags) ++ old_flags = os.stat(file1).st_flags ++ test(file1, target_is_directory=False) ++ new_flags = os.stat(file1).st_flags ++ self.assertEqual(new_flags, old_flags) ++ ++ with self.subTest('existing dir'): ++ os.chflags(dir1, flags) ++ old_flags = os.stat(dir1).st_flags ++ test(dir1, target_is_directory=True) ++ new_flags = os.stat(dir1).st_flags ++ self.assertEqual(new_flags, old_flags) ++ + @support.cpython_only + def test_del_on_collection(self): + # A TemporaryDirectory is deleted when garbage collected +@@ -1506,9 +1603,27 @@ def test_modes(self): + d.cleanup() + self.assertFalse(os.path.exists(d.name)) + +- @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.lchflags') ++ def check_flags(self, flags): ++ # skip the test if these flags are not supported (ex: FreeBSD 13) ++ filename = support.TESTFN ++ try: ++ open(filename, "w").close() ++ try: ++ os.chflags(filename, flags) ++ except OSError as exc: ++ # "OSError: [Errno 45] Operation not supported" ++ self.skipTest(f"chflags() doesn't support flags " ++ f"{flags:#b}: {exc}") ++ else: ++ os.chflags(filename, 0) ++ finally: ++ support.unlink(filename) ++ ++ @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags') + def test_flags(self): + flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK ++ self.check_flags(flags) ++ + d = self.do_create(recurse=3, dirs=2, files=2) + with d: + # Change files and directories flags recursively. +diff --git a/Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst b/Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst +new file mode 100644 +index 0000000000..7991048fc4 +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst +@@ -0,0 +1,2 @@ ++Fix a bug in :class:`tempfile.TemporaryDirectory` cleanup, which now no longer ++dereferences symlinks when working around file system permission errors. +-- +2.34.1.windows.1 + diff --git a/backport-gh-93065-Fix-HAMT-to-iterate-correctly-over-7-level-.patch b/backport-gh-93065-Fix-HAMT-to-iterate-correctly-over-7-level-.patch new file mode 100644 index 0000000000000000000000000000000000000000..6dfb71b5fcab6c42d9956c33bdf8a5d38371ae72 --- /dev/null +++ b/backport-gh-93065-Fix-HAMT-to-iterate-correctly-over-7-level-.patch @@ -0,0 +1,143 @@ +From 95c9c2b9cb2d3c1d29c8ce77f154de8bd5313dae Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 24 May 2022 01:52:49 -0700 +Subject: [PATCH] gh-93065: Fix HAMT to iterate correctly over 7-level +deep + trees (GH-93066) (#93147) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Also while there, clarify a few things about why we reduce the hash to +32 bits. + +Co-authored-by: Eli Libman +Co-authored-by: Yury Selivanov +Co-authored-by: Łukasz Langa + +(cherry picked from commit c1f5c903a7e4ed27190488f4e33b00d3c3d952e5) + +--- + Include/internal/pycore_hamt.h | 14 +++++++++++++- + Lib/test/test_context.py | 35 ++++++++++++++++++++++++++++++++++ + Misc/ACKS | 1 + + Python/hamt.c | 14 +++++++++++--- + 4 files changed, 60 insertions(+), 4 deletions(-) + +diff --git a/Include/internal/pycore_hamt.h b/Include/internal/pycore_hamt.h +index aaf6559..357d966 100644 +--- a/Include/internal/pycore_hamt.h ++++ b/Include/internal/pycore_hamt.h +@@ -5,7 +5,19 @@ + # error "this header requires Py_BUILD_CORE define" + #endif + +-#define _Py_HAMT_MAX_TREE_DEPTH 7 ++ ++/* ++HAMT tree is shaped by hashes of keys. Every group of 5 bits of a hash denotes ++the exact position of the key in one level of the tree. Since we're using ++32 bit hashes, we can have at most 7 such levels. Although if there are ++two distinct keys with equal hashes, they will have to occupy the same ++cell in the 7th level of the tree -- so we'd put them in a "collision" node. ++Which brings the total possible tree depth to 8. Read more about the actual ++layout of the HAMT tree in `hamt.c`. ++ ++This constant is used to define a datastucture for storing iteration state. ++*/ ++#define _Py_HAMT_MAX_TREE_DEPTH 8 + + + #define PyHamt_Check(o) Py_IS_TYPE(o, &_PyHamt_Type) +diff --git a/Lib/test/test_context.py b/Lib/test/test_context.py +index 2d8b63a..689e3d4 100644 +--- a/Lib/test/test_context.py ++++ b/Lib/test/test_context.py +@@ -533,6 +533,41 @@ class HamtTest(unittest.TestCase): + self.assertEqual(len(h4), 2) + self.assertEqual(len(h5), 3) + ++ def test_hamt_collision_3(self): ++ # Test that iteration works with the deepest tree possible. ++ # https://github.com/python/cpython/issues/93065 ++ ++ C = HashKey(0b10000000_00000000_00000000_00000000, 'C') ++ D = HashKey(0b10000000_00000000_00000000_00000000, 'D') ++ ++ E = HashKey(0b00000000_00000000_00000000_00000000, 'E') ++ ++ h = hamt() ++ h = h.set(C, 'C') ++ h = h.set(D, 'D') ++ h = h.set(E, 'E') ++ ++ # BitmapNode(size=2 count=1 bitmap=0b1): ++ # NULL: ++ # BitmapNode(size=2 count=1 bitmap=0b1): ++ # NULL: ++ # BitmapNode(size=2 count=1 bitmap=0b1): ++ # NULL: ++ # BitmapNode(size=2 count=1 bitmap=0b1): ++ # NULL: ++ # BitmapNode(size=2 count=1 bitmap=0b1): ++ # NULL: ++ # BitmapNode(size=2 count=1 bitmap=0b1): ++ # NULL: ++ # BitmapNode(size=4 count=2 bitmap=0b101): ++ # : 'E' ++ # NULL: ++ # CollisionNode(size=4 id=0x107a24520): ++ # : 'C' ++ # : 'D' ++ ++ self.assertEqual({k.name for k in h.keys()}, {'C', 'D', 'E'}) ++ + def test_hamt_stress(self): + COLLECTION_SIZE = 7000 + TEST_ITERS_EVERY = 647 +diff --git a/Misc/ACKS b/Misc/ACKS +index ac893ac..8699b98 100644 +--- a/Misc/ACKS ++++ b/Misc/ACKS +@@ -1031,6 +1031,7 @@ Robert Li + Xuanji Li + Zekun Li + Zheao Li ++Eli Libman + Dan Lidral-Porter + Robert van Liere + Ross Light +diff --git a/Python/hamt.c b/Python/hamt.c +index 8801c5e..3296109 100644 +--- a/Python/hamt.c ++++ b/Python/hamt.c +@@ -407,14 +407,22 @@ hamt_hash(PyObject *o) + return -1; + } + +- /* While it's suboptimal to reduce Python's 64 bit hash to ++ /* While it's somewhat suboptimal to reduce Python's 64 bit hash to + 32 bits via XOR, it seems that the resulting hash function + is good enough (this is also how Long type is hashed in Java.) + Storing 10, 100, 1000 Python strings results in a relatively + shallow and uniform tree structure. + +- Please don't change this hashing algorithm, as there are many +- tests that test some exact tree shape to cover all code paths. ++ Also it's worth noting that it would be possible to adapt the tree ++ structure to 64 bit hashes, but that would increase memory pressure ++ and provide little to no performance benefits for collections with ++ fewer than billions of key/value pairs. ++ ++ Important: do not change this hash reducing function. There are many ++ tests that need an exact tree shape to cover all code paths and ++ we do that by specifying concrete values for test data's `__hash__`. ++ If this function is changed most of the regression tests would ++ become useless. + */ + int32_t xored = (int32_t)(hash & 0xffffffffl) ^ (int32_t)(hash >> 32); + return xored == -1 ? -2 : xored; +-- +2.33.0 + diff --git a/python3.spec b/python3.spec index 6da98d2c977d515d8165d7073733cbf1c33823e7..645b5e6415b730f7d2e835c422b4d7de72fbcb55 100644 --- a/python3.spec +++ b/python3.spec @@ -3,7 +3,7 @@ Summary: Interpreter of the Python3 programming language URL: https://www.python.org/ Version: 3.9.9 -Release: 28 +Release: 29 License: Python-2.0 %global branchversion 3.9 @@ -109,6 +109,11 @@ Patch6015: backport-CVE-2007-4559.patch Patch6016: backport-CVE-2023-40217.patch Patch6017: backport-3.9-gh-104049-do-not-expose-on-disk-location-from-Si.patch Patch6018: backport-3.9-gh-99889-Fix-directory-traversal-security-flaw-i.patch +Patch6019: backport-gh-93065-Fix-HAMT-to-iterate-correctly-over-7-level-.patch +Patch6020: backport-3.9-bpo-37013-Fix-the-error-handling-in-socket.if_in.patch +Patch6021: backport-3.9-gh-91133-tempfile.TemporaryDirectory-fix-symlink.patch +Patch6022: backport-3.9-gh-109858-Protect-zipfile-from-quoted-overlap-zi.patch +Patch6023: backport-3.9-gh-113659-Skip-hidden-.pth-files-GH-113660-GH-11.patch Patch9000: add-the-sm3-method-for-obtaining-the-salt-value.patch Patch9001: python3-Add-sw64-architecture.patch @@ -216,6 +221,11 @@ rm -r Modules/expat %patch6016 -p1 %patch6017 -p1 %patch6018 -p1 +%patch6019 -p1 +%patch6020 -p1 +%patch6021 -p1 +%patch6022 -p1 +%patch6023 -p1 %patch9000 -p1 %patch9001 -p1 @@ -843,6 +853,17 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %{_mandir}/*/* %changelog +* Web Mar 23 2024 xinsheng - 3.9.9-29 +- Type:bugfix +- CVE:NA +- SUG:NA +- DESC:backport upstream patches + - Fix HAMT to iterate correctly over 7 level + - Fix the error handling in socket.if_in + - tempfile.TemporaryDirectory fix symlink + - Protect zipfile from quoted overlap zi + - Skip hidden .pth files GH 113660 GH 11 + * Wed Oct 25 zhuofeng - 3.9.9-28 - Type:bugfix - CVE:NA