mbedTLS serializes duplicate supported_groups entries
Summary
When an application configures a duplicate TLS group list, for example x25519,x25519, mbedTLS accepts the list and serializes both entries into the TLS 1.3 ClientHello supported_groups extension. The default mbedTLS group list was not shown to contain duplicates, so this is not a default-configuration malformed ClientHello. The issue is a conditional sender-side conformance defect exposed through the public group configuration path.
No material uncertainty remains for the reproduced condition. The finding is limited to explicit duplicate group configuration and does not claim that the default mbedTLS ClientHello contains duplicates.
Standard Requirement
RFC 9846 defines the extension data as a NamedGroupList whose vector field is NamedGroup named_group_list<2..2^16-1>. It then adds an explicit duplicate prohibition:
Items in "named_group_list" are ordered according to the sender's
preferences (most preferred choice first). The "named_group_list"
MUST NOT contain any duplicate entries. A recipient MAY abort a
connection with a fatal "illegal_parameter" alert if it detects a
duplicate entry.
The MUST NOT applies to the sender's named_group_list. The following MAY abort sentence only makes receiver-side rejection optional; it does not make a duplicate sender list conformant. RFC 9846 also permits servers to send supported_groups in EncryptedExtensions, so the rule is a general sender-side rule for this extension, while this report's reproduced behavior is the client ClientHello case.
RFC 8446's corresponding NamedGroupList text only states that items are ordered by sender preference and does not contain the duplicate prohibition. RFC 9846 Section 1.2 says it is a backward-compatible TLS 1.3 update that tightens some requirements, so this finding should be described as an RFC 9846 conformance issue rather than as a requirement already proven from RFC 8446 alone.
Relevant Source Code
include/mbedtls/ssl.h:3874-3908 documents mbedtls_ssl_conf_groups() as accepting an ordered, zero-terminated list of valid IANA NamedGroup IDs. It says the list is not copied, but it does not state that callers must provide a unique list.
/**
* \brief Set the allowed groups in order of preference.
*
* \note This list should be ordered by decreasing preference
* (preferred group first).
*
* \note The list is not copied internally, only the reference to it
* is saved in \p conf.
*
* \param groups List of allowed groups ordered by preference, terminated by 0.
* Must contain valid IANA NamedGroup IDs.
*/
void mbedtls_ssl_conf_groups(mbedtls_ssl_config *conf,
const uint16_t *groups);
library/ssl_tls.c:2439-2443 stores the caller-provided pointer directly and performs no uniqueness validation:
void mbedtls_ssl_conf_groups(mbedtls_ssl_config *conf,
const uint16_t *group_list)
{
conf->group_list = group_list;
}
programs/ssl/ssl_test_lib.c:451-495 parses the groups= option by appending each recognized name to group_list in order:
while (i < group_list_len - 1 && *p != '\0') {
...
if ((curve_tls_id = mbedtls_ssl_get_curve_tls_id_from_name(q)) != 0) {
group_list[i++] = curve_tls_id;
} else {
...
return -1;
}
}
programs/ssl/ssl_client2.c:1558-1562 calls that parser, and programs/ssl/ssl_client2.c:1965-1968 passes the resulting list to mbedtls_ssl_conf_groups(). There is no duplicate check in this path.
library/ssl_client.c:249-305 serializes every configured group that is supported in the current build and protocol mode:
for (; *group_list != 0; group_list++) {
int propose_group = 0;
MBEDTLS_SSL_DEBUG_MSG(3, ("got supported group(%04x)", *group_list));
...
if (propose_group) {
MBEDTLS_SSL_CHK_BUF_PTR(p, end, 2);
MBEDTLS_PUT_UINT16_BE(*group_list, p, 0);
p += 2;
MBEDTLS_SSL_DEBUG_MSG(3, ("NamedGroup: %s ( %x )",
mbedtls_ssl_named_group_to_str(*group_list),
*group_list));
}
}
named_group_list_len = (size_t) (p - named_group_list);
MBEDTLS_PUT_UINT16_BE(named_group_list_len, buf, 4);
MBEDTLS_SSL_DEBUG_BUF(3, "Supported groups extension",
buf + 4, named_group_list_len + 2);
library/ssl_tls13_server.c:832-873 parses the peer's supported_groups list entry by entry. It accepts the duplicate vector, logs both x25519 entries, and keeps the first selected group. That receiver behavior is allowed by RFC 9846's MAY abort wording; it is included here only as evidence that the duplicate vector was actually received.
Implementation Behavior
The public groups=/mbedtls_ssl_conf_groups() configuration path preserves duplicate values. The TLS 1.3 ClientHello writer then iterates over the configured list and writes each supported group into named_group_list. For x25519,x25519, both entries are recognized and supported, so both are serialized as 00 1d.
There is no evidence that uniqueness is an application-side precondition: the public API documentation requires ordering, a zero terminator, and valid IANA IDs, but not uniqueness. The implementation also neither rejects duplicate configuration nor normalizes the outgoing extension.
Inconsistency Reason
RFC 9846 requires the sender's named_group_list to contain no duplicate entries. mbedTLS accepts a duplicate configured group list through its public API path and serializes each supported configured value without checking whether the same NamedGroup has already been written. The runtime run confirms that groups=x25519,x25519 produces a ClientHello supported_groups vector containing two identical x25519 entries.
The server's decision to continue the handshake does not weaken the finding, because RFC 9846 only says a recipient MAY abort on duplicates. The conformance gap is on the sender side: mbedTLS constructs and sends a vector that the standard says MUST NOT contain duplicates.
Runtime Evidence
Fresh runtime evidence was collected on 2026-08-10 with real ssl_server2.exe and ssl_client2.exe processes from Mbed TLS 4.2.0 (build 67239936). Both runs forced TLS 1.3, disabled certificate authentication, and enabled debug-level extension dumps.
For the positive control, the client was configured with one x25519 group and connected to the server. The client exited with code 0; both peers negotiated TLS 1.3 and completed an HTTP exchange. The client and server observed this extension encoding:
For the reproducer, the client was configured with x25519,x25519 and connected to the same server configuration. The client again exited with code 0; both peers negotiated TLS 1.3 and completed an HTTP exchange. Both peers observed the duplicated extension bytes, and the server parser reported x25519 twice:
00 04 00 1d 00 1d
got named group: x25519(001d)
got named group: x25519(001d)
The first two bytes of the vector are the named_group_list length. The positive control has length 00 02 and one x25519 value, 00 1d. The duplicate run has length 00 04 and two identical x25519 values, 00 1d 00 1d. The server-side dump confirms the duplicate bytes crossed the TLS connection and were parsed by the peer process.
Impact
The trigger surface is limited: the reproduced behavior requires an explicit duplicate group configuration and was not observed in the default group list. Once triggered, however, the outgoing ClientHello is non-conformant under RFC 9846. A strict peer is permitted to abort with a fatal illegal_parameter alert when it detects the duplicate entry.
Fix Direction
mbedTLS should prevent duplicate NamedGroup values from reaching the emitted supported_groups vector. Reasonable fix points include rejecting duplicates in mbedtls_ssl_conf_groups(), normalizing the configured list before use, or deduplicating in ssl_write_supported_groups_ext() while preserving first-occurrence preference order. Receiver-side duplicate rejection is optional and should not be treated as the required fix for this sender-side issue.
mbedTLS serializes duplicate supported_groups entries
Summary
When an application configures a duplicate TLS group list, for example
x25519,x25519, mbedTLS accepts the list and serializes both entries into the TLS 1.3 ClientHellosupported_groupsextension. The default mbedTLS group list was not shown to contain duplicates, so this is not a default-configuration malformed ClientHello. The issue is a conditional sender-side conformance defect exposed through the public group configuration path.No material uncertainty remains for the reproduced condition. The finding is limited to explicit duplicate group configuration and does not claim that the default mbedTLS ClientHello contains duplicates.
Standard Requirement
RFC 9846 defines the extension data as a
NamedGroupListwhose vector field isNamedGroup named_group_list<2..2^16-1>. It then adds an explicit duplicate prohibition:The
MUST NOTapplies to the sender'snamed_group_list. The followingMAY abortsentence only makes receiver-side rejection optional; it does not make a duplicate sender list conformant. RFC 9846 also permits servers to sendsupported_groupsinEncryptedExtensions, so the rule is a general sender-side rule for this extension, while this report's reproduced behavior is the clientClientHellocase.RFC 8446's corresponding
NamedGroupListtext only states that items are ordered by sender preference and does not contain the duplicate prohibition. RFC 9846 Section 1.2 says it is a backward-compatible TLS 1.3 update that tightens some requirements, so this finding should be described as an RFC 9846 conformance issue rather than as a requirement already proven from RFC 8446 alone.Relevant Source Code
include/mbedtls/ssl.h:3874-3908documentsmbedtls_ssl_conf_groups()as accepting an ordered, zero-terminated list of valid IANA NamedGroup IDs. It says the list is not copied, but it does not state that callers must provide a unique list.library/ssl_tls.c:2439-2443stores the caller-provided pointer directly and performs no uniqueness validation:programs/ssl/ssl_test_lib.c:451-495parses thegroups=option by appending each recognized name togroup_listin order:programs/ssl/ssl_client2.c:1558-1562calls that parser, andprograms/ssl/ssl_client2.c:1965-1968passes the resulting list tombedtls_ssl_conf_groups(). There is no duplicate check in this path.library/ssl_client.c:249-305serializes every configured group that is supported in the current build and protocol mode:library/ssl_tls13_server.c:832-873parses the peer'ssupported_groupslist entry by entry. It accepts the duplicate vector, logs bothx25519entries, and keeps the first selected group. That receiver behavior is allowed by RFC 9846'sMAY abortwording; it is included here only as evidence that the duplicate vector was actually received.Implementation Behavior
The public
groups=/mbedtls_ssl_conf_groups()configuration path preserves duplicate values. The TLS 1.3 ClientHello writer then iterates over the configured list and writes each supported group intonamed_group_list. Forx25519,x25519, both entries are recognized and supported, so both are serialized as00 1d.There is no evidence that uniqueness is an application-side precondition: the public API documentation requires ordering, a zero terminator, and valid IANA IDs, but not uniqueness. The implementation also neither rejects duplicate configuration nor normalizes the outgoing extension.
Inconsistency Reason
RFC 9846 requires the sender's
named_group_listto contain no duplicate entries. mbedTLS accepts a duplicate configured group list through its public API path and serializes each supported configured value without checking whether the sameNamedGrouphas already been written. The runtime run confirms thatgroups=x25519,x25519produces a ClientHellosupported_groupsvector containing two identicalx25519entries.The server's decision to continue the handshake does not weaken the finding, because RFC 9846 only says a recipient
MAY aborton duplicates. The conformance gap is on the sender side: mbedTLS constructs and sends a vector that the standard saysMUST NOTcontain duplicates.Runtime Evidence
Fresh runtime evidence was collected on 2026-08-10 with real
ssl_server2.exeandssl_client2.exeprocesses from Mbed TLS 4.2.0 (build 67239936). Both runs forced TLS 1.3, disabled certificate authentication, and enabled debug-level extension dumps.For the positive control, the client was configured with one
x25519group and connected to the server. The client exited with code0; both peers negotiated TLS 1.3 and completed an HTTP exchange. The client and server observed this extension encoding:For the reproducer, the client was configured with
x25519,x25519and connected to the same server configuration. The client again exited with code0; both peers negotiated TLS 1.3 and completed an HTTP exchange. Both peers observed the duplicated extension bytes, and the server parser reportedx25519twice:The first two bytes of the vector are the
named_group_listlength. The positive control has length00 02and onex25519value,00 1d. The duplicate run has length00 04and two identicalx25519values,00 1d 00 1d. The server-side dump confirms the duplicate bytes crossed the TLS connection and were parsed by the peer process.Impact
The trigger surface is limited: the reproduced behavior requires an explicit duplicate group configuration and was not observed in the default group list. Once triggered, however, the outgoing ClientHello is non-conformant under RFC 9846. A strict peer is permitted to abort with a fatal
illegal_parameteralert when it detects the duplicate entry.Fix Direction
mbedTLS should prevent duplicate
NamedGroupvalues from reaching the emittedsupported_groupsvector. Reasonable fix points include rejecting duplicates inmbedtls_ssl_conf_groups(), normalizing the configured list before use, or deduplicating inssl_write_supported_groups_ext()while preserving first-occurrence preference order. Receiver-side duplicate rejection is optional and should not be treated as the required fix for this sender-side issue.