[master] bfdfa6a5c Implement handling of padding bytes in received data frames

Dridi Boukelmoune dridi.boukelmoune at gmail.com
Mon Aug 30 08:31:09 UTC 2021


commit bfdfa6a5c884a17bbf265649ef9ecf90af6940f7
Author: Martin Blix Grydeland <martin at varnish-software.com>
Date:   Tue Jun 22 11:50:11 2021 +0200

    Implement handling of padding bytes in received data frames
    
    This was found lacking in our H2 implementation. Previously we would have
    included any padding bytes in the request body. Possibly it would have
    caused errors if there also was a C-L present, or more likely just corrupt
    request bodies.
    
    If the client sends nothing but padding bytes and ends up consuming the
    entire stream window with no actual request bytes buffered, the request
    thread side of things would not send any stream window updates. Handle
    this corner case by sending a window update from the session thread.

diff --git a/bin/varnishd/http2/cache_http2_proto.c b/bin/varnishd/http2/cache_http2_proto.c
index 570ef9209..de2ac5965 100644
--- a/bin/varnishd/http2/cache_http2_proto.c
+++ b/bin/varnishd/http2/cache_http2_proto.c
@@ -763,6 +763,7 @@ h2_rx_data(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 	char buf[4];
 	uint64_t l, l2, head;
 	const uint8_t *src;
+	unsigned len;
 
 	/* XXX: Shouldn't error handling, setting of r2->error and
 	 * r2->cond signalling be handled more generally at the end of
@@ -790,13 +791,31 @@ h2_rx_data(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 		return (h2->error ? h2->error : r2->error);
 	}
 
+	/* Check padding if present */
+	src = h2->rxf_data;
+	len = h2->rxf_len;
+	if (h2->rxf_flags & H2FF_DATA_PADDED) {
+		if (*src >= len) {
+			VSLb(h2->vsl, SLT_Debug,
+			    "H2: stream %u: Padding larger than frame length",
+			    h2->rxf_stream);
+			r2->error = H2CE_PROTOCOL_ERROR;
+			if (r2->cond)
+				AZ(pthread_cond_signal(r2->cond));
+			Lck_Unlock(&h2->sess->mtx);
+			return (H2CE_PROTOCOL_ERROR);
+		}
+		len -= 1 + *src;
+		src += 1;
+	}
+
 	/* Check against the Content-Length header if given */
 	if (r2->req->htc->content_length >= 0) {
 		if (r2->rxbuf)
 			l = r2->rxbuf->head;
 		else
 			l = 0;
-		l += h2->rxf_len;
+		l += len;
 		if (l > r2->req->htc->content_length ||
 		    ((h2->rxf_flags & H2FF_DATA_END_STREAM) &&
 		     l != r2->req->htc->content_length)) {
@@ -811,17 +830,8 @@ h2_rx_data(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 		}
 	}
 
-	/* Handle zero size frame before starting to allocate buffers */
-	if (h2->rxf_len == 0) {
-		if (h2->rxf_flags & H2FF_DATA_END_STREAM)
-			r2->state = H2_S_CLOS_REM;
-		if (r2->cond)
-			AZ(pthread_cond_signal(r2->cond));
-		Lck_Unlock(&h2->sess->mtx);
-		return (0);
-	}
-
-	/* Check and charge connection window */
+	/* Check and charge connection window. The entire frame including
+	 * padding (h2->rxf_len) counts towards the window. */
 	if (h2->rxf_len > h2->req0->r_window) {
 		VSLb(h2->vsl, SLT_Debug,
 		    "H2: stream %u: Exceeded connection receive window",
@@ -843,7 +853,8 @@ h2_rx_data(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 		Lck_Lock(&h2->sess->mtx);
 	}
 
-	/* Check stream window */
+	/* Check stream window. The entire frame including padding
+	 * (h2->rxf_len) counts towards the window. */
 	if (h2->rxf_len > r2->r_window) {
 		VSLb(h2->vsl, SLT_Debug,
 		    "H2: stream %u: Exceeded stream receive window",
@@ -855,6 +866,41 @@ h2_rx_data(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 		return (H2SE_FLOW_CONTROL_ERROR);
 	}
 
+	/* Handle zero size frame before starting to allocate buffers */
+	if (len == 0) {
+		r2->r_window -= h2->rxf_len;
+
+		/* Handle the specific corner case where the entire window
+		 * has been exhausted using nothing but padding
+		 * bytes. Since no bytes have been buffered, no bytes
+		 * would be consumed by the request thread and no stream
+		 * window updates sent. Unpaint ourselves from this corner
+		 * by sending a stream window update here. */
+		CHECK_OBJ_ORNULL(r2->rxbuf, H2_RXBUF_MAGIC);
+		if (r2->r_window == 0 &&
+		    (r2->rxbuf == NULL || r2->rxbuf->tail == r2->rxbuf->head)) {
+			if (r2->rxbuf)
+				l = r2->rxbuf->size;
+			else
+				l = h2->local_settings.initial_window_size;
+			r2->r_window += l;
+			Lck_Unlock(&h2->sess->mtx);
+			vbe32enc(buf, l);
+			H2_Send_Get(wrk, h2, h2->req0);
+			H2_Send_Frame(wrk, h2, H2_F_WINDOW_UPDATE, 0, 4,
+			    r2->stream, buf);
+			H2_Send_Rel(h2, h2->req0);
+			Lck_Lock(&h2->sess->mtx);
+		}
+
+		if (h2->rxf_flags & H2FF_DATA_END_STREAM)
+			r2->state = H2_S_CLOS_REM;
+		if (r2->cond)
+			AZ(pthread_cond_signal(r2->cond));
+		Lck_Unlock(&h2->sess->mtx);
+		return (0);
+	}
+
 	/* Make the buffer on demand */
 	if (r2->rxbuf == NULL) {
 		unsigned bufsize;
@@ -864,15 +910,20 @@ h2_rx_data(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 
 		Lck_Unlock(&h2->sess->mtx);
 
-		bufsize = r2->r_window;
-		/* This is the first data frame, so r_window will be the
-		 * initial window size. */
+		bufsize = h2->local_settings.initial_window_size;
+		if (bufsize < r2->r_window) {
+			/* This will not happen because we do not have any
+			 * mechanism to change the initial window size on
+			 * a running session. But if we gain that ability,
+			 * this future proofs it. */
+			bufsize = r2->r_window;
+		}
 		assert(bufsize > 0);
 		if ((h2->rxf_flags & H2FF_DATA_END_STREAM) &&
-		    bufsize > h2->rxf_len)
+		    bufsize > len)
 			/* Cap the buffer size when we know this is the
 			 * single data frame. */
-			bufsize = h2->rxf_len;
+			bufsize = len;
 		stvbuf = STV_AllocBuf(wrk, stv_transient,
 		    bufsize + sizeof *rxbuf);
 		if (stvbuf == NULL) {
@@ -905,13 +956,11 @@ h2_rx_data(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 	l = r2->rxbuf->head - r2->rxbuf->tail;
 	assert(l <= r2->rxbuf->size);
 	l = r2->rxbuf->size - l;
-	assert(h2->rxf_len <= l); /* Stream window handling ensures
-				   * this */
+	assert(len <= l); /* Stream window handling ensures this */
 
 	Lck_Unlock(&h2->sess->mtx);
 
-	src = h2->rxf_data;
-	l = h2->rxf_len;
+	l = len;
 	head = r2->rxbuf->head;
 	do {
 		l2 = l;
@@ -925,9 +974,14 @@ h2_rx_data(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 	} while (l > 0);
 
 	Lck_Lock(&h2->sess->mtx);
-	/* Charge stream window */
+
+	/* Charge stream window. The entire frame including padding
+	 * (h2->rxf_len) counts towards the window. The used padding
+	 * bytes will be included in the next connection window update
+	 * sent when the buffer bytes are consumed because that is
+	 * calculated against the available buffer space. */
 	r2->r_window -= h2->rxf_len;
-	r2->rxbuf->head += h2->rxf_len;
+	r2->rxbuf->head += len;
 	assert(r2->rxbuf->tail <= r2->rxbuf->head);
 	if (h2->rxf_flags & H2FF_DATA_END_STREAM)
 		r2->state = H2_S_CLOS_REM;
diff --git a/bin/varnishtest/tests/t02020.vtc b/bin/varnishtest/tests/t02020.vtc
new file mode 100644
index 000000000..77738e17f
--- /dev/null
+++ b/bin/varnishtest/tests/t02020.vtc
@@ -0,0 +1,66 @@
+varnishtest "H/2 received data frames with padding"
+
+barrier b1 sock 2
+
+server s1 {
+	rxreq
+	expect req.url == /1
+	expect req.body == abcde
+	txresp
+	rxreq
+	txresp
+	rxreq
+	txresp
+	expect req.body == a
+} -start
+
+varnish v1 -vcl+backend {
+	import vtc;
+	sub vcl_recv {
+		if (req.url == "/3") {
+			vtc.barrier_sync("${b1_sock}");
+		}
+	}
+} -start
+
+varnish v1 -cliok "param.set feature +http2"
+varnish v1 -cliok "param.reset h2_initial_window_size"
+varnish v1 -cliok "param.reset h2_rx_window_low_water"
+varnish v1 -cliok "param.set debug +syncvsl"
+
+client c1 {
+	stream 1 {
+		txreq -req POST -url /1 -hdr "content-length" "5" -nostrend
+		txdata -data abcde -padlen 1
+		rxresp
+		expect resp.status == 200
+	} -run
+
+	stream 3 {
+		txreq -req POST -url /3 -hdr "content-length" "131072" -nostrend
+		txdata -datalen 16300 -padlen 83 -nostrend
+		txdata -datalen 16300 -padlen 83 -nostrend
+		txdata -datalen 16300 -padlen 83 -nostrend
+		txdata -datalen 16300 -padlen 82 -nostrend
+		barrier b1 sync
+		rxwinup
+		txdata -datalen 16300 -padlen 83 -nostrend
+		rxwinup
+		txdata -datalen 16300 -padlen 83 -nostrend
+		rxwinup
+		txdata -datalen 16300 -padlen 83 -nostrend
+		rxwinup
+		txdata -datalen 16300 -padlen 83 -nostrend
+		rxwinup
+		txdata -datalen 672
+		rxresp
+		expect resp.status == 200
+	} -run
+
+	stream 5 {
+		txreq -req POST -url /5 -nostrend
+		txdata -data a -padlen 255
+		rxresp
+		expect resp.status == 200
+	} -run
+} -run


More information about the varnish-commit mailing list