Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP request and response egress validation #3973

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bin/varnishd/cache/cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,8 @@ int HTTP_IterHdrPack(struct worker *, struct objcore *, const char **);
const char *HTTP_GetHdrPack(struct worker *, struct objcore *, hdr_t);
stream_close_t http_DoConnection(struct http *hp, stream_close_t sc_close);
int http_IsFiltered(const struct http *hp, unsigned u, unsigned how);
int HTTP_ValidateReq(const struct http *hp);
int HTTP_ValidateResp(const struct http *hp);

#define HTTPH_R_PASS (1 << 0) /* Request (c->b) in pass mode */
#define HTTPH_R_FETCH (1 << 1) /* Request (c->b) for fetch */
Expand Down
160 changes: 160 additions & 0 deletions bin/varnishd/cache/cache_http.c
Original file line number Diff line number Diff line change
Expand Up @@ -1643,3 +1643,163 @@ http_Unset(struct http *hp, hdr_t hdr)
}
hp->nhd = v;
}

/*--------------------------------------------------------------------*/

static inline int
http_valid_Method(struct vsl_log *vsl, const txt t)
{
const char *p;

if (t.b == t.e) {
VSLb(vsl, SLT_HttpGarbage, "Empty method");
return (-1);
}
for (p = t.b; p < t.e && *p != ':'; p++) {
if (!vct_istchar(*p)) {
VSLb(vsl, SLT_HttpGarbage, "Method: %.*s",
(int)(t.e - t.b), t.b);
return (-1);
}
}
return (0);
}

static inline int
http_valid_URL(struct vsl_log *vsl, const txt t)
{
const char *p;

if (t.b == t.e) {
VSLb(vsl, SLT_HttpGarbage, "Empty URL");
return (-1);
}
for (p = t.b; p < t.e; p++) {
if (vct_islws(*p) || vct_isctl(*p)) {
VSLb(vsl, SLT_HttpGarbage, "URL: %.*s",
(int)(t.e - t.b), t.b);
return (-1);
}
}
return (0);
}

static inline int
http_valid_Protocol(struct vsl_log *vsl, const txt t)
{
const char *p;

for (p = t.b; p < t.e; p++) {
if (vct_islws(*p) || vct_isctl(*p)) {
VSLb(vsl, SLT_HttpGarbage, "Protocol: %.*s",
(int)(t.e - t.b), t.b);
return (-1);
}
}
return (0);
}

static inline int
http_valid_Status(struct vsl_log *vsl, const txt t)
{
const char *p;
int n;

for (p = t.b, n = 0; p < t.e; p++, n++) {
if (!vct_isdigit(*p)) {
VSLb(vsl, SLT_HttpGarbage, "Status: %.*s",
(int)(t.e - t.b), t.b);
return (-1);
}
}
if (n != 3) {
VSLb(vsl, SLT_HttpGarbage, "Status: %.*s",
(int)(t.e - t.b), t.b);
return (-1);
}
return (0);
}

static inline int
http_valid_Reason(struct vsl_log *vsl, const txt t)
{
const char *p;

for (p = t.b; p < t.e; p++) {
if (!vct_ishdrval(*p)) {
VSLb(vsl, SLT_HttpGarbage, "Reason: %.*s",
(int)(t.e - t.b), t.b);
return (-1);
}
}
return (0);
}

static inline int
http_valid_header(struct vsl_log *vsl, const txt t)
{
const char *p;

(void)vsl;
for (p = t.b; p < t.e && *p != ':'; p++) {
if (!vct_istchar(*p)) {
VSLb(vsl, SLT_HttpGarbage, "Header: %.*s",
(int)(t.e - t.b), t.b);
return (-1);
}
}
if (*p != ':') {
VSLb(vsl, SLT_HttpGarbage, "Header: %.*s",
(int)(t.e - t.b), t.b);
return (-1);
}
for (; p < t.e; p++) {
if (!vct_ishdrval(*p)) {
VSLb(vsl, SLT_HttpGarbage, "Header: %.*s",
(int)(t.e - t.b), t.b);
return (-1);
}
}
return (0);
}

static inline int
http_validate(const struct http *hp, int do_req, int do_resp)
{
unsigned u;
int r = 0;

CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
for (u = 0; r == 0 && u < hp->nhd; u++) {
#define SLTH(tag, ind, req, resp, sdesc, ldesc) \
if (req && do_req && u == ind) { \
r = http_valid_##tag(hp->vsl, hp->hd[u]); \
continue; \
} \
if (resp && do_resp && u == ind) { \
r = http_valid_##tag(hp->vsl, hp->hd[u]); \
continue; \
}
#define SLTH_SKIP_EXTRA
#include "tbl/vsl_tags_http.h"
if (u >= HTTP_HDR_FIRST)
r = http_valid_header(hp->vsl, hp->hd[u]);
}

return (r);
}

int
HTTP_ValidateReq(const struct http *hp)
{
return (http_validate(hp, 1, 0));
}

int
HTTP_ValidateResp(const struct http *hp)
{
return (http_validate(hp, 0, 1));
}

/*--------------------------------------------------------------------*/

9 changes: 9 additions & 0 deletions bin/varnishd/http1/cache_http1_deliver.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "cache/cache_varnishd.h"
#include "cache/cache_filter.h"
#include "cache_http1.h"
#include "cache/cache_transport.h"

#include "vtcp.h"

Expand Down Expand Up @@ -115,6 +116,14 @@ V1D_Deliver(struct req *req, struct boc *boc, int sendbody)
return;
}

