Description: HTTP Smuggling issues: Double Content Length and bad EOL
 Varnish 3.x before 3.0.7, when used in certain stacked installations, allows
 remote attackers to inject arbitrary HTTP headers and conduct HTTP response
 splitting attacks via a header line terminated by a \r (carriage return)
 character in conjunction with multiple Content-Length headers in an HTTP
 request.
 .
 This fixes CVE-2015-8852.
Author: Martin Blix Grydeland <martin@varnish-software.com>
Origin: upstream
Bug-Ubuntu: https://pad.lv/1709153
Applied-Upstream: 85e8468, 29870c8
Last-Update: 2017-08-07
--- a/bin/varnishd/cache_http.c
+++ b/bin/varnishd/cache_http.c
@@ -502,7 +502,7 @@ http_dissect_hdrs(struct worker *w, stru
 		/* Find end of next header */
 		q = r = p;
 		while (r < t.e) {
-			if (!vct_iscrlf(*r)) {
+			if (!vct_iscrlf(r)) {
 				r++;
 				continue;
 			}
@@ -611,8 +611,8 @@ http_splitline(struct worker *w, int fd,
 
 	/* Third field is optional and cannot contain CTL */
 	q = p;
-	if (!vct_iscrlf(*p)) {
-		for (; !vct_iscrlf(*p); p++)
+	if (!vct_iscrlf(p)) {
+		for (; !vct_iscrlf(p); p++)
 			if (!vct_issep(*p) && vct_isctl(*p))
 				return (400);
 	}
@@ -639,10 +639,12 @@ http_splitline(struct worker *w, int fd,
 /*--------------------------------------------------------------------*/
 
 static int
-htc_request_check_host_hdr(struct http *hp)
+htc_request_check_hdrs(struct sess *sp, struct http *hp)
 {
 	int u;
 	int seen_host = 0;
+	int seen_cl = 0;
+
 	for (u = HTTP_HDR_FIRST; u < hp->nhd; u++) {
 		if (hp->hd[u].b == NULL)
 			continue;
@@ -650,10 +652,19 @@ htc_request_check_host_hdr(struct http *
 		AN(hp->hd[u].e);
 		if (http_IsHdr(&hp->hd[u], H_Host)) {
 			if (seen_host) {
+				WSP(sp, SLT_Error, "Duplicated Host header");
 				return (400);
 			}
 			seen_host = 1;
 		}
+		if (http_IsHdr(&hp->hd[u], H_Content_Length)) {
+			if (seen_cl) {
+				WSP(sp, SLT_Error,
+				    "Duplicated Content-Length header");
+				return (400);
+			}
+			seen_cl = 1;
+		}
 	}
 	return (0);
 }
@@ -698,11 +709,7 @@ http_DissectRequest(struct sess *sp)
 	}
 	http_ProtoVer(hp);
 
-	retval = htc_request_check_host_hdr(hp);
-	if (retval != 0) {
-		WSP(sp, SLT_Error, "Duplicated Host header");
-		return (retval);
-	}
+	retval = htc_request_check_hdrs(sp, hp);
 	return (retval);
 }
 
--- /dev/null
+++ b/bin/varnishtest/tests/b00040.vtc
@@ -0,0 +1,24 @@
+varnishtest "Do not consider CR as a valid line separator"
+
+server s1 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	sub vcl_deliver {
+		if (req.http.foo) {
+			set resp.http.Foo = req.http.foo;
+		}
+		if (req.http.bar) {
+			set resp.http.Bar = req.http.bar;
+		}
+	}
+} -start
+
+client c1 {
+	send "GET / HTTP/1.1\r\nFoo: foo\rBar: bar\r\n\r\n"
+	rxresp
+	expect resp.http.foo == "foo\rBar: bar"
+	expect resp.http.bar == "<undef>"
+} -run
--- a/bin/varnishtest/vtc_http.c
+++ b/bin/varnishtest/vtc_http.c
@@ -283,17 +283,17 @@ http_splitheader(struct http *hp, int re
 	hh[n++] = p;
 	while (!vct_islws(*p))
 		p++;
-	assert(!vct_iscrlf(*p));
+	assert(!vct_iscrlf(p));
 	*p++ = '\0';
 
 	/* URL/STATUS */
 	while (vct_issp(*p))		/* XXX: H space only */
 		p++;
-	assert(!vct_iscrlf(*p));
+	assert(!vct_iscrlf(p));
 	hh[n++] = p;
 	while (!vct_islws(*p))
 		p++;
-	if (vct_iscrlf(*p)) {
+	if (vct_iscrlf(p)) {
 		hh[n++] = NULL;
 		q = p;
 		p += vct_skipcrlf(p);
@@ -304,7 +304,7 @@ http_splitheader(struct http *hp, int re
 		while (vct_issp(*p))		/* XXX: H space only */
 			p++;
 		hh[n++] = p;
-		while (!vct_iscrlf(*p))
+		while (!vct_iscrlf(p))
 			p++;
 		q = p;
 		p += vct_skipcrlf(p);
@@ -314,10 +314,10 @@ http_splitheader(struct http *hp, int re
 
 	while (*p != '\0') {
 		assert(n < MAX_HDR);
-		if (vct_iscrlf(*p))
+		if (vct_iscrlf(p))
 			break;
 		hh[n++] = p++;
-		while (*p != '\0' && !vct_iscrlf(*p))
+		while (*p != '\0' && !vct_iscrlf(p))
 			p++;
 		q = p;
 		p += vct_skipcrlf(p);
@@ -408,11 +408,11 @@ http_rxchunk(struct http *hp)
 	}
 	l = hp->prxbuf;
 	(void)http_rxchar(hp, 2, 0);
-	if(!vct_iscrlf(hp->rxbuf[l]))
+	if(!vct_iscrlf(&hp->rxbuf[l]))
 		vtc_log(hp->vl, hp->fatal,
 		    "Wrong chunk tail[0] = %02x",
 		    hp->rxbuf[l] & 0xff);
-	if(!vct_iscrlf(hp->rxbuf[l + 1]))
+	if(!vct_iscrlf(&hp->rxbuf[l + 1]))
 		vtc_log(hp->vl, hp->fatal,
 		    "Wrong chunk tail[1] = %02x",
 		    hp->rxbuf[l + 1] & 0xff);
--- a/include/vct.h
+++ b/include/vct.h
@@ -54,7 +54,6 @@ vct_is(unsigned char x, uint16_t y)
 
 #define vct_issp(x) vct_is(x, VCT_SP)
 #define vct_ishex(x) vct_is(x, VCT_HEX)
-#define vct_iscrlf(x) vct_is(x, VCT_CRLF)
 #define vct_islws(x) vct_is(x, VCT_LWS)
 #define vct_isctl(x) vct_is(x, VCT_CTL)
 #define vct_isdigit(x) vct_is(x, VCT_DIGIT)
@@ -64,5 +63,7 @@ vct_is(unsigned char x, uint16_t y)
 #define vct_isxmlnamestart(x) vct_is(x, VCT_XMLNAMESTART)
 #define vct_isxmlname(x) vct_is(x, VCT_XMLNAMESTART | VCT_XMLNAME)
 
+#define vct_iscrlf(p) (((p)[0] == '\r' && (p)[1] == '\n') || (p)[0] == '\n')
+
 /* NB: VCT always operate in ASCII, don't replace 0x0d with \r etc. */
 #define vct_skipcrlf(p) (p[0] == 0x0d && p[1] == 0x0a ? 2 : 1)
--- /dev/null
+++ b/bin/varnishtest/tests/b00041.vtc
@@ -0,0 +1,23 @@
+varnishtest "Fail request on duplicate Content-Length headers in requests"
+
+server s1 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	sub vcl_deliver {
+		if (req.http.foo) {
+			set resp.http.Foo = req.http.foo;
+		}
+		if (req.http.bar) {
+			set resp.http.Bar = req.http.bar;
+		}
+	}
+} -start
+
+client c1 {
+	txreq -req POST -hdr "Content-Length: 5" -body "12345"
+	rxresp
+	expect resp.status == 400
+} -run
--- a/bin/varnishtest/tests/r00102.vtc
+++ b/bin/varnishtest/tests/r00102.vtc
@@ -17,14 +17,12 @@ varnish v1 -vcl+backend {
 
 client c1 {
 	txreq -req POST -url "/" \
-		-hdr "Content-Length: 10" \
 		-body "123456789\n"
 	rxresp
 	expect resp.status == 200
 	expect resp.http.X-Varnish == "1001"
 
 	txreq -req POST -url "/" \
-		-hdr "Content-Length: 10" \
 		-body "123456789\n"
 	rxresp
 	expect resp.status == 200
