From 57f09bb4bb051d3bc2a1abd36e9525313d5cd408 Mon Sep 17 00:00:00 2001 From: Alex Gaynor 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 Upstream-Status: Backport [https://github.com/pyca/pyopenssl/commit/57f09bb4bb051d3bc2a1abd36e9525313d5cd408] CVE: CVE-2026-27459 Signed-off-by: Vijay Anusuri --- 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 12e60e4..6041fdc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,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. - Added ``OpenSSL.SSL.Connection.get_selected_srtp_profile`` to determine which SRTP profile was negotiated. `#1279 `_. - ``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 a6263c4..2e4da78 100644 --- a/src/OpenSSL/SSL.py +++ b/src/OpenSSL/SSL.py @@ -691,11 +691,18 @@ class _CookieGenerateCallbackHelper(_CallbackExceptionHelper): def __init__(self, callback): _CallbackExceptionHelper.__init__(self) + max_cookie_len = getattr(_lib, "DTLS1_COOKIE_LENGTH", 255) + @wraps(callback) def wrapper(ssl, out, outlen): 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 55489b9..683e368 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -4560,6 +4560,44 @@ class TestDTLS: def test_it_works_with_srtp(self): 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): c_ctx = Context(DTLS_METHOD) c = Connection(c_ctx) -- 2.43.0