Skip to content

mbedTLS Client Accepts an Over-Limit NewSessionTicket.extensions Vector #10907

Description

@LiD0209

mbedTLS Client Accepts an Over-Limit NewSessionTicket.extensions Vector

Summary

This is a confirmed protocol-compliance issue in the TLS 1.3 client receive path. The corrected syntax limits NewSessionTicket.extensions to Extension extensions<0..2^16-2>, so the encoded vector length cannot exceed 65534. mbedTLS treats the field as an unrestricted 16-bit length and checks only whether the message buffer contains that many bytes. A focused runtime harness confirmed that the parser accepts both the legal boundary 65534 and the first illegal value 65535.

Standard Requirement

struct {
    uint32 ticket_lifetime;
    uint32 ticket_age_add;
    opaque ticket_nonce<0..255>;
    opaque ticket<1..2^16-1>;
    Extension extensions<0..2^16-2>;
} NewSessionTicket;

RFC 9846 identifies this upper bound as a correction rather than an implementation choice:

*  Correct the upper bound on NewSessionTicket.extensions.  This was
   a calculation error.

RFC 8446 also states:

In TLS 1.3, a client receiving a CertificateRequest or
NewSessionTicket MUST also ignore all unrecognized extensions.

Together, these rules mean that the legal encoded length is 0..65534, that 65535 is invalid, and that filling a test vector with an unknown extension does not create a separate violation because a client must ignore unknown NewSessionTicket extensions.

Relevant Source Code

library/ssl_tls13_client.c:2926

MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, 2);
extensions_len = MBEDTLS_GET_UINT16_BE(p, 0);
p += 2;
MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, extensions_len);

MBEDTLS_SSL_DEBUG_BUF(3, "ticket extension", p, extensions_len);

ret = ssl_tls13_parse_new_session_ticket_exts(ssl, p, p + extensions_len);

The parser reads an ordinary uint16 and verifies only that the buffer contains extensions_len bytes. It does not enforce extensions_len <= 65534.

library/ssl_misc.h:198

#define MBEDTLS_SSL_TLS1_3_ALLOWED_EXTS_OF_NST                                 \
    (MBEDTLS_SSL_EXT_MASK(EARLY_DATA)                             | \
     MBEDTLS_SSL_TLS1_3_EXT_MASK_UNRECOGNIZED)

The implementation explicitly permits unrecognized extension types in a NewSessionTicket.

library/ssl_tls13_client.c:2792

while (p < end) {
    unsigned int extension_type;
    size_t extension_data_len;
    int ret;

    MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, 4);
    extension_type = MBEDTLS_GET_UINT16_BE(p, 0);
    extension_data_len = MBEDTLS_GET_UINT16_BE(p, 2);
    p += 4;

    MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, extension_data_len);

    ret = mbedtls_ssl_tls13_check_received_extension(
        ssl, MBEDTLS_SSL_HS_NEW_SESSION_TICKET, extension_type,
        MBEDTLS_SSL_TLS1_3_ALLOWED_EXTS_OF_NST);
    if (ret != 0) {
        return ret;
    }

    switch (extension_type) {
        /* ... */
        default:
            MBEDTLS_SSL_PRINT_EXT(
                3, MBEDTLS_SSL_HS_NEW_SESSION_TICKET,
                extension_type, "( ignored )");
            break;
    }

    p += extension_data_len;
}

The inner extension parser imposes no additional outer-vector bound and ignores unknown types.

Implementation Behavior

The receive path continues whenever the outer extensions_len fits in a uint16 and the buffer contains that many bytes. Unknown inner extensions are allowed and ignored. Consequently, a structurally self-consistent vector with the illegal total length 65535 is accepted. Using one unknown extension to fill the vector isolates the outer-length check because the extension type itself is valid input for this message.

Inconsistency Reason

RFC 8446 as corrected by RFC 9846 restricts NewSessionTicket.extensions to 0..65534. mbedTLS accepts 65535. This is a missing receive-side protocol constraint, not a documentation ambiguity: the standard rejects the first over-limit boundary, while the implementation parses it successfully.

Runtime Evidence

A harness compiled the TLS 1.3 client parser into the test translation unit and directly exercised ssl_tls13_parse_new_session_ticket(). It constructed two otherwise identical messages:

  • control_valid_max used extensions_len = 65534.
  • probe_over_limit used extensions_len = 65535.

Both messages used a zero-length ticket nonce, a one-byte ticket, and one unknown extension whose data length exactly filled the outer vector. Thus, the probe's only invalid property was exceeding the corrected vector bound by one byte.

The observed output was:

control_valid_max extensions_len=65534 ret=0 accepted ticket_nonce_len=0
probe_over_limit extensions_len=65535 ret=0 accepted ticket_nonce_len=0

The legal boundary succeeded, validating the harness and target path. The illegal boundary also returned 0, proving that the current parser accepts the over-limit value at runtime.

Impact

The client accepts a NewSessionTicket that violates the formal TLS 1.3 structure and is therefore more permissive than the standard. The available evidence does not show an out-of-bounds access, crash, remote-code execution, or key disclosure; the direct impact is protocol-parser correctness and incomplete input validation.

Fix Direction

  1. Reject extensions_len > 65534 immediately after reading the field in library/ssl_tls13_client.c.
  2. Perform the check before calling ssl_tls13_parse_new_session_ticket_exts().
  3. Add regression cases where 65534 succeeds and 65535 fails.
  4. Scope the fix to the client receive path established by this report; no conclusion is made here about the server send path.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions