QUIC: always validate stream final size

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.
This commit is contained in:
Roman Arutyunyan 2026-07-28 18:34:36 +04:00 committed by Roman Arutyunyan
parent e3548e3056
commit 269cdf7806

View 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;