Commit 211b564f86 for openssl.org
commit 211b564f8631f8a9e970dba8aa77ffc04beef719
Author: Alexandr Nedvedicky <sashan@openssl.org>
Date: Mon Apr 20 22:35:16 2026 +0200
Make SSL_get_stream_write_state() safe for concluded streams
QUIC stack may panic when application calls SSL_get_stream_write_state()
on cocluded QUIC stream onject. The sequence of action which leads
to NULL pointer dereference is as follows:
- application uses SSL_stream_conclude(ssl_stream, 0) to conclude
the stream (let remote peer know no to expect more data)
- application uses SSL_get_stream_write_state(ssl_stream)
to query stream state.
If underlying sstream object is gone by the time when
SSL_get_stream_wtite_state() is called, then application
may see NULL pointer dereference. The underlying sstream
object is freed when FIN sent on beahalf of SSL_stream_conclude()
is ACKed by remote peer.
Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
MergeDate: Tue Apr 28 12:35:41 2026
(Merged from https://github.com/openssl/openssl/pull/30913)
diff --git a/include/internal/quic_stream_map.h b/include/internal/quic_stream_map.h
index 925f516a09..36753d7196 100644
--- a/include/internal/quic_stream_map.h
+++ b/include/internal/quic_stream_map.h
@@ -299,7 +299,7 @@ struct quic_stream_st {
* STOP_SENDING.]
*
* TODO(QUIC FUTURE): Implement the latter case (currently we
- just always do STOP_SENDING).
+ * just always do STOP_SENDING).
*
* and;
*
@@ -315,6 +315,7 @@ struct quic_stream_st {
unsigned int ready_for_gc : 1;
/* Set to 1 if this is currently counted in the shutdown flush stream count. */
unsigned int shutdown_flush : 1;
+ unsigned int have_final_size : 1;
};
#define QUIC_STREAM_INITIATOR_CLIENT 0
diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c
index 3d49857458..ab6da2f566 100644
--- a/ssl/quic/quic_impl.c
+++ b/ssl/quic/quic_impl.c
@@ -4396,14 +4396,14 @@ static void quic_classify_stream(QUIC_CONNECTION *qc,
uint64_t *app_error_code)
{
int local_init;
- uint64_t final_size;
+ uint64_t scratch_pad; /* throw away value */
local_init = (ossl_quic_stream_is_server_init(qs) == qc->as_server);
if (app_error_code != NULL)
*app_error_code = UINT64_MAX;
else
- app_error_code = &final_size; /* throw away value */
+ app_error_code = &scratch_pad;
if (!ossl_quic_stream_is_bidi(qs) && local_init != is_write) {
/*
@@ -4436,7 +4436,7 @@ static void quic_classify_stream(QUIC_CONNECTION *qc,
*app_error_code = !is_write
? qs->peer_reset_stream_aec
: qs->peer_stop_sending_aec;
- } else if (is_write && ossl_quic_sstream_get_final_size(qs->sstream, &final_size)) {
+ } else if (is_write && qs->have_final_size) {
/*
* Stream has been finished. Stream reset takes precedence over this for
* the write case as peer may not have received all data.
diff --git a/ssl/quic/quic_stream_map.c b/ssl/quic/quic_stream_map.c
index 6f516e9cc8..b160553452 100644
--- a/ssl/quic/quic_stream_map.c
+++ b/ssl/quic/quic_stream_map.c
@@ -446,6 +446,13 @@ int ossl_quic_stream_map_notify_totally_acked(QUIC_STREAM_MAP *qsm,
case QUIC_SSTREAM_STATE_DATA_SENT:
qs->send_state = QUIC_SSTREAM_STATE_DATA_RECVD;
+ /*
+ * Remember final size in case SSL_get_stream_write_state()
+ * gets called.
+ */
+ qs->have_final_size = ossl_quic_sstream_get_final_size(qs->sstream,
+ NULL);
+
/* We no longer need a QUIC_SSTREAM in this state. */
ossl_quic_sstream_free(qs->sstream);
qs->sstream = NULL;