From 2544c41d2e3c3388870438e4daedc6d4465c09b0bbb00519bf1c3cfa56b8a6e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Thu, 18 Dec 2025 16:07:31 +0100 Subject: [PATCH] Add CVE-2025-13836-http-resp-cont-len.patch (bsc#1254400, CVE-2025-13836) Prevent reading an HTTP response from a server, if no read amount is specified, with using Content-Length per default as the length. --- CVE-2025-13836-http-resp-cont-len.patch | 154 ++++++++++++++++++++++++ python315.changes | 8 ++ python315.spec | 3 + 3 files changed, 165 insertions(+) create mode 100644 CVE-2025-13836-http-resp-cont-len.patch diff --git a/CVE-2025-13836-http-resp-cont-len.patch b/CVE-2025-13836-http-resp-cont-len.patch new file mode 100644 index 0000000..a390918 --- /dev/null +++ b/CVE-2025-13836-http-resp-cont-len.patch @@ -0,0 +1,154 @@ +From b3a7998115e195c40e00cfa662bcaa899d937c05 Mon Sep 17 00:00:00 2001 +From: Serhiy Storchaka +Date: Mon, 1 Dec 2025 17:26:07 +0200 +Subject: [PATCH] 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 ++++++++++ + Misc/NEWS.d/next/Security/2024-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 + +Index: Python-3.15.0a2/Lib/http/client.py +=================================================================== +--- Python-3.15.0a2.orig/Lib/http/client.py 2025-12-18 22:39:49.435421792 +0100 ++++ Python-3.15.0a2/Lib/http/client.py 2025-12-18 22:40:02.599038188 +0100 +@@ -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 +@@ -642,10 +647,25 @@ + 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.""" +Index: Python-3.15.0a2/Lib/test/test_httplib.py +=================================================================== +--- Python-3.15.0a2.orig/Lib/test/test_httplib.py 2025-12-18 22:39:51.081332214 +0100 ++++ Python-3.15.0a2/Lib/test/test_httplib.py 2025-12-18 22:40:02.599675565 +0100 +@@ -1511,6 +1511,72 @@ + thread.join() + self.assertEqual(result, b"proxied data\n") + ++ def test_large_content_length(self): ++ serv = socket.create_server((HOST, 0)) ++ self.addCleanup(serv.close) ++ ++ 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.create_server((HOST, 0)) ++ self.addCleanup(serv.close) ++ ++ 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 +Index: Python-3.15.0a2/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ Python-3.15.0a2/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst 2025-12-18 22:40:02.600168090 +0100 +@@ -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. diff --git a/python315.changes b/python315.changes index 2144723..e612c98 100644 --- a/python315.changes +++ b/python315.changes @@ -1,3 +1,11 @@ +------------------------------------------------------------------- +Thu Dec 18 10:33:44 UTC 2025 - Matej Cepl + +- Add CVE-2025-13836-http-resp-cont-len.patch (bsc#1254400, + CVE-2025-13836) to prevent reading an HTTP response from + a server, if no read amount is specified, with using + Content-Length per default as the length. + ------------------------------------------------------------------- Wed Dec 10 03:48:24 UTC 2025 - Steve Kowalik diff --git a/python315.spec b/python315.spec index 6af6bf2..aaa44c4 100644 --- a/python315.spec +++ b/python315.spec @@ -224,6 +224,9 @@ Patch40: fix-test-recursion-limit-15.6.patch Patch41: bsc1243155-sphinx-non-determinism.patch # PATCH-FIX-OPENSUSE gh139257-Support-docutils-0.22.patch gh#python/cpython#139257 daniel.garcia@suse.com Patch42: gh139257-Support-docutils-0.22.patch +# PATCH-FIX-UPSTREAM CVE-2025-13836-http-resp-cont-len.patch bsc#1254400 mcepl@suse.com +# Avoid loading possibly compromised length of HTTP response +Patch43: CVE-2025-13836-http-resp-cont-len.patch #### Python 3.15 DEVELOPMENT PATCHES BuildRequires: autoconf-archive BuildRequires: automake