backport upstream patches

This commit is contained in:
xinsheng3 2024-03-13 17:10:51 +08:00
parent d0bb0111a7
commit c1943ea60d
6 changed files with 736 additions and 1 deletions

View File

@ -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 <zspytz@gmail.com>
---
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

View File

@ -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 <storchaka@gmail.com>
---
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

View File

@ -0,0 +1,122 @@
From 8fc8c45b6717be58ad927def1bf3ea05c83cab8c Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
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 <lukasz@langa.pl>
---
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

View File

@ -0,0 +1,214 @@
From d54e22a669ae6e987199bb5d2c69bb5a46b0083b Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
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 <sorenl@unity3d.com>
---
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

View File

@ -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 <eli@hyro.ai>
Co-authored-by: Yury Selivanov <yury@edgedb.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
(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):
+ # <Key name:E hash:0>: 'E'
+ # NULL:
+ # CollisionNode(size=4 id=0x107a24520):
+ # <Key name:C hash:2147483648>: 'C'
+ # <Key name:D hash:2147483648>: '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

View File

@ -3,7 +3,7 @@ Summary: Interpreter of the Python3 programming language
URL: https://www.python.org/
Version: 3.9.9
Release: 29
Release: 30
License: Python-2.0
%global branchversion 3.9
@ -112,6 +112,11 @@ Patch6018: backport-3.9-gh-99889-Fix-directory-traversal-security-flaw-i.patch
Patch6019: backport-Fix-parsing-errors-in-email-_parseaddr.py.patch
Patch6020: backport-Revert-fixes-for-CVE-2023-27043.patch
Patch6021: backport-CVE-2023-27043.patch
Patch6022: backport-gh-93065-Fix-HAMT-to-iterate-correctly-over-7-level-.patch
Patch6023: backport-3.9-bpo-37013-Fix-the-error-handling-in-socket.if_in.patch
Patch6024: backport-3.9-gh-91133-tempfile.TemporaryDirectory-fix-symlink.patch
Patch6025: backport-3.9-gh-109858-Protect-zipfile-from-quoted-overlap-zi.patch
Patch6026: 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
@ -222,6 +227,11 @@ rm -r Modules/expat
%patch6019 -p1
%patch6020 -p1
%patch6021 -p1
%patch6022 -p1
%patch6023 -p1
%patch6024 -p1
%patch6025 -p1
%patch6026 -p1
%patch9000 -p1
%patch9001 -p1
@ -849,6 +859,17 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP"
%{_mandir}/*/*
%changelog
* Web Mar 23 2024 xinsheng <xinsheng3@huawei.com> - 3.9.9-30
- 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 Jan 10 2024 zhaoyu <zhaoyu64@huawei.com>- 3.9.9-29
- Type:CVE
- CVE:CVE-2023-27043