Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > linux.debian.bugs.dist > #1292600

Bug#1135877: trixie-pu: package python3.13/3.13.5-2+deb13u2

From Arnaud Rebillout <arnaudr@debian.org>
Newsgroups linux.debian.bugs.dist, linux.debian.devel.release
Subject Bug#1135877: trixie-pu: package python3.13/3.13.5-2+deb13u2
Date 2026-05-07 11:30 +0200
Message-ID <MS3do-3f68-5@gated-at.bofh.it> (permalink)
References <MRRvA-36qX-3@gated-at.bofh.it> <MRRvA-36qX-3@gated-at.bofh.it>
Organization linux.* mail to news gateway

Cross-posted to 2 groups.

Show all headers | View raw


Hello Moritz,

I intend to propose a similar upload for bookworm, so I was looking at 
your debdiff first.

May I ask: why not fixing CVE-2026-4786 as well? It is marked as fixed 
in the tracker, but it's only because it is introduced by the fix 
for CVE-2026-4519, which is not yet in trixie (it's in your debdiff here).

Best,

Arnaud

On 07/05/2026 03:50, Moritz Muehlenhoff wrote:
> Package: release.debian.org
> Severity: normal
> Tags: trixie
> X-Debbugs-Cc: python3.13@packages.debian.org, doko@debian.org
> Control: affects -1 + src:python3.13
> User: release.debian.org@packages.debian.org
> Usertags: pu
>
> This is a followup update to the +deb13u1 update already
> accepeted to trixie-proposed-updates a few weeks ago,
> which fixes a more low severity security issues, which
> have since then been backported to the upstream 3.13
> branch.
>
> Tests were fine and everything looks in order in the tests
> triggered in debusine as well. The debdiff below is relative
> to the version already on trixie-p-u.
>
> Cheers,
>          Moritz
>
> diff -Nru python3.13-3.13.5/debian/changelog python3.13-3.13.5/debian/changelog
> --- python3.13-3.13.5/debian/changelog	2026-04-06 14:24:14.000000000 +0200
> +++ python3.13-3.13.5/debian/changelog	2026-05-05 23:05:52.000000000 +0200
> @@ -1,3 +1,14 @@
> +python3.13 (3.13.5-2+deb13u2) trixie; urgency=medium
> +
> +  * CVE-2026-3446
> +  * CVE-2026-4224
> +  * CVE-2026-3644
> +  * CVE-2026-4519
> +  * CVE-2026-6019 (Closes: #1135116)
> +  * CVE-2026-6100
> +
> + -- Moritz Mühlenhoff <jmm@debian.org>  Tue, 05 May 2026 23:05:52 +0200
> +
>   python3.13 (3.13.5-2+deb13u1) trixie; urgency=medium
>   
>     * CVE-2025-11468 (Closes: #1126787)
> diff -Nru python3.13-3.13.5/debian/patches/CVE-2026-3446.patch python3.13-3.13.5/debian/patches/CVE-2026-3446.patch
> --- python3.13-3.13.5/debian/patches/CVE-2026-3446.patch	1970-01-01 01:00:00.000000000 +0100
> +++ python3.13-3.13.5/debian/patches/CVE-2026-3446.patch	2026-05-05 14:21:14.000000000 +0200
> @@ -0,0 +1,209 @@
> +From 1f9958f909c1b41a4ffc0b613ef8ec8fa5e7c474 Mon Sep 17 00:00:00 2001
> +From: "Miss Islington (bot)"
> + <31488909+miss-islington@users.noreply.github.com>
> +Date: Tue, 24 Mar 2026 00:52:20 +0100
> +Subject: [PATCH] [3.13] gh-145264: Do not ignore excess Base64 data after the
> + first padded quad (GH-145267) (GH-146326) (GH-146348)
> +
> +--- python3.13-3.13.5.orig/Lib/test/test_binascii.py
> ++++ python3.13-3.13.5/Lib/test/test_binascii.py
> +@@ -143,17 +143,16 @@ class BinASCIITest(unittest.TestCase):
> +             _assertRegexTemplate(r'(?i)Excess padding', data, non_strict_mode_expected_result)
> +
> +         # Test excess data exceptions
> +-        assertExcessData(b'ab==a', b'i')
> +-        assertExcessData(b'ab===', b'i')
> +-        assertExcessData(b'ab====', b'i')
> +-        assertExcessData(b'ab==:', b'i')
> +-        assertExcessData(b'abc=a', b'i\xb7')
> +-        assertExcessData(b'abc=:', b'i\xb7')
> +-        assertExcessData(b'ab==\n', b'i')
> +-        assertExcessData(b'abc==', b'i\xb7')
> +-        assertExcessData(b'abc===', b'i\xb7')
> +-        assertExcessData(b'abc====', b'i\xb7')
> +-        assertExcessData(b'abc=====', b'i\xb7')
> ++        assertExcessPadding(b'ab===', b'i')
> ++        assertExcessPadding(b'ab====', b'i')
> ++        assertNonBase64Data(b'ab==:', b'i')
> ++        assertExcessData(b'abc=a', b'i\xb7\x1a')
> ++        assertNonBase64Data(b'abc=:', b'i\xb7')
> ++        assertNonBase64Data(b'ab==\n', b'i')
> ++        assertExcessPadding(b'abc==', b'i\xb7')
> ++        assertExcessPadding(b'abc===', b'i\xb7')
> ++        assertExcessPadding(b'abc====', b'i\xb7')
> ++        assertExcessPadding(b'abc=====', b'i\xb7')
> +
> +         # Test non-base64 data exceptions
> +         assertNonBase64Data(b'\nab==', b'i')
> +@@ -175,6 +174,20 @@ class BinASCIITest(unittest.TestCase):
> +         assertExcessPadding(b'abcd====', b'i\xb7\x1d')
> +         assertExcessPadding(b'abcd=====', b'i\xb7\x1d')
> +
> ++    def test_base64_excess_data(self):
> ++        # Test excess data exceptions
> ++        def assertExcessData(data, expected):
> ++            assert_regex = r'(?i)Excess data'
> ++            data = self.type2test(data)
> ++            with self.assertRaisesRegex(binascii.Error, assert_regex):
> ++                binascii.a2b_base64(data, strict_mode=True)
> ++            self.assertEqual(binascii.a2b_base64(data, strict_mode=False),
> ++                             expected)
> ++            self.assertEqual(binascii.a2b_base64(data), expected)
> ++
> ++        assertExcessData(b'ab==c=', b'i\xb7')
> ++        assertExcessData(b'ab==cd', b'i\xb7\x1d')
> ++        assertExcessData(b'abc=d', b'i\xb7\x1d')
> +
> +     def test_base64errors(self):
> +         # Test base64 with invalid padding
> +--- python3.13-3.13.5.orig/Modules/binascii.c
> ++++ python3.13-3.13.5/Modules/binascii.c
> +@@ -383,7 +383,6 @@ binascii_a2b_base64_impl(PyObject *modul
> +     const unsigned char *ascii_data = data->buf;
> +     size_t ascii_len = data->len;
> +     binascii_state *state = NULL;
> +-    char padding_started = 0;
> +
> +     /* Allocate the buffer */
> +     Py_ssize_t bin_len = ((ascii_len+3)/4)*3; /* Upper bound, corrected later */
> +@@ -394,14 +393,6 @@ binascii_a2b_base64_impl(PyObject *modul
> +         return NULL;
> +     unsigned char *bin_data_start = bin_data;
> +
> +-    if (strict_mode && ascii_len > 0 && ascii_data[0] == '=') {
> +-        state = get_binascii_state(module);
> +-        if (state) {
> +-            PyErr_SetString(state->Error, "Leading padding not allowed");
> +-        }
> +-        goto error_end;
> +-    }
> +-
> +     int quad_pos = 0;
> +     unsigned char leftchar = 0;
> +     int pads = 0;
> +@@ -412,35 +403,34 @@ binascii_a2b_base64_impl(PyObject *modul
> +         ** the invalid ones.
> +         */
> +         if (this_ch == BASE64_PAD) {
> +-            padding_started = 1;
> +-
> +-            if (strict_mode && quad_pos == 0) {
> +-                state = get_binascii_state(module);
> +-                if (state) {
> +-                    PyErr_SetString(state->Error, "Excess padding not allowed");
> +-                }
> +-                goto error_end;
> ++            pads++;
> ++            if (quad_pos >= 2 && quad_pos + pads <= 4) {
> ++                continue;
> +             }
> +-            if (quad_pos >= 2 && quad_pos + ++pads >= 4) {
> +-                /* A pad sequence means we should not parse more input.
> +-                ** We've already interpreted the data from the quad at this point.
> +-                ** in strict mode, an error should raise if there's excess data after the padding.
> +-                */
> +-                if (strict_mode && i + 1 < ascii_len) {
> +-                    state = get_binascii_state(module);
> +-                    if (state) {
> +-                        PyErr_SetString(state->Error, "Excess data after padding");
> +-                    }
> +-                    goto error_end;
> +-                }
> +-
> +-                goto done;
> ++            // See RFC 4648, section-3.3: "specifications MAY ignore the
> ++            // pad character, "=", treating it as non-alphabet data, if
> ++            // it is present before the end of the encoded data" and
> ++            // "the excess pad characters MAY also be ignored."
> ++            if (!strict_mode) {
> ++                continue;
> +             }
> +-            continue;
> ++            if (quad_pos == 1) {
> ++                /* Set an error below. */
> ++                break;
> ++            }
> ++            state = get_binascii_state(module);
> ++            if (state) {
> ++                PyErr_SetString(state->Error,
> ++                                (quad_pos == 0 && i == 0)
> ++                                ? "Leading padding not allowed"
> ++                                : "Excess padding not allowed");
> ++            }
> ++            goto error_end;
> +         }
> +
> +         this_ch = table_a2b_base64[this_ch];
> +         if (this_ch >= 64) {
> ++            // See RFC 4648, section-3.3.
> +             if (strict_mode) {
> +                 state = get_binascii_state(module);
> +                 if (state) {
> +@@ -451,11 +441,14 @@ binascii_a2b_base64_impl(PyObject *modul
> +             continue;
> +         }
> +
> +-        // Characters that are not '=', in the middle of the padding, are not allowed
> +-        if (strict_mode && padding_started) {
> ++        // Characters that are not '=', in the middle of the padding, are
> ++        // not allowed (except when they are). See RFC 4648, section-3.3.
> ++        if (pads && strict_mode) {
> +             state = get_binascii_state(module);
> +             if (state) {
> +-                PyErr_SetString(state->Error, "Discontinuous padding not allowed");
> ++                PyErr_SetString(state->Error, (quad_pos + pads == 4)
> ++                    ? "Excess data after padding"
> ++                    : "Discontinuous padding not allowed");
> +             }
> +             goto error_end;
> +         }
> +@@ -484,31 +477,35 @@ binascii_a2b_base64_impl(PyObject *modul
> +         }
> +     }
> +
> +-    if (quad_pos != 0) {
> ++    if (quad_pos == 1) {
> ++        /* There is exactly one extra valid, non-padding, base64 character.
> ++         * * This is an invalid length, as there is no possible input that
> ++         ** could encoded into such a base64 string.
> ++         */
> +         state = get_binascii_state(module);
> +-        if (state == NULL) {
> +-            /* error already set, from get_binascii_state */
> +-        } else if (quad_pos == 1) {
> +-            /*
> +-            ** There is exactly one extra valid, non-padding, base64 character.
> +-            ** This is an invalid length, as there is no possible input that
> +-            ** could encoded into such a base64 string.
> +-            */
> ++        if (state) {
> +             PyErr_Format(state->Error,
> +                          "Invalid base64-encoded string: "
> +                          "number of data characters (%zd) cannot be 1 more "
> +                          "than a multiple of 4",
> +                          (bin_data - bin_data_start) / 3 * 4 + 1);
> +-        } else {
> ++        }
> ++        goto error_end;
> ++    }
> ++
> ++    if (quad_pos != 0 && quad_pos + pads < 4) {
> ++        state = get_binascii_state(module);
> ++        if (state) {
> +             PyErr_SetString(state->Error, "Incorrect padding");
> +         }
> +-        error_end:
> +-        _PyBytesWriter_Dealloc(&writer);
> +-        return NULL;
> ++        goto error_end;
> +     }
> +
> +-done:
> +     return _PyBytesWriter_Finish(&writer, bin_data);
> ++
> ++error_end:
> ++    _PyBytesWriter_Dealloc(&writer);
> ++    return NULL;
> + }
> +
> +
> diff -Nru python3.13-3.13.5/debian/patches/CVE-2026-3644.patch python3.13-3.13.5/debian/patches/CVE-2026-3644.patch
> --- python3.13-3.13.5/debian/patches/CVE-2026-3644.patch	1970-01-01 01:00:00.000000000 +0100
> +++ python3.13-3.13.5/debian/patches/CVE-2026-3644.patch	2026-05-05 23:01:49.000000000 +0200
> @@ -0,0 +1,124 @@
> +From d16ecc6c3626f0e2cc8f08c309c83934e8a979dd Mon Sep 17 00:00:00 2001
> +From: "Miss Islington (bot)"
> + <31488909+miss-islington@users.noreply.github.com>
> +Date: Mon, 16 Mar 2026 15:05:13 +0100
> +Subject: [PATCH] [3.13] gh-145599, CVE 2026-3644: Reject control characters in
> + `http.cookies.Morsel.update()` (GH-145600) (#146024)
> +
> +--- python3.13-3.13.5.orig/Lib/http/cookies.py
> ++++ python3.13-3.13.5/Lib/http/cookies.py
> +@@ -335,9 +335,16 @@ class Morsel(dict):
> +             key = key.lower()
> +             if key not in self._reserved:
> +                 raise CookieError("Invalid attribute %r" % (key,))
> ++            if _has_control_character(key, val):
> ++                raise CookieError("Control characters are not allowed in "
> ++                                  f"cookies {key!r} {val!r}")
> +             data[key] = val
> +         dict.update(self, data)
> +
> ++    def __ior__(self, values):
> ++        self.update(values)
> ++        return self
> ++
> +     def isReservedKey(self, K):
> +         return K.lower() in self._reserved
> +
> +@@ -363,9 +370,15 @@ class Morsel(dict):
> +         }
> +
> +     def __setstate__(self, state):
> +-        self._key = state['key']
> +-        self._value = state['value']
> +-        self._coded_value = state['coded_value']
> ++        key = state['key']
> ++        value = state['value']
> ++        coded_value = state['coded_value']
> ++        if _has_control_character(key, value, coded_value):
> ++            raise CookieError("Control characters are not allowed in cookies "
> ++                              f"{key!r} {value!r} {coded_value!r}")
> ++        self._key = key
> ++        self._value = value
> ++        self._coded_value = coded_value
> +
> +     def output(self, attrs=None, header="Set-Cookie:"):
> +         return "%s %s" % (header, self.OutputString(attrs))
> +@@ -377,13 +390,16 @@ class Morsel(dict):
> +
> +     def js_output(self, attrs=None):
> +         # Print javascript
> ++        output_string = self.OutputString(attrs)
> ++        if _has_control_character(output_string):
> ++            raise CookieError("Control characters are not allowed in cookies")
> +         return """
> +         <script type="text/javascript">
> +         <!-- begin hiding
> +         document.cookie = \"%s\";
> +         // end hiding -->
> +         </script>
> +-        """ % (self.OutputString(attrs).replace('"', r'\"'))
> ++        """ % (output_string.replace('"', r'\"'))
> +
> +     def OutputString(self, attrs=None):
> +         # Build up our result
> +--- python3.13-3.13.5.orig/Lib/test/test_http_cookies.py
> ++++ python3.13-3.13.5/Lib/test/test_http_cookies.py
> +@@ -574,6 +574,14 @@ class MorselTests(unittest.TestCase):
> +             with self.assertRaises(cookies.CookieError):
> +                 morsel["path"] = c0
> +
> ++            # .__setstate__()
> ++            with self.assertRaises(cookies.CookieError):
> ++                morsel.__setstate__({'key': c0, 'value': 'val', 'coded_value': 'coded'})
> ++            with self.assertRaises(cookies.CookieError):
> ++                morsel.__setstate__({'key': 'key', 'value': c0, 'coded_value': 'coded'})
> ++            with self.assertRaises(cookies.CookieError):
> ++                morsel.__setstate__({'key': 'key', 'value': 'val', 'coded_value': c0})
> ++
> +             # .setdefault()
> +             with self.assertRaises(cookies.CookieError):
> +                 morsel.setdefault("path", c0)
> +@@ -588,6 +596,18 @@ class MorselTests(unittest.TestCase):
> +             with self.assertRaises(cookies.CookieError):
> +                 morsel.set("path", "val", c0)
> +
> ++            # .update()
> ++            with self.assertRaises(cookies.CookieError):
> ++                morsel.update({"path": c0})
> ++            with self.assertRaises(cookies.CookieError):
> ++                morsel.update({c0: "val"})
> ++
> ++            # .__ior__()
> ++            with self.assertRaises(cookies.CookieError):
> ++                morsel |= {"path": c0}
> ++            with self.assertRaises(cookies.CookieError):
> ++                morsel |= {c0: "val"}
> ++
> +     def test_control_characters_output(self):
> +         # Tests that even if the internals of Morsel are modified
> +         # that a call to .output() has control character safeguards.
> +@@ -608,6 +628,24 @@ class MorselTests(unittest.TestCase):
> +             with self.assertRaises(cookies.CookieError):
> +                 cookie.output()
> +
> ++        # Tests that .js_output() also has control character safeguards.
> ++        for c0 in support.control_characters_c0():
> ++            morsel = cookies.Morsel()
> ++            morsel.set("key", "value", "coded-value")
> ++            morsel._key = c0  # Override private variable.
> ++            cookie = cookies.SimpleCookie()
> ++            cookie["cookie"] = morsel
> ++            with self.assertRaises(cookies.CookieError):
> ++                cookie.js_output()
> ++
> ++            morsel = cookies.Morsel()
> ++            morsel.set("key", "value", "coded-value")
> ++            morsel._coded_value = c0  # Override private variable.
> ++            cookie = cookies.SimpleCookie()
> ++            cookie["cookie"] = morsel
> ++            with self.assertRaises(cookies.CookieError):
> ++                cookie.js_output()
> ++
> +
> + def load_tests(loader, tests, pattern):
> +     tests.addTest(doctest.DocTestSuite(cookies))
> diff -Nru python3.13-3.13.5/debian/patches/CVE-2026-4224.patch python3.13-3.13.5/debian/patches/CVE-2026-4224.patch
> --- python3.13-3.13.5/debian/patches/CVE-2026-4224.patch	1970-01-01 01:00:00.000000000 +0100
> +++ python3.13-3.13.5/debian/patches/CVE-2026-4224.patch	2026-05-05 14:48:22.000000000 +0200
> @@ -0,0 +1,71 @@
> +From 196edfb06a7458377d4d0f4b3cd41724c1f3bd4a Mon Sep 17 00:00:00 2001
> +From: "Miss Islington (bot)"
> + <31488909+miss-islington@users.noreply.github.com>
> +Date: Mon, 16 Mar 2026 10:09:27 +0100
> +Subject: [PATCH] [3.13] gh-145986: Avoid unbound C recursion in
> + `conv_content_model` in `pyexpat.c` (CVE 2026-4224) (GH-145987) (#145996)
> +
> +--- python3.13-3.13.5.orig/Lib/test/test_pyexpat.py
> ++++ python3.13-3.13.5/Lib/test/test_pyexpat.py
> +@@ -668,6 +668,22 @@ class ChardataBufferTest(unittest.TestCa
> +         parser.Parse(xml2, True)
> +         self.assertEqual(self.n, 4)
> +
> ++    def test_deeply_nested_content_model(self):
> ++        # This should raise a RecursionError and not crash.
> ++        # See https://github.com/python/cpython/issues/145986.
> ++        N = 500_000
> ++        data = (
> ++            b'<!DOCTYPE root [\n<!ELEMENT root '
> ++            + b'(a, ' * N + b'a' + b')' * N
> ++            + b'>\n]>\n<root/>\n'
> ++        )
> ++
> ++        parser = expat.ParserCreate()
> ++        parser.ElementDeclHandler = lambda _1, _2: None
> ++        with support.infinite_recursion():
> ++            with self.assertRaises(RecursionError):
> ++                parser.Parse(data)
> ++
> + class MalformedInputTest(unittest.TestCase):
> +     def test1(self):
> +         xml = b"\0\r\n"
> +--- python3.13-3.13.5.orig/Modules/pyexpat.c
> ++++ python3.13-3.13.5/Modules/pyexpat.c
> +@@ -3,6 +3,7 @@
> + #endif
> +
> + #include "Python.h"
> ++#include "pycore_ceval.h"         // _Py_EnterRecursiveCall()
> + #include "pycore_import.h"        // _PyImport_SetModule()
> + #include "pycore_pyhash.h"        // _Py_HashSecret
> + #include "pycore_traceback.h"     // _PyTraceback_Add()
> +@@ -522,6 +523,10 @@ static PyObject *
> + conv_content_model(XML_Content * const model,
> +                    PyObject *(*conv_string)(const XML_Char *))
> + {
> ++    if (_Py_EnterRecursiveCall(" in conv_content_model")) {
> ++        return NULL;
> ++    }
> ++
> +     PyObject *result = NULL;
> +     PyObject *children = PyTuple_New(model->numchildren);
> +     int i;
> +@@ -533,7 +538,7 @@ conv_content_model(XML_Content * const m
> +                                                  conv_string);
> +             if (child == NULL) {
> +                 Py_XDECREF(children);
> +-                return NULL;
> ++                goto done;
> +             }
> +             PyTuple_SET_ITEM(children, i, child);
> +         }
> +@@ -541,6 +546,8 @@ conv_content_model(XML_Content * const m
> +                                model->type, model->quant,
> +                                conv_string,model->name, children);
> +     }
> ++done:
> ++    _Py_LeaveRecursiveCall();
> +     return result;
> + }
> +
> diff -Nru python3.13-3.13.5/debian/patches/CVE-2026-4519.patch python3.13-3.13.5/debian/patches/CVE-2026-4519.patch
> --- python3.13-3.13.5/debian/patches/CVE-2026-4519.patch	1970-01-01 01:00:00.000000000 +0100
> +++ python3.13-3.13.5/debian/patches/CVE-2026-4519.patch	2026-05-05 23:03:56.000000000 +0200
> @@ -0,0 +1,206 @@
> +From 43fe06b96f6a6cf5cfd5bdab20b8649374956866 Mon Sep 17 00:00:00 2001
> +From: "Miss Islington (bot)"
> + <31488909+miss-islington@users.noreply.github.com>
> +Date: Tue, 24 Mar 2026 00:17:50 +0100
> +Subject: [PATCH] [3.13] gh-143930: Reject leading dashes in webbrowser URLs
> + (GH-146215)
> +
> +From 89bfb8e5ed3c7caa241028f1a4eac5f6275a46a4 Mon Sep 17 00:00:00 2001
> +From: =?UTF-8?q?=C5=81ukasz=20Langa?= <lukasz@langa.pl>
> +Date: Fri, 3 Apr 2026 19:45:02 +0200
> +Subject: [PATCH] [3.13] gh-143930: Tweak the exception message and increase
> + test coverage (GH-146476) (GH-148045)
> +
> +From d6d68494be70bdbda20f89f83801ba52ec37daa4 Mon Sep 17 00:00:00 2001
> +From: "Miss Islington (bot)"
> + <31488909+miss-islington@users.noreply.github.com>
> +Date: Wed, 29 Apr 2026 12:00:10 +0200
> +Subject: [PATCH] [3.13] gh-148169: Fix webbrowser `%action` substitution
> + bypass of dash-prefix check (GH-148170) (#148517)
> +
> +
> +--- python3.13-3.13.5.orig/Lib/test/test_webbrowser.py
> ++++ python3.13-3.13.5/Lib/test/test_webbrowser.py
> +@@ -1,3 +1,4 @@
> ++import io
> + import os
> + import re
> + import shlex
> +@@ -55,6 +56,14 @@ class CommandTestMixin:
> +             popen_args.pop(popen_args.index(option))
> +         self.assertEqual(popen_args, arguments)
> +
> ++    def test_reject_dash_prefixes(self):
> ++        browser = self.browser_class(name=CMD_NAME)
> ++        with self.assertRaisesRegex(
> ++            ValueError,
> ++            r"^Invalid URL \(leading dash disallowed\): '--key=val http.*'$"
> ++        ):
> ++            browser.open(f"--key=val {URL}")
> ++
> +
> + class GenericBrowserCommandTest(CommandTestMixin, unittest.TestCase):
> +
> +@@ -109,6 +118,15 @@ class ChromeCommandTest(CommandTestMixin
> +                        arguments=[URL],
> +                        kw=dict(new=999))
> +
> ++    def test_reject_action_dash_prefixes(self):
> ++        browser = self.browser_class(name=CMD_NAME)
> ++        with self.assertRaises(ValueError):
> ++            browser.open('%action--incognito')
> ++        # new=1: action is "--new-window", so "%action" itself expands to
> ++        # a dash-prefixed flag even with no dash in the original URL.
> ++        with self.assertRaises(ValueError):
> ++            browser.open('%action', new=1)
> ++
> +
> + class EdgeCommandTest(CommandTestMixin, unittest.TestCase):
> +
> +@@ -301,6 +319,72 @@ class IOSBrowserTest(unittest.TestCase):
> +         self._test('open_new_tab')
> +
> +
> ++class MockPopenPipe:
> ++    def __init__(self, cmd, mode):
> ++        self.cmd = cmd
> ++        self.mode = mode
> ++        self.pipe = io.StringIO()
> ++        self._closed = False
> ++
> ++    def write(self, buf):
> ++        self.pipe.write(buf)
> ++
> ++    def close(self):
> ++        self._closed = True
> ++        return None
> ++
> ++
> ++@unittest.skipUnless(sys.platform == "darwin", "macOS specific test")
> ++@requires_subprocess()
> ++class MacOSXOSAScriptTest(unittest.TestCase):
> ++    def setUp(self):
> ++        # Ensure that 'BROWSER' is not set to 'open' or something else.
> ++        # See: https://github.com/python/cpython/issues/131254.
> ++        env = self.enterContext(os_helper.EnvironmentVarGuard())
> ++        env.unset("BROWSER")
> ++
> ++        support.patch(self, os, "popen", self.mock_popen)
> ++        self.browser = webbrowser.MacOSXOSAScript("default")
> ++
> ++    def mock_popen(self, cmd, mode):
> ++        self.popen_pipe = MockPopenPipe(cmd, mode)
> ++        return self.popen_pipe
> ++
> ++    def test_default(self):
> ++        browser = webbrowser.get()
> ++        assert isinstance(browser, webbrowser.MacOSXOSAScript)
> ++        self.assertEqual(browser.name, "default")
> ++
> ++    def test_default_open(self):
> ++        url = "https://python.org"
> ++        self.browser.open(url)
> ++        self.assertTrue(self.popen_pipe._closed)
> ++        self.assertEqual(self.popen_pipe.cmd, "osascript")
> ++        script = self.popen_pipe.pipe.getvalue()
> ++        self.assertEqual(script.strip(), f'open location "{url}"')
> ++
> ++    def test_url_quote(self):
> ++        self.browser.open('https://python.org/"quote"')
> ++        script = self.popen_pipe.pipe.getvalue()
> ++        self.assertEqual(
> ++            script.strip(), 'open location "https://python.org/%22quote%22"'
> ++        )
> ++
> ++    def test_explicit_browser(self):
> ++        browser = webbrowser.MacOSXOSAScript("safari")
> ++        browser.open("https://python.org")
> ++        script = self.popen_pipe.pipe.getvalue()
> ++        self.assertIn('tell application "safari"', script)
> ++        self.assertIn('open location "https://python.org"', script)
> ++
> ++    def test_reject_dash_prefixes(self):
> ++        with self.assertRaisesRegex(
> ++            ValueError,
> ++            r"^Invalid URL \(leading dash disallowed\): '--key=val http.*'$"
> ++        ):
> ++            self.browser.open(f"--key=val {URL}")
> ++
> ++
> + class BrowserRegistrationTest(unittest.TestCase):
> +
> +     def setUp(self):
> +--- python3.13-3.13.5.orig/Lib/webbrowser.py
> ++++ python3.13-3.13.5/Lib/webbrowser.py
> +@@ -164,6 +164,12 @@ class BaseBrowser:
> +     def open_new_tab(self, url):
> +         return self.open(url, 2)
> +
> ++    @staticmethod
> ++    def _check_url(url):
> ++        """Ensures that the URL is safe to pass to subprocesses as a parameter"""
> ++        if url and url.lstrip().startswith("-"):
> ++            raise ValueError(f"Invalid URL (leading dash disallowed): {url!r}")
> ++
> +
> + class GenericBrowser(BaseBrowser):
> +     """Class for all browsers started with a command
> +@@ -181,6 +187,7 @@ class GenericBrowser(BaseBrowser):
> +
> +     def open(self, url, new=0, autoraise=True):
> +         sys.audit("webbrowser.open", url)
> ++        self._check_url(url)
> +         cmdline = [self.name] + [arg.replace("%s", url)
> +                                  for arg in self.args]
> +         try:
> +@@ -201,6 +208,7 @@ class BackgroundBrowser(GenericBrowser):
> +         cmdline = [self.name] + [arg.replace("%s", url)
> +                                  for arg in self.args]
> +         sys.audit("webbrowser.open", url)
> ++        self._check_url(url)
> +         try:
> +             if sys.platform[:3] == 'win':
> +                 p = subprocess.Popen(cmdline)
> +@@ -280,7 +288,9 @@ class UnixBrowser(BaseBrowser):
> +             raise Error("Bad 'new' parameter to open(); "
> +                         f"expected 0, 1, or 2, got {new}")
> +
> +-        args = [arg.replace("%s", url).replace("%action", action)
> ++        self._check_url(url.replace("%action", action))
> ++
> ++        args = [arg.replace("%action", action).replace("%s", url)
> +                 for arg in self.remote_args]
> +         args = [arg for arg in args if arg]
> +         success = self._invoke(args, True, autoraise, url)
> +@@ -358,6 +368,7 @@ class Konqueror(BaseBrowser):
> +
> +     def open(self, url, new=0, autoraise=True):
> +         sys.audit("webbrowser.open", url)
> ++        self._check_url(url)
> +         # XXX Currently I know no way to prevent KFM from opening a new win.
> +         if new == 2:
> +             action = "newTab"
> +@@ -576,6 +587,7 @@ if sys.platform[:3] == "win":
> +     class WindowsDefault(BaseBrowser):
> +         def open(self, url, new=0, autoraise=True):
> +             sys.audit("webbrowser.open", url)
> ++            self._check_url(url)
> +             try:
> +                 os.startfile(url)
> +             except OSError:
> +@@ -596,6 +608,7 @@ if sys.platform == 'darwin':
> +
> +         def open(self, url, new=0, autoraise=True):
> +             sys.audit("webbrowser.open", url)
> ++            self._check_url(url)
> +             url = url.replace('"', '%22')
> +             if self.name == 'default':
> +                 script = f'open location "{url}"'  # opens in default browser
> +@@ -627,6 +640,7 @@ if sys.platform == "ios":
> +     class IOSBrowser(BaseBrowser):
> +         def open(self, url, new=0, autoraise=True):
> +             sys.audit("webbrowser.open", url)
> ++            self._check_url(url)
> +             # If ctypes isn't available, we can't open a browser
> +             if objc is None:
> +                 return False
> diff -Nru python3.13-3.13.5/debian/patches/CVE-2026-6019.patch python3.13-3.13.5/debian/patches/CVE-2026-6019.patch
> --- python3.13-3.13.5/debian/patches/CVE-2026-6019.patch	1970-01-01 01:00:00.000000000 +0100
> +++ python3.13-3.13.5/debian/patches/CVE-2026-6019.patch	2026-05-05 23:04:58.000000000 +0200
> @@ -0,0 +1,109 @@
> +From 3c59b8b53fc75c7f9578d16fb8201ceb43e8f76c Mon Sep 17 00:00:00 2001
> +From: "Miss Islington (bot)"
> + <31488909+miss-islington@users.noreply.github.com>
> +Date: Thu, 23 Apr 2026 15:05:17 +0200
> +Subject: [PATCH] [3.13] gh-90309: Base64-encode cookie values embedded in JS
> + (GH-148888)
> +
> +--- python3.13-3.13.5.orig/Lib/http/cookies.py
> ++++ python3.13-3.13.5/Lib/http/cookies.py
> +@@ -389,17 +389,21 @@ class Morsel(dict):
> +         return '<%s: %s>' % (self.__class__.__name__, self.OutputString())
> +
> +     def js_output(self, attrs=None):
> ++        import base64
> +         # Print javascript
> +         output_string = self.OutputString(attrs)
> +         if _has_control_character(output_string):
> +             raise CookieError("Control characters are not allowed in cookies")
> ++        # Base64-encode value to avoid template
> ++        # injection in cookie values.
> ++        output_encoded = base64.b64encode(output_string.encode('utf-8')).decode("ascii")
> +         return """
> +         <script type="text/javascript">
> +         <!-- begin hiding
> +-        document.cookie = \"%s\";
> ++        document.cookie = atob(\"%s\");
> +         // end hiding -->
> +         </script>
> +-        """ % (output_string.replace('"', r'\"'))
> ++        """ % (output_encoded,)
> +
> +     def OutputString(self, attrs=None):
> +         # Build up our result
> +--- python3.13-3.13.5.orig/Lib/test/test_http_cookies.py
> ++++ python3.13-3.13.5/Lib/test/test_http_cookies.py
> +@@ -1,5 +1,5 @@
> + # Simple test suite for http/cookies.py
> +-
> ++import base64
> + import copy
> + import unittest
> + import doctest
> +@@ -153,17 +153,19 @@ class CookieTests(unittest.TestCase, Ext
> +
> +         self.assertEqual(C.output(['path']),
> +             'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
> +-        self.assertEqual(C.js_output(), r"""
> ++        cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme; Version=1').decode('ascii')
> ++        self.assertEqual(C.js_output(), fr"""
> +         <script type="text/javascript">
> +         <!-- begin hiding
> +-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1";
> ++        document.cookie = atob("{cookie_encoded}");
> +         // end hiding -->
> +         </script>
> +         """)
> +-        self.assertEqual(C.js_output(['path']), r"""
> ++        cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme').decode('ascii')
> ++        self.assertEqual(C.js_output(['path']), fr"""
> +         <script type="text/javascript">
> +         <!-- begin hiding
> +-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme";
> ++        document.cookie = atob("{cookie_encoded}");
> +         // end hiding -->
> +         </script>
> +         """)
> +@@ -260,17 +262,19 @@ class CookieTests(unittest.TestCase, Ext
> +
> +         self.assertEqual(C.output(['path']),
> +                          'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
> +-        self.assertEqual(C.js_output(), r"""
> ++        expected_encoded_cookie = base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1').decode('ascii')
> ++        self.assertEqual(C.js_output(), fr"""
> +         <script type="text/javascript">
> +         <!-- begin hiding
> +-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1";
> ++        document.cookie = atob("{expected_encoded_cookie}");
> +         // end hiding -->
> +         </script>
> +         """)
> +-        self.assertEqual(C.js_output(['path']), r"""
> ++        expected_encoded_cookie = base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme').decode('ascii')
> ++        self.assertEqual(C.js_output(['path']), fr"""
> +         <script type="text/javascript">
> +         <!-- begin hiding
> +-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme";
> ++        document.cookie = atob("{expected_encoded_cookie}");
> +         // end hiding -->
> +         </script>
> +         """)
> +@@ -361,13 +365,16 @@ class MorselTests(unittest.TestCase):
> +             self.assertEqual(
> +                 M.output(),
> +                 "Set-Cookie: %s=%s; Path=/foo" % (i, "%s_coded_val" % i))
> ++            expected_encoded_cookie = base64.b64encode(
> ++                ("%s=%s; Path=/foo" % (i, "%s_coded_val" % i)).encode("ascii")
> ++            ).decode('ascii')
> +             expected_js_output = """
> +         <script type="text/javascript">
> +         <!-- begin hiding
> +-        document.cookie = "%s=%s; Path=/foo";
> ++        document.cookie = atob("%s");
> +         // end hiding -->
> +         </script>
> +-        """ % (i, "%s_coded_val" % i)
> ++        """ % (expected_encoded_cookie,)
> +             self.assertEqual(M.js_output(), expected_js_output)
> +         for i in ["foo bar", "foo@bar"]:
> +             # Try some illegal characters
> diff -Nru python3.13-3.13.5/debian/patches/CVE-2026-6100.patch python3.13-3.13.5/debian/patches/CVE-2026-6100.patch
> --- python3.13-3.13.5/debian/patches/CVE-2026-6100.patch	1970-01-01 01:00:00.000000000 +0100
> +++ python3.13-3.13.5/debian/patches/CVE-2026-6100.patch	2026-05-05 23:05:45.000000000 +0200
> @@ -0,0 +1,37 @@
> +From c3cf71c3366fe49acb776a639405c0eea6169c20 Mon Sep 17 00:00:00 2001
> +From: "Miss Islington (bot)"
> + <31488909+miss-islington@users.noreply.github.com>
> +Date: Mon, 13 Apr 2026 03:35:24 +0200
> +Subject: [PATCH] [3.13] gh-148395: Fix a possible UAF in
> + `{LZMA,BZ2,_Zlib}Decompressor` (GH-148396) (#148479)
> +
> +--- python3.13-3.13.5.orig/Modules/_bz2module.c
> ++++ python3.13-3.13.5/Modules/_bz2module.c
> +@@ -589,6 +589,7 @@ decompress(BZ2Decompressor *d, char *dat
> +     return result;
> +
> + error:
> ++    bzs->next_in = NULL;
> +     Py_XDECREF(result);
> +     return NULL;
> + }
> +--- python3.13-3.13.5.orig/Modules/_lzmamodule.c
> ++++ python3.13-3.13.5/Modules/_lzmamodule.c
> +@@ -1112,6 +1112,7 @@ decompress(Decompressor *d, uint8_t *dat
> +     return result;
> +
> + error:
> ++    lzs->next_in = NULL;
> +     Py_XDECREF(result);
> +     return NULL;
> + }
> +--- python3.13-3.13.5.orig/Modules/zlibmodule.c
> ++++ python3.13-3.13.5/Modules/zlibmodule.c
> +@@ -1648,6 +1648,7 @@ decompress(ZlibDecompressor *self, uint8
> +     return result;
> +
> + error:
> ++    self->zst.next_in = NULL;
> +     Py_XDECREF(result);
> +     return NULL;
> + }
> diff -Nru python3.13-3.13.5/debian/patches/series python3.13-3.13.5/debian/patches/series
> --- python3.13-3.13.5/debian/patches/series	2026-04-06 14:23:46.000000000 +0200
> +++ python3.13-3.13.5/debian/patches/series	2026-05-05 23:05:32.000000000 +0200
> @@ -42,3 +42,9 @@
>   CVE-2026-0865.patch
>   CVE-2026-1299.patch
>   CVE-2026-2297.patch
> +CVE-2026-3446.patch
> +CVE-2026-4224.patch
> +CVE-2026-3644.patch
> +CVE-2026-4519.patch
> +CVE-2026-6019.patch
> +CVE-2026-6100.patch

Back to linux.debian.bugs.dist | Previous | NextNext in thread | Find similar


Thread

Bug#1135877: trixie-pu: package python3.13/3.13.5-2+deb13u2 Arnaud Rebillout <arnaudr@debian.org> - 2026-05-07 11:30 +0200
  Bug#1135877: trixie-pu: package python3.13/3.13.5-2+deb13u2 Moritz Mühlenhoff <jmm@inutil.org> - 2026-05-07 21:00 +0200

csiph-web