Commit message: Avoid some code duplication

Apply some adjustments to recent patches based off of review by Nils
Goroll at UPLEX (@nigoroll)
CVE: CVE-2019-15892
CWE: CWE-617
Vulnerability description: An issue was discovered in Varnish Cache before 6.0.4 LTS, and 6.1.x and 6.2.x before 6.2.1. An HTTP/1 parsing failure allows a remote attacker to trigger an assert by sending crafted HTTP/1 requests. The assert will cause an automatic restart with a clean cache, which makes it a Denial of Service attack.

Project: varnish-cache
File: cache_http1_proto.c

http1_dissect_hdrs(struct http *hp, char *p, struct http_conn *htc,
    unsigned maxhdr)
{
	char *q, *r, *s;
	int i;

	assert(p > htc->rxbuf_b);
	assert(p <= htc->rxbuf_e);
	hp->nhd = HTTP_HDR_FIRST;
	r = NULL;		/* For FlexeLint */
	for (; p < htc->rxbuf_e; p = r) {

		/* Find end of next header */
		q = r = p;
		if (vct_iscrlf(p, htc->rxbuf_e))
			break;
		while (r < htc->rxbuf_e) {
			if (!vct_isctl(*r) || vct_issp(*r)) {
				r++;
				continue;
			}
			i = vct_iscrlf(r, htc->rxbuf_e);
			if (i == 0) {
				VSLb(hp->vsl, SLT_BogoHeader,
				    "Header has ctrl char 0x%02x", *r);
				return (400);
			}
			q = r;
			r += i;
			assert(r <= htc->rxbuf_e);
			if (r == htc->rxbuf_e)
				break;
			if (vct_iscrlf(r, htc->rxbuf_e))
				break;
			/* If line does not continue: got it. */
			if (!vct_issp(*r))
				break;

			/* Clear line continuation LWS to spaces */
			while (q < r)
				*q++ = ' ';
			while (q < htc->rxbuf_e && vct_issp(*q))
				*q++ = ' ';
		}

		/* Empty header = end of headers */
		if (p == q)
			break;

		if (q - p > maxhdr) {
			VSLb(hp->vsl, SLT_BogoHeader, "Header too long: %.*s",
			    (int)(q - p > 20 ? 20 : q - p), p);
			return (400);
		}

		if (vct_islws(*p)) {
			VSLb(hp->vsl, SLT_BogoHeader,
			    "1st header has white space: %.*s",
			    (int)(q - p > 20 ? 20 : q - p), p);
			return (400);
		}

		if (*p == ':') {
			VSLb(hp->vsl, SLT_BogoHeader,
			    "Missing header name: %.*s",
			    (int)(q - p > 20 ? 20 : q - p), p);
			return (400);
		}

		while (q > p && vct_issp(q[-1]))
			q--;
		*q = '\0';

		for (s = p; *s != ':' && s < q; s++) {
			if (!vct_istchar(*s)) {
				VSLb(hp->vsl, SLT_BogoHeader,
				    "Illegal char 0x%02x in header name", *s);
				return (400);
			}
		}
		if (*s != ':') {
			VSLb(hp->vsl, SLT_BogoHeader, "Header without ':' %.*s",
			    (int)(q - p > 20 ? 20 : q - p), p);
			return (400);
		}

		if (hp->nhd < hp->shd) {
			hp->hdf[hp->nhd] = 0;
			hp->hd[hp->nhd].b = p;
			hp->hd[hp->nhd].e = q;
			hp->nhd++;
		} else {
			VSLb(hp->vsl, SLT_BogoHeader, "Too many headers: %.*s",
			    (int)(q - p > 20 ? 20 : q - p), p);
			return (400);
		}
	}
	i = vct_iscrlf(p, htc->rxbuf_e);
	assert(i > 0);		/* HTTP1_Complete guarantees this */
	p += i;
	HTC_RxPipeline(htc, p);
	htc->rxbuf_e = p;
	return (0);
}

Project: varnish-cache
File: cache_http1_proto.c

HTTP1_DissectRequest(struct http_conn *htc, struct http *hp)
{
	uint16_t retval;
	const char *p;
	const char *b = NULL, *e;

	CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);
	CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);

	retval = http1_splitline(hp, htc,
	    HTTP1_Req, cache_param->http_req_hdr_len);
	if (retval != 0)
		return (retval);

	if (hp->protover < 10 || hp->protover > 11)
		return (400);

	if (http_CountHdr(hp, H_Host) > 1)
		return (400);

	if (http_CountHdr(hp, H_Content_Length) > 1)
		return (400);

	/* RFC2616, section 5.2, point 1 */
	if (!strncasecmp(hp->hd[HTTP_HDR_URL].b, "http://", 7))
		b = hp->hd[HTTP_HDR_URL].b + 7;
	else if (FEATURE(FEATURE_HTTPS_SCHEME) &&
	    !strncasecmp(hp->hd[HTTP_HDR_URL].b, "https://", 8))
		b = hp->hd[HTTP_HDR_URL].b + 8;
	if (b) {
		e = strchr(b, '/');
		if (e) {
			http_Unset(hp, H_Host);
			http_PrintfHeader(hp, "Host: %.*s", (int)(e - b), b);
			hp->hd[HTTP_HDR_URL].b = e;
		}
	}

	htc->body_status = http1_body_status(hp, htc, 1);
	if (htc->body_status == BS_ERROR)
		return (400);

	p = http_GetMethod(hp);
	AN(p);

	if (htc->body_status == BS_EOF) {
		assert(hp->protover == 10);
		/* RFC1945 8.3 p32 and D.1.1 p58 */
		if (!strcasecmp(p, "post") || !strcasecmp(p, "put"))
			return (400);
		htc->body_status = BS_NONE;
	}

	/* HEAD with a body is a hard error */
	if (htc->body_status != BS_NONE && !strcasecmp(p, "head"))
		return (400);

	return (retval);
}

Project: varnish-cache
File: cache_http1_proto.c

HTTP1_Write(const struct worker *w, const struct http *hp, const int *hf)
{
	unsigned u, l;

	assert(hf == HTTP1_Req || hf == HTTP1_Resp);
	AN(hp->hd[hf[0]].b);
	AN(hp->hd[hf[1]].b);
	AN(hp->hd[hf[2]].b);
	l = http1_WrTxt(w, &hp->hd[hf[0]], " ");
	l += http1_WrTxt(w, &hp->hd[hf[1]], " ");
	l += http1_WrTxt(w, &hp->hd[hf[2]], "\r\n");

	for (u = HTTP_HDR_FIRST; u < hp->nhd; u++)
		l += http1_WrTxt(w, &hp->hd[u], "\r\n");
	l += V1L_Write(w, "\r\n", -1);
	return (l);
}

Project: varnish-cache
File: cache_http1_proto.c

http1_body_status(const struct http *hp, struct http_conn *htc, int request)
{
	ssize_t cl;
	const char *b;

	CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);
	CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);

	htc->content_length = -1;

	cl = http_GetContentLength(hp);
	if (cl == -2)
		return (BS_ERROR);
	if (http_GetHdr(hp, H_Transfer_Encoding, &b)) {
		if (strcasecmp(b, "chunked"))
			return (BS_ERROR);
		if (cl != -1) {
			/*
			 * RFC7230 3.3.3 allows more lenient handling
			 * but we're going to be strict.
			 */
			return (BS_ERROR);
		}
		return (BS_CHUNKED);
	}
	if (cl >= 0) {
		htc->content_length = cl;
		return (cl == 0 ? BS_NONE : BS_LENGTH);
	}

	if (hp->protover == 11 && request)
		return (BS_NONE);

	if (http_HdrIs(hp, H_Connection, "keep-alive")) {
		/*
		 * Keep alive with neither TE=Chunked or C-Len is impossible.
		 * We assume a zero length body.
		 */
		return (BS_NONE);
	}

	/*
	 * Fall back to EOF transfer.
	 */
	return (BS_EOF);
}

Project: varnish-cache
File: cache_http1_proto.c

HTTP1_Complete(struct http_conn *htc)
{
	char *p;
	enum htc_status_e retval;

	CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);

	assert(htc->rxbuf_e >= htc->rxbuf_b);
	assert(htc->rxbuf_e <= htc->ws->r);

	/* Skip any leading white space */
	for (p = htc->rxbuf_b ; p < htc->rxbuf_e && vct_islws(*p); p++)
		continue;
	if (p == htc->rxbuf_e)
		return (HTC_S_EMPTY);

	/* Do not return a partial H2 connection preface */
	retval = H2_prism_complete(htc);
	if (retval != HTC_S_JUNK)
		return (retval);

	/*
	 * Here we just look for NL[CR]NL to see that reception
	 * is completed.  More stringent validation happens later.
	 */
	while (1) {
		p = memchr(p, '\n', htc->rxbuf_e - p);
		if (p == NULL)
			return (HTC_S_MORE);
		if (++p == htc->rxbuf_e)
			return (HTC_S_MORE);
		if (*p == '\r' && ++p == htc->rxbuf_e)
			return (HTC_S_MORE);
		if (*p == '\n')
			break;
	}
	return (HTC_S_COMPLETE);
}

Project: varnish-cache
File: cache_http1_proto.c

