diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 5b4e4f080607726..4e56d485870e9e4 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -6465,6 +6465,45 @@ def msg_cb(conn, direction, version, content_type, msg_type, data): msg ) + def test_msg_callback_header_version_unsigned(self): + # OpenSSL passes the record header to the callback before it + # validates the version, and both version bytes are controlled by + # the peer. + received = [] + + def msg_cb(conn, direction, version, content_type, msg_type, data): + if (direction == "read" and + content_type == _TLSContentType.HEADER): + received.append((version, data)) + + versions = [ + (b"\x80\x03", 0x8003), + (b"\x03\x80", 0x0380), + (b"\x80\x80", 0x8080), + ] + if IS_AWS_LC: + # AWS-LC validates the record version before invoking the + # callback, so headers whose major version byte is not 3 + # never reach it. + versions = [(b"\x03\x80", 0x0380)] + + for version_bytes, expected_version in versions: + with self.subTest(version_bytes=version_bytes): + received.clear() + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context._msg_callback = msg_cb + incoming = ssl.MemoryBIO() + outgoing = ssl.MemoryBIO() + sslobj = context.wrap_bio(incoming, outgoing, server_side=True) + + header = b"\x16" + version_bytes + b"\x00\x00" + incoming.write(header) + + with self.assertRaises(ssl.SSLError): + sslobj.do_handshake() + + self.assertEqual(received, [(expected_version, header)]) + def test_msg_callback_deadlock_bpo43577(self): client_context, server_context, hostname = testing_context() server_context2 = testing_context()[1] diff --git a/Misc/NEWS.d/next/Library/2026-11-06-13-54-30.gh-issue-161472.dfa7DG.rst b/Misc/NEWS.d/next/Library/2026-11-06-13-54-30.gh-issue-161472.dfa7DG.rst new file mode 100644 index 000000000000000..b597c38c08b4148 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-06-13-54-30.gh-issue-161472.dfa7DG.rst @@ -0,0 +1 @@ +Fix :mod:`ssl` message callbacks receiving incorrect TLS record header version values when either version byte has its high bit set. diff --git a/Modules/_ssl/debughelpers.c b/Modules/_ssl/debughelpers.c index 0e5422b73a89d4f..b65ea030a2e93a8 100644 --- a/Modules/_ssl/debughelpers.c +++ b/Modules/_ssl/debughelpers.c @@ -54,7 +54,7 @@ _PySSL_msg_callback(int write_p, int version, int content_type, #ifdef SSL3_RT_HEADER case SSL3_RT_HEADER: /* frame header encodes version in bytes 1..2 */ - version = cbuf[1] << 8 | cbuf[2]; + version = ((unsigned char)cbuf[1] << 8) | (unsigned char)cbuf[2]; msg_type = (int)cbuf[0]; break; #endif