QUIC CONFORMANCE: Validate preferred_addr transport parameter
authorHugo Landau <hlandau@openssl.org>
Tue, 6 Jun 2023 15:25:12 +0000 (16:25 +0100)
committerPauli <pauli@openssl.org>
Sun, 16 Jul 2023 22:17:57 +0000 (08:17 +1000)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21135)

include/internal/quic_types.h
include/internal/quic_wire.h
ssl/quic/quic_channel.c
ssl/quic/quic_wire.c

index 6b86076ff8fc687c1273fab0c22b8109c963d082..cc41adc5ab1f5252648d3a1173c528f46cab159c 100644 (file)
@@ -101,6 +101,13 @@ static ossl_unused ossl_inline int ossl_quic_conn_id_eq(const QUIC_CONN_ID *a,
 
 #  define QUIC_STATELESS_RESET_TOKEN_LEN    16
 
+/*
+ * An encoded preferred_addr transport parameter cannot be longer than this
+ * number of bytes.
+ */
+#  define QUIC_MIN_ENCODED_PREFERRED_ADDR_LEN   41
+#  define QUIC_MAX_ENCODED_PREFERRED_ADDR_LEN   61
+
 # endif
 
 #endif
index 8a1ef34ead6f20b28d1fdc84e3a8e3566ac9d299..d6423415eaf7b9933ccf2bdb51a482a14072d621 100644 (file)
@@ -764,6 +764,19 @@ int ossl_quic_wire_decode_transport_param_cid(PACKET *pkt,
                                               uint64_t *id,
                                               QUIC_CONN_ID *cid);
 
+/*
+ * Decodes a QUIC transport parameter TLV containing a preferred_address.
+ */
+typedef struct quic_preferred_addr_st {
+    uint16_t      ipv4_port, ipv6_port;
+    unsigned char ipv4[4], ipv6[16];
+    unsigned char stateless_reset_token[QUIC_STATELESS_RESET_TOKEN_LEN];
+    QUIC_CONN_ID  cid;
+} QUIC_PREFERRED_ADDR;
+
+int ossl_quic_wire_decode_transport_param_preferred_addr(PACKET *pkt,
+                                                         QUIC_PREFERRED_ADDR *p);
+
 # endif
 
 #endif
index cf19d3b51c98e044ff90858d2d1fee2e29389ad8..41d42896bc6091205a3dad3c959dd571d6cbdb2b 100644 (file)
@@ -1344,18 +1344,38 @@ static int ch_on_transport_params(const unsigned char *params,
             break;
 
         case QUIC_TPARAM_PREFERRED_ADDR:
-            /* TODO(QUIC): Handle preferred address. */
-            if (ch->is_server) {
-                reason = TP_REASON_SERVER_ONLY("PREFERRED_ADDR");
-                goto malformed;
+            {
+                /* TODO(QUIC): Handle preferred address. */
+                QUIC_PREFERRED_ADDR pfa;
+
+                /*
+                 * RFC 9000 s. 18.2: "A server that chooses a zero-length
+                 * connection ID MUST NOT provide a preferred address.
+                 * Similarly, a server MUST NOT include a zero-length connection
+                 * ID in this transport parameter. A client MUST treat a
+                 * violation of these requirements as a connection error of type
+                 * TRANSPORT_PARAMETER_ERROR."
+                 */
+                if (ch->is_server) {
+                    reason = TP_REASON_SERVER_ONLY("PREFERRED_ADDR");
+                    goto malformed;
+                }
+
+                if (ch->cur_remote_dcid.id_len == 0) {
+                    reason = "PREFERRED_ADDR provided for zero-length CID";
+                    goto malformed;
+                }
+
+                if (!ossl_quic_wire_decode_transport_param_preferred_addr(&pkt, &pfa)) {
+                    reason = TP_REASON_MALFORMED("PREFERRED_ADDR");
+                    goto malformed;
+                }
+
+                if (pfa.cid.id_len == 0) {
+                    reason = "zero-length CID in PREFERRED_ADDR";
+                    goto malformed;
+                }
             }
-
-            body = ossl_quic_wire_decode_transport_param_bytes(&pkt, &id, &len);
-            if (body == NULL) {
-                reason = TP_REASON_MALFORMED("PREFERRED_ADDR");
-                goto malformed;
-            }
-
             break;
 
         case QUIC_TPARAM_DISABLE_ACTIVE_MIGRATION:
@@ -1488,7 +1508,7 @@ static int ch_generate_transport_params(QUIC_CHANNEL *ch)
         goto err;
 
     if (!ossl_quic_wire_encode_transport_param_int(&wpkt, QUIC_TPARAM_ACTIVE_CONN_ID_LIMIT,
-                                                   2))
+                                                   QUIC_MIN_ACTIVE_CONN_ID_LIMIT))
         goto err;
 
     if (!ossl_quic_wire_encode_transport_param_int(&wpkt, QUIC_TPARAM_INITIAL_MAX_DATA,
index 0545b8f9569e078682c1ede0d161d9b74cd0cc0c..2412e9afa5c6cfea58a1f1d93b314f0eb58b1551 100644 (file)
@@ -937,3 +937,39 @@ int ossl_quic_wire_decode_transport_param_cid(PACKET *pkt,
     memcpy(cid->id, body, cid->id_len);
     return 1;
 }
+
+int ossl_quic_wire_decode_transport_param_preferred_addr(PACKET *pkt,
+                                                         QUIC_PREFERRED_ADDR *p)
+{
+    const unsigned char *body;
+    uint64_t id;
+    size_t len = 0;
+    PACKET pkt2;
+    unsigned int ipv4_port, ipv6_port, cidl;
+
+    body = ossl_quic_wire_decode_transport_param_bytes(pkt, &id, &len);
+    if (body == NULL
+        || len < QUIC_MIN_ENCODED_PREFERRED_ADDR_LEN
+        || len > QUIC_MAX_ENCODED_PREFERRED_ADDR_LEN
+        || id != QUIC_TPARAM_PREFERRED_ADDR)
+        return 0;
+
+    if (!PACKET_buf_init(&pkt2, body, len))
+        return 0;
+
+    if (!PACKET_copy_bytes(&pkt2, p->ipv4, sizeof(p->ipv4))
+        || !PACKET_get_net_2(&pkt2, &ipv4_port)
+        || !PACKET_copy_bytes(&pkt2, p->ipv6, sizeof(p->ipv6))
+        || !PACKET_get_net_2(&pkt2, &ipv6_port)
+        || !PACKET_get_1(&pkt2, &cidl)
+        || cidl > QUIC_MAX_CONN_ID_LEN
+        || !PACKET_copy_bytes(&pkt2, p->cid.id, cidl)
+        || !PACKET_copy_bytes(&pkt2, p->stateless_reset_token,
+                              sizeof(p->stateless_reset_token)))
+        return 0;
+
+    p->ipv4_port    = (uint16_t)ipv4_port;
+    p->ipv6_port    = (uint16_t)ipv6_port;
+    p->cid.id_len   = (unsigned char)cidl;
+    return 1;
+}