Version
wolfssl 5.9.1
Description
A WolfSSL TLS 1.3 client silently rejects a TLS record whose length exceeds the 2^14-byte limit
mandated by RFC 8446 §5.1, without sending a record_overflow alert. The connection is correctly terminated.
OpenSSL correctly terminates the connection with a record_overflow alert for the same input.
RFC 8446 §5.1 states:
"The length MUST NOT exceed 2^14 bytes. An endpoint that receives a record that exceeds this length MUST terminate the connection with a "record_overflow" alert."
Impact
RFC violation.
Reproduction steps
The Python script below acts as a minimal fake TLS server. It reads the client's ClientHello,
then replies with a single TLS ServerHello record whose TLSPlaintext.length field is above the 16,384-byte limit. The ServerHello is otherwise structurally
valid for TLS 1.3: it carries a correct supported_versions extension selecting TLS 1.3 and a
well-formed key_share entry for x25519. The excess length is produced by appending 16
transport_parameters_draft (type 0xffa5) extensions of 1,195 bytes each.
import os, socket, struct
HOST = "127.0.0.1"
PORT = 4433
ALERT_DESC = {
10: "unexpected_message", 22: "record_overflow", 40: "handshake_failure",
47: "illegal_parameter", 50: "decode_error", 70: "protocol_version",
}
def ext(t, data):
return struct.pack(">HH", t, len(data)) + data
def extract_session_id(ch: bytes) -> bytes:
"""Parse legacy_session_id from a raw TLS ClientHello record."""
# 5 (record hdr) + 4 (hs hdr) + 2 (legacy_version) + 32 (random) = 43
off = 43
if len(ch) <= off:
return b""
sid_len = ch[off]
return ch[off + 1: off + 1 + sid_len]
def make_oversized_server_hello(session_id: bytes) -> bytes:
"""
Build a TLS 1.3 ServerHello with a record length > 2^14 bytes.
Extensions:
supported_versions → TLS 1.3 (0x0304) 6 B
key_share → x25519 (32-byte public value) 40 B
transport_parameters_draft (0xffa5) × 14, 1195 B each 16730 B
total: 16776 B
ServerHello body: 40 B (fixed fields) + 16776 B (extensions) = 16816 B
Handshake message: 4 B (header) + 16816 B (body) = 16820 B
TLSPlaintext.length field: 16820 B
RFC 8446 §5.1 limit: 16384 B
"""
e_sv13 = ext(0x002B, b"\x03\x04")
# x25519 (0x001d): any 32-byte value is a valid-looking Curve25519 public key
e_ks = ext(0x0033, struct.pack(">HH", 0x001d, 32) + os.urandom(32))
pad = os.urandom(1191)
e_pad = b"".join(ext(0xffa5, pad) for _ in range(16))
extensions = e_sv13 + e_ks + e_pad
body = (
b"\x03\x03"
+ os.urandom(32)
+ bytes([len(session_id)]) + session_id
+ b"\x13\x01" # TLS_AES_128_GCM_SHA256
+ b"\x00" # compression: null
+ struct.pack(">H", len(extensions)) + extensions
)
hs = b"\x02" + struct.pack(">I", len(body))[1:] + body
record = b"\x16\x03\x03" + struct.pack(">H", len(hs)) + hs
print(f"[*] TLSPlaintext.length = {len(hs)} B (RFC 8446 §5.1 limit: 16384 B)")
return record
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()
print(f"[+] Connection from {addr}")
with conn:
conn.settimeout(5)
ch = conn.recv(65536)
sid = extract_session_id(ch)
conn.sendall(make_oversized_server_hello(sid))
data = b""
try:
while True:
chunk = conn.recv(4096)
if not chunk:
break
data += chunk
except socket.timeout:
pass
alerts = parse_alerts(data)
if not alerts:
print(f"[!] BUG: client sent no alert (raw: {data[:20].hex() if data else 'empty'})")
elif "record_overflow" in alerts[0]:
print(f"[+] OK: {alerts[0]}")
else:
print(f"[!] NOK: {alerts[0]} (expected record_overflow)")
Start the server in one terminal, then connect a TLS 1.3 client in a second terminal:
# Terminal 1
python3 reproducer.py
# Terminal 2 — WolfSSL 5.8.0 client
./build/examples/client/client -h 127.0.0.1 -p 4433 -v 4 -d
Acknowledgements
This bug was found thanks to the tlspuffin fuzzer designed and developed by the tlspuffin team:
- Nataël Baffou - Engineer, Inria, France
- Olivier Demengeon - Engineer, Inria, France
- Tom Gouville - PhD student, Inria, France
- Lucca Hirschi - Researcher, Inria, France
- Steve Kremer - Researcher, Inria, France
Version
wolfssl 5.9.1
Description
A WolfSSL TLS 1.3 client silently rejects a TLS record whose length exceeds the 2^14-byte limit
mandated by RFC 8446 §5.1, without sending a
record_overflowalert. The connection is correctly terminated.OpenSSL correctly terminates the connection with a
record_overflowalert for the same input.RFC 8446 §5.1 states:
Impact
RFC violation.
Reproduction steps
The Python script below acts as a minimal fake TLS server. It reads the client's
ClientHello,then replies with a single TLS
ServerHellorecord whoseTLSPlaintext.lengthfield is above the 16,384-byte limit. TheServerHellois otherwise structurallyvalid for TLS 1.3: it carries a correct
supported_versionsextension selecting TLS 1.3 and awell-formed
key_shareentry for x25519. The excess length is produced by appending 16transport_parameters_draft(type0xffa5) extensions of 1,195 bytes each.Start the server in one terminal, then connect a TLS 1.3 client in a second terminal:
Acknowledgements
This bug was found thanks to the tlspuffin fuzzer designed and developed by the tlspuffin team: