Skip to content

INVALID_TOKEN Is Not Emitted for Invalid Initial Token Fields #137

@LiD0209

Description

@LiD0209

INVALID_TOKEN Is Not Emitted for Invalid Initial Token Fields

Summary

RFC 9000 defines INVALID_TOKEN for the case where a server receives a client Initial containing an invalid Token field. For an otherwise valid Initial containing an invalid Retry token, the standard recommends an immediate connection close with INVALID_TOKEN.

quiche defines the IETF INVALID_TOKEN transport error and parses the Initial Token field, but the reviewed receive path does not turn token validation failure into a QUIC connection close using INVALID_TOKEN. Instead, validation success is used only to mark the peer address as validated.

Standard Requirement

INVALID_TOKEN (0x0b):

A server received a client Initial that contained an invalid Token field.
If a server receives a client Initial that contains an invalid Retry token but
is otherwise valid, it knows the client will not accept another Retry token.
Instead, the server SHOULD immediately close the connection with an
INVALID_TOKEN error.

This Retry-token behavior is distinct from NEW_TOKEN/address-validation-token handling, where an invalid token can leave the client address unvalidated.

Relevant Source Code

Source: quiche-main/quiche-main/quiche/quic/core/quic_framer.cc:6898-6912

6898:   *retry_token_length_length = reader->PeekVarInt62Length();
6899:   uint64_t retry_token_length;
6900:   if (!reader->ReadVarInt62(&retry_token_length)) {
6901:     *retry_token_length_length = quiche::VARIABLE_LENGTH_INTEGER_LENGTH_0;
6902:     set_detailed_error_static(detailed_error,
6903:                               "Unable to read retry token length.");
6904:     return QUIC_INVALID_PACKET_HEADER;
6905:   }
6906:
6907:   if (!reader->ReadStringPiece(retry_token, retry_token_length)) {
6908:     set_detailed_error_static(detailed_error, "Unable to read retry token.");
6909:     return QUIC_INVALID_PACKET_HEADER;
6910:   }
6911:
6912:   return QUIC_NO_ERROR;

The Initial Token field is parsed from the packet header.

Source: quiche-main/quiche-main/quiche/quic/core/quic_dispatcher.cc:738-740

738:   if (packet_info.retry_token.has_value()) {
739:     parsed_chlo.retry_token = std::string(*packet_info.retry_token);
740:   }

The dispatcher carries the parsed token into ParsedClientHello.

Source: quiche-main/quiche-main/quiche/quic/core/quic_dispatcher.h:231-235

231:   virtual QuicPacketFate ValidityChecksOnFullChlo(
232:       const ReceivedPacketInfo& /*packet_info*/,
233:       const ParsedClientHello& /*parsed_chlo*/) const {
234:     return kFateProcess;
235:   }

The default validity hook does not reject invalid Initial tokens.

Source: quiche-main/quiche-main/quiche/quic/core/quic_dispatcher.cc:1258-1275

1258:   auto session_ptr = CreateSessionFromChlo(
1274:   session_ptr->ProcessUdpPacket(packet_info->self_address,
1275:                                 packet_info->peer_address, packet_info->packet);

After session creation, the Initial packet is delivered to the connection.

Source: quiche-main/quiche-main/quiche/quic/core/quic_connection.cc:1464-1471

1464:   if (EnforceAntiAmplificationLimit() && !IsHandshakeConfirmed() &&
1465:       !header.retry_token.empty() &&
1466:       visitor_->ValidateToken(header.retry_token)) {
1467:     QUIC_DLOG(INFO) << ENDPOINT << "Address validated via token.";
1468:     QUIC_CODE_COUNT(quic_address_validated_via_token);
1469:     default_path_.validated = true;
1470:     stats_.address_validated_via_token = true;
1471:   }

The connection only reacts to token validation success.

Source: quiche-main/quiche-main/quiche/quic/core/quic_session.cc:3035-3055

3035: bool QuicSession::ValidateToken(absl::string_view token) {
3036:   QUICHE_DCHECK_EQ(perspective_, Perspective::IS_SERVER);
3037:   if (GetQuicFlag(quic_reject_retry_token_in_initial_packet)) {
3038:     return false;
3039:   }
3040:   if (token.empty() || token[0] != kAddressTokenPrefix) {
3041:     // Validate the prefix for token received in NEW_TOKEN frame.
3042:     return false;
3043:   }
3044:   const bool valid = GetCryptoStream()->ValidateAddressToken(
3045:       absl::string_view(token.data() + 1, token.length() - 1));
3054:   return valid;
3055: }

ValidateToken() returns false for invalid tokens, and the caller has no corresponding close path for that result.

Source: quiche-main/quiche-main/quiche/quic/core/quic_error_codes.h:747-759

747: enum QuicIetfTransportErrorCodes : uint64_t {
758:   PROTOCOL_VIOLATION = 0xA,
759:   INVALID_TOKEN = 0xB,

The IETF transport error value is available.

Source: quiche-main/quiche-main/quiche/quic/core/quic_error_codes.cc:578-581

578:     case IETF_QUIC_PROTOCOL_VIOLATION:
579:       return {true, static_cast<uint64_t>(PROTOCOL_VIOLATION)};
580:     case QUIC_INVALID_NEW_TOKEN:
581:       return {true, static_cast<uint64_t>(PROTOCOL_VIOLATION)};

The nearby internal error for NEW_TOKEN parsing maps to PROTOCOL_VIOLATION, not INVALID_TOKEN.

Implementation Behavior

The framer parses the Initial Token field and the dispatcher carries it through CHLO processing. The dispatcher then creates a session and delivers the packet to the connection.

The connection calls ValidateToken() only as an address-validation mechanism. A true result marks the peer address as validated. A false result leaves the address unvalidated, but the reviewed path does not send an IETF INVALID_TOKEN close.

Inconsistency Reason

RFC 9000 defines INVALID_TOKEN for a client Initial containing an invalid Token field and recommends immediate INVALID_TOKEN close for an otherwise valid Initial with an invalid Retry token.

quiche performs parsing and validation, but validation failure is not connected to an INVALID_TOKEN close path. The behavior therefore does not provide the standard's expected peer feedback for invalid Retry-token Initial packets.

Impact

A client that sends an otherwise valid Initial with an invalid Retry token may not receive an immediate INVALID_TOKEN close. The connection can continue as an unvalidated-address case or fail later.

The security impact is limited because invalid tokens do not grant address validation. The main issue is wire-visible error handling and conformance with the expected Retry-token failure behavior.

Fix Direction

Track or encode token provenance so the server can distinguish Retry tokens from NEW_TOKEN/address-validation tokens.

When an otherwise valid client Initial contains an invalid Retry token, close the connection using the IETF transport error INVALID_TOKEN. NEW_TOKEN/address-validation-token failures can continue to leave the address unvalidated where RFC 9000 Section 8.1.3 permits that behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions