[Buildroot] [PATCH 2025.02.x] package/python3: security bump to v3.12.14
Thomas Perale via buildroot <[email protected]>
| Newsgroups | net.busybox.buildroot |
|---|---|
| Message-ID | <[email protected]> |
See the release notes: https://www.python.org/downloads/release/python-31214/ - CVE-2026-7210 https://github.com/python/cpython/issues/149018 https://github.com/python/cpython/commit/e37df2a6a71d6538698e2d3188a7c345b827640b - CVE-2026-4519 https://github.com/python/cpython/issues/143930 https://github.com/python/cpython/commit/cbba6119391112aba9c5aebf7b94aea447922c48 - CVE-2026-3644 https://github.com/python/cpython/issues/145599 https://github.com/python/cpython/commit/3974092b037f9a3b000fb15b48ea61ce3b25d330 - CVE-2026-15308 https://github.com/python/cpython/issues/153030 https://github.com/python/cpython/commit/785df8f743800661961528970f8598edcd291c14 - CVE-2025-13462 https://github.com/python/cpython/issues/141707 https://github.com/python/cpython/commit/d10950739a78f54d0718d88fb5a868374603c084 - CVE-2026-2297 https://github.com/python/cpython/issues/145506 https://github.com/python/cpython/commit/c70adad78caeeea33f92f560ecb93331ca11bf66 - CVE-2026-4224 https://github.com/python/cpython/issues/145986 https://github.com/python/cpython/commit/24ce88b285f56ee11626cf5e472af3cd8cc7c621 - CVE‑2026‑4360 https://github.com/python/cpython/issues/151987 https://github.com/python/cpython/commit/0367912be336348b30572f8029cec4a282782d92 - CVE‑2026‑0864 https://github.com/python/cpython/issues/143927 https://github.com/python/cpython/commit/db4a157c790479710a1a840d7937c5c815a6f8b6 - CVE‑2026‑1502 https://github.com/python/cpython/commit/c00c386faa579ad71196d33408644478488e43ec https://github.com/python/cpython/issues/146211 - CVE‑2026‑3087 https://github.com/python/cpython/commit/a6650a2cdf0c49fb8ce0c982903aa2aa274beefe https://github.com/python/cpython/issues/146581 - CVE‑2026‑4786 https://github.com/python/cpython/issues/148169 https://github.com/python/cpython/commit/a4d3edf3a6ecfde504d02126410d2a65a859b744 - CVE‑2026‑6100 https://github.com/python/cpython/issues/148395 https://github.com/python/cpython/commit/ea8d735eb084cf8cc021df1a30e90d10a8f052e3 - CVE‑2026‑6879 https://github.com/python/cpython/issues/152674 https://github.com/python/cpython/commit/96510a3758f4a075f43223afdee3b6ee1a7a7f02 - CVE‑2026‑11972 https://github.com/python/cpython/issues/151981 https://github.com/python/cpython/commit/f5e2776ff0383a902c12acf2b703e7e951fc8438 - CVE‑2026‑12003 https://github.com/python/cpython/issues/151544 https://github.com/python/cpython/commit/03ab7b44788bfd6b8927e16bcdbd025aa08dce06 The CVE-2026-3276, CVE-2026-7774, CVE-2026-8328, CVE-2026-9669, CVE-2026-11940 were already fixed with a backport on 2025.02.x. Signed-off-by: Thomas Perale <[email protected]> --- ...al-ordering-in-unicodedata-normalize.patch | 275 ------------------ ...le-data_filter-validate-written-link.patch | 178 ------------ ...E-2021-4189-PASV-fix-to-ftplib-ftpcp.patch | 118 -------- ...-bz2-decompressor-reuse-after-errors.patch | 108 ------- ...tarfile-hardlink-extraction-fallback.patch | 77 ----- package/python3/python3.hash | 4 +- package/python3/python3.mk | 13 +- 7 files changed, 3 insertions(+), 770 deletions(-) delete mode 100644 package/python3/0013-Fix-O-n-2-canonical-ordering-in-unicodedata-normalize.patch delete mode 100644 package/python3/0014-tarfile-data_filter-validate-written-link.patch delete mode 100644 package/python3/0015-Apply-CVE-2021-4189-PASV-fix-to-ftplib-ftpcp.patch delete mode 100644 package/python3/0016-prevent-bz2-decompressor-reuse-after-errors.patch delete mode 100644 package/python3/0017-Fix-symlink-escape-via-tarfile-hardlink-extraction-fallback.patch diff --git a/package/python3/0013-Fix-O-n-2-canonical-ordering-in-unicodedata-normalize.patch b/package/python3/0013-Fix-O-n-2-canonical-ordering-in-unicodedata-normalize.patch deleted file mode 100644 index 1bfce116dc..0000000000 --- a/package/python3/0013-Fix-O-n-2-canonical-ordering-in-unicodedata-normalize.patch +++ /dev/null @@ -1,275 +0,0 @@ -From d8deb45e25f8eae007449f4018a4f1e117a772d4 Mon Sep 17 00:00:00 2001 -From: Petr Viktorin <[email protected]> -Date: Tue, 2 Jun 2026 18:12:42 +0200 -Subject: [PATCH] [3.12] gh-149079: Fix O(n^2) canonical ordering in - unicodedata.normalize() (GH-149080) -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Replace the insertion sort used for canonical ordering of combining -characters with a hybrid approach: insertion sort for short runs (< 20) -and counting sort for longer runs, reducing worst-case complexity from -O(n^2) to O(n). This prevents denial of service via crafted Unicode -strings with many combining characters in alternating CCC order. - -(cherry picked from commit 991224b1e8311c85f198f6dd8208bf8cff7fc26f) - -Co-authored-by: Seth Larson <[email protected]> -Co-authored-by: ch4n3-yoon <[email protected]> -Co-authored-by: Seokchan Yoon <[email protected]> -Co-authored-by: Stan Ulbrych <[email protected]> -Co-authored-by: Bénédikt Tran <[email protected]> -Co-authored-by: Serhiy Storchaka <[email protected]> -Co-authored-by: Maurycy Pawłowski-Wieroński <[email protected]> - -Upstream: https://github.com/python/cpython/pull/150843 -CVE: CVE-2026-3276 -Signed-off-by: Titouan Christophe <[email protected]> ---- - Lib/test/test_unicodedata.py | 28 ++++ - ...-04-27-16-36-11.gh-issue-149079.vKl-LM.rst | 5 + - Modules/unicodedata.c | 144 ++++++++++++++---- - 3 files changed, 151 insertions(+), 26 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst - -diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py -index 515c3840cb36474..6b4bff194eb4b59 100644 ---- a/Lib/test/test_unicodedata.py -+++ b/Lib/test/test_unicodedata.py -@@ -203,6 +203,34 @@ def test_issue10254(self): - b = 'C\u0338' * 20 + '\xC7' - self.assertEqual(self.db.normalize('NFC', a), b) - -+ def test_long_combining_mark_run(self): -+ # gh-149079: avoid quadratic canonical ordering. -+ payload = "a" + ("\u0300\u0327" * 32) -+ nfd = "a" + ("\u0327" * 32) + ("\u0300" * 32) -+ nfc = "\u00e0" + ("\u0327" * 32) + ("\u0300" * 31) -+ -+ self.assertEqual(self.db.normalize("NFD", payload), nfd) -+ self.assertEqual(self.db.normalize("NFKD", payload), nfd) -+ self.assertEqual(self.db.normalize("NFC", payload), nfc) -+ self.assertEqual(self.db.normalize("NFKC", payload), nfc) -+ -+ def test_combining_mark_run_fast_paths(self): -+ # gh-149079: cover short runs and already-sorted long runs. -+ short_payload = "a" + ("\u0300\u0327" * 9) + "\u0300" -+ short_nfd = "a" + ("\u0327" * 9) + ("\u0300" * 10) -+ short_nfc = "\u00e0" + ("\u0327" * 9) + ("\u0300" * 9) -+ long_sorted = "a" + ("\u0327" * 30) + ("\u0300" * 30) -+ long_sorted_nfc = "\u00e0" + ("\u0327" * 30) + ("\u0300" * 29) -+ -+ self.assertEqual(self.db.normalize("NFD", short_payload), short_nfd) -+ self.assertEqual(self.db.normalize("NFKD", short_payload), short_nfd) -+ self.assertEqual(self.db.normalize("NFC", short_payload), short_nfc) -+ self.assertEqual(self.db.normalize("NFKC", short_payload), short_nfc) -+ self.assertEqual(self.db.normalize("NFD", long_sorted), long_sorted) -+ self.assertEqual(self.db.normalize("NFKD", long_sorted), long_sorted) -+ self.assertEqual(self.db.normalize("NFC", long_sorted), long_sorted_nfc) -+ self.assertEqual(self.db.normalize("NFKC", long_sorted), long_sorted_nfc) -+ - def test_issue29456(self): - # Fix #29456 - u1176_str_a = '\u1100\u1176\u11a8' -diff --git a/Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst b/Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst -new file mode 100644 -index 000000000000000..4ed22b58f7405f5 ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst -@@ -0,0 +1,5 @@ -+Fix a potential denial of service in :func:`unicodedata.normalize`. The -+canonical ordering step of Unicode normalization used a quadratic-time insertion -+sort for reordering combining characters, which could be exploited with -+crafted input containing many combining characters in non-canonical order. -+Replaced with a linear-time counting sort for long runs. -diff --git a/Modules/unicodedata.c b/Modules/unicodedata.c -index 41dcd5f8f883f2c..de3451628c6ef9f 100644 ---- a/Modules/unicodedata.c -+++ b/Modules/unicodedata.c -@@ -490,19 +490,80 @@ get_decomp_record(PyObject *self, Py_UCS4 code, - #define NCount (VCount*TCount) - #define SCount (LCount*NCount) - -+/* Small combining runs are usually cheaper with insertion sort. */ -+#define CANONICAL_ORDERING_COUNTING_SORT_THRESHOLD 20 -+ -+static void -+canonical_ordering_sort_insertion(int kind, void *data, -+ Py_ssize_t start, Py_ssize_t end) -+{ -+ for (Py_ssize_t i = start + 1; i < end; i++) { -+ Py_UCS4 code = PyUnicode_READ(kind, data, i); -+ unsigned char combining = _getrecord_ex(code)->combining; -+ Py_ssize_t j = i; -+ -+ while (j > start) { -+ Py_UCS4 previous = PyUnicode_READ(kind, data, j - 1); -+ if (_getrecord_ex(previous)->combining <= combining) { -+ break; -+ } -+ PyUnicode_WRITE(kind, data, j, previous); -+ j--; -+ } -+ if (j != i) { -+ PyUnicode_WRITE(kind, data, j, code); -+ } -+ } -+} -+ -+static void -+canonical_ordering_sort_counting(int kind, void *data, -+ Py_ssize_t start, Py_ssize_t end, -+ Py_UCS4 *sortbuf) -+{ -+ Py_ssize_t counts[256] = {0}; -+ Py_ssize_t run_length = end - start; -+ Py_ssize_t total = 0; -+ -+ for (Py_ssize_t i = start; i < end; i++) { -+ Py_UCS4 code = PyUnicode_READ(kind, data, i); -+ unsigned char combining = _getrecord_ex(code)->combining; -+ counts[combining]++; -+ } -+ -+ for (size_t i = 0; i < Py_ARRAY_LENGTH(counts); i++) { -+ Py_ssize_t count = counts[i]; -+ counts[i] = total; -+ total += count; -+ } -+ -+ /* Reuse counts[] as the next output slot for each CCC. */ -+ for (Py_ssize_t i = start; i < end; i++) { -+ Py_UCS4 code = PyUnicode_READ(kind, data, i); -+ unsigned char combining = _getrecord_ex(code)->combining; -+ sortbuf[counts[combining]++] = code; -+ } -+ for (Py_ssize_t i = 0; i < run_length; i++) { -+ PyUnicode_WRITE(kind, data, start + i, sortbuf[i]); -+ } -+} -+ - static PyObject* - nfd_nfkd(PyObject *self, PyObject *input, int k) - { - PyObject *result; - Py_UCS4 *output; - Py_ssize_t i, o, osize; -- int kind; -- const void *data; -+ int input_kind, result_kind; -+ const void *input_data; -+ void *result_data; - /* Longest decomposition in Unicode 3.2: U+FDFA */ - Py_UCS4 stack[20]; - Py_ssize_t space, isize; - int index, prefix, count, stackptr; - unsigned char prev, cur; -+ Py_UCS4 *sortbuf = NULL; -+ Py_ssize_t sortbuflen = 0; - - stackptr = 0; - isize = PyUnicode_GET_LENGTH(input); -@@ -522,11 +583,11 @@ nfd_nfkd(PyObject *self, PyObject *input, int k) - return NULL; - } - i = o = 0; -- kind = PyUnicode_KIND(input); -- data = PyUnicode_DATA(input); -+ input_kind = PyUnicode_KIND(input); -+ input_data = PyUnicode_DATA(input); - - while (i < isize) { -- stack[stackptr++] = PyUnicode_READ(kind, data, i++); -+ stack[stackptr++] = PyUnicode_READ(input_kind, input_data, i++); - while(stackptr) { - Py_UCS4 code = stack[--stackptr]; - /* Hangul Decomposition adds three characters in -@@ -591,35 +652,66 @@ nfd_nfkd(PyObject *self, PyObject *input, int k) - PyMem_Free(output); - if (!result) - return NULL; -+ - /* result is guaranteed to be ready, as it is compact. */ -- kind = PyUnicode_KIND(result); -- data = PyUnicode_DATA(result); -+ result_kind = PyUnicode_KIND(result); -+ result_data = PyUnicode_DATA(result); - -- /* Sort canonically. */ -+ /* Sort each consecutive combining-character run canonically. */ - i = 0; -- prev = _getrecord_ex(PyUnicode_READ(kind, data, i))->combining; -- for (i++; i < PyUnicode_GET_LENGTH(result); i++) { -- cur = _getrecord_ex(PyUnicode_READ(kind, data, i))->combining; -- if (prev == 0 || cur == 0 || prev <= cur) { -- prev = cur; -+ while (i < o) { -+ Py_ssize_t run_length, run_start; -+ int needs_sort = 0; -+ -+ Py_UCS4 ch = PyUnicode_READ(result_kind, result_data, i); -+ prev = _getrecord_ex(ch)->combining; -+ if (prev == 0) { -+ i++; - continue; - } -- /* Non-canonical order. Need to switch *i with previous. */ -- o = i - 1; -- while (1) { -- Py_UCS4 tmp = PyUnicode_READ(kind, data, o+1); -- PyUnicode_WRITE(kind, data, o+1, -- PyUnicode_READ(kind, data, o)); -- PyUnicode_WRITE(kind, data, o, tmp); -- o--; -- if (o < 0) -- break; -- prev = _getrecord_ex(PyUnicode_READ(kind, data, o))->combining; -- if (prev == 0 || prev <= cur) -+ -+ run_start = i++; -+ while (i < o) { -+ Py_UCS4 ch = PyUnicode_READ(result_kind, result_data, i); -+ cur = _getrecord_ex(ch)->combining; -+ if (cur == 0) { - break; -+ } -+ if (prev > cur) { -+ needs_sort = 1; -+ } -+ prev = cur; -+ i++; -+ } -+ if (!needs_sort) { -+ continue; -+ } -+ -+ run_length = i - run_start; -+ if (run_length < CANONICAL_ORDERING_COUNTING_SORT_THRESHOLD) { -+ canonical_ordering_sort_insertion(result_kind, result_data, -+ run_start, i); -+ continue; - } -- prev = _getrecord_ex(PyUnicode_READ(kind, data, i))->combining; -+ -+ if (run_length > sortbuflen) { -+ Py_UCS4 *new_sortbuf = PyMem_Resize(sortbuf, -+ Py_UCS4, -+ run_length); -+ if (new_sortbuf == NULL) { -+ PyErr_NoMemory(); -+ PyMem_Free(sortbuf); -+ Py_DECREF(result); -+ return NULL; -+ } -+ sortbuf = new_sortbuf; -+ sortbuflen = run_length; -+ } -+ -+ canonical_ordering_sort_counting(result_kind, result_data, -+ run_start, i, sortbuf); - } -+ PyMem_Free(sortbuf); - return result; - } - diff --git a/package/python3/0014-tarfile-data_filter-validate-written-link.patch b/package/python3/0014-tarfile-data_filter-validate-written-link.patch deleted file mode 100644 index b6337e9cbd..0000000000 --- a/package/python3/0014-tarfile-data_filter-validate-written-link.patch +++ /dev/null @@ -1,178 +0,0 @@ -From 6df9892a802c124c4223e17c4a05ea7054166ac7 Mon Sep 17 00:00:00 2001 -From: Petr Viktorin <[email protected]> -Date: Fri, 8 May 2026 14:16:06 +0200 -Subject: [PATCH] [3.12] gh-149486: tarfile.data_filter: validate written - link target (GH-149487) - -The data filter rewrote linknames with normpath() but ran the -containment check against the un-normalised value, and computed a -symlink's directory before stripping trailing slashes. Both let a -crafted archive create links pointing outside the destination. Also -reject link members that resolve to the destination directory itself, -which could otherwise replace it with a symlink and redirect all -subsequent members. - -(Patch by Greg; Petr's just reviewing & merging.) -(cherry picked from commit 578411982c16f753f4893532510099ef665117da) - -Co-authored-by: Petr Viktorin <[email protected]> -Co-authored-by: Gregory P. Smith <[email protected]> - -Upstream: https://github.com/python/cpython/pull/149556 -CVE: CVE-2026-7774 -Signed-off-by: Titouan Christophe <[email protected]> ---- - Lib/tarfile.py | 16 ++-- - Lib/test/test_tarfile.py | 87 ++++++++++++++++++- - ...-05-03-21-00-00.gh-issue-149486.tarflt.rst | 5 ++ - 3 files changed, 99 insertions(+), 9 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2026-05-03-21-00-00.gh-issue-149486.tarflt.rst - -diff --git a/Lib/tarfile.py b/Lib/tarfile.py -index 59d3f6e5cce1650..fcb1040f2aefe29 100755 ---- a/Lib/tarfile.py -+++ b/Lib/tarfile.py -@@ -816,16 +816,22 @@ def _get_filtered_attrs(member, dest_path, for_data=True): - if member.islnk() or member.issym(): - if os.path.isabs(member.linkname): - raise AbsoluteLinkError(member) -+ # A link member that resolves to the destination directory itself -+ # would replace it with a (sym)link, redirecting the destination -+ # for all subsequent members. -+ if target_path == dest_path: -+ raise OutsideDestinationError(member, target_path) - normalized = os.path.normpath(member.linkname) - if normalized != member.linkname: - new_attrs['linkname'] = normalized - if member.issym(): -- target_path = os.path.join(dest_path, -- os.path.dirname(name), -- member.linkname) -+ # The symlink is created at `name` with trailing separators -+ # stripped, so its target is relative to the directory -+ # containing that path. -+ link_dir = os.path.dirname(name.rstrip('/' + os.sep)) -+ target_path = os.path.join(dest_path, link_dir, normalized) - else: -- target_path = os.path.join(dest_path, -- member.linkname) -+ target_path = os.path.join(dest_path, normalized) - target_path = os.path.realpath(target_path, - strict=os.path.ALLOW_MISSING) - if os.path.commonpath([target_path, dest_path]) != dest_path: -diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py -index 759fa03ead70b04..fefae4b64c6182c 100644 ---- a/Lib/test/test_tarfile.py -+++ b/Lib/test/test_tarfile.py -@@ -3701,10 +3701,19 @@ def test_parent_symlink(self): - + "which is outside the destination") - - with self.check_context(arc.open(), 'data'): -- self.expect_exception( -- tarfile.LinkOutsideDestinationError, -- """'parent' would link to ['"].*outerdir['"], """ -- + "which is outside the destination") -+ if self.dotdot_resolves_early: -+ # 'current/../..' normalises to '..', which is rejected. -+ self.expect_exception( -+ tarfile.LinkOutsideDestinationError, -+ """'parent' would link to ['"].*outerdir['"], """ -+ + "which is outside the destination") -+ else: -+ # 'current/..' normalises to '.'; the rewritten link is -+ # created and 'parent/evil' lands harmlessly inside the -+ # destination. -+ self.expect_file('current', symlink_to='.') -+ self.expect_file('parent', symlink_to='.') -+ self.expect_file('evil') - - else: - # No symlink support. The symlinks are ignored. -@@ -3978,6 +3987,76 @@ def test_sly_relative2(self): - + """['"].*moo['"], which is outside the """ - + "destination") - -+ @symlink_test -+ @os_helper.skip_unless_symlink -+ def test_normpath_realpath_mismatch(self): -+ # The link-target check must validate the value that will actually -+ # be written to disk (the normalised linkname), not the original. -+ # Here 'a' is a symlink to a deep nonexistent path, so realpath() -+ # of 'a/../../...' stays inside the destination while normpath() -+ # collapses 'a/..' lexically and escapes. -+ depth = len(self.destdir.parts) + 5 -+ deep = '/'.join(f'p{i}' for i in range(depth)) -+ sneaky = 'a/' + '../' * depth + 'flag' -+ for kind in 'symlink_to', 'hardlink_to': -+ with self.subTest(kind): -+ with ArchiveMaker() as arc: -+ arc.add('a', symlink_to=deep) -+ arc.add('escape', **{kind: sneaky}) -+ with self.check_context(arc.open(), 'data'): -+ self.expect_exception( -+ tarfile.LinkOutsideDestinationError) -+ -+ @symlink_test -+ @os_helper.skip_unless_symlink -+ def test_symlink_trailing_slash(self): -+ # A trailing slash on a symlink member's name must not cause the -+ # link target to be resolved relative to the wrong directory. -+ with ArchiveMaker() as arc: -+ t = tarfile.TarInfo('x/') -+ t.type = tarfile.SYMTYPE -+ t.linkname = '..' -+ arc.tar_w.addfile(t) -+ arc.add('x/escaped', content='hi') -+ -+ with self.check_context(arc.open(), 'data'): -+ self.expect_exception(tarfile.LinkOutsideDestinationError) -+ -+ @symlink_test -+ @os_helper.skip_unless_symlink -+ def test_link_at_destination(self): -+ # A link member whose name resolves to the destination directory -+ # itself must be rejected: otherwise the destination is replaced -+ # by a symlink and later members can be redirected through it. -+ for name in '', '.', './': -+ with ArchiveMaker() as arc: -+ t = tarfile.TarInfo(name) -+ t.type = tarfile.SYMTYPE -+ t.linkname = '.' -+ arc.tar_w.addfile(t) -+ -+ with self.check_context(arc.open(), 'data'): -+ self.expect_exception(tarfile.OutsideDestinationError) -+ -+ @symlink_test -+ @os_helper.skip_unless_symlink -+ def test_empty_name_symlink_chain(self): -+ # Regression test for a chain of empty-named symlinks that -+ # incrementally redirects the destination outwards. -+ with ArchiveMaker() as arc: -+ for name, target in [('', ''), ('a/', '..'), -+ ('', 'dummy'), ('', 'a'), -+ ('b/', '..'), -+ ('', 'dummy'), ('', 'a/b')]: -+ t = tarfile.TarInfo(name) -+ t.type = tarfile.SYMTYPE -+ t.linkname = target -+ arc.tar_w.addfile(t) -+ arc.add('escaped', content='hi') -+ -+ with self.check_context(arc.open(), 'data'): -+ self.expect_exception(tarfile.FilterError) -+ - @symlink_test - def test_deep_symlink(self): - # Test that symlinks and hardlinks inside a directory -diff --git a/Misc/NEWS.d/next/Security/2026-05-03-21-00-00.gh-issue-149486.tarflt.rst b/Misc/NEWS.d/next/Security/2026-05-03-21-00-00.gh-issue-149486.tarflt.rst -new file mode 100644 -index 000000000000000..7c69edb683cf80a ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2026-05-03-21-00-00.gh-issue-149486.tarflt.rst -@@ -0,0 +1,5 @@ -+:func:`tarfile.data_filter` now validates link targets using the same -+normalised value that is written to disk, strips trailing separators from -+the member name when resolving a symlink's directory, and rejects link -+members that would replace the destination directory itself. This closes -+several path-traversal bypasses of the ``data`` extraction filter. diff --git a/package/python3/0015-Apply-CVE-2021-4189-PASV-fix-to-ftplib-ftpcp.patch b/package/python3/0015-Apply-CVE-2021-4189-PASV-fix-to-ftplib-ftpcp.patch deleted file mode 100644 index c39ebfa1c3..0000000000 --- a/package/python3/0015-Apply-CVE-2021-4189-PASV-fix-to-ftplib-ftpcp.patch +++ /dev/null @@ -1,118 +0,0 @@ -From b026be60f7e023719a4d8de89ae3c3248b7c5d40 Mon Sep 17 00:00:00 2001 -From: "Gregory P. Smith" <[email protected]> -Date: Wed, 13 May 2026 10:33:43 -0700 -Subject: [PATCH] gh-87451: Apply CVE-2021-4189 PASV fix to ftplib.ftpcp() - (GH-149648) - -ftpcp() called parse227() directly and passed the source server's -self-reported PASV IPv4 address to the target server's PORT command, -bypassing the CVE-2021-4189 fix that was applied only to FTP.makepasv(). -A malicious source FTP server could use this to redirect the target -server's data connection to an arbitrary host:port (SSRF). - -ftpcp() now uses the source server's actual peer address, honoring the -existing trust_server_pasv_ipv4_address opt-out, the same as makepasv(). - -Thanks to Qi Ding at Aurascape AI for the report. (GHSA-w8c5-q2xf-gf7c) -(cherry picked from commit eac4fe3b2c77693790a5ef7dfab127c1fee81bf9) - -Co-authored-by: Gregory P. Smith <[email protected]> - -Upstream: https://github.com/python/cpython/pull/149795 -CVE: CVE-2026-8328 -Signed-off-by: Titouan Christophe <[email protected]> ---- - Lib/ftplib.py | 11 +++++- - Lib/test/test_ftplib.py | 36 ++++++++++++++++++- - ...6-05-10-18-05-32.gh-issue-87451.XkKB6M.rst | 6 ++++ - 3 files changed, 51 insertions(+), 2 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst - -diff --git a/Lib/ftplib.py b/Lib/ftplib.py -index 10c5d1ea08ab115..463da58de85d721 100644 ---- a/Lib/ftplib.py -+++ b/Lib/ftplib.py -@@ -883,7 +883,16 @@ def ftpcp(source, sourcename, target, targetname = '', type = 'I'): - type = 'TYPE ' + type - source.voidcmd(type) - target.voidcmd(type) -- sourcehost, sourceport = parse227(source.sendcmd('PASV')) -+ # Don't trust the IPv4 address the source server advertises in its PASV -+ # reply: a malicious source could otherwise point the target's data -+ # connection at an arbitrary host (SSRF). A caller that needs the old -+ # behavior can set trust_server_pasv_ipv4_address on the source FTP -+ # object. See FTP.makepasv(), which applies the same rule. -+ untrusted_host, sourceport = parse227(source.sendcmd('PASV')) -+ if source.trust_server_pasv_ipv4_address: -+ sourcehost = untrusted_host -+ else: -+ sourcehost = source.sock.getpeername()[0] - target.sendport(sourcehost, sourceport) - # RFC 959: the user must "listen" [...] BEFORE sending the - # transfer request. -diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py -index 204a77d14f03a50..7542f015f78c421 100644 ---- a/Lib/test/test_ftplib.py -+++ b/Lib/test/test_ftplib.py -@@ -16,7 +16,7 @@ - except ImportError: - ssl = None - --from unittest import TestCase, skipUnless -+from unittest import mock, TestCase, skipUnless - from test import support - from test.support import threading_helper - from test.support import socket_helper -@@ -1142,6 +1142,40 @@ def testTimeoutDirectAccess(self): - ftp.close() - - -+class TestFtpcpSecurity(TestCase): -+ """ftpcp() must not trust the host a source server advertises in PASV. -+ -+ A malicious source server can otherwise redirect the target server's -+ data connection to an arbitrary host:port (SSRF), so ftpcp() uses the -+ source server's actual peer address instead, the same as FTP.makepasv(). -+ """ -+ -+ def _make_pair(self, *, advertised_host, real_host, trust=False): -+ source = mock.Mock(spec=ftplib.FTP) -+ source.trust_server_pasv_ipv4_address = trust -+ source.sock.getpeername.return_value = (real_host, 21) -+ # PASV replies give the host as comma-separated octets, not dotted. -+ advertised = advertised_host.replace('.', ',') -+ source.sendcmd.side_effect = lambda cmd: ( -+ f'227 Entering Passive Mode ({advertised},1,2).' -+ if cmd == 'PASV' else '150 ok') -+ target = mock.Mock(spec=ftplib.FTP) -+ target.sendcmd.return_value = '150 ok' -+ return source, target -+ -+ def test_ftpcp_ignores_untrusted_pasv_host(self): -+ source, target = self._make_pair(advertised_host='10.0.0.5', -+ real_host='198.51.100.7') -+ ftplib.ftpcp(source, 'a', target, 'b') -+ target.sendport.assert_called_once_with('198.51.100.7', 258) -+ -+ def test_ftpcp_trust_server_pasv_ipv4_address(self): -+ source, target = self._make_pair(advertised_host='10.0.0.5', -+ real_host='198.51.100.7', trust=True) -+ ftplib.ftpcp(source, 'a', target, 'b') -+ target.sendport.assert_called_once_with('10.0.0.5', 258) -+ -+ - class MiscTestCase(TestCase): - def test__all__(self): - not_exported = { -diff --git a/Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst b/Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst -new file mode 100644 -index 000000000000000..21a79c3e0e7db74 ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst -@@ -0,0 +1,6 @@ -+The :mod:`ftplib` module's undocumented ``ftpcp`` function no longer trusts -+the IPv4 address value returned from the source server in response to the -+``PASV`` command by default, completing the fix for CVE-2021-4189. As with -+:class:`ftplib.FTP`, the former behavior can be re-enabled by setting the -+``trust_server_pasv_ipv4_address`` attribute on the source :class:`ftplib.FTP` -+instance to ``True``. Thanks to Qi Deng at Aurascape AI for the report. diff --git a/package/python3/0016-prevent-bz2-decompressor-reuse-after-errors.patch b/package/python3/0016-prevent-bz2-decompressor-reuse-after-errors.patch deleted file mode 100644 index 3fecf86ab7..0000000000 --- a/package/python3/0016-prevent-bz2-decompressor-reuse-after-errors.patch +++ /dev/null @@ -1,108 +0,0 @@ -From c36ef35e7e5601739755d883f5dab5c6e02b2e30 Mon Sep 17 00:00:00 2001 -From: Stan Ulbrych <[email protected]> -Date: Sun, 7 Jun 2026 19:37:10 +0100 -Subject: [PATCH] [3.12] gh-150599: Prevent bz2 decompressor reuse after - errors (#150600) (#151054) - -(cherry picked from commit 5755d0f083949ff3c5bf3a37e673e24e306b036e) - -Upstream: https://github.com/python/cpython/pull/151057 -CVE: CVE-2026-9669 -[Titouan: Squash fixup commit 0c2466a0fc2270c87e9f67c250c9454c28c09c3 - from the upstream PR in the patch below] -Signed-off-by: Titouan Christophe <[email protected]> ---- - Lib/test/test_bz2.py | 15 +++++++++++++++ - ...6-05-30-09-36-20.gh-issue-150599.nlHqU-.rst | 3 +++ - Modules/_bz2module.c | 18 +++++++++++++++--- - 3 files changed, 33 insertions(+), 3 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2026-05-30-09-36-20.gh-issue-150599.nlHqU-.rst - -diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py -index cb730a1a46e25a1..dcbf6a298264a40 100644 ---- a/Lib/test/test_bz2.py -+++ b/Lib/test/test_bz2.py -@@ -958,6 +958,21 @@ def test_failure(self): - # Previously, a second call could crash due to internal inconsistency - self.assertRaises(Exception, bzd.decompress, self.BAD_DATA * 30) - -+ def test_decompress_after_data_error(self): -+ data = bytes.fromhex( -+ "425a6839314159265359000000000000007fffff000000000000000000000000" -+ "00000000000000000000000000000000000000e0370000000000000000000000" -+ "000000000000000000000000000000000000000000000000000083f3" -+ ) -+ bzd = BZ2Decompressor() -+ with self.assertRaisesRegex(OSError, "Invalid data stream"): -+ bzd.decompress(data) -+ # Previously, a second call could crash due to internal inconsistency -+ self.assertFalse(bzd.needs_input) -+ self.assertFalse(bzd.eof) -+ with self.assertRaisesRegex(ValueError, "previous error"): -+ bzd.decompress(b'\x00' * 18) -+ - @support.refcount_test - def test_refleaks_in___init__(self): - gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount') -diff --git a/Misc/NEWS.d/next/Security/2026-05-30-09-36-20.gh-issue-150599.nlHqU-.rst b/Misc/NEWS.d/next/Security/2026-05-30-09-36-20.gh-issue-150599.nlHqU-.rst -new file mode 100644 -index 000000000000000..a37d86cf423f820 ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2026-05-30-09-36-20.gh-issue-150599.nlHqU-.rst -@@ -0,0 +1,3 @@ -+Fix a possible stack buffer overflow in :mod:`bz2` when a -+:class:`bz2.BZ2Decompressor` is reused after a decompression error. -+The decompressor now becomes unusable after libbz2 reports an error. -diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c -index 97bd44b4ac96944..0b0916142f575be 100644 ---- a/Modules/_bz2module.c -+++ b/Modules/_bz2module.c -@@ -114,6 +114,7 @@ typedef struct { - typedef struct { - PyObject_HEAD - bz_stream bzs; -+ int bzerror; - char eof; /* T_BOOL expects a char */ - PyObject *unused_data; - char needs_input; -@@ -453,8 +454,11 @@ decompress_buf(BZ2Decompressor *d, Py_ssize_t max_length) - - d->bzs_avail_in_real += bzs->avail_in; - -- if (catch_bz2_error(bzret)) -+ if (catch_bz2_error(bzret)) { -+ d->bzerror = bzret; -+ d->needs_input = 0; - goto error; -+ } - if (bzret == BZ_STREAM_END) { - d->eof = 1; - break; -@@ -621,10 +625,17 @@ _bz2_BZ2Decompressor_decompress_impl(BZ2Decompressor *self, Py_buffer *data, - PyObject *result = NULL; - - ACQUIRE_LOCK(self); -- if (self->eof) -+ if (self->eof) { - PyErr_SetString(PyExc_EOFError, "End of stream already reached"); -- else -+ } -+ else if (self->bzerror) { -+ // Re-entering BZ2_bzDecompress() after an error can write out of bounds. -+ PyErr_SetString(PyExc_ValueError, -+ "Decompressor is unusable after a previous error"); -+ } -+ else { - result = decompress(self, data->buf, data->len, max_length); -+ } - RELEASE_LOCK(self); - return result; - } -@@ -658,6 +669,7 @@ _bz2_BZ2Decompressor_impl(PyTypeObject *type) - return NULL; - } - -+ self->bzerror = 0; - self->needs_input = 1; - self->bzs_avail_in_real = 0; - self->input_buffer = NULL; diff --git a/package/python3/0017-Fix-symlink-escape-via-tarfile-hardlink-extraction-fallback.patch b/package/python3/0017-Fix-symlink-escape-via-tarfile-hardlink-extraction-fallback.patch deleted file mode 100644 index d5ec1626bf..0000000000 --- a/package/python3/0017-Fix-symlink-escape-via-tarfile-hardlink-extraction-fallback.patch +++ /dev/null @@ -1,77 +0,0 @@ -From d0ba16dce6df2f68e8968699d5c247eddeef9960 Mon Sep 17 00:00:00 2001 -From: Stan Ulbrych <[email protected]> -Date: Tue, 23 Jun 2026 14:31:38 +0100 -Subject: [PATCH] gh-151558: Fix symlink escape via `tarfile` - hardlink-extraction fallback (GH-151559) (cherry picked from commit - 27dd970bf6b17ebca7c8ed486a40ab043ed7af8f) - -Co-authored-by: Stan Ulbrych <[email protected]> - -Upstream: https://github.com/python/cpython/pull/152000 -CVE: CVE-2026-11940 -Signed-off-by: Titouan Christophe <[email protected]> ---- - Lib/tarfile.py | 3 +++ - Lib/test/test_tarfile.py | 24 +++++++++++++++++++ - ...-06-10-13-08-19.gh-issue-151558.mL74i2.rst | 3 +++ - 3 files changed, 30 insertions(+) - create mode 100644 Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst - -diff --git a/Lib/tarfile.py b/Lib/tarfile.py -index 461ec16dcd8592..73e9ece4153170 100755 ---- a/Lib/tarfile.py -+++ b/Lib/tarfile.py -@@ -2677,6 +2677,9 @@ def makelink_with_filter(self, tarinfo, targetpath, - "makelink_with_filter: if filter_function is not None, " - + "extraction_root must also not be None") - try: -+ filter_function( -+ unfiltered.replace(name=tarinfo.name, deep=False), -+ extraction_root) - filtered = filter_function(unfiltered, extraction_root) - except _FILTER_ERRORS as cause: - raise LinkFallbackError(tarinfo, unfiltered.name) from cause -diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py -index 9a4c5a301b30d0..902538d2262ceb 100644 ---- a/Lib/test/test_tarfile.py -+++ b/Lib/test/test_tarfile.py -@@ -4182,6 +4182,30 @@ def test_sneaky_hardlink_fallback(self): - self.expect_file("boom", symlink_to='../../link_here') - self.expect_file("c", symlink_to='b') - -+ @symlink_test -+ def test_sneaky_hardlink_fallback_deep(self): -+ # (CVE-2026-11940) -+ with ArchiveMaker() as arc: -+ arc.add("a/b/s", symlink_to=os.path.join("..", "escape")) -+ arc.add("s", hardlink_to=os.path.join("a", "b", "s")) -+ -+ with self.check_context(arc.open(), 'data'): -+ e = self.expect_exception( -+ tarfile.LinkFallbackError, -+ "link 's' would be extracted as a copy of " -+ + "'a/b/s', which was rejected") -+ self.assertIsInstance(e.__cause__, -+ tarfile.LinkOutsideDestinationError) -+ -+ for filter in 'tar', 'fully_trusted': -+ with self.subTest(filter), self.check_context(arc.open(), filter): -+ if not os_helper.can_symlink(): -+ self.expect_file("a/") -+ self.expect_file("a/b/") -+ else: -+ self.expect_file("a/b/s", symlink_to=os.path.join('..', 'escape')) -+ self.expect_file("s", symlink_to=os.path.join('..', 'escape')) -+ - @symlink_test - def test_exfiltration_via_symlink(self): - # (CVE-2025-4138) -diff --git a/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst b/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst -new file mode 100644 -index 00000000000000..74459d5680e21a ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst -@@ -0,0 +1,3 @@ -+Fixed an vulnerability in the :mod:`tarfile` ``data`` and ``tar`` extraction -+filters where crafted archives could create a symlink pointing outside the -+destination directory. This was a bypass of :cve:`2025-4330`. diff --git a/package/python3/python3.hash b/package/python3/python3.hash index 5b10f24f70..186addbf06 100644 --- a/package/python3/python3.hash +++ b/package/python3/python3.hash @@ -1,5 +1,5 @@ -# From https://www.python.org/downloads/release/python-31213/ -md5 b67dc5d55b27c98a36615f7d0dfa6e4c Python-3.12.13.tar.xz +# From https://www.python.org/downloads/release/python-31214/ +sha256 5c8462af5790baf43a321a1559dbe0db06d1be4300fb85fb53c40060668e548a Python-3.12.14.tar.xz # Locally computed sha256 c08bc65a81971c1dd5783182826503369466c7e67374d1646519adf05207b684 Python-3.12.13.tar.xz sha256 3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf LICENSE diff --git a/package/python3/python3.mk b/package/python3/python3.mk index 4fd16b4204..6266c8220a 100644 --- a/package/python3/python3.mk +++ b/package/python3/python3.mk @@ -5,7 +5,7 @@ ################################################################################ PYTHON3_VERSION_MAJOR = 3.12 -PYTHON3_VERSION = $(PYTHON3_VERSION_MAJOR).13 +PYTHON3_VERSION = $(PYTHON3_VERSION_MAJOR).14 PYTHON3_SOURCE = Python-$(PYTHON3_VERSION).tar.xz PYTHON3_SITE = https://python.org/ftp/python/$(PYTHON3_VERSION) PYTHON3_LICENSE = Python-2.0, others @@ -13,17 +13,6 @@ PYTHON3_LICENSE_FILES = LICENSE PYTHON3_CPE_ID_VENDOR = python PYTHON3_CPE_ID_PRODUCT = python -# 0013-Fix-O-n-2-canonical-ordering-in-unicodedata-normalize.patch -PYTHON3_IGNORE_CVES += CVE-2026-3276 -# 0014-tarfile-data_filter-validate-written-link.patch -PYTHON3_IGNORE_CVES += CVE-2026-7774 -# 0015-Apply-CVE-2021-4189-PASV-fix-to-ftplib-ftpcp.patch -PYTHON3_IGNORE_CVES += CVE-2026-8328 -# 0016-prevent-bz2-decompressor-reuse-after-errors.patch -PYTHON3_IGNORE_CVES += CVE-2026-9669 -# 0017-Fix-symlink-escape-via-tarfile-hardlink-extraction-fallback.patch -PYTHON3_IGNORE_CVES += CVE-2026-11940 - # This host Python is installed in $(HOST_DIR), as it is needed when # cross-compiling third-party Python modules. -- 2.55.0 _______________________________________________ buildroot mailing list [email protected] https://lists.buildroot.org/mailman/listinfo/buildroot