diff mbox series

[scarthgap] python3: patch CVE-2025-13837

Message ID 20260118211749.3870312-1-peter.marko@siemens.com
State New
Headers show
Series [scarthgap] python3: patch CVE-2025-13837 | expand

Commit Message

Peter Marko Jan. 18, 2026, 9:17 p.m. UTC
From: Peter Marko <peter.marko@siemens.com>

Pick patch from 3.12 branch per NVD report.

Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
 .../python/python3/CVE-2025-13837.patch       | 162 ++++++++++++++++++
 .../python/python3_3.12.12.bb                 |   1 +
 2 files changed, 163 insertions(+)
 create mode 100644 meta/recipes-devtools/python/python3/CVE-2025-13837.patch
diff mbox series

Patch

diff --git a/meta/recipes-devtools/python/python3/CVE-2025-13837.patch b/meta/recipes-devtools/python/python3/CVE-2025-13837.patch
new file mode 100644
index 00000000000..0f2e06a4912
--- /dev/null
+++ b/meta/recipes-devtools/python/python3/CVE-2025-13837.patch
@@ -0,0 +1,162 @@ 
+From 5a8b19677d818fb41ee55f310233772e15aa1a2b Mon Sep 17 00:00:00 2001
+From: Serhiy Storchaka <storchaka@gmail.com>
+Date: Mon, 22 Dec 2025 15:49:44 +0200
+Subject: [PATCH] [3.12] gh-119342: Fix a potential denial of service in
+ plistlib (GH-119343) (#142149)
+
+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.
+(cherry picked from commit 694922cf40aa3a28f898b5f5ee08b71b4922df70)
+
+CVE: CVE-2025-13837
+Upstream-Status: Backport [https://github.com/python/cpython/commit/5a8b19677d818fb41ee55f310233772e15aa1a2b]
+Signed-off-by: Peter Marko <peter.marko@siemens.com>
+---
+ 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 3292c30d5f..c5554ea1f7 100644
+--- a/Lib/plistlib.py
++++ b/Lib/plistlib.py
+@@ -73,6 +73,9 @@ from xml.parsers.expat import ParserCreate
+ 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
+ 
+ class UID:
+     def __init__(self, data):
+@@ -499,12 +502,24 @@ class _BinaryPlistParser:
+ 
+         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))
+@@ -561,22 +576,16 @@ class _BinaryPlistParser:
+ 
+         elif tokenH == 0x40:  # data
+             s = self._get_size(tokenL)
+-            result = self._fp.read(s)
+-            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')
+ 
+         elif tokenH == 0x80:  # UID
+diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py
+index fa46050658..229a5a242e 100644
+--- a/Lib/test/test_plistlib.py
++++ b/Lib/test/test_plistlib.py
+@@ -841,8 +841,7 @@ class TestPlistlib(unittest.TestCase):
+ 
+ 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 = []
+@@ -854,7 +853,11 @@ class TestBinaryPlistlib(unittest.TestCase):
+                            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
+@@ -963,6 +966,34 @@ class TestBinaryPlistlib(unittest.TestCase):
+                 with self.assertRaises(plistlib.InvalidFileException):
+                     plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY)
+ 
++    def test_truncated_large_data(self):
++        self.addCleanup(os_helper.unlink, os_helper.TESTFN)
++        def check(data):
++            with open(os_helper.TESTFN, 'wb') as f:
++                f.write(data)
++            # buffered file
++            with open(os_helper.TESTFN, 'rb') as f:
++                with self.assertRaises(plistlib.InvalidFileException):
++                    plistlib.load(f, fmt=plistlib.FMT_BINARY)
++            # unbuffered file
++            with open(os_helper.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 TestKeyedArchive(unittest.TestCase):
+     def test_keyed_archive_data(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 0000000000..04fd8faca4
+--- /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.
diff --git a/meta/recipes-devtools/python/python3_3.12.12.bb b/meta/recipes-devtools/python/python3_3.12.12.bb
index b70f434ca95..2cb7ab4b591 100644
--- a/meta/recipes-devtools/python/python3_3.12.12.bb
+++ b/meta/recipes-devtools/python/python3_3.12.12.bb
@@ -35,6 +35,7 @@  SRC_URI = "http://www.python.org/ftp/python/${PV}/Python-${PV}.tar.xz \
 	   file://0001-test_active_children-skip-problematic-test.patch \
            file://0001-test_readline-skip-limited-history-test.patch \
            file://CVE-2025-6075.patch \
+           file://CVE-2025-13837.patch \
            "
 
 SRC_URI:append:class-native = " \