diff --git a/backport-CVE-2025-12084-3.14-gh-142145-Remove-quadratic-behavior-in-node-ID-.patch b/backport-CVE-2025-12084-3.14-gh-142145-Remove-quadratic-behavior-in-node-ID-.patch new file mode 100644 index 0000000000000000000000000000000000000000..0b288cc16bd381b9e6640a237abb166c3262725e --- /dev/null +++ b/backport-CVE-2025-12084-3.14-gh-142145-Remove-quadratic-behavior-in-node-ID-.patch @@ -0,0 +1,100 @@ +From 027f21e417b26eed4505ac2db101a4352b7c51a0 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Wed, 3 Dec 2025 20:09:44 +0100 +Subject: [PATCH] [3.14] gh-142145: Remove quadratic behavior in node ID cache + clearing (GH-142146) (#142209) + +gh-142145: Remove quadratic behavior in node ID cache clearing (GH-142146) + +* Remove quadratic behavior in node ID cache clearing + + + +* Add news fragment + +--------- +(cherry picked from commit 08d8e18ad81cd45bc4a27d6da478b51ea49486e4) + +Co-authored-by: Seth Michael Larson +Co-authored-by: Jacob Walls <38668450+jacobtylerwalls@users.noreply.github.com> +--- + Lib/test/test_minidom.py | 18 ++++++++++++++++++ + Lib/xml/dom/minidom.py | 9 +-------- + ...5-12-01-09-36-45.gh-issue-142145.tcAUhg.rst | 1 + + 3 files changed, 20 insertions(+), 8 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst + +diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py +index 4f25e9c2a03..4fa5a4e6768 100644 +--- a/Lib/test/test_minidom.py ++++ b/Lib/test/test_minidom.py +@@ -2,6 +2,7 @@ + + import copy + import pickle ++import time + from test import support + import unittest + +@@ -173,6 +174,23 @@ def testAppendChild(self): + self.confirm(dom.documentElement.childNodes[-1].data == "Hello") + dom.unlink() + ++ def testAppendChildNoQuadraticComplexity(self): ++ impl = getDOMImplementation() ++ ++ newdoc = impl.createDocument(None, "some_tag", None) ++ top_element = newdoc.documentElement ++ children = [newdoc.createElement(f"child-{i}") for i in range(1, 2 ** 15 + 1)] ++ element = top_element ++ ++ start = time.time() ++ for child in children: ++ element.appendChild(child) ++ element = child ++ end = time.time() ++ ++ # This example used to take at least 30 seconds. ++ self.assertLess(end - start, 1) ++ + def testAppendChildFragment(self): + dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes() + dom.documentElement.appendChild(frag) +diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py +index db51f350ea0..0a2ccc00f18 100644 +--- a/Lib/xml/dom/minidom.py ++++ b/Lib/xml/dom/minidom.py +@@ -292,13 +292,6 @@ def _append_child(self, node): + childNodes.append(node) + node.parentNode = self + +-def _in_document(node): +- # return True iff node is part of a document tree +- while node is not None: +- if node.nodeType == Node.DOCUMENT_NODE: +- return True +- node = node.parentNode +- return False + + def _write_data(writer, data): + "Writes datachars to writer." +@@ -1555,7 +1548,7 @@ def _clear_id_cache(node): + if node.nodeType == Node.DOCUMENT_NODE: + node._id_cache.clear() + node._id_search_stack = None +- elif _in_document(node): ++ elif node.ownerDocument: + node.ownerDocument._id_cache.clear() + node.ownerDocument._id_search_stack= None + +diff --git a/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst b/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst +new file mode 100644 +index 00000000000..440bc7794c6 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst +@@ -0,0 +1 @@ ++Remove quadratic behavior in ``xml.minidom`` node ID cache clearing. +-- +2.33.0 + diff --git a/backport-CVE-2025-13836-3.13-gh-119451-Fix-a-potential-denial-of-service-in-.patch b/backport-CVE-2025-13836-3.13-gh-119451-Fix-a-potential-denial-of-service-in-.patch new file mode 100644 index 0000000000000000000000000000000000000000..13a86d12837a2a4baa3ca96e341ea0cfd689caee --- /dev/null +++ b/backport-CVE-2025-13836-3.13-gh-119451-Fix-a-potential-denial-of-service-in-.patch @@ -0,0 +1,167 @@ +From 289f29b0fe38baf2d7cb5854f4bb573cc34a6a15 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Fri, 5 Dec 2025 16:21:57 +0100 +Subject: [PATCH] [3.13] gh-119451: Fix a potential denial of service in + http.client (GH-119454) (#142139) + +gh-119451: Fix a potential denial of service in http.client (GH-119454) + +Reading the whole body of the HTTP response could cause OOM if +the Content-Length value is too large even if the server does not send +a large amount of data. Now the HTTP client reads large data by chunks, +therefore the amount of consumed memory is proportional to the amount +of sent data. +(cherry picked from commit 5a4c4a033a4a54481be6870aa1896fad732555b5) + +Co-authored-by: Serhiy Storchaka +--- + Lib/http/client.py | 28 ++++++-- + Lib/test/test_httplib.py | 66 +++++++++++++++++++ + ...-05-23-11-47-48.gh-issue-119451.qkJe9-.rst | 5 ++ + 3 files changed, 95 insertions(+), 4 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst + +diff --git a/Lib/http/client.py b/Lib/http/client.py +index 8a728438439..dd5f4136e9e 100644 +--- a/Lib/http/client.py ++++ b/Lib/http/client.py +@@ -111,6 +111,11 @@ + _MAXLINE = 65536 + _MAXHEADERS = 100 + ++# Data larger than this will be read in chunks, to prevent extreme ++# overallocation. ++_MIN_READ_BUF_SIZE = 1 << 20 ++ ++ + # Header name/value ABNF (http://tools.ietf.org/html/rfc7230#section-3.2) + # + # VCHAR = %x21-7E +@@ -639,10 +644,25 @@ def _safe_read(self, amt): + reading. If the bytes are truly not available (due to EOF), then the + IncompleteRead exception can be used to detect the problem. + """ +- data = self.fp.read(amt) +- if len(data) < amt: +- raise IncompleteRead(data, amt-len(data)) +- return data ++ cursize = min(amt, _MIN_READ_BUF_SIZE) ++ data = self.fp.read(cursize) ++ if len(data) >= amt: ++ return data ++ if len(data) < cursize: ++ raise IncompleteRead(data, amt - len(data)) ++ ++ data = io.BytesIO(data) ++ data.seek(0, 2) ++ while True: ++ # This is a geometric increase in read size (never more than ++ # doubling out the current length of data per loop iteration). ++ delta = min(cursize, amt - cursize) ++ data.write(self.fp.read(delta)) ++ if data.tell() >= amt: ++ return data.getvalue() ++ cursize += delta ++ if data.tell() < cursize: ++ raise IncompleteRead(data.getvalue(), amt - data.tell()) + + def _safe_readinto(self, b): + """Same as _safe_read, but for reading into a buffer.""" +diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py +index 84e99a15603..5267d2fe011 100644 +--- a/Lib/test/test_httplib.py ++++ b/Lib/test/test_httplib.py +@@ -1455,6 +1455,78 @@ def run_server(): + thread.join() + self.assertEqual(result, b"proxied data\n") + ++ def test_large_content_length(self): ++ serv = socket.socket( ++ socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) ++ self.addCleanup(serv.close) ++ serv.bind((HOST, 0)) ++ serv.listen() ++ ++ def run_server(): ++ [conn, address] = serv.accept() ++ with conn: ++ while conn.recv(1024): ++ conn.sendall( ++ b"HTTP/1.1 200 Ok\r\n" ++ b"Content-Length: %d\r\n" ++ b"\r\n" % size) ++ conn.sendall(b'A' * (size//3)) ++ conn.sendall(b'B' * (size - size//3)) ++ ++ thread = threading.Thread(target=run_server) ++ thread.start() ++ self.addCleanup(thread.join, 1.0) ++ ++ conn = client.HTTPConnection(*serv.getsockname()) ++ try: ++ for w in range(15, 27): ++ size = 1 << w ++ conn.request("GET", "/") ++ with conn.getresponse() as response: ++ self.assertEqual(len(response.read()), size) ++ finally: ++ conn.close() ++ thread.join(1.0) ++ ++ def test_large_content_length_truncated(self): ++ serv = socket.socket( ++ socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) ++ self.addCleanup(serv.close) ++ serv.bind((HOST, 0)) ++ serv.listen() ++ ++ def run_server(): ++ while True: ++ [conn, address] = serv.accept() ++ with conn: ++ conn.recv(1024) ++ if not size: ++ break ++ conn.sendall( ++ b"HTTP/1.1 200 Ok\r\n" ++ b"Content-Length: %d\r\n" ++ b"\r\n" ++ b"Text" % size) ++ ++ thread = threading.Thread(target=run_server) ++ thread.start() ++ self.addCleanup(thread.join, 1.0) ++ ++ conn = client.HTTPConnection(*serv.getsockname()) ++ try: ++ for w in range(18, 65): ++ size = 1 << w ++ conn.request("GET", "/") ++ with conn.getresponse() as response: ++ self.assertRaises(client.IncompleteRead, response.read) ++ conn.close() ++ finally: ++ conn.close() ++ size = 0 ++ conn.request("GET", "/") ++ conn.close() ++ thread.join(1.0) ++ + def test_putrequest_override_domain_validation(self): + """ + It should be possible to override the default validation +diff --git a/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst b/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst +new file mode 100644 +index 00000000000..6d6f25cd2f8 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst +@@ -0,0 +1,5 @@ ++Fix a potential memory denial of service in the :mod:`http.client` module. ++When connecting to a malicious server, it could cause ++an arbitrary amount of memory to be allocated. ++This could have led to symptoms including a :exc:`MemoryError`, swapping, out ++of memory (OOM) killed processes or containers, or even system crashes. +-- +2.33.0 + diff --git a/backport-CVE-2025-13837-gh-119342-Fix-a-potential-denial-of-service-in-plist.patch b/backport-CVE-2025-13837-gh-119342-Fix-a-potential-denial-of-service-in-plist.patch new file mode 100644 index 0000000000000000000000000000000000000000..d78b69a76a190c24041e06e97506e65a0a665ef7 --- /dev/null +++ b/backport-CVE-2025-13837-gh-119342-Fix-a-potential-denial-of-service-in-plist.patch @@ -0,0 +1,159 @@ +From 694922cf40aa3a28f898b5f5ee08b71b4922df70 Mon Sep 17 00:00:00 2001 +From: Serhiy Storchaka +Date: Mon, 1 Dec 2025 17:28:15 +0200 +Subject: [PATCH] gh-119342: Fix a potential denial of service in plistlib + (GH-119343) + +Reading a specially prepared small Plist file could cause OOM because file's +read(n) preallocates a bytes object for reading the specified amount of +data. Now plistlib reads large data by chunks, therefore the upper limit of +consumed memory is proportional to the size of the input file. +--- + Lib/plistlib.py | 31 ++++++++++------ + Lib/test/test_plistlib.py | 37 +++++++++++++++++-- + ...-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst | 5 +++ + 3 files changed, 59 insertions(+), 14 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2024-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst + +diff --git a/Lib/plistlib.py b/Lib/plistlib.py +index 67e832db217..655c51eea3d 100644 +--- a/Lib/plistlib.py ++++ b/Lib/plistlib.py +@@ -73,6 +73,9 @@ + PlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__) + globals().update(PlistFormat.__members__) + ++# Data larger than this will be read in chunks, to prevent extreme ++# overallocation. ++_MIN_READ_BUF_SIZE = 1 << 20 + + # + # +@@ -508,12 +511,24 @@ def _get_size(self, tokenL): + + return tokenL + ++ def _read(self, size): ++ cursize = min(size, _MIN_READ_BUF_SIZE) ++ data = self._fp.read(cursize) ++ while True: ++ if len(data) != cursize: ++ raise InvalidFileException ++ if cursize == size: ++ return data ++ delta = min(cursize, size - cursize) ++ data += self._fp.read(delta) ++ cursize += delta ++ + def _read_ints(self, n, size): +- data = self._fp.read(size * n) ++ data = self._read(size * n) + if size in _BINARY_FORMAT: + return struct.unpack(f'>{n}{_BINARY_FORMAT[size]}', data) + else: +- if not size or len(data) != size * n: ++ if not size: + raise InvalidFileException() + return tuple(int.from_bytes(data[i: i + size], 'big') + for i in range(0, size * n, size)) +@@ -573,21 +588,16 @@ def _read_object(self, ref): + + elif tokenH == 0x40: # data + s = self._get_size(tokenL) +- if len(result) != s: +- raise InvalidFileException() ++ result = self._read(s) + + elif tokenH == 0x50: # ascii string + s = self._get_size(tokenL) +- data = self._fp.read(s) +- if len(data) != s: +- raise InvalidFileException() ++ data = self._read(s) + result = data.decode('ascii') + + elif tokenH == 0x60: # unicode string + s = self._get_size(tokenL) * 2 +- data = self._fp.read(s) +- if len(data) != s: +- raise InvalidFileException() ++ data = self._read(s) + result = data.decode('utf-16be') + + # tokenH == 0x80 is documented as 'UID' and appears to be used for +diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py +index a0c76e5dec5..de2a2fd1fc3 100644 +--- a/Lib/test/test_plistlib.py ++++ b/Lib/test/test_plistlib.py +@@ -903,8 +903,7 @@ def test_xml_encodings(self): + + class TestBinaryPlistlib(unittest.TestCase): + +- @staticmethod +- def decode(*objects, offset_size=1, ref_size=1): ++ def build(self, *objects, offset_size=1, ref_size=1): + data = [b'bplist00'] + offset = 8 + offsets = [] +@@ -916,7 +915,11 @@ def decode(*objects, offset_size=1, ref_size=1): + len(objects), 0, offset) + data.extend(offsets) + data.append(tail) +- return plistlib.loads(b''.join(data), fmt=plistlib.FMT_BINARY) ++ return b''.join(data) ++ ++ def decode(self, *objects, offset_size=1, ref_size=1): ++ data = self.build(*objects, offset_size=offset_size, ref_size=ref_size) ++ return plistlib.loads(data, fmt=plistlib.FMT_BINARY) + + def test_nonstandard_refs_size(self): + # Issue #21538: Refs and offsets are 24-bit integers +@@ -1024,6 +1027,34 @@ def test_invalid_binary(self): + with self.assertRaises(plistlib.InvalidFileException): + plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY) + ++ def test_truncated_large_data(self): ++ self.addCleanup(support.unlink, support.TESTFN) ++ def check(data): ++ with open(support.TESTFN, 'wb') as f: ++ f.write(data) ++ # buffered file ++ with open(support.TESTFN, 'rb') as f: ++ with self.assertRaises(plistlib.InvalidFileException): ++ plistlib.load(f, fmt=plistlib.FMT_BINARY) ++ # unbuffered file ++ with open(support.TESTFN, 'rb', buffering=0) as f: ++ with self.assertRaises(plistlib.InvalidFileException): ++ plistlib.load(f, fmt=plistlib.FMT_BINARY) ++ for w in range(20, 64): ++ s = 1 << w ++ # data ++ check(self.build(b'\x4f\x13' + s.to_bytes(8, 'big'))) ++ # ascii string ++ check(self.build(b'\x5f\x13' + s.to_bytes(8, 'big'))) ++ # unicode string ++ check(self.build(b'\x6f\x13' + s.to_bytes(8, 'big'))) ++ # array ++ check(self.build(b'\xaf\x13' + s.to_bytes(8, 'big'))) ++ # dict ++ check(self.build(b'\xdf\x13' + s.to_bytes(8, 'big'))) ++ # number of objects ++ check(b'bplist00' + struct.pack('>6xBBQQQ', 1, 1, s, 0, 8)) ++ + + class TestPlistlibDeprecated(unittest.TestCase): + def test_io_deprecated(self): +diff --git a/Misc/NEWS.d/next/Security/2024-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst b/Misc/NEWS.d/next/Security/2024-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst +new file mode 100644 +index 00000000000..04fd8faca4c +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2024-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst +@@ -0,0 +1,5 @@ ++Fix a potential memory denial of service in the :mod:`plistlib` module. ++When reading a Plist file received from untrusted source, it could cause ++an arbitrary amount of memory to be allocated. ++This could have led to symptoms including a :exc:`MemoryError`, swapping, out ++of memory (OOM) killed processes or containers, or even system crashes. +-- +2.33.0 + diff --git a/backport-pre-CVE-2025-13836-bpo-36050-optimize-HTTPResponse.read-GH-12698.patch b/backport-pre-CVE-2025-13836-bpo-36050-optimize-HTTPResponse.read-GH-12698.patch new file mode 100644 index 0000000000000000000000000000000000000000..acd54ca624c6c68b1f9a96b589d4b402ab264904 --- /dev/null +++ b/backport-pre-CVE-2025-13836-bpo-36050-optimize-HTTPResponse.read-GH-12698.patch @@ -0,0 +1,92 @@ +From d6bf6f2d0c83f0c64ce86e7b9340278627798090 Mon Sep 17 00:00:00 2001 +From: Inada Naoki +Date: Sat, 6 Apr 2019 18:06:19 +0900 +Subject: [PATCH] bpo-36050: optimize HTTPResponse.read() (GH-12698) + +* No need to chunking for now. +* No need to partial read caused by EINTR for now. +--- + Lib/http/client.py | 42 +++++-------------- + .../2019-04-05-21-29-53.bpo-36050.x9DRKE.rst | 2 + + 2 files changed, 12 insertions(+), 32 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2019-04-05-21-29-53.bpo-36050.x9DRKE.rst + +diff --git a/Lib/http/client.py b/Lib/http/client.py +index 1de151c38e9..5a2225276b1 100644 +--- a/Lib/http/client.py ++++ b/Lib/http/client.py +@@ -105,9 +105,6 @@ + # Mapping status codes to official W3C names + responses = {v: v.phrase for v in http.HTTPStatus.__members__.values()} + +-# maximal amount of data to read at one time in _safe_read +-MAXAMOUNT = 1048576 +- + # maximal line length when calling readline(). + _MAXLINE = 65536 + _MAXHEADERS = 100 +@@ -592,43 +589,24 @@ def _readinto_chunked(self, b): + raise IncompleteRead(bytes(b[0:total_bytes])) + + def _safe_read(self, amt): +- """Read the number of bytes requested, compensating for partial reads. +- +- Normally, we have a blocking socket, but a read() can be interrupted +- by a signal (resulting in a partial read). +- +- Note that we cannot distinguish between EOF and an interrupt when zero +- bytes have been read. IncompleteRead() will be raised in this +- situation. ++ """Read the number of bytes requested. + + This function should be used when bytes "should" be present for + reading. If the bytes are truly not available (due to EOF), then the + IncompleteRead exception can be used to detect the problem. + """ +- s = [] +- while amt > 0: +- chunk = self.fp.read(min(amt, MAXAMOUNT)) +- if not chunk: +- raise IncompleteRead(b''.join(s), amt) +- s.append(chunk) +- amt -= len(chunk) +- return b"".join(s) ++ data = self.fp.read(amt) ++ if len(data) < amt: ++ raise IncompleteRead(data, amt-len(data)) ++ return data + + def _safe_readinto(self, b): + """Same as _safe_read, but for reading into a buffer.""" +- total_bytes = 0 +- mvb = memoryview(b) +- while total_bytes < len(b): +- if MAXAMOUNT < len(mvb): +- temp_mvb = mvb[0:MAXAMOUNT] +- n = self.fp.readinto(temp_mvb) +- else: +- n = self.fp.readinto(mvb) +- if not n: +- raise IncompleteRead(bytes(mvb[0:total_bytes]), len(b)) +- mvb = mvb[n:] +- total_bytes += n +- return total_bytes ++ amt = len(b) ++ n = self.fp.readinto(b) ++ if n < amt: ++ raise IncompleteRead(bytes(b[:n]), amt-n) ++ return n + + def read1(self, n=-1): + """Read with at most one underlying system call. If at least one +diff --git a/Misc/NEWS.d/next/Library/2019-04-05-21-29-53.bpo-36050.x9DRKE.rst b/Misc/NEWS.d/next/Library/2019-04-05-21-29-53.bpo-36050.x9DRKE.rst +new file mode 100644 +index 00000000000..92318f877b6 +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2019-04-05-21-29-53.bpo-36050.x9DRKE.rst +@@ -0,0 +1,2 @@ ++Optimized ``http.client.HTTPResponse.read()`` for large response. Patch by ++Inada Naoki. +-- +2.33.0 + diff --git a/backport-pre-CVE-2025-13837-bpo-42103-Improve-validation-of-Plist-files.-GH-2288.patch b/backport-pre-CVE-2025-13837-bpo-42103-Improve-validation-of-Plist-files.-GH-2288.patch new file mode 100644 index 0000000000000000000000000000000000000000..5543029ae3e80a9214905e1517dae01217bcad88 --- /dev/null +++ b/backport-pre-CVE-2025-13837-bpo-42103-Improve-validation-of-Plist-files.-GH-2288.patch @@ -0,0 +1,543 @@ +From 34637a0ce21e7261b952fbd9d006474cc29b681f Mon Sep 17 00:00:00 2001 +From: Serhiy Storchaka +Date: Mon, 2 Nov 2020 23:01:40 +0200 +Subject: [PATCH] bpo-42103: Improve validation of Plist files. (GH-22882) + +* Prevent some possible DoS attacks via providing invalid Plist files + with extremely large number of objects or collection sizes. +* Raise InvalidFileException for too large bytes and string size instead of returning garbage. +* Raise InvalidFileException instead of ValueError for specific invalid datetime (NaN). +* Raise InvalidFileException instead of TypeError for non-hashable dict keys. +* Add more tests for invalid Plist files. +--- + Lib/plistlib.py | 26 +- + Lib/test/test_plistlib.py | 389 +++++++++++++++--- + .../2020-10-23-19-20-14.bpo-42103.C5obK2.rst | 3 + + .../2020-10-23-19-19-30.bpo-42103.cILT66.rst | 2 + + 4 files changed, 363 insertions(+), 57 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2020-10-23-19-20-14.bpo-42103.C5obK2.rst + create mode 100644 Misc/NEWS.d/next/Security/2020-10-23-19-19-30.bpo-42103.cILT66.rst + +diff --git a/Lib/plistlib.py b/Lib/plistlib.py +index a7403510a32..83b214e9dc4 100644 +--- a/Lib/plistlib.py ++++ b/Lib/plistlib.py +@@ -477,7 +477,7 @@ def parse(self, fp): + return self._read_object(top_object) + + except (OSError, IndexError, struct.error, OverflowError, +- UnicodeDecodeError): ++ ValueError): + raise InvalidFileException() + + def _get_size(self, tokenL): +@@ -493,7 +493,7 @@ def _get_size(self, tokenL): + def _read_ints(self, n, size): + data = self._fp.read(size * n) + if size in _BINARY_FORMAT: +- return struct.unpack('>' + _BINARY_FORMAT[size] * n, data) ++ return struct.unpack(f'>{n}{_BINARY_FORMAT[size]}', data) + else: + if not size or len(data) != size * n: + raise InvalidFileException() +@@ -553,19 +553,22 @@ def _read_object(self, ref): + + elif tokenH == 0x40: # data + s = self._get_size(tokenL) +- if self._use_builtin_types: +- result = self._fp.read(s) +- else: +- result = Data(self._fp.read(s)) ++ if len(result) != s: ++ raise InvalidFileException() + + elif tokenH == 0x50: # ascii string + s = self._get_size(tokenL) +- result = self._fp.read(s).decode('ascii') +- result = result ++ data = self._fp.read(s) ++ if len(data) != s: ++ raise InvalidFileException() ++ result = data.decode('ascii') + + elif tokenH == 0x60: # unicode string +- s = self._get_size(tokenL) +- result = self._fp.read(s * 2).decode('utf-16be') ++ s = self._get_size(tokenL) * 2 ++ data = self._fp.read(s) ++ if len(data) != s: ++ raise InvalidFileException() ++ result = data.decode('utf-16be') + + # tokenH == 0x80 is documented as 'UID' and appears to be used for + # keyed-archiving, not in plists. +@@ -585,9 +593,11 @@ def _read_object(self, ref): + obj_refs = self._read_refs(s) + result = self._dict_type() + self._objects[ref] = result +- for k, o in zip(key_refs, obj_refs): +- result[self._read_object(k)] = self._read_object(o) +- ++ try: ++ for k, o in zip(key_refs, obj_refs): ++ result[self._read_object(k)] = self._read_object(o) ++ except TypeError: ++ raise InvalidFileException() + else: + raise InvalidFileException() + +diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py +index c9dce0047b7..ef96c6ceda2 100644 +--- a/Lib/test/test_plistlib.py ++++ b/Lib/test/test_plistlib.py +@@ -2,5 +2,6 @@ + # Copyright (C) 2003-2013 Python Software Foundation + ++import struct + import unittest + import plistlib + import os +@@ -119,6 +120,285 @@ + + ''' + ++INVALID_BINARY_PLISTS = [ ++ ('too short data', ++ b'' ++ ), ++ ('too large offset_table_offset and offset_size = 1', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x2a' ++ ), ++ ('too large offset_table_offset and nonstandard offset_size', ++ b'\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x03\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x2c' ++ ), ++ ('integer overflow in offset_table_offset', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\xff\xff\xff\xff\xff\xff\xff\xff' ++ ), ++ ('too large top_object', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('integer overflow in top_object', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\xff\xff\xff\xff\xff\xff\xff\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('too large num_objects and offset_size = 1', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('too large num_objects and nonstandard offset_size', ++ b'\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x03\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('extremally large num_objects (32 bit)', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x7f\xff\xff\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('extremally large num_objects (64 bit)', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\xff\xff\xff\xff\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('integer overflow in num_objects', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\xff\xff\xff\xff\xff\xff\xff\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('offset_size = 0', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('ref_size = 0', ++ b'\xa1\x01\x00\x08\x0a' ++ b'\x00\x00\x00\x00\x00\x00\x01\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0b' ++ ), ++ ('too large offset', ++ b'\x00\x2a' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('integer overflow in offset', ++ b'\x00\xff\xff\xff\xff\xff\xff\xff\xff' ++ b'\x00\x00\x00\x00\x00\x00\x08\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('too large array size', ++ b'\xaf\x00\x01\xff\x00\x08\x0c' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0d' ++ ), ++ ('extremally large array size (32-bit)', ++ b'\xaf\x02\x7f\xff\xff\xff\x01\x00\x08\x0f' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x10' ++ ), ++ ('extremally large array size (64-bit)', ++ b'\xaf\x03\x00\x00\x00\xff\xff\xff\xff\xff\x01\x00\x08\x13' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x14' ++ ), ++ ('integer overflow in array size', ++ b'\xaf\x03\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x08\x13' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x14' ++ ), ++ ('too large reference index', ++ b'\xa1\x02\x00\x08\x0a' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0b' ++ ), ++ ('integer overflow in reference index', ++ b'\xa1\xff\xff\xff\xff\xff\xff\xff\xff\x00\x08\x11' ++ b'\x00\x00\x00\x00\x00\x00\x01\x08' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x12' ++ ), ++ ('too large bytes size', ++ b'\x4f\x00\x23\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0c' ++ ), ++ ('extremally large bytes size (32-bit)', ++ b'\x4f\x02\x7f\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0f' ++ ), ++ ('extremally large bytes size (64-bit)', ++ b'\x4f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x13' ++ ), ++ ('integer overflow in bytes size', ++ b'\x4f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x13' ++ ), ++ ('too large ASCII size', ++ b'\x5f\x00\x23\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0c' ++ ), ++ ('extremally large ASCII size (32-bit)', ++ b'\x5f\x02\x7f\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0f' ++ ), ++ ('extremally large ASCII size (64-bit)', ++ b'\x5f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x13' ++ ), ++ ('integer overflow in ASCII size', ++ b'\x5f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x13' ++ ), ++ ('invalid ASCII', ++ b'\x51\xff\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0a' ++ ), ++ ('too large UTF-16 size', ++ b'\x6f\x00\x13\x20\xac\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0e' ++ ), ++ ('extremally large UTF-16 size (32-bit)', ++ b'\x6f\x02\x4f\xff\xff\xff\x20\xac\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x11' ++ ), ++ ('extremally large UTF-16 size (64-bit)', ++ b'\x6f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x20\xac\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x15' ++ ), ++ ('integer overflow in UTF-16 size', ++ b'\x6f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x20\xac\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x15' ++ ), ++ ('invalid UTF-16', ++ b'\x61\xd8\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0b' ++ ), ++ ('non-hashable key', ++ b'\xd1\x01\x01\xa0\x08\x0b' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0c' ++ ), ++ ('too large datetime (datetime overflow)', ++ b'\x33\x42\x50\x00\x00\x00\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x11' ++ ), ++ ('too large datetime (timedelta overflow)', ++ b'\x33\x42\xe0\x00\x00\x00\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x11' ++ ), ++ ('invalid datetime (Infinity)', ++ b'\x33\x7f\xf0\x00\x00\x00\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x11' ++ ), ++ ('invalid datetime (NaN)', ++ b'\x33\x7f\xf8\x00\x00\x00\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x11' ++ ), ++] ++ + + class TestPlistlib(unittest.TestCase): + +@@ -558,6 +838,21 @@ def test_xml_plist_with_entity_decl(self): + + class TestBinaryPlistlib(unittest.TestCase): + ++ @staticmethod ++ def decode(*objects, offset_size=1, ref_size=1): ++ data = [b'bplist00'] ++ offset = 8 ++ offsets = [] ++ for x in objects: ++ offsets.append(offset.to_bytes(offset_size, 'big')) ++ data.append(x) ++ offset += len(x) ++ tail = struct.pack('>6xBBQQQ', offset_size, ref_size, ++ len(objects), 0, offset) ++ data.extend(offsets) ++ data.append(tail) ++ return plistlib.loads(b''.join(data), fmt=plistlib.FMT_BINARY) ++ + def test_nonstandard_refs_size(self): + # Issue #21538: Refs and offsets are 24-bit integers + data = (b'bplist00' +@@ -572,7 +867,7 @@ def test_nonstandard_refs_size(self): + + def test_dump_duplicates(self): + # Test effectiveness of saving duplicated objects +- for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde', ++ for x in (None, False, True, 12345, 123.45, 'abcde', 'абвгд', b'abcde', + datetime.datetime(2004, 10, 26, 10, 33, 33), + plistlib.Data(b'abcde'), bytearray(b'abcde'), + [12, 345], (12, 345), {'12': 345}): +@@ -609,6 +904,20 @@ def test_cycles(self): + b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY)) + self.assertIs(b['x'], b) + ++ def test_deep_nesting(self): ++ for N in [300, 100000]: ++ chunks = [b'\xa1' + (i + 1).to_bytes(4, 'big') for i in range(N)] ++ try: ++ result = self.decode(*chunks, b'\x54seed', offset_size=4, ref_size=4) ++ except RecursionError: ++ pass ++ else: ++ for i in range(N): ++ self.assertIsInstance(result, list) ++ self.assertEqual(len(result), 1) ++ result = result[0] ++ self.assertEqual(result, 'seed') ++ + def test_large_timestamp(self): + # Issue #26709: 32-bit timestamp out of range + for ts in -2**31-1, 2**31: +@@ -618,55 +927,37 @@ def test_large_timestamp(self): + data = plistlib.dumps(d, fmt=plistlib.FMT_BINARY) + self.assertEqual(plistlib.loads(data), d) + ++ def test_load_singletons(self): ++ self.assertIs(self.decode(b'\x00'), None) ++ self.assertIs(self.decode(b'\x08'), False) ++ self.assertIs(self.decode(b'\x09'), True) ++ self.assertEqual(self.decode(b'\x0f'), b'') ++ ++ def test_load_int(self): ++ self.assertEqual(self.decode(b'\x10\x00'), 0) ++ self.assertEqual(self.decode(b'\x10\xfe'), 0xfe) ++ self.assertEqual(self.decode(b'\x11\xfe\xdc'), 0xfedc) ++ self.assertEqual(self.decode(b'\x12\xfe\xdc\xba\x98'), 0xfedcba98) ++ self.assertEqual(self.decode(b'\x13\x01\x23\x45\x67\x89\xab\xcd\xef'), ++ 0x0123456789abcdef) ++ self.assertEqual(self.decode(b'\x13\xfe\xdc\xba\x98\x76\x54\x32\x10'), ++ -0x123456789abcdf0) ++ ++ def test_unsupported(self): ++ unsupported = [*range(1, 8), *range(10, 15), ++ 0x20, 0x21, *range(0x24, 0x33), *range(0x34, 0x40)] ++ for i in [0x70, 0x90, 0xb0, 0xc0, 0xe0, 0xf0]: ++ unsupported.extend(i + j for j in range(16)) ++ for token in unsupported: ++ with self.subTest(f'token {token:02x}'): ++ with self.assertRaises(plistlib.InvalidFileException): ++ self.decode(bytes([token]) + b'\x00'*16) ++ + def test_invalid_binary(self): +- for data in [ +- # too short data +- b'', +- # too large offset_table_offset and nonstandard offset_size +- b'\x00\x08' +- b'\x00\x00\x00\x00\x00\x00\x03\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x2a', +- # integer overflow in offset_table_offset +- b'\x00\x08' +- b'\x00\x00\x00\x00\x00\x00\x01\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\xff\xff\xff\xff\xff\xff\xff\xff', +- # offset_size = 0 +- b'\x00\x08' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x09', +- # ref_size = 0 +- b'\xa1\x01\x00\x08\x0a' +- b'\x00\x00\x00\x00\x00\x00\x01\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x02' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x0b', +- # integer overflow in offset +- b'\x00\xff\xff\xff\xff\xff\xff\xff\xff' +- b'\x00\x00\x00\x00\x00\x00\x08\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x09', +- # invalid ASCII +- b'\x51\xff\x08' +- b'\x00\x00\x00\x00\x00\x00\x01\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x0a', +- # invalid UTF-16 +- b'\x61\xd8\x00\x08' +- b'\x00\x00\x00\x00\x00\x00\x01\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x0b', +- ]: +- with self.assertRaises(plistlib.InvalidFileException): +- plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY) ++ for name, data in INVALID_BINARY_PLISTS: ++ with self.subTest(name): ++ with self.assertRaises(plistlib.InvalidFileException): ++ plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY) + + + class TestPlistlibDeprecated(unittest.TestCase): +diff --git a/Misc/NEWS.d/next/Library/2020-10-23-19-20-14.bpo-42103.C5obK2.rst b/Misc/NEWS.d/next/Library/2020-10-23-19-20-14.bpo-42103.C5obK2.rst +new file mode 100644 +index 00000000000..4eb694c16a0 +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2020-10-23-19-20-14.bpo-42103.C5obK2.rst +@@ -0,0 +1,3 @@ ++:exc:`~plistlib.InvalidFileException` and :exc:`RecursionError` are now ++the only errors caused by loading malformed binary Plist file (previously ++ValueError and TypeError could be raised in some specific cases). +diff --git a/Misc/NEWS.d/next/Security/2020-10-23-19-19-30.bpo-42103.cILT66.rst b/Misc/NEWS.d/next/Security/2020-10-23-19-19-30.bpo-42103.cILT66.rst +new file mode 100644 +index 00000000000..15d7b6549ed +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2020-10-23-19-19-30.bpo-42103.cILT66.rst +@@ -0,0 +1,2 @@ ++Prevented potential DoS attack via CPU and RAM exhaustion when processing ++malformed Apple Property List files in binary format. +-- +2.33.0 + diff --git a/python3.spec b/python3.spec index fd1f24391ddfc58cc67331647f7e93a507a848e8..fcf243519ab38b9718e9eea6f522446f3f5faf0d 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.7.9 -Release: 49 +Release: 50 License: Python-2.0 %global branchversion 3.7 @@ -176,6 +176,11 @@ Patch6064: backport-0002-CVE-2023-40217.patch Patch6065: backport-0003-CVE-2023-40217.patch Patch6066: backport-CVE-2025-1795.patch Patch6067: backport-CVE-2025-8291.patch +Patch6068: backport-CVE-2025-12084-3.14-gh-142145-Remove-quadratic-behavior-in-node-ID-.patch +Patch6069: backport-pre-CVE-2025-13836-bpo-36050-optimize-HTTPResponse.read-GH-12698.patch +Patch6070: backport-CVE-2025-13836-3.13-gh-119451-Fix-a-potential-denial-of-service-in-.patch +Patch6071: backport-pre-CVE-2025-13837-bpo-42103-Improve-validation-of-Plist-files.-GH-2288.patch +Patch6072: backport-CVE-2025-13837-gh-119342-Fix-a-potential-denial-of-service-in-plist.patch patch9000: Don-t-override-PYTHONPATH-which-is-already-set.patch patch9001: add-the-sm3-method-for-obtaining-the-salt-value.patch @@ -357,6 +362,11 @@ rm Lib/ensurepip/_bundled/*.whl %patch6065 -p1 %patch6066 -p1 %patch6067 -p1 +%patch6068 -p1 +%patch6069 -p1 +%patch6070 -p1 +%patch6071 -p1 +%patch6072 -p1 %patch9000 -p1 %patch9001 -p1 @@ -982,6 +992,9 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %{_mandir}/*/* %changelog +* Tue Dec 16 2025 fuanan - 3.7.9-50 +- fix CVE-2025-12084 CVE-2025-13836 CVE-2025-13837 + * Fri Nov 21 2025 chenjianhu - 3.7.9-49 - fix incorrect changelog and release version