HTTP1_DissectResponse(struct http_conn *htc, struct http *hp,
    const struct http *rhttp)
{
	uint16_t retval = 0;
	const char *p;

	CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);
	CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
	CHECK_OBJ_NOTNULL(rhttp, HTTP_MAGIC);

	if (http1_splitline(hp, htc,
	    HTTP1_Resp, cache_param->http_resp_hdr_len))
		retval = 503;

	if (retval == 0 && hp->protover < 10)
		retval = 503;

	if (retval == 0 && hp->protover > rhttp->protover)
		http_SetH(hp, HTTP_HDR_PROTO, rhttp->hd[HTTP_HDR_PROTO].b);

	if (retval == 0 && Tlen(hp->hd[HTTP_HDR_STATUS]) != 3)
		retval = 503;

	if (retval == 0) {
		p = hp->hd[HTTP_HDR_STATUS].b;

		if (p[0] >= '1' && p[0] <= '9' &&
		    p[1] >= '0' && p[1] <= '9' &&
		    p[2] >= '0' && p[2] <= '9')
			hp->status =
			    100 * (p[0] - '0') + 10 * (p[1] - '0') + p[2] - '0';
		else
			retval = 503;
	}

	if (retval != 0) {
		VSLb(hp->vsl, SLT_HttpGarbage, "%.*s",
		    (int)(htc->rxbuf_e - htc->rxbuf_b), htc->rxbuf_b);
		assert(retval >= 100 && retval <= 999);
		assert(retval == 503);
		http_SetStatus(hp, 503);
	}

	if (hp->hd[HTTP_HDR_REASON].b == NULL ||
	    !Tlen(hp->hd[HTTP_HDR_REASON])) {
		http_SetH(hp, HTTP_HDR_REASON,
		    http_Status2Reason(hp->status, NULL));
	}

	htc->body_status = http1_body_status(hp, htc, 0);

	return (retval);
}

Project: varnish-cache
File: cache_http1_proto.c

http1_splitline(struct http *hp, struct http_conn *htc, const int *hf,
    unsigned maxhdr)
{
	char *p, *q;
	int i;

	assert(hf == HTTP1_Req || hf == HTTP1_Resp);
	CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);
	CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
	assert(htc->rxbuf_e >= htc->rxbuf_b);

	AZ(hp->hd[hf[0]].b);
	AZ(hp->hd[hf[1]].b);
	AZ(hp->hd[hf[2]].b);

	/* Skip leading LWS */
	for (p = htc->rxbuf_b ; vct_islws(*p); p++)
		continue;
	hp->hd[hf[0]].b = p;

	/* First field cannot contain SP or CTL */
	for (; !vct_issp(*p); p++) {
		if (vct_isctl(*p))
			return (400);
	}
	hp->hd[hf[0]].e = p;
	assert(Tlen(hp->hd[hf[0]]));
	*p++ = '\0';

	/* Skip SP */
	for (; vct_issp(*p); p++) {
		if (vct_isctl(*p))
			return (400);
	}
	hp->hd[hf[1]].b = p;

	/* Second field cannot contain LWS or CTL */
	for (; !vct_islws(*p); p++) {
		if (vct_isctl(*p))
			return (400);
	}
	hp->hd[hf[1]].e = p;
	if (!Tlen(hp->hd[hf[1]]))
		return (400);

	/* Skip SP */
	q = p;
	for (; vct_issp(*p); p++) {
		if (vct_isctl(*p))
			return (400);
	}
	if (q < p)
		*q = '\0';	/* Nul guard for the 2nd field. If q == p
				 * (the third optional field is not
				 * present), the last nul guard will
				 * cover this field. */

	/* Third field is optional and cannot contain CTL except TAB */
	q = p;
	for (; p < htc->rxbuf_e && !vct_iscrlf(p, htc->rxbuf_e); p++) {
		if (vct_isctl(*p) && !vct_issp(*p))
			return (400);
	}
	if (p > q) {
		hp->hd[hf[2]].b = q;
		hp->hd[hf[2]].e = p;
	}

	/* Skip CRLF */
	i = vct_iscrlf(p, htc->rxbuf_e);
	if (!i)
		return (400);
	*p = '\0';
	p += i;

	http_Proto(hp);

	return (http1_dissect_hdrs(hp, p, htc, maxhdr));
}

Project: varnish-cache
File: cache_http1_proto.c

http1_WrTxt(const struct worker *wrk, const txt *hh, const char *suf)
{
	unsigned u;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	AN(wrk);
	AN(hh);
	AN(hh->b);
	AN(hh->e);
	u = V1L_Write(wrk, hh->b, hh->e - hh->b);
	if (suf != NULL)
		u += V1L_Write(wrk, suf, -1);
	return (u);
}