QUIC CONFORMANCE: Enforce minimal frame type encoding
authorHugo Landau <hlandau@openssl.org>
Tue, 6 Jun 2023 15:25:10 +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/packet_quic.h
include/internal/quic_wire.h
ssl/quic/quic_rx_depack.c
ssl/quic/quic_trace.c
ssl/quic/quic_txp.c
ssl/quic/quic_wire.c
test/helpers/quictestlib.c
test/helpers/quictestlib.h
test/quic_txp_test.c
test/quic_wire_test.c
test/quicfaultstest.c

index 7784e62c16181afca6862b9b1102d1012a9fb9ff..e75b81e4222817af36df6b0c3804c80f3339f445 100644 (file)
@@ -40,10 +40,12 @@ __owur static ossl_inline int PACKET_get_quic_vlint(PACKET *pkt,
 /*
  * Decodes a QUIC variable-length integer in |pkt| and stores the result in
  * |data|. Unlike PACKET_get_quic_vlint, this does not advance the current
- * position.
+ * position. If was_minimal is non-NULL, *was_minimal is set to 1 if the integer
+ * was encoded using the minimal possible number of bytes and 0 otherwise.
  */
-__owur static ossl_inline int PACKET_peek_quic_vlint(PACKET *pkt,
-                                                     uint64_t *data)
+__owur static ossl_inline int PACKET_peek_quic_vlint_ex(PACKET *pkt,
+                                                        uint64_t *data,
+                                                        int *was_minimal)
 {
     size_t enclen;
 
@@ -56,9 +58,19 @@ __owur static ossl_inline int PACKET_peek_quic_vlint(PACKET *pkt,
         return 0;
 
     *data = ossl_quic_vlint_decode_unchecked(pkt->curr);
+
+    if (was_minimal != NULL)
+        *was_minimal = (enclen == ossl_quic_vlint_encode_len(*data));
+
     return 1;
 }
 
+__owur static ossl_inline int PACKET_peek_quic_vlint(PACKET *pkt,
+                                                     uint64_t *data)
+{
+    return PACKET_peek_quic_vlint_ex(pkt, data, NULL);
+}
+
 /*
  * Skips over a QUIC variable-length integer in |pkt| without decoding it.
  */
index e9ff8e6f356fbd1a9ece54aae8e48365b0379700..8a1ef34ead6f20b28d1fdc84e3a8e3566ac9d299 100644 (file)
@@ -493,7 +493,8 @@ int ossl_quic_wire_encode_transport_param_cid(WPACKET *wpkt,
  * position). This can be used to determine the frame type and determine which
  * frame decoding function to call.
  */
-int ossl_quic_wire_peek_frame_header(PACKET *pkt, uint64_t *type);
+int ossl_quic_wire_peek_frame_header(PACKET *pkt, uint64_t *type,
+                                     int *was_minimal);
 
 /*
  * Like ossl_quic_wire_peek_frame_header, but advances the current position
index 88a893cf247748da33c2bb62928fce8dea3b5c61..6e2067f451c995ad613ec4848b6f14edb673cd6c 100644 (file)
@@ -928,6 +928,7 @@ static int depack_process_frames(QUIC_CHANNEL *ch, PACKET *pkt,
     }
 
     while (PACKET_remaining(pkt) > 0) {
+        int was_minimal;
         uint64_t frame_type;
         const unsigned char *sof = NULL;
         uint64_t datalen = 0;
@@ -935,8 +936,21 @@ static int depack_process_frames(QUIC_CHANNEL *ch, PACKET *pkt,
         if (ch->msg_callback != NULL)
             sof = PACKET_data(pkt);
 
-        if (!ossl_quic_wire_peek_frame_header(pkt, &frame_type))
+        if (!ossl_quic_wire_peek_frame_header(pkt, &frame_type, &was_minimal)) {
+            ossl_quic_channel_raise_protocol_error(ch,
+                                                   QUIC_ERR_PROTOCOL_VIOLATION,
+                                                   0,
+                                                   "malformed frame header");
+            return 0;
+        }
+
+        if (!was_minimal) {
+            ossl_quic_channel_raise_protocol_error(ch,
+                                                   QUIC_ERR_PROTOCOL_VIOLATION,
+                                                   frame_type,
+                                                   "non-minimal frame type encoding");
             return 0;
+        }
 
         switch (frame_type) {
         case OSSL_QUIC_FRAME_TYPE_PING:
@@ -1211,7 +1225,7 @@ static int depack_process_frames(QUIC_CHANNEL *ch, PACKET *pkt,
             /* Unknown frame type */
             ackm_data->is_ack_eliciting = 1;
             ossl_quic_channel_raise_protocol_error(ch,
-                                                   QUIC_ERR_PROTOCOL_VIOLATION,
+                                                   QUIC_ERR_FRAME_ENCODING_ERROR,
                                                    frame_type,
                                                    "Unknown frame type received");
             return 0;
index ed25e05ee3504792d63f7b24a3556d5f58476e61..5a57e675f99e90f8efbfef7e5b13a0ae101c0ecb 100644 (file)
@@ -392,7 +392,7 @@ static int trace_frame_data(BIO *bio, PACKET *pkt)
 {
     uint64_t frame_type;
 
-    if (!ossl_quic_wire_peek_frame_header(pkt, &frame_type))
+    if (!ossl_quic_wire_peek_frame_header(pkt, &frame_type, NULL))
         return 0;
 
     switch (frame_type) {
index 42329b42de16c06683285e2d2f2fdd7030b883f0..18e0c507bae9bd588500d71beb835d665d1b729c 100644 (file)
@@ -318,7 +318,7 @@ static int tx_helper_commit(struct tx_helper *h)
         PACKET pkt;
 
         if (!PACKET_buf_init(&pkt, h->txn.data, l)
-                || !ossl_quic_wire_peek_frame_header(&pkt, &ftype)) {
+                || !ossl_quic_wire_peek_frame_header(&pkt, &ftype, NULL)) {
             tx_helper_end(h, 0);
             return 0;
         }
index 937d16e1c8c7e24de801c6e3c5dae11d44f249e1..0545b8f9569e078682c1ede0d161d9b74cd0cc0c 100644 (file)
@@ -439,9 +439,10 @@ int ossl_quic_wire_encode_transport_param_cid(WPACKET *wpkt,
  * QUIC Wire Format Decoding
  * =========================
  */
-int ossl_quic_wire_peek_frame_header(PACKET *pkt, uint64_t *type)
+int ossl_quic_wire_peek_frame_header(PACKET *pkt, uint64_t *type,
+                                     int *was_minimal)
 {
-    return PACKET_peek_quic_vlint(pkt, type);
+    return PACKET_peek_quic_vlint_ex(pkt, type, was_minimal);
 }
 
 int ossl_quic_wire_skip_frame_header(PACKET *pkt, uint64_t *type)
index 1053a102de651b242e7cb9cd111bc4bcb3040e9d..fcff11727aa74826419e01ab881e215c5adb5c02 100644 (file)
@@ -347,6 +347,7 @@ int qtest_check_server_transport_err(QUIC_TSERVER *qtserv, uint64_t code)
     cause = ossl_quic_tserver_get_terminate_cause(qtserv);
     if  (!TEST_ptr(cause)
             || !TEST_true(cause->remote)
+            || !TEST_false(cause->app)
             || !TEST_uint64_t_eq(cause->error_code, code))
         return 0;
 
@@ -358,6 +359,11 @@ int qtest_check_server_protocol_err(QUIC_TSERVER *qtserv)
     return qtest_check_server_transport_err(qtserv, QUIC_ERR_PROTOCOL_VIOLATION);
 }
 
+int qtest_check_server_frame_encoding_err(QUIC_TSERVER *qtserv)
+{
+    return qtest_check_server_transport_err(qtserv, QUIC_ERR_FRAME_ENCODING_ERROR);
+}
+
 void qtest_fault_free(QTEST_FAULT *fault)
 {
     if (fault == NULL)
index 90e1b68c1fa3a49e8c2903a12319f9b43240ea83..8b24ab600963ef67a27f31ca088ba6f7cb5a9ca5 100644 (file)
@@ -74,6 +74,12 @@ int qtest_check_server_transport_err(QUIC_TSERVER *qtserv, uint64_t code);
  */
 int qtest_check_server_protocol_err(QUIC_TSERVER *qtserv);
 
+/*
+ * Confirm the server has received a frame encoding error. Equivalent to calling
+ * qtest_check_server_transport_err with a code of QUIC_ERR_FRAME_ENCODING_ERROR
+ */
+int qtest_check_server_frame_encoding_err(QUIC_TSERVER *qtserv);
+
 /*
  * Enable tests to listen for pre-encryption QUIC packets being sent
  */
index b733dcaf1c5fc15a2e07f1abc8dca07c7aeec884..52026d3569130ccec6524388296b98f51313aeab 100644 (file)
@@ -1217,7 +1217,7 @@ static void skip_padding(struct helper *h)
 {
     uint64_t frame_type;
 
-    if (!ossl_quic_wire_peek_frame_header(&h->pkt, &frame_type))
+    if (!ossl_quic_wire_peek_frame_header(&h->pkt, &frame_type, NULL))
         return; /* EOF */
 
     if (frame_type == OSSL_QUIC_FRAME_TYPE_PADDING)
@@ -1296,7 +1296,7 @@ static int run_script(const struct script_op *script)
             break;
         case OPK_NEXT_FRAME:
             skip_padding(&h);
-            if (!ossl_quic_wire_peek_frame_header(&h.pkt, &h.frame_type)) {
+            if (!ossl_quic_wire_peek_frame_header(&h.pkt, &h.frame_type, NULL)) {
                 h.frame_type = UINT64_MAX;
                 break;
             }
index e3cd218e27543eeaf9f4b6edd7945c7fe3303870..c307643c1b3fc83cb09bc1b55592d359ef456507 100644 (file)
@@ -533,20 +533,29 @@ static int encode_case_12_dec(PACKET *pkt, ossl_ssize_t fail)
 {
     uint64_t max_streams_1 = 0, max_streams_2 = 0,
             frame_type_1 = 0, frame_type_2 = 0;
+    int is_minimal;
 
-    if (!TEST_int_eq(ossl_quic_wire_peek_frame_header(pkt, &frame_type_1),
+    if (!TEST_int_eq(ossl_quic_wire_peek_frame_header(pkt, &frame_type_1,
+                                                      &is_minimal),
                      fail < 0 || fail >= 1))
         return 0;
 
+    if (!TEST_true(is_minimal))
+        return 0;
+
     if (!TEST_int_eq(ossl_quic_wire_decode_frame_max_streams(pkt,
                                                              &max_streams_1),
                      fail < 0 || fail >= 3))
         return 0;
 
-    if (!TEST_int_eq(ossl_quic_wire_peek_frame_header(pkt, &frame_type_2),
+    if (!TEST_int_eq(ossl_quic_wire_peek_frame_header(pkt, &frame_type_2,
+                                                      &is_minimal),
                      fail < 0 || fail >= 4))
         return 0;
 
+    if (!TEST_true(is_minimal))
+        return 0;
+
     if (!TEST_int_eq(ossl_quic_wire_decode_frame_max_streams(pkt,
                                                              &max_streams_2),
                      fail < 0))
@@ -663,20 +672,29 @@ static int encode_case_15_dec(PACKET *pkt, ossl_ssize_t fail)
 {
     uint64_t max_streams_1 = 0, max_streams_2 = 0,
             frame_type_1 = 0, frame_type_2 = 0;
+    int is_minimal;
 
-    if (!TEST_int_eq(ossl_quic_wire_peek_frame_header(pkt, &frame_type_1),
+    if (!TEST_int_eq(ossl_quic_wire_peek_frame_header(pkt, &frame_type_1,
+                                                      &is_minimal),
                      fail < 0 || fail >= 1))
         return 0;
 
+    if (!TEST_true(is_minimal))
+        return 0;
+
     if (!TEST_int_eq(ossl_quic_wire_decode_frame_streams_blocked(pkt,
                                                                  &max_streams_1),
                      fail < 0 || fail >= 3))
         return 0;
 
-    if (!TEST_int_eq(ossl_quic_wire_peek_frame_header(pkt, &frame_type_2),
+    if (!TEST_int_eq(ossl_quic_wire_peek_frame_header(pkt, &frame_type_2,
+                                                      &is_minimal),
                      fail < 0 || fail >= 4))
         return 0;
 
+    if (!TEST_true(is_minimal))
+        return 0;
+
     if (!TEST_int_eq(ossl_quic_wire_decode_frame_streams_blocked(pkt,
                                                                  &max_streams_2),
                      fail < 0 || fail >= 8))
@@ -1539,11 +1557,78 @@ err:
     return testresult;
 }
 
+/* is_minimal=0 test */
+static const unsigned char non_minimal_1[] = {
+    0x40, 0x00,
+};
+
+static const unsigned char non_minimal_2[] = {
+    0x40, 0x3F,
+};
+
+static const unsigned char non_minimal_3[] = {
+    0x80, 0x00, 0x00, 0x00,
+};
+
+static const unsigned char non_minimal_4[] = {
+    0x80, 0x00, 0x3F, 0xFF,
+};
+
+static const unsigned char non_minimal_5[] = {
+    0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const unsigned char non_minimal_6[] = {
+    0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF
+};
+
+static const unsigned char *const non_minimal[] = {
+    non_minimal_1,
+    non_minimal_2,
+    non_minimal_3,
+    non_minimal_4,
+    non_minimal_5,
+    non_minimal_6,
+};
+
+static const size_t non_minimal_len[] = {
+    OSSL_NELEM(non_minimal_1),
+    OSSL_NELEM(non_minimal_2),
+    OSSL_NELEM(non_minimal_3),
+    OSSL_NELEM(non_minimal_4),
+    OSSL_NELEM(non_minimal_5),
+    OSSL_NELEM(non_minimal_6),
+};
+
+static int test_wire_minimal(int idx)
+{
+    int testresult = 0;
+    int is_minimal;
+    uint64_t frame_type;
+    PACKET pkt;
+
+    if (!TEST_true(PACKET_buf_init(&pkt, non_minimal[idx],
+                                   non_minimal_len[idx])))
+        goto err;
+
+    if (!TEST_true(ossl_quic_wire_peek_frame_header(&pkt, &frame_type,
+                                                    &is_minimal)))
+        goto err;
+
+    if (!TEST_false(is_minimal))
+        goto err;
+
+    testresult = 1;
+err:
+    return testresult;
+}
+
 int setup_tests(void)
 {
     ADD_ALL_TESTS(test_wire_encode,     OSSL_NELEM(encode_cases));
     ADD_ALL_TESTS(test_wire_ack,        OSSL_NELEM(ack_cases));
     ADD_ALL_TESTS(test_wire_pkt_hdr_pn, OSSL_NELEM(pn_tests));
     ADD_TEST(test_wire_retry_integrity_tag);
+    ADD_ALL_TESTS(test_wire_minimal,    OSSL_NELEM(non_minimal_len));
     return 1;
 }
index 4d1cae01e4c07dd4c6e7426a92a149309c1c37a1..406b09a9ea3af53b9b5c5d9f889a96ae3bccd370 100644 (file)
@@ -155,7 +155,7 @@ static int test_unknown_frame(void)
         goto err;
 #endif
 
-    if (!TEST_true(qtest_check_server_protocol_err(qtserv)))
+    if (!TEST_true(qtest_check_server_frame_encoding_err(qtserv)))
         goto err;
 
     testresult = 1;