Version
5.9.1
Description
When a wolfSSL TLS 1.3 client receives a HelloRetryRequest (HRR) that is missing the
mandatory supported_versions extension, it commits two distinct RFC violations:
-
Bug A — double alert: wolfSSL sends two fatal protocol_version alerts for the
same error, violating the requirement that a fatal alert terminates the connection
immediately.
-
Bug B — wrong alert description: wolfSSL sends a protocol_version alert (code 70)
instead of the missing_extension alert (code 109) required by RFC 8446.
Impact
Two RFC violations. Neither poses a direct security risk.
RFC 8446 violations
Bug A — double alert
RFC 8446 §6.2 states:
"Whenever an implementation encounters a fatal error condition, it
SHOULD send an appropriate fatal alert and MUST close the connection
without sending or receiving any additional data."
After the first fatal alert the connection must be closed. wolfSSL instead sends a second
fatal alert.
Bug B — wrong alert description
RFC 8446 §4.1.4 states:
"Upon receipt of a HelloRetryRequest, the client MUST check the
legacy_version, legacy_session_id_echo, cipher_suite, and
legacy_compression_method as specified in Section 4.1.3 and then
process the extensions, starting with determining the version using
"supported_versions"."
and §6.2:
_"missing_extension: Sent by endpoints that receive a handshake
message not containing an extension that is mandatory to send for
the offered TLS version or other negotiated parameters."
When wolfSSL's client receives an HRR without the supported_versions extension, it
sends protocol_version (70) instead of missing_extension (109).
Reproduction steps
Craft a HelloRetryRequest with only a key_share extension requesting secp384r1
and no supported_versions extension:
- Record Layer:
- ContentType: Handshake (22)
- Version: TLS 1.2 (legacy marker, 0x0303)
- Handshake:
- Type: ServerHello (2)
- Version: 0x0303
- Random:
cf21ad74e59a6111be1d8c021e65b891c2a211167abb8c5e079e09e2c8a8339c
(HelloRetryRequest magic value)
- SessionID: echoed from the ClientHello
- CipherSuite: TLS_AES_128_GCM_SHA256 (0x1301)
- Compression: null
- Extensions:
key_share requesting secp384r1 (0x0018)
- (no
supported_versions extension)
Start the following Python TCP server, which parses the session ID from the client's
ClientHello and echoes it in the malformed HRR:
import socket
import struct
HOST = "0.0.0.0"
PORT = 3000
HRR_MAGIC = bytes.fromhex(
"CF21AD74E59A6111BE1D8C021E65B891C2A211167ABB8C5E079E09E2C8A8339C"
)
ALERT_DESC = {
0: "close_notify", 10: "unexpected_message", 40: "handshake_failure",
47: "illegal_parameter", 70: "protocol_version", 109: "missing_extension",
100: "no_renegotiation",
}
def extract_session_id(ch: bytes) -> bytes:
"""Return the session_id bytes from a raw TLS ClientHello record."""
# Record (5) + Handshake header (4) + legacy_version (2) + random (32) = 43
off = 43
if len(ch) <= off:
return b""
sid_len = ch[off]
return ch[off + 1 : off + 1 + sid_len]
def make_hrr(session_id: bytes) -> bytes:
"""
Build a HelloRetryRequest with only a key_share extension (secp384r1).
The required supported_versions extension is intentionally omitted.
RFC 8446 §4.2.1: the client MUST abort with a 'missing_extension' alert.
"""
body = b"\x03\x03" # legacy_version
body += HRR_MAGIC # HRR random
body += bytes([len(session_id)]) + session_id # session_id echo
body += b"\x13\x01" # TLS_AES_128_GCM_SHA256
body += b"\x00" # compression = null
# Extensions: key_share only (type 0x0033, len 2, group secp384r1 = 0x0018)
exts = b"\x00\x33\x00\x02\x00\x18"
body += struct.pack(">H", len(exts)) + exts
hs = b"\x02" + struct.pack(">I", len(body))[1:] + body
return b"\x16\x03\x03" + struct.pack(">H", len(hs)) + hs
def parse_alerts(data: bytes) -> list[str]:
msgs = []
i = 0
while i + 5 <= len(data):
rec_t = data[i]
rec_l = struct.unpack(">H", data[i + 3 : i + 5])[0]
body = data[i + 5 : i + 5 + rec_l]
i += 5 + rec_l
if rec_t == 0x15 and len(body) >= 2:
level = "fatal" if body[0] == 2 else "warning"
desc = ALERT_DESC.get(body[1], body[1])
msgs.append(f"Alert({level},{desc})")
return msgs
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as srv:
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, PORT))
srv.listen(1)
print(f"[*] Listening on {HOST}:{PORT} ...")
conn, addr = srv.accept()
with conn:
print(f"[+] Connection from {addr}")
ch = conn.recv(4096)
print(f"[>] Received ClientHello ({len(ch)} bytes)")
sid = extract_session_id(ch)
hrr = make_hrr(sid)
conn.sendall(hrr)
print(f"[<] Sent malformed HRR (no supported_versions): {hrr.hex()}")
conn.settimeout(3)
data = b""
try:
while True:
chunk = conn.recv(4096)
if not chunk:
break
data += chunk
except socket.timeout:
pass
alerts = parse_alerts(data)
result = ", ".join(alerts) if alerts else f"raw: {data[:20].hex()}"
print(f"[>] Client response: {result}")
Then initiate a TLS 1.3 handshake with a wolfSSL client:
./build/examples/client/client -p 3000 -v 4
Expected behavior (RFC 8446): the client sends exactly one missing_extension
fatal alert and closes the connection.
Acknowledgements
This bug was found thanks to the tlspuffin fuzzer designed and developed by the tlspuffin team:
- Max Ammann
- Olivier Demengeon - Loria, Inria
- Tom Gouville - Loria, Inria
- Lucca Hirschi - Loria, Inria
- Steve Kremer - Loria, Inria
- Michael Mera - Loria, Inria
Version
5.9.1
Description
When a wolfSSL TLS 1.3 client receives a
HelloRetryRequest(HRR) that is missing themandatory
supported_versionsextension, it commits two distinct RFC violations:Bug A — double alert: wolfSSL sends two fatal
protocol_versionalerts for thesame error, violating the requirement that a fatal alert terminates the connection
immediately.
Bug B — wrong alert description: wolfSSL sends a
protocol_versionalert (code 70)instead of the
missing_extensionalert (code 109) required by RFC 8446.Impact
Two RFC violations. Neither poses a direct security risk.
RFC 8446 violations
Bug A — double alert
RFC 8446 §6.2 states:
After the first fatal alert the connection must be closed. wolfSSL instead sends a second
fatal alert.
Bug B — wrong alert description
RFC 8446 §4.1.4 states:
and §6.2:
When wolfSSL's client receives an HRR without the
supported_versionsextension, itsends
protocol_version(70) instead ofmissing_extension(109).Reproduction steps
Craft a
HelloRetryRequestwith only akey_shareextension requestingsecp384r1and no
supported_versionsextension:cf21ad74e59a6111be1d8c021e65b891c2a211167abb8c5e079e09e2c8a8339c(HelloRetryRequest magic value)
key_sharerequesting secp384r1 (0x0018)supported_versionsextension)Start the following Python TCP server, which parses the session ID from the client's
ClientHelloand echoes it in the malformed HRR:Then initiate a TLS 1.3 handshake with a wolfSSL client:
Expected behavior (RFC 8446): the client sends exactly one
missing_extensionfatal alert and closes the connection.
Acknowledgements
This bug was found thanks to the tlspuffin fuzzer designed and developed by the tlspuffin team: