Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected], [email protected]
Control: affects -1 + src:pyopenssl
User: [email protected]
Usertags: pu

  * CVE-2026-27448: Unhandled exceptions in set_tlsext_servername_callback
    callbacks did not cancel connections
  * CVE-2026-27459: Buffer overflow in DTLS cookie callback
diffstat for pyopenssl-25.0.0 pyopenssl-25.0.0

 changelog                                                               |    9 
 patches/0001-Handle-exceptions-in-set_tlsext_servername_callback-.patch |  107 
++++++++++
 patches/0002-Fix-buffer-overflow-in-DTLS-cookie-generation-callba.patch |   91 
++++++++
 patches/series                                                          |    2 
 4 files changed, 209 insertions(+)

diff -Nru pyopenssl-25.0.0/debian/changelog pyopenssl-25.0.0/debian/changelog
--- pyopenssl-25.0.0/debian/changelog   2025-01-17 20:22:27.000000000 +0200
+++ pyopenssl-25.0.0/debian/changelog   2026-06-22 19:13:17.000000000 +0300
@@ -1,3 +1,12 @@
+pyopenssl (25.0.0-1+deb13u1) trixie; urgency=medium
+
+  * Non-maintainer upload.
+  * CVE-2026-27448: Unhandled exceptions in set_tlsext_servername_callback
+    callbacks did not cancel connections
+  * CVE-2026-27459: Buffer overflow in DTLS cookie callback
+
+ -- Adrian Bunk <[email protected]>  Mon, 22 Jun 2026 19:13:17 +0300
+
 pyopenssl (25.0.0-1) unstable; urgency=medium
 
   * New upstream version.
diff -Nru 
pyopenssl-25.0.0/debian/patches/0001-Handle-exceptions-in-set_tlsext_servername_callback-.patch
 
pyopenssl-25.0.0/debian/patches/0001-Handle-exceptions-in-set_tlsext_servername_callback-.patch
--- 
pyopenssl-25.0.0/debian/patches/0001-Handle-exceptions-in-set_tlsext_servername_callback-.patch
     1970-01-01 02:00:00.000000000 +0200
+++ 
pyopenssl-25.0.0/debian/patches/0001-Handle-exceptions-in-set_tlsext_servername_callback-.patch
     2026-06-22 19:11:08.000000000 +0300
