Commit 9fb911d3fa for openssl.org
commit 9fb911d3fa1e3ff355355165d6979ea6006f6a52
Author: Alexandr Nedvedicky <sashan@openssl.org>
Date: Wed Nov 4 23:46:18 2026 +0100
test/quic_stream_test.c: add a test for CVE-2026-95691
Co-Authored-by: Filipe Casal <filipe.casal@trailofbits.com>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Andrew Dinh <andrewd@openssl.org>
Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
Merge-date: Mon Nov 9 03:03:12 2026
Merged-from: https://github.com/openssl/openssl/pull/33543
diff --git a/test/quic_stream_test.c b/test/quic_stream_test.c
index 1da50a3ee5..e36a70899a 100644
--- a/test/quic_stream_test.c
+++ b/test/quic_stream_test.c
@@ -806,6 +806,76 @@ err:
return ret;
}
+static int test_rstream_checked_out_multirange_cover(void)
+{
+ QUIC_RSTREAM *rstream = NULL;
+ QUIC_CHANNEL *ch = NULL;
+ QUIC_RSTREAM_QPARM *rsqp = NULL;
+ OSSL_QRX_PKT *pkt[5] = { 0 };
+ const unsigned char *record = NULL;
+ const unsigned char *nested_record = NULL;
+ unsigned char head[16], future[3] = { 0 }, cover[98], read_buf[1];
+ size_t rec_len = 0, nested_rec_len = 0, readbytes = 0, i;
+ int fin = 0, nested_fin = 0, ret = 0;
+
+ memset(head, 0x41, sizeof(head));
+ memset(future, 0x42, sizeof(future));
+ memset(cover, 0x58, sizeof(cover));
+
+ for (i = 0; i < OSSL_NELEM(pkt); ++i)
+ if (!TEST_ptr(pkt[i] = pkt_test_new(1200)))
+ goto err;
+
+ if (!TEST_ptr(ch = OPENSSL_zalloc(sizeof(*ch)))
+ || !TEST_ptr(rsqp = ossl_quic_rstream_qparm_new(ch))
+ || !TEST_ptr(rstream = ossl_quic_rstream_new(NULL, NULL, rsqp)))
+ goto err;
+
+ if (!TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[0], 0,
+ head, sizeof(head), 0))
+ || !TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[1], 32,
+ &future[0], 1, 0))
+ || !TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[2], 64,
+ &future[1], 1, 0))
+ || !TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[3], 96,
+ &future[2], 1, 0))
+ || !TEST_true(ossl_quic_rstream_get_record(rstream, &record,
+ &rec_len, &fin))
+ || !TEST_size_t_eq(rec_len, sizeof(head))
+ || !TEST_size_t_eq(pkt_test_refcount(pkt[0]), 2))
+ goto err;
+
+ /* Consuming reads and nested checkout cannot bypass the active lock. */
+ if (!TEST_false(ossl_quic_rstream_read(rstream, read_buf,
+ sizeof(read_buf), &readbytes, &nested_fin))
+ || !TEST_false(ossl_quic_rstream_get_record(rstream, &nested_record,
+ &nested_rec_len, &nested_fin))
+ || !TEST_false(ossl_quic_rstream_release_record(rstream,
+ rec_len + 1)))
+ goto err;
+
+ /* The broad cover must not release or modify the checked-out chunk. */
+ if (!TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[4], 0,
+ cover, sizeof(cover), 0))
+ || !TEST_size_t_eq(pkt_test_refcount(pkt[0]), 2)
+ || !TEST_mem_eq(record, rec_len, head, sizeof(head)))
+ goto err;
+
+ if (!TEST_true(ossl_quic_rstream_release_record(rstream, rec_len))
+ || !TEST_size_t_eq(pkt_test_refcount(pkt[0]), 1))
+ goto err;
+
+ ret = 1;
+
+err:
+ ossl_quic_rstream_free(rstream);
+ ossl_quic_rstream_qparm_destroy(rsqp);
+ for (i = 0; i < OSSL_NELEM(pkt); ++i)
+ pkt_test_free(pkt[i]);
+ ossl_quic_channel_free(ch);
+ return ret;
+}
+
/*
* Many small contiguous frames, each pinning its own packet while the reader
* lags behind, so a large number of packets are held at once and released only
@@ -3422,6 +3492,7 @@ int setup_tests(void)
ADD_ALL_TESTS(test_rstream_simple, 2);
ADD_ALL_TESTS(test_rstream_random, 100);
ADD_TEST(test_rstream_pkt);
+ ADD_TEST(test_rstream_checked_out_multirange_cover);
ADD_TEST(test_rstream_pkt_overhead);
ADD_ALL_TESTS(test_rstream_reorder, 40);
ADD_TEST(test_rstream_dstorage_two_sided_overlap);