ceph: pyopenssl CVE fixes

Belated fixes for some CVEs for the vendored pyopenssl.
The Ceph source code directly is very unlikely to use (and in particular misuse) the affected parts of the API.
Both `set_cookie_generate_callback` and `set_tlsext_servername_callback` have no actual occurrences in the tarball, so any use would be limited to dependencies, which would be hard to track.
The major merge conflicts for backporting have been changes to the changlog which I've simply cut from the diff altogether.
Contained should be the fixes and the tests only.

Since this version of Ceph is phased out with the ongoing release of 26.05, moving to the new release and thus Ceph Tentacle is the recommended approach anyway, this is sort of a stopgap measure.

Not-cherry-picked-because: only applicable to 25.11

Signed-off-by: benaryorg <binary@benary.org>
This commit is contained in:
benaryorg
2026-05-23 17:44:52 +00:00
parent 2c1a6bd414
commit 511547908a
2 changed files with 205 additions and 1 deletions

View File

@@ -0,0 +1,202 @@
From 776f2a97d34c2ccfba90c0bcb448de7792edfdb6 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 1/2] 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>
---
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 efbf7907e618c912d48352f74fb80a9c19b9b98b..e28e10ab81ade8d79aff0cb9232fa71b1fb5314b 100644
--- a/src/OpenSSL/SSL.py
+++ b/src/OpenSSL/SSL.py
@@ -561,11 +561,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 024436f064ddadbf79a3e6b78e2a9e4aeeee7ac2..5f427e92b48e57276fee7acb5ffdbaf136462cee 100644
--- a/tests/test_ssl.py
+++ b/tests/test_ssl.py
@@ -4497,6 +4497,44 @@ class TestDTLS:
except NotImplementedError: # OpenSSL 1.1.0 and earlier
pass
+ 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.53.0
From d39f020cc63c1da4d44be683f310fbc9f44f61bb Mon Sep 17 00:00:00 2001
From: Alex Gaynor <alex.gaynor@gmail.com>
Date: Mon, 16 Feb 2026 21:04:37 -0500
Subject: [PATCH 2/2] 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 <noreply@anthropic.com>
---
src/OpenSSL/SSL.py | 9 +++++++--
tests/test_ssl.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+), 2 deletions(-)
diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py
index e28e10ab81ade8d79aff0cb9232fa71b1fb5314b..a2d5f5b086b3fe27c6e30848cdd027ee60f69677 100644
--- a/src/OpenSSL/SSL.py
+++ b/src/OpenSSL/SSL.py
@@ -1,5 +1,6 @@
import os
import socket
+import sys
from errno import errorcode
from functools import partial, wraps
from itertools import chain, count
@@ -1444,8 +1445,12 @@ class Context:
"""
@wraps(callback)
- def wrapper(ssl, alert, arg):
- callback(Connection._reverse_mapping[ssl])
+ def wrapper(ssl, alert, arg): # type: ignore[no-untyped-def]
+ 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 5f427e92b48e57276fee7acb5ffdbaf136462cee..d42beace175c1ea79929050ec6f88faa539ff6b4 100644
--- a/tests/test_ssl.py
+++ b/tests/test_ssl.py
@@ -1854,6 +1854,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.53.0

View File

@@ -282,7 +282,9 @@ let
inherit version;
hash = "sha256-hBSYub7GFiOxtsR+u8AjZ8B9YODhlfGXkIF/EMyNsLc=";
};
patches = [ ]; # those two CVE patches do not apply (!)
patches = [
./old-python-packages/pyopenssl-Cherry-pick-fix-for-CVE-2026-27459-and-CVE-2026-27448.patch
];
disabledTests = old.disabledTests or [ ] ++ [
"test_export_md5_digest"
];