@@ -0,0 +1,107 @@
+From df3c6da11667645e7ca886a7f4fe7f2d9d8d80d7 Mon Sep 17 00:00:00 2001
+From: Alex Gaynor <[email protected]>
+Date: Mon, 16 Feb 2026 21:04:37 -0500
+Subject: Handle exceptions in set_tlsext_servername_callback callbacks (#1478)
+
+When the servername callback raises an exception, call sys.excepthook
+with the exception info and return SSL_TLSEXT_ERR_ALERT_FATAL to abort
+the handshake. Previously, exceptions would propagate uncaught through
+the CFFI callback boundary.
+
+https://claude.ai/code/session_01P7y1XmWkdtC5UcmZwGDvGi
+
+Co-authored-by: Claude <[email protected]>
+---
+ src/OpenSSL/SSL.py |  7 ++++++-
+ tests/test_ssl.py  | 50 ++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 56 insertions(+), 1 deletion(-)
+
+diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py
+index bc9c188..1dbd1c8 100644
+--- a/src/OpenSSL/SSL.py
++++ b/src/OpenSSL/SSL.py
+@@ -2,6 +2,7 @@ from __future__ import annotations
+ 
+ import os
+ import socket
++import sys
+ import typing
+ import warnings
+ from collections.abc import Sequence
+@@ -1686,7 +1687,11 @@ class Context:
+ 
+         @wraps(callback)
+         def wrapper(ssl, alert, arg):  # type: ignore[no-untyped-def]
+-            callback(Connection._reverse_mapping[ssl])
++            try:
++                callback(Connection._reverse_mapping[ssl])
++            except Exception:
++                sys.excepthook(*sys.exc_info())
++                return _lib.SSL_TLSEXT_ERR_ALERT_FATAL
+             return 0
+ 
+         self._tlsext_servername_callback = _ffi.callback(
+diff --git a/tests/test_ssl.py b/tests/test_ssl.py
+index 21d30cf..0f0dad1 100644
+--- a/tests/test_ssl.py
++++ b/tests/test_ssl.py
+@@ -1936,6 +1936,56 @@ class TestServerNameCallback:
+ 
+         assert args == [(server, b"foo1.example.com")]
+ 
++    def test_servername_callback_exception(
++        self, monkeypatch: pytest.MonkeyPatch
++    ) -> None:
++        """
++        When the callback passed to `Context.set_tlsext_servername_callback`
++        raises an exception, ``sys.excepthook`` is called with the exception
++        and the handshake fails with an ``Error``.
++        """
++        exc = TypeError("server name callback failed")
++
++        def servername(conn: Connection) -> None:
++            raise exc
++
++        excepthook_calls: list[
++            tuple[type[BaseException], BaseException, object]
++        ] = []
++
++        def custom_excepthook(
++            exc_type: type[BaseException],
++            exc_value: BaseException,
++            exc_tb: object,
++        ) -> None:
++            excepthook_calls.append((exc_type, exc_value, exc_tb))
++
++        context = Context(SSLv23_METHOD)
++        context.set_tlsext_servername_callback(servername)
++
++        # Necessary to actually accept the connection
++        context.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
++        context.use_certificate(
++            load_certificate(FILETYPE_PEM, server_cert_pem)
++        )
++
++        # Do a little connection to trigger the logic
++        server = Connection(context, None)
++        server.set_accept_state()
++
++        client = Connection(Context(SSLv23_METHOD), None)
++        client.set_connect_state()
++        client.set_tlsext_host_name(b"foo1.example.com")
++
++        monkeypatch.setattr(sys, "excepthook", custom_excepthook)
++        with pytest.raises(Error):
++            interact_in_memory(server, client)
++
++        assert len(excepthook_calls) == 1
++        assert excepthook_calls[0][0] is TypeError
++        assert excepthook_calls[0][1] is exc
++        assert excepthook_calls[0][2] is not None
++
+ 
+ class TestApplicationLayerProtoNegotiation:
+     """
+-- 
+2.47.3
+
diff -Nru 
pyopenssl-25.0.0/debian/patches/0002-Fix-buffer-overflow-in-DTLS-cookie-generation-callba.patch
 
pyopenssl-25.0.0/debian/patches/0002-Fix-buffer-overflow-in-DTLS-cookie-generation-callba.patch
--- 
pyopenssl-25.0.0/debian/patches/0002-Fix-buffer-overflow-in-DTLS-cookie-generation-callba.patch
     1970-01-01 02:00:00.000000000 +0200
+++ 
pyopenssl-25.0.0/debian/patches/0002-Fix-buffer-overflow-in-DTLS-cookie-generation-callba.patch
     2026-06-22 19:11:08.000000000 +0300
@@ -0,0 +1,91 @@
+From 434d56816d36adb05c87a6fea0b1ee3da1cea8bf Mon Sep 17 00:00:00 2001
+From: Alex Gaynor <[email protected]>
+Date: Wed, 18 Feb 2026 07:46:15 -0500
+Subject: Fix buffer overflow in DTLS cookie generation callback (#1479)
+
+The cookie generate callback copied user-returned bytes into a
+fixed-size native buffer without enforcing a maximum length. A
+callback returning more than DTLS1_COOKIE_LENGTH bytes would overflow
+the OpenSSL-provided buffer, corrupting adjacent memory.
+
+Co-authored-by: Claude Opus 4.6 <[email protected]>
+---
+ src/OpenSSL/SSL.py |  7 +++++++
+ tests/test_ssl.py  | 38 ++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 45 insertions(+)
+
+diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py
+index 1dbd1c8..bccf920 100644
+--- a/src/OpenSSL/SSL.py
++++ b/src/OpenSSL/SSL.py
+@@ -716,11 +716,18 @@ class 
_CookieGenerateCallbackHelper(_CallbackExceptionHelper):
+     def __init__(self, callback: _CookieGenerateCallback) -> None:
+         _CallbackExceptionHelper.__init__(self)
+ 
++        max_cookie_len = getattr(_lib, "DTLS1_COOKIE_LENGTH", 255)
++
+         @wraps(callback)
+         def wrapper(ssl, out, outlen):  # type: ignore[no-untyped-def]
+             try:
+                 conn = Connection._reverse_mapping[ssl]
+                 cookie = callback(conn)
++                if len(cookie) > max_cookie_len:
++                    raise ValueError(
++                        f"Cookie too long (got {len(cookie)} bytes, "
++                        f"max {max_cookie_len})"
++                    )
+                 out[0 : len(cookie)] = cookie
+                 outlen[0] = len(cookie)
+                 return 1
+diff --git a/tests/test_ssl.py b/tests/test_ssl.py
+index 0f0dad1..41b37be 100644
+--- a/tests/test_ssl.py
++++ b/tests/test_ssl.py
+@@ -4727,6 +4727,44 @@ class TestDTLS:
+     def test_it_works_with_srtp(self) -> None:
+         self._test_handshake_and_data(srtp_profile=b"SRTP_AES128_CM_SHA1_80")
+ 
++    def test_cookie_generate_too_long(self) -> None:
++        s_ctx = Context(DTLS_METHOD)
++
++        def generate_cookie(ssl: Connection) -> bytes:
++            return b"\x00" * 256
++
++        def verify_cookie(ssl: Connection, cookie: bytes) -> bool:
++            return True
++
++        s_ctx.set_cookie_generate_callback(generate_cookie)
++        s_ctx.set_cookie_verify_callback(verify_cookie)
++        s_ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
++        s_ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem))
++        s_ctx.set_options(OP_NO_QUERY_MTU)
++        s = Connection(s_ctx)
++        s.set_accept_state()
++
++        c_ctx = Context(DTLS_METHOD)
++        c_ctx.set_options(OP_NO_QUERY_MTU)
++        c = Connection(c_ctx)
++        c.set_connect_state()
++
++        c.set_ciphertext_mtu(1500)
++        s.set_ciphertext_mtu(1500)
++
++        # Client sends ClientHello
++        try:
++            c.do_handshake()
++        except SSL.WantReadError:
++            pass
++        chunk = c.bio_read(self.LARGE_BUFFER)
++        s.bio_write(chunk)
++
++        # Server tries DTLSv1_listen, which triggers cookie generation.
++        # The oversized cookie should raise ValueError.
++        with pytest.raises(ValueError, match="Cookie too long"):
++            s.DTLSv1_listen()
++
+     def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None:
+         c_ctx = Context(DTLS_METHOD)
+         c = Connection(c_ctx)
+-- 
+2.47.3
+
diff -Nru pyopenssl-25.0.0/debian/patches/series 
pyopenssl-25.0.0/debian/patches/series
--- pyopenssl-25.0.0/debian/patches/series      2025-01-17 20:22:27.000000000 
+0200
+++ pyopenssl-25.0.0/debian/patches/series      2026-06-22 19:13:16.000000000 
+0300
@@ -1,2 +1,4 @@
 0001-disable-test_set_default_verify_paths-since-it-tries.patch
 0003-Fix-running-tests-on-32-bit-time_t-platforms.patch
+0001-Handle-exceptions-in-set_tlsext_servername_callback-.patch
+0002-Fix-buffer-overflow-in-DTLS-cookie-generation-callba.patch

Reply via email to