]> git.kaiwu.me - nginx.git/commitdiff
QUIC: always validate stream final size
authorRoman Arutyunyan <arut@nginx.com>
Tue, 28 Jul 2026 14:34:36 +0000 (18:34 +0400)
committerRoman Arutyunyan <arutyunyan.roman@gmail.com>
Wed, 29 Jul 2026 16:14:43 +0000 (20:14 +0400)
As per RFC 9000, Section 4.5:

Once a final size for a stream is known, it cannot change. If a
RESET_STREAM or STREAM frame is received indicating a change in the
final size for the stream, an endpoint SHOULD respond with an error of
type FINAL_SIZE_ERROR.  A receiver SHOULD treat receipt of data at or
beyond the final size as an error of type FINAL_SIZE_ERROR, even after
a stream is closed.

Previously, the stream final size was validated only after other checks
that could return earlier and skip validation: the receive state check
and, for STREAM frames, the check for data below the current receive
offset.  As a result, a STREAM or RESET_STREAM frame carrying a final
size error was silently ignored once the receiving part of the stream
had reached a state in which the frame would otherwise be discarded, or
when a STREAM frame did not advance the receive offset.  Now the final
size is validated first, so a FINAL_SIZE_ERROR is generated in these
cases as well.

src/event/quic/ngx_event_quic_streams.c

index bc2b4014bf0c9db650cbae8553aae51199b80167..cb885a013073227ce349e86ac6ec7422a8c10352 100644 (file)
@@ -1260,6 +1260,19 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
         return NGX_OK;
     }
 
+    if (qs->recv_final_size != (uint64_t) -1
+        && (qs->recv_final_size < last
+            || (qs->recv_final_size > last && f->fin)))
+    {
+        qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
+        return NGX_ERROR;
+    }
+
+    if (qs->recv_last > last && f->fin) {
+        qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
+        return NGX_ERROR;
+    }
+
     if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV
         && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN)
     {
@@ -1270,27 +1283,11 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
         return NGX_ERROR;
     }
 
-    if (qs->recv_final_size != (uint64_t) -1 && last > qs->recv_final_size) {
-        qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
-        return NGX_ERROR;
-    }
-
     if (last < qs->recv_offset) {
         return NGX_OK;
     }
 
     if (f->fin) {
-        if (qs->recv_final_size != (uint64_t) -1 && qs->recv_final_size != last)
-        {
-            qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
-            return NGX_ERROR;
-        }
-
-        if (qs->recv_last > last) {
-            qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
-            return NGX_ERROR;
-        }
-
         qs->recv_final_size = last;
         qs->recv_state = NGX_QUIC_STREAM_RECV_SIZE_KNOWN;
     }
@@ -1475,16 +1472,6 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
         return NGX_OK;
     }
 
-    if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD
-        || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ)
-    {
-        return NGX_OK;
-    }
-
-    if (ngx_quic_control_flow(qs, f->final_size) != NGX_OK) {
-        return NGX_ERROR;
-    }
-
     if (qs->recv_final_size != (uint64_t) -1
         && qs->recv_final_size != f->final_size)
     {
@@ -1497,6 +1484,16 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
         return NGX_ERROR;
     }
 
+    if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD
+        || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ)
+    {
+        return NGX_OK;
+    }
+
+    if (ngx_quic_control_flow(qs, f->final_size) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
     qs->recv_final_size = f->final_size;
     qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD;