[master] 4ab110047 vrg: Consistency check of backend responses

Dridi Boukelmoune dridi.boukelmoune at gmail.com
Tue Aug 31 10:23:07 UTC 2021


commit 4ab110047130e3a89936104d74f4729b650676f9
Author: Dridi Boukelmoune <dridi.boukelmoune at gmail.com>
Date:   Mon Aug 23 17:27:36 2021 +0200

    vrg: Consistency check of backend responses
    
    That is, only when http_range_support is on, which is the default.
    
    Refs #3246

diff --git a/bin/varnishd/cache/cache_fetch.c b/bin/varnishd/cache/cache_fetch.c
index 16ab14558..eb09990ff 100644
--- a/bin/varnishd/cache/cache_fetch.c
+++ b/bin/varnishd/cache/cache_fetch.c
@@ -492,6 +492,11 @@ vbf_stp_startfetch(struct worker *wrk, struct busyobj *bo)
 	    http_GetHdrField(bo->beresp, H_Connection, "close", NULL))
 		bo->htc->doclose = SC_RESP_CLOSE;
 
+	if (VRG_CheckBo(bo) < 0) {
+		VDI_Finish(bo);
+		return (F_STP_ERROR);
+	}
+
 	if (wrk->handling == VCL_RET_ABANDON || wrk->handling == VCL_RET_FAIL ||
 	    wrk->handling == VCL_RET_ERROR) {
 		/* do not count deliberately ending the backend connection as
diff --git a/bin/varnishd/cache/cache_range.c b/bin/varnishd/cache/cache_range.c
index 75c434a94..20f326455 100644
--- a/bin/varnishd/cache/cache_range.c
+++ b/bin/varnishd/cache/cache_range.c
@@ -255,3 +255,73 @@ const struct vdp VDP_range = {
 	.bytes =	vrg_range_bytes,
 	.fini =		vrg_range_fini,
 };
+
+/*--------------------------------------------------------------------*/
+
+int
+VRG_CheckBo(struct busyobj *bo)
+{
+	ssize_t rlo, rhi, crlo, crhi, crlen, clen;
+	const char *err;
+
+	CHECK_OBJ_NOTNULL(bo, BUSYOBJ_MAGIC);
+
+	if (!cache_param->http_range_support)
+		return (0);
+
+	err = http_GetRange(bo->bereq0, &rlo, &rhi);
+	clen = http_GetContentLength(bo->beresp);
+	crlen = http_GetContentRange(bo->beresp, &crlo, &crhi);
+
+	if (err != NULL) {
+		VSLb(bo->vsl, SLT_Error, "Invalid range header (%s)", err);
+		return (-1);
+	}
+
+	if (crlen < -1) {
+		VSLb(bo->vsl, SLT_Error, "Invalid content-range header");
+		return (-1);
+	}
+
+	if (clen < -1) {
+		VSLb(bo->vsl, SLT_Error, "Invalid content-length header");
+		return (-1);
+	}
+
+	if (crlo < 0 || crhi < 0) {
+		AZ(http_GetHdr(bo->beresp, H_Content_Range, NULL));
+		return (0);
+	}
+
+	if (rlo < 0 && rhi < 0) {
+		VSLb(bo->vsl, SLT_Error, "Unexpected content-range header");
+		return (-1);
+	}
+
+#define RANGE_CHECK(val, op, crval, what)			\
+	do {							\
+		if (val >= 0 && !(val op crval)) {		\
+			VSLb(bo->vsl, SLT_Error,		\
+			    "Expected " what " %zd, got %zd",	\
+			    crval, val);			\
+			return (-1);				\
+		}						\
+	} while (0)
+
+	crlen = crhi - crlo + 1;
+	RANGE_CHECK(clen, ==, crlen, "content length");
+
+	/* NB: if the client didn't specify a low range the high range
+	 * was adjusted based on the resource length, and a high range
+	 * is allowed to be out of bounds so at this point there is
+	 * nothing left to check.
+	 */
+	if (rlo < 0)
+		return (0);
+
+	RANGE_CHECK(rlo, ==, crlo, "low range");
+	RANGE_CHECK(rhi, >=, crhi, "minimum high range");
+#undef RANGE_CHECK
+
+	return (0);
+}
diff --git a/bin/varnishd/cache/cache_varnishd.h b/bin/varnishd/cache/cache_varnishd.h
index 653976cdc..47213fff6 100644
--- a/bin/varnishd/cache/cache_varnishd.h
+++ b/bin/varnishd/cache/cache_varnishd.h
@@ -396,6 +396,9 @@ void Pool_PurgeStat(unsigned nobj);
 int Pool_Task_Any(struct pool_task *task, enum task_prio prio);
 void pan_pool(struct vsb *);
 
+/* cache_range.c */
+int VRG_CheckBo(struct busyobj *);
+
 /* cache_req.c */
 struct req *Req_New(const struct worker *, struct sess *);
 void Req_Release(struct req *);
diff --git a/bin/varnishtest/tests/c00034.vtc b/bin/varnishtest/tests/c00034.vtc
index a605977c4..44773ffe2 100644
--- a/bin/varnishtest/tests/c00034.vtc
+++ b/bin/varnishtest/tests/c00034.vtc
@@ -190,6 +190,7 @@ client c5 {
 
 server s1 {
 	rxreq
+	expect req.url == "/3"
 	txresp -hdr "Content-length: 3" -body "BLA"
 } -start
 
@@ -234,3 +235,42 @@ client c7 {
 	expect resp.http.foobar == foobar
 	expect resp.http.accept-ranges == foobar
 } -run
+
+server s1 {
+	rxreq
+	expect req.http.range == "bytes=10-19"
+	txresp -status 206 -hdr "content-range: bytes 0-49/100" -bodylen 40
+
+	rxreq
+	expect req.http.range == "bytes=10-19"
+	txresp -status 206 -hdr "content-range: bytes 10-19/100" -bodylen 40
+
+	rxreq
+	expect req.url == "/4"
+	expect req.http.range == <undef>
+	txresp -hdr "content-range: bytes 0-49/100" -bodylen 40
+} -start
+
+varnish v1 -vcl+backend {
+	sub vcl_recv {
+		if (req.http.return == "pass") {
+			return (pass);
+		}
+	}
+}
+
+client c8 {
+	# range vs content-range vs content-length
+
+	txreq -hdr "range: bytes=10-19" -hdr "return: pass"
+	rxresp
+	expect resp.status == 503
+
+	txreq -hdr "range: bytes=10-19" -hdr "return: pass"
+	rxresp
+	expect resp.status == 503
+
+	txreq -url /4
+	rxresp
+	expect resp.status == 503
+} -run
diff --git a/bin/varnishtest/tests/r00466.vtc b/bin/varnishtest/tests/r00466.vtc
index d530b443c..94c700a22 100644
--- a/bin/varnishtest/tests/r00466.vtc
+++ b/bin/varnishtest/tests/r00466.vtc
@@ -36,10 +36,14 @@ client c1 {
 	expect resp.status == 206
 	expect resp.http.Content-Length == 2
 	expect resp.http.X-Varnish == "1001"
+} -run
+
+varnish v1 -cliok "param.set http_range_support off"
 
+client c2 {
 	# NB: Deliberately bogus Range header
 	txreq -url "/bar" -hdr "Range: 200-300"
 	rxresp
 	expect resp.status == 206
-	expect resp.http.X-Varnish == "1003"
+	expect resp.http.X-Varnish == "1004"
 } -run
diff --git a/bin/varnishtest/tests/r02530.vtc b/bin/varnishtest/tests/r02530.vtc
index c458b6a5b..f664caee4 100644
--- a/bin/varnishtest/tests/r02530.vtc
+++ b/bin/varnishtest/tests/r02530.vtc
@@ -30,6 +30,8 @@ server s1 {
 	sendhex 1f8b
 } -start
 
+varnish v1 -cliok "param.set http_range_support off"
+
 varnish v1 -vcl+backend {
 	sub vcl_recv {
 		if (req.url == "/pass") {


More information about the varnish-commit mailing list