]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
QUIC CHANNEL: Handle incoming remotely-created streams
authorHugo Landau <hlandau@openssl.org>
Tue, 18 Apr 2023 18:30:54 +0000 (19:30 +0100)
committerHugo Landau <hlandau@openssl.org>
Fri, 12 May 2023 13:47:11 +0000 (14:47 +0100)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/20765)

include/internal/quic_channel.h
include/internal/quic_stream_map.h
ssl/quic/quic_channel.c
ssl/quic/quic_impl.c
ssl/quic/quic_rx_depack.c
ssl/quic/quic_stream_map.c

index 7ccf6e0bb250073e350c7d4d3785a4cb5a71fa1c..084c465c3add3dde46c44ee70a556bf1815dac92 100644 (file)
@@ -273,9 +273,18 @@ CRYPTO_MUTEX *ossl_quic_channel_get_mutex(QUIC_CHANNEL *ch);
 /*
  * Creates a new locally-initiated stream in the stream mapper, choosing an
  * appropriate stream ID. If is_uni is 1, creates a unidirectional stream, else
- * creates a bidirectional stream.
+ * creates a bidirectional stream. Returns NULL on failure.
  */
-QUIC_STREAM *ossl_quic_channel_new_stream(QUIC_CHANNEL *ch, int is_uni);
+QUIC_STREAM *ossl_quic_channel_new_stream_local(QUIC_CHANNEL *ch, int is_uni);
+
+/*
+ * Creates a new remotely-initiated stream in the stream mapper. The stream ID
+ * is used to confirm the initiator and determine the stream type. The stream is
+ * automatically added to the QSM's accept queue. A pointer to the stream is
+ * also returned. Returns NULL on failure.
+ */
+QUIC_STREAM *ossl_quic_channel_new_stream_remote(QUIC_CHANNEL *ch,
+                                                 uint64_t stream_id);
 
 # endif
 
index 0bdd7e88cfd237b0bdb0e48811ffccd2c35c9d3a..78ec703fbc9420b6e6ce9a2f016d9e01b9f8f547 100644 (file)
@@ -36,6 +36,7 @@ struct quic_stream_list_node_st {
 
 struct quic_stream_st {
     QUIC_STREAM_LIST_NODE active_node; /* for use by QUIC_STREAM_MAP */
+    QUIC_STREAM_LIST_NODE accept_node; /* accept queue of remotely-created streams */
 
     /* Temporary link used by TXP. */
     QUIC_STREAM    *txp_next;
@@ -132,7 +133,8 @@ int ossl_quic_stream_reset(QUIC_STREAM *s, uint64_t aec);
 typedef struct quic_stream_map_st {
     LHASH_OF(QUIC_STREAM)   *map;
     QUIC_STREAM_LIST_NODE   active_list;
-    size_t                  rr_stepping, rr_counter;
+    QUIC_STREAM_LIST_NODE   accept_list;
+    size_t                  rr_stepping, rr_counter, num_accept;
     QUIC_STREAM             *rr_cur;
     uint64_t                (*get_stream_limit_cb)(int uni, void *arg);
     void                    *get_stream_limit_cb_arg;
@@ -231,6 +233,29 @@ void ossl_quic_stream_map_update_state(QUIC_STREAM_MAP *qsm, QUIC_STREAM *s);
  */
 void ossl_quic_stream_map_set_rr_stepping(QUIC_STREAM_MAP *qsm, size_t stepping);
 
+/*
+ * Adds a stream to the accept queue.
+ */
+void ossl_quic_stream_map_push_accept_queue(QUIC_STREAM_MAP *qsm,
+                                            QUIC_STREAM *s);
+
+/*
+ * Returns the next item to be popped from the accept queue, or NULL if it is
+ * empty.
+ */
+QUIC_STREAM *ossl_quic_stream_map_peek_accept_queue(QUIC_STREAM_MAP *qsm);
+
+/*
+ * Removes a stream from the accept queue.
+ *
+ * Precondition: s is in the accept queue.
+ */
+void ossl_quic_stream_map_remove_from_accept_queue(QUIC_STREAM_MAP *qsm,
+                                                   QUIC_STREAM *s);
+
+/* Returns the length of the accept queue. */
+size_t ossl_quic_stream_map_get_accept_queue_len(QUIC_STREAM_MAP *qsm);
+
 /*
  * QUIC Stream Iterator
  * ====================
index 8e1d41593542128cb9004f28b53afd8cbc855c4f..b11393955d3fc33e5addeed199c43f2698d92aad 100644 (file)
@@ -2214,7 +2214,7 @@ SSL *ossl_quic_channel_get0_ssl(QUIC_CHANNEL *ch)
     return ch->tls;
 }
 
-QUIC_STREAM *ossl_quic_channel_new_stream(QUIC_CHANNEL *ch, int is_uni)
+QUIC_STREAM *ossl_quic_channel_new_stream_local(QUIC_CHANNEL *ch, int is_uni)
 {
     QUIC_STREAM *qs;
     int type = 0;
@@ -2242,3 +2242,26 @@ QUIC_STREAM *ossl_quic_channel_new_stream(QUIC_CHANNEL *ch, int is_uni)
     ++*p_next_ordinal;
     return qs;
 }
+
+QUIC_STREAM *ossl_quic_channel_new_stream_remote(QUIC_CHANNEL *ch,
+                                                 uint64_t stream_id)
+{
+    uint64_t peer_role;
+    QUIC_STREAM *qs;
+
+    peer_role = ch->is_server
+        ? QUIC_STREAM_INITIATOR_CLIENT
+        : QUIC_STREAM_INITIATOR_SERVER;
+
+    if ((stream_id & QUIC_STREAM_INITIATOR_MASK) != peer_role)
+        return NULL;
+
+    qs = ossl_quic_stream_map_alloc(&ch->qsm, stream_id,
+                                    stream_id & (QUIC_STREAM_INITIATOR_MASK
+                                                 | QUIC_STREAM_DIR_MASK));
+    if (qs == NULL)
+        return NULL;
+
+    ossl_quic_stream_map_push_accept_queue(&ch->qsm, qs);
+    return qs;
+}
index 0dd6576633525e2f762d59f769f40ba68cdea8eb..41de167dd231c50db1f646987db44fe9856f53ad 100644 (file)
@@ -296,6 +296,15 @@ void ossl_quic_free(SSL *s)
 
         ctx.xso->stream->deleted = 1;
 
+        /* Auto-conclude stream. */
+        /* TODO(QUIC): Do RESET_STREAM here instead of auto-conclude */
+        if (ctx.xso->stream->sstream != NULL)
+            ossl_quic_sstream_fin(ctx.xso->stream->sstream);
+
+        /* Update stream state. */
+        ossl_quic_stream_map_update_state(ossl_quic_channel_get_qsm(ctx.xso->conn->ch),
+                                          ctx.xso->stream);
+
         quic_unlock(ctx.qc);
 
         /* Note: SSL_free calls OPENSSL_free(xso) for us */
@@ -1106,7 +1115,7 @@ SSL *ossl_quic_conn_stream_new(SSL *s, uint64_t flags)
     xso->blocking   = ctx.qc->default_blocking;
     xso->ssl_mode   = ctx.qc->default_ssl_mode;
 
-    xso->stream     = ossl_quic_channel_new_stream(ctx.qc->ch, is_uni);
+    xso->stream     = ossl_quic_channel_new_stream_local(ctx.qc->ch, is_uni);
     if (xso->stream == NULL)
         goto err;
 
index ea54bc3c08d04e83f8ae9aebb5c02131706c1991..7adec6d7b0b22a2af17b0bc32fce374016542c1d 100644 (file)
@@ -254,12 +254,80 @@ static int depack_do_frame_stream(PACKET *pkt, QUIC_CHANNEL *ch,
 
     stream = ossl_quic_stream_map_get_by_id(&ch->qsm, frame_data.stream_id);
     if (stream == NULL) {
-        ossl_quic_channel_raise_protocol_error(ch,
-                                               QUIC_ERR_STREAM_STATE_ERROR,
-                                               frame_type,
-                                               "STREAM frame for nonexistent "
-                                               "stream");
-        return 0;
+        uint64_t peer_role, stream_ordinal, *p_next_ordinal;
+        int is_uni;
+
+        /*
+         * If we do not yet have a stream with the given ID, there are three
+         * possibilities:
+         *
+         *   (a) The stream ID is for a remotely-created stream and the peer
+         *       is creating a stream.
+         *
+         *   (b) The stream ID is for a locally-created stream which has
+         *       previously been deleted.
+         *
+         *   (c) The stream ID is for a locally-created stream which does
+         *       not exist yet. This is a protocol violation and we must
+         *       terminate the connection in this case.
+         *
+         * We distinguish between (b) and (c) using the stream ID allocator
+         * variable. Since stream ordinals are allocated monotonically, we
+         * simply determine if the stream ordinal is in the future.
+         */
+
+        peer_role = ch->is_server
+            ? QUIC_STREAM_INITIATOR_CLIENT
+            : QUIC_STREAM_INITIATOR_SERVER;
+
+        is_uni = ((frame_data.stream_id & QUIC_STREAM_DIR_MASK)
+                  == QUIC_STREAM_DIR_UNI);
+
+        stream_ordinal = frame_data.stream_id >> 2;
+
+        if ((frame_data.stream_id & QUIC_STREAM_INITIATOR_MASK) == peer_role) {
+            /* Peer-created stream which does not yet exist. Create it. */
+            stream = ossl_quic_channel_new_stream_remote(ch, frame_data.stream_id);
+            if (stream == NULL) {
+                ossl_quic_channel_raise_protocol_error(ch,
+                                                       QUIC_ERR_INTERNAL_ERROR,
+                                                       frame_type,
+                                                       "internal error (stream allocation)");
+                return 0;
+            }
+
+            /*
+             * Fallthrough to processing of stream data for newly created
+             * stream.
+             */
+        } else {
+            /* Locally-created stream which does not yet exist. */
+
+            p_next_ordinal = is_uni
+                ? &ch->next_local_stream_ordinal_uni
+                : &ch->next_local_stream_ordinal_bidi;
+
+            if (stream_ordinal >= *p_next_ordinal) {
+                /*
+                 * We never created this stream yet, this is a protocol
+                 * violation.
+                 */
+                ossl_quic_channel_raise_protocol_error(ch,
+                                                       QUIC_ERR_STREAM_STATE_ERROR,
+                                                       frame_type,
+                                                       "STREAM frame for nonexistent "
+                                                       "stream");
+                return 0;
+            }
+
+            /*
+             * Otherwise this is for an old locally-initiated stream which we
+             * have subsequently deleted. Ignore the data; it may simply be a
+             * retransmission. We already take care of notifying the peer of the
+             * termination of the stream during the stream deletion lifecycle.
+             */
+            return 1;
+        }
     }
 
     if (stream->rstream == NULL) {
index 52e8e14bf8f547e159db331e3272510dcc534a11..06bf1cfa349227c24c902c7ec067c3f0c3aabf2e 100644 (file)
@@ -66,17 +66,26 @@ static void list_remove(QUIC_STREAM_LIST_NODE *l,
     n->next = n->prev = NULL;
 }
 
-static QUIC_STREAM *active_next(QUIC_STREAM_LIST_NODE *l, QUIC_STREAM *s)
+static QUIC_STREAM *list_next(QUIC_STREAM_LIST_NODE *l, QUIC_STREAM_LIST_NODE *n,
+                              size_t off)
 {
-    QUIC_STREAM_LIST_NODE *n = s->active_node.next;
+    n = n->next;
 
     if (n == l)
         n = n->next;
     if (n == l)
         return NULL;
-    return (QUIC_STREAM *)n;
+
+    return (QUIC_STREAM *)(((char *)n) - off);
 }
 
+#define active_next(l, s) list_next((l), &(s)->active_node, \
+                                    offsetof(QUIC_STREAM, active_node))
+#define accept_next(l, s) list_next((l), &(s)->accept_node, \
+                                    offsetof(QUIC_STREAM, accept_node))
+#define accept_head(l)    list_next((l), (l), \
+                                    offsetof(QUIC_STREAM, accept_node))
+
 static unsigned long hash_stream(const QUIC_STREAM *s)
 {
     return (unsigned long)s->id;
@@ -97,9 +106,11 @@ int ossl_quic_stream_map_init(QUIC_STREAM_MAP *qsm,
 {
     qsm->map = lh_QUIC_STREAM_new(hash_stream, cmp_stream);
     qsm->active_list.prev = qsm->active_list.next = &qsm->active_list;
+    qsm->accept_list.prev = qsm->accept_list.next = &qsm->accept_list;
     qsm->rr_stepping = 1;
     qsm->rr_counter  = 0;
     qsm->rr_cur      = NULL;
+    qsm->num_accept  = 0;
 
     qsm->get_stream_limit_cb        = get_stream_limit_cb;
     qsm->get_stream_limit_cb_arg    = get_stream_limit_cb_arg;
@@ -271,6 +282,30 @@ void ossl_quic_stream_map_update_state(QUIC_STREAM_MAP *qsm, QUIC_STREAM *s)
         stream_map_mark_inactive(qsm, s);
 }
 
+QUIC_STREAM *ossl_quic_stream_map_peek_accept_queue(QUIC_STREAM_MAP *qsm)
+{
+    return accept_head(&qsm->accept_list);
+}
+
+void ossl_quic_stream_map_push_accept_queue(QUIC_STREAM_MAP *qsm,
+                                            QUIC_STREAM *s)
+{
+    list_insert_tail(&qsm->accept_list, &s->accept_node);
+    ++qsm->num_accept;
+}
+
+void ossl_quic_stream_map_remove_from_accept_queue(QUIC_STREAM_MAP *qsm,
+                                                   QUIC_STREAM *s)
+{
+    list_remove(&qsm->accept_list, &s->accept_node);
+    --qsm->num_accept;
+}
+
+size_t ossl_quic_stream_map_get_accept_queue_len(QUIC_STREAM_MAP *qsm)
+{
+    return qsm->num_accept;
+}
+
 /*
  * QUIC Stream Iterator
  * ====================