ClientHello supported_versions Accepts a Trailing Byte
1. Summary
mbedTLS accepts a malformed TLS 1.3 ClientHello where the
supported_versions extension contains a valid ProtocolVersion followed by
one trailing byte.
The malformed payload tested was logically:
supported_versions.extension_data = 03 03 04 00
| |-----| |
| TLS 1.3 |
| trailing byte
versions_len = 3
This should be rejected as a syntax error because the ClientHello form of
SupportedVersions is a vector of 2-byte ProtocolVersion elements. A vector
length of 3 is not an exact multiple of the element size.
Runtime testing shows that mbedTLS still negotiates TLS 1.3 and sends a
ServerHello instead of terminating the handshake with decode_error.
2. Specification Description
2.1 ProtocolVersion is 2 bytes
From RFC 8446, Section 3.4:
So each ProtocolVersion element occupies exactly 2 bytes.
2.2 ClientHello supported_versions is a ProtocolVersion vector
From RFC 8446, Section 4.2.1, the ClientHello arm of SupportedVersions is:
ProtocolVersion versions<2..254>;
This means the extension body is not arbitrary bytes. It is a TLS vector whose
elements are ProtocolVersion values.
2.3 Vector length must match the element size
From the TLS presentation language rules in RFC 8446, Section 3.4:
must be an exact multiple of the length of a single element
Because the element is ProtocolVersion and ProtocolVersion is 2 bytes, the
versions vector length must be even. A length of 3 is therefore syntactically
invalid.
2.4 Syntax parse failures are decode_error
From RFC 8446, Section 6.2:
MUST terminate the connection with a "decode_error" alert
Therefore, a ClientHello containing ProtocolVersion versions<2..254> with an
odd byte length should be rejected with decode_error.
3. mbedTLS Code Description
The relevant parser is:
mbedtls-development/library/ssl_tls13_server.c:773
ssl_tls13_parse_supported_versions_ext()
The important control flow is at
mbedtls-development/library/ssl_tls13_server.c:783-804:
versions_len = p[0];
...
while (p < versions_end) {
read 2-byte ProtocolVersion
...
if TLS 1.3 is found:
found_supported_version = 1
break
}
The code checks that versions_len bytes are inside the extension boundary, and
each loop iteration checks that 2 bytes are available before reading a
ProtocolVersion.
However, once TLS 1.3 or TLS 1.2 is found, the loop exits immediately. The
parser does not verify either of these conditions:
versions_len % 2 == 0
p == versions_end after parsing
As a result, if the first two bytes contain 0x0304 and one extra byte follows,
the parser accepts the extension before it ever reaches the malformed trailing
byte.
4. Runtime Reproduction
The reproduction script is:
m601-700/repro_id637_supported_versions_tail.py
The script starts a real mbedTLS TLS 1.3 server and client, then uses a small TCP
proxy to mutate only the ClientHello supported_versions extension.
The mutation is implemented at
m601-700/repro_id637_supported_versions_tail.py:70-84:
original_versions_len: 2
mutated_versions_len: 3
inserted_byte: 00
Run:
python .\m601-700\repro_id637_supported_versions_tail.py
Observed result:
[mutated]
mutated: True
mutation: {'original_versions_len': 2, 'mutated_versions_len': 3, 'inserted_byte': '00'}
server_first_record_type: 22
server_first_record_hex: 160303007a020000760303...
client_returncode: 1
server_first_record_type: 22 means the server sent a TLS Handshake record. If
the malformed ClientHello had been rejected immediately, the first response
should have been an Alert record, type 21.
The server log confirms acceptance:
m601-700/id637_mutated_server.log:69
Negotiated version: [0304]
m601-700/id637_mutated_server.log:168
handshake state: CLIENT_HELLO -> SERVER_HELLO
m601-700/id637_mutated_server.log:233
output record: msgtype = 22
The client later fails with a MAC verification error. That is expected because
the proxy mutates the ClientHello seen by the server, while the client computes
the TLS 1.3 transcript using its original ClientHello. This later failure does
not change the server-side finding: the malformed supported_versions vector
was already accepted and processed.
5. Inconsistency
The specification requires the ClientHello supported_versions extension to be
parsed as:
ProtocolVersion versions<2..254>
Since ProtocolVersion is uint16, the vector body must consist of complete
2-byte elements. A vector body with length 3 does not conform to the TLS
presentation syntax and should trigger decode_error.
mbedTLS instead accepts the malformed vector if a supported version appears
before the trailing byte. The immediate cause is the early break after finding
TLS 1.3/TLS 1.2, combined with the absence of an exact-consumption or
element-size-multiple check.
In short:
Expected behavior:
reject versions_len = 3 with decode_error
Observed mbedTLS behavior:
accept 03 03 04 00, negotiate TLS 1.3, send ServerHello
6. Suggested Fix Direction
The parser should reject malformed vector lengths before scanning versions, for
example:
if versions_len < 2 or versions_len > 254 or versions_len % 2 != 0:
fail with decode_error
Alternatively, the parser can require exact consumption after scanning the
vector. The clearer fix is to validate the vector length modulo the
ProtocolVersion element size before entering the loop.
ClientHello
supported_versionsAccepts a Trailing Byte1. Summary
mbedTLS accepts a malformed TLS 1.3 ClientHello where the
supported_versionsextension contains a validProtocolVersionfollowed byone trailing byte.
The malformed payload tested was logically:
This should be rejected as a syntax error because the ClientHello form of
SupportedVersionsis a vector of 2-byteProtocolVersionelements. A vectorlength of 3 is not an exact multiple of the element size.
Runtime testing shows that mbedTLS still negotiates TLS 1.3 and sends a
ServerHello instead of terminating the handshake with
decode_error.2. Specification Description
2.1
ProtocolVersionis 2 bytesFrom RFC 8446, Section 3.4:
So each
ProtocolVersionelement occupies exactly 2 bytes.2.2 ClientHello
supported_versionsis aProtocolVersionvectorFrom RFC 8446, Section 4.2.1, the ClientHello arm of
SupportedVersionsis:This means the extension body is not arbitrary bytes. It is a TLS vector whose
elements are
ProtocolVersionvalues.2.3 Vector length must match the element size
From the TLS presentation language rules in RFC 8446, Section 3.4:
Because the element is
ProtocolVersionandProtocolVersionis 2 bytes, theversionsvector length must be even. A length of 3 is therefore syntacticallyinvalid.
2.4 Syntax parse failures are
decode_errorFrom RFC 8446, Section 6.2:
Therefore, a ClientHello containing
ProtocolVersion versions<2..254>with anodd byte length should be rejected with
decode_error.3. mbedTLS Code Description
The relevant parser is:
The important control flow is at
mbedtls-development/library/ssl_tls13_server.c:783-804:The code checks that
versions_lenbytes are inside the extension boundary, andeach loop iteration checks that 2 bytes are available before reading a
ProtocolVersion.However, once TLS 1.3 or TLS 1.2 is found, the loop exits immediately. The
parser does not verify either of these conditions:
versions_len % 2 == 0p == versions_endafter parsingAs a result, if the first two bytes contain
0x0304and one extra byte follows,the parser accepts the extension before it ever reaches the malformed trailing
byte.
4. Runtime Reproduction
The reproduction script is:
The script starts a real mbedTLS TLS 1.3 server and client, then uses a small TCP
proxy to mutate only the ClientHello
supported_versionsextension.The mutation is implemented at
m601-700/repro_id637_supported_versions_tail.py:70-84:Run:
Observed result:
server_first_record_type: 22means the server sent a TLS Handshake record. Ifthe malformed ClientHello had been rejected immediately, the first response
should have been an Alert record, type
21.The server log confirms acceptance:
The client later fails with a MAC verification error. That is expected because
the proxy mutates the ClientHello seen by the server, while the client computes
the TLS 1.3 transcript using its original ClientHello. This later failure does
not change the server-side finding: the malformed
supported_versionsvectorwas already accepted and processed.
5. Inconsistency
The specification requires the ClientHello
supported_versionsextension to beparsed as:
Since
ProtocolVersionisuint16, the vector body must consist of complete2-byte elements. A vector body with length 3 does not conform to the TLS
presentation syntax and should trigger
decode_error.mbedTLS instead accepts the malformed vector if a supported version appears
before the trailing byte. The immediate cause is the early
breakafter findingTLS 1.3/TLS 1.2, combined with the absence of an exact-consumption or
element-size-multiple check.
In short:
6. Suggested Fix Direction
The parser should reject malformed vector lengths before scanning versions, for
example:
Alternatively, the parser can require exact consumption after scanning the
vector. The clearer fix is to validate the vector length modulo the
ProtocolVersionelement size before entering the loop.