Commit 20da449b76 for openssl.org
commit 20da449b76daf8c93fd38cfbfac2a6776217a8f8
Author: Darren Ray Carreras <carrerasdarren@gmail.com>
Date: Mon Jul 20 18:28:19 2026 -0400
QUIC: ignore rejected default stream candidates
Auto-rejected streams remain in the stream map until garbage collection, but
they are never linked to the incoming accept queue. Connection-level reads
currently treat such a stream as a default stream candidate and try to remove
its unlinked accept node, which aborts in debug builds and dereferences NULL
when assertions are disabled.
Only select peer streams that are still linked to the accept queue, and add
a regression test requiring SSL_ERROR_WANT_READ after the peer stream is
rejected.
Fixes #31685
Assisted-by: Codex:GPT-5
Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
MergeDate: Tue Aug 11 07:15:53 2026
(Merged from https://github.com/openssl/openssl/pull/32020)
diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c
index 99ddfd082e..3141912a61 100644
--- a/ssl/quic/quic_impl.c
+++ b/ssl/quic/quic_impl.c
@@ -2149,6 +2149,23 @@ struct quic_wait_for_stream_args {
uint64_t expect_id;
};
+QUIC_NEEDS_LOCK
+static QUIC_STREAM *quic_get_incoming_default_stream(QUIC_CONNECTION *qc,
+ uint64_t expect_id)
+{
+ QUIC_STREAM_MAP *qsm = ossl_quic_channel_get_qsm(qc->ch);
+ QUIC_STREAM *qs;
+
+ qs = ossl_quic_stream_map_get_by_id(qsm,
+ expect_id | QUIC_STREAM_DIR_BIDI);
+ if (qs == NULL)
+ qs = ossl_quic_stream_map_get_by_id(qsm,
+ expect_id | QUIC_STREAM_DIR_UNI);
+
+ /* Auto-rejected streams remain in the map until garbage collection. */
+ return qs != NULL && qs->accept_node.next != NULL ? qs : NULL;
+}
+
QUIC_NEEDS_LOCK
static int quic_wait_for_stream(void *arg)
{
@@ -2160,11 +2177,7 @@ static int quic_wait_for_stream(void *arg)
return -1;
}
- args->qs = ossl_quic_stream_map_get_by_id(ossl_quic_channel_get_qsm(args->qc->ch),
- args->expect_id | QUIC_STREAM_DIR_BIDI);
- if (args->qs == NULL)
- args->qs = ossl_quic_stream_map_get_by_id(ossl_quic_channel_get_qsm(args->qc->ch),
- args->expect_id | QUIC_STREAM_DIR_UNI);
+ args->qs = quic_get_incoming_default_stream(args->qc, args->expect_id);
if (args->qs != NULL)
return 1; /* stream now exists */
@@ -2201,17 +2214,12 @@ static int qc_wait_for_default_xso_for_read(QCTX *ctx, int peek)
? QUIC_STREAM_INITIATOR_CLIENT
: QUIC_STREAM_INITIATOR_SERVER;
- qs = ossl_quic_stream_map_get_by_id(ossl_quic_channel_get_qsm(qc->ch),
- expect_id | QUIC_STREAM_DIR_BIDI);
- if (qs == NULL)
- qs = ossl_quic_stream_map_get_by_id(ossl_quic_channel_get_qsm(qc->ch),
- expect_id | QUIC_STREAM_DIR_UNI);
+ qs = quic_get_incoming_default_stream(qc, expect_id);
if (qs == NULL) {
qctx_maybe_autotick(ctx);
- qs = ossl_quic_stream_map_get_by_id(ossl_quic_channel_get_qsm(qc->ch),
- expect_id);
+ qs = quic_get_incoming_default_stream(qc, expect_id);
}
if (qs == NULL) {
diff --git a/test/radix/quic_tests.c b/test/radix/quic_tests.c
index c06bd05376..311947645c 100644
--- a/test/radix/quic_tests.c
+++ b/test/radix/quic_tests.c
@@ -54,6 +54,21 @@ err:
return ok;
}
+DEF_FUNC(check_want_read)
+{
+ int ok = 0;
+ SSL *ssl;
+
+ REQUIRE_SSL(ssl);
+ if (!TEST_int_eq(SSL_get_error(ssl, 0), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(SSL_want(ssl), SSL_READING))
+ goto err;
+
+ ok = 1;
+err:
+ return ok;
+}
+
/*
* Multi-stream test
*/
@@ -148,6 +163,20 @@ DEF_SCRIPT(multi_stream, "multi stream test")
OP_FUNC(check_rejected);
}
+/*
+ * Reject an incoming stream before a default stream has been established.
+ */
+DEF_SCRIPT(reject_before_default_stream, "reject before default stream")
+{
+ OP_SIMPLE_PAIR_CONN();
+ OP_ACCEPT_CONN_WAIT(L, S, 0);
+ OP_SET_INCOMING_STREAM_POLICY(C, SSL_INCOMING_STREAM_POLICY_REJECT, 42);
+ OP_WRITE_B(S, "unseen");
+ OP_SLEEP(100);
+ OP_READ_FAIL(C);
+ OP_FUNC(check_want_read);
+}
+
/*
* Simple single-stream test
*/
@@ -2606,6 +2635,7 @@ DEF_SCRIPT(script_106, "place holder for multistrem script_106")
static SCRIPT_INFO *const scripts[] = {
USE(simple_stream),
USE(multi_stream),
+ USE(reject_before_default_stream),
USE(simple_conn),
USE(simple_thread),
USE(ssl_poll),