From 57f09bb4bb051d3bc2a1abd36e9525313d5cd408 Mon Sep 17 00:00:00 2001
From: Alex Gaynor <alex.gaynor@gmail.com>
Date: Wed, 18 Feb 2026 07:46:15 -0500
Subject: [PATCH] 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 <noreply@anthropic.com>

Upstream-Status: Backport [https://github.com/pyca/pyopenssl/commit/57f09bb4bb051d3bc2a1abd36e9525313d5cd408]
CVE: CVE-2026-27459
Signed-off-by: Vijay Anusuri <vanusuri@mvista.com>
---
 CHANGELOG.rst      |  1 +
 src/OpenSSL/SSL.py |  7 +++++++
 tests/test_ssl.py  | 38 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 46 insertions(+)

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 5d953c9..de8b14a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -35,6 +35,7 @@ Deprecations:
 Changes:
 ^^^^^^^^
 
+- Properly raise an error if a DTLS cookie callback returned a cookie longer than ``DTLS1_COOKIE_LENGTH`` bytes. Previously this would result in a buffer-overflow.
 - Corrected type annotations on ``Context.set_alpn_select_callback``, ``Context.set_session_cache_mode``, ``Context.set_options``, ``Context.set_mode``, ``X509.subject_name_hash``, and ``X509Store.load_locations``.
 - Deprecated APIs are now marked using ``warnings.deprecated``. ``mypy`` will emit deprecation notices for them when used with ``--enable-error-code deprecated``.
 - ``Context.set_tlsext_servername_callback`` now handles exceptions raised in the callback by calling ``sys.excepthook`` and returning a fatal TLS alert. Previously, exceptions were silently swallowed and the handshake would proceed as if the callback had succeeded.
diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py
index 178961f..6c7d6a2 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 9a5b19b..7dd3af8 100644
--- a/tests/test_ssl.py
+++ b/tests/test_ssl.py
@@ -4720,6 +4720,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.43.0