if (FEATURE(FEATURE_VALIDATE_CLIENT_RESPONSES) && HTTP_ValidateResp(req->resp)) {
VSLb(req->vsl, SLT_VCL_Error,
"Response failed HTTP validation");
req->doclose = SC_TX_ERROR;
req->transport->minimal_response(req, 503);
return;
}

V1L_Open(req->wrk, req->wrk->aws, &req->sp->fd, req->vsl,
req->t_prev + SESS_TMO(req->sp, send_timeout),
cache_param->http1_iovs);
Expand Down
9 changes: 9 additions & 0 deletions bin/varnishd/http1/cache_http1_fetch.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ V1F_SendReq(struct worker *wrk, struct busyobj *bo, uint64_t *ctr_hdrbytes,
}

VTCP_blocking(*htc->rfd); /* XXX: we should timeout instead */

if (FEATURE(FEATURE_VALIDATE_BACKEND_REQUESTS) &&
HTTP_ValidateReq(hp)) {
VSLb(bo->vsl, SLT_VCL_Error,
"Backend request failed HTTP validation");
htc->doclose = SC_TX_ERROR;
return (-1);
}

/* XXX: need a send_timeout for the backend side */
V1L_Open(wrk, wrk->aws, htc->rfd, bo->vsl, nan(""), 0);
hdrbytes = HTTP1_Write(wrk, hp, HTTP1_Req);
Expand Down
7 changes: 7 additions & 0 deletions bin/varnishd/http2/cache_http2_deliver.c
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,13 @@ h2_deliver(struct req *req, struct boc *boc, int sendbody)
sendbody = 0;
}

if (FEATURE(FEATURE_VALIDATE_CLIENT_RESPONSES) && HTTP_ValidateResp(req->resp)) {
VSLb(req->vsl, SLT_VCL_Error,
"Response failed HTTP validation");
req->doclose = SC_TX_ERROR;
req->transport->minimal_response(req, 503);
return;
}
AZ(req->wrk->v1l);

r2->t_send = req->t_prev;
Expand Down
2 changes: 1 addition & 1 deletion bin/varnishtest/tests/b00040.vtc
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ client c1 {

logexpect l1 -wait

varnish v1 -cliok "param.set feature -validate_headers"
varnish v1 -cliok "param.set feature -validate_headers,-validate_backend_requests"

client c1 {
txreq -url /9
Expand Down
123 changes: 123 additions & 0 deletions bin/varnishtest/tests/c00125.vtc
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
varnishtest "HTTP request and response egress validation"

server s1 {
rxreq
txresp
} -start

varnish v1 -syntax 4.0 -vcl+backend {
import blob;

sub vcl_recv {
if (req.http.do_recv_url) {
set req.url = blob.transcode(encoded="new%0aline", decoding=URL);
}
}

sub vcl_deliver {
if (req.http.do_deliver_proto) {
set resp.proto = blob.transcode(encoded="new%0aline", decoding=URL);
}
if (req.http.do_deliver_reason) {
set resp.reason = blob.transcode(encoded="new%0aline", decoding=URL);
}
if (req.http.do_deliver_header) {
set resp.http.foo = blob.transcode(encoded="new%0aline", decoding=URL);
}
if (req.http.do_synth_header) {
return (synth(200));
}
}

sub vcl_synth {
if (req.http.do_synth_header) {
set resp.http.foo = blob.transcode(encoded="new%0aline", decoding=URL);
}
}

sub vcl_backend_fetch {
if (bereq.url == "/do_backend_fetch_method") {
set bereq.method = blob.transcode(encoded="new%0aline", decoding=URL);
}
if (bereq.url == "/do_backend_fetch_url") {
set bereq.url = blob.transcode(encoded="new%0aline", decoding=URL);
}
if (bereq.url == "/do_backend_fetch_header") {
set bereq.http.foo = blob.transcode(encoded="new%0aline", decoding=URL);
}
}
} -start

# Prime the cache
client c1 {
txreq
rxresp
expect resp.status == 200
} -run

# Test bad vcl_deliver
client c1 {
txreq -hdr "do_deliver_proto: true"
rxresp
expect resp.status == 503
expect_close
} -run
client c1 {
txreq -hdr "do_deliver_reason: true"
rxresp
expect resp.status == 503
expect_close
} -run
client c1 {
txreq -hdr "do_deliver_header: true"
rxresp
expect resp.status == 503
expect_close
} -run

# Test bad vcl_synth
client c2 {
txreq -hdr "do_synth_header: true"
rxresp
expect resp.status == 500
expect_close
} -run

# Test bad vcl_backend_fetch
client c3 {
txreq -url /do_backend_fetch_method
rxresp
expect resp.status == 503
# No expect_close as this will be a regular 503 object delivery
} -run
client c3 {
txreq -url /do_backend_fetch_url
rxresp
expect resp.status == 503
# No expect_close as this will be a regular 503 object delivery
} -run
client c3 {
txreq -url /do_backend_fetch_header
rxresp
expect resp.status == 503
# No expect_close as this will be a regular 503 object delivery
} -run

# Test vcl_backend_fetch on bad URL inherited from request
client c4 {
txreq -hdr "do_recv_url: true"
rxresp
expect resp.status == 503
# No expect_close as this will be a regular 503 object delivery
} -run

varnish v1 -cliok "param.set feature +http2"

client c5 {
stream 1 {
txreq -hdr "do_deliver_proto" "true"
rxresp
expect resp.status == 503

} -run
} -run
Loading