diff --git a/meta/recipes-devtools/python/python3/CVE-2026-9669.patch b/meta/recipes-devtools/python/python3/CVE-2026-9669.patch new file mode 100644 index 0000000000..266c8beef0 --- /dev/null +++ b/meta/recipes-devtools/python/python3/CVE-2026-9669.patch @@ -0,0 +1,96 @@ +From 5b412e1f7bdb3e0667b2bc8b216ad216d59d8373 Mon Sep 17 00:00:00 2001 +From: Stan Ulbrych +Date: Mon, 8 Jun 2026 11:55:32 +0200 +Subject: [PATCH] gh-150599: Prevent bz2 decompressor reuse after errors + (GH-150600) + +CVE: CVE-2026-9669 +Upstream-Status: Backport [https://github.com/python/cpython/commit/5755d0f083949ff3c5bf3a37e673e24e306b036e] + +Signed-off-by: Benjamin Robin +--- + Lib/test/test_bz2.py | 15 +++++++++++++++ + Modules/_bz2module.c | 18 +++++++++++++++--- + 2 files changed, 30 insertions(+), 3 deletions(-) + +diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py +index cb730a1a46e2..dcbf6a298264 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/Modules/_bz2module.c b/Modules/_bz2module.c +index 97bd44b4ac96..0b0916142f57 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; +-- +2.54.0 diff --git a/meta/recipes-devtools/python/python3_3.12.13.bb b/meta/recipes-devtools/python/python3_3.12.13.bb index 72daee1d0e..de174f7bfd 100644 --- a/meta/recipes-devtools/python/python3_3.12.13.bb +++ b/meta/recipes-devtools/python/python3_3.12.13.bb @@ -46,6 +46,7 @@ SRC_URI = "http://www.python.org/ftp/python/${PV}/Python-${PV}.tar.xz \ file://CVE-2026-4224.patch \ file://CVE-2026-11940.patch \ file://CVE-2026-11972.patch \ + file://CVE-2026-9669.patch \ " SRC_URI:append:class-native = " \