Commit 9379daa594 for openssl.org
commit 9379daa594eab882192b278009fd1957beee3e3b
Author: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Date: Mon Sep 7 15:29:04 2026 +0200
test: cover zero length read of a QUIC rstream
A zero length read has been a successful no-op returning zero read
bytes, and releasing a record without consuming any bytes succeeded
likewise. The new ossl_sframe_set_move_offset() refuses to keep the
offset in place, so both operations now fail once at least one byte
has been consumed, and quic_read_actual() turns the failed read into
a fatal SSL error for SSL_read_ex() with a zero length buffer.
Assisted-by: Claude:claude-fable-5
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
Merge-date: Sat Sep 26 11:35:36 2026
Merged-from: https://github.com/openssl/openssl/pull/32038
diff --git a/test/quic_stream_test.c b/test/quic_stream_test.c
index a8754e3dbb..fdeca3bee9 100644
--- a/test/quic_stream_test.c
+++ b/test/quic_stream_test.c
@@ -1257,6 +1257,81 @@ err:
return ret;
}
+/*
+ * A zero length read is a successful no-op returning zero read bytes,
+ * and releasing a record without consuming any bytes succeeds likewise.
+ * Neither may fail once at least one byte has been consumed, otherwise
+ * quic_read_actual() turns the failed read into a fatal SSL error for
+ * SSL_read_ex() called with a zero length buffer.
+ */
+static int test_rstream_zero_length_read(void)
+{
+ QUIC_RSTREAM *rstream = NULL;
+ QUIC_CHANNEL *ch = NULL;
+ QUIC_RSTREAM_QPARM *rsqp = NULL;
+ OSSL_QRX_PKT *pkt = NULL;
+ unsigned char pdata[10], buf[10];
+ const unsigned char *record = NULL;
+ size_t readbytes = 0, rec_len = 0, i;
+ int fin = 0;
+ int ret = 0;
+
+ for (i = 0; i < sizeof(pdata); ++i)
+ pdata[i] = (unsigned char)(0x40 + i);
+
+ if (!TEST_ptr(pkt = pkt_test_new(1200))
+ || !TEST_ptr(ch = OPENSSL_zalloc(sizeof(QUIC_CHANNEL)))
+ || !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,
+ pdata, sizeof(pdata), 0)))
+ goto err;
+
+ /* a zero length read before anything is consumed */
+ if (!TEST_true(ossl_quic_rstream_read(rstream, buf, 0, &readbytes, &fin))
+ || !TEST_size_t_eq(readbytes, 0))
+ goto err;
+
+ /* consume some bytes so the stream offset is not zero */
+ if (!TEST_true(ossl_quic_rstream_read(rstream, buf, 5, &readbytes, &fin))
+ || !TEST_size_t_eq(readbytes, 5)
+ || !TEST_mem_eq(buf, 5, pdata, 5))
+ goto err;
+
+ /* a zero length read with data pending at a nonzero offset */
+ if (!TEST_true(ossl_quic_rstream_read(rstream, buf, 0, &readbytes, &fin))
+ || !TEST_size_t_eq(readbytes, 0))
+ goto err;
+
+ /* releasing a record without consuming anything succeeds too */
+ if (!TEST_true(ossl_quic_rstream_get_record(rstream, &record, &rec_len,
+ &fin))
+ || !TEST_size_t_eq(rec_len, 5)
+ || !TEST_true(ossl_quic_rstream_release_record(rstream, 0)))
+ goto err;
+
+ /* the remaining bytes are intact and still readable */
+ if (!TEST_true(ossl_quic_rstream_read(rstream, buf, sizeof(buf),
+ &readbytes, &fin))
+ || !TEST_size_t_eq(readbytes, 5)
+ || !TEST_mem_eq(buf, 5, pdata + 5, 5))
+ goto err;
+
+ if (!TEST_int_eq(ch->protocol_error, 0))
+ goto err;
+
+ ret = 1;
+
+err:
+ ossl_quic_rstream_free(rstream);
+ ossl_quic_rstream_qparm_destroy(rsqp);
+ pkt_test_free(pkt);
+ ossl_quic_channel_free(ch);
+ return ret;
+}
+
#define FILL_PATTERN "abcdefghijklmnopqrstuvwxyz0123456789" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"