diff --git a/bin/varnishd/cache/cache_gzip.c b/bin/varnishd/cache/cache_gzip.c index 472ac2e5b9f..1e73c900623 100644 --- a/bin/varnishd/cache/cache_gzip.c +++ b/bin/varnishd/cache/cache_gzip.c @@ -49,6 +49,7 @@ #include "vend.h" #include "vgz.h" +#include "vrnd.h" struct vgz { unsigned magic; @@ -344,11 +345,10 @@ vdp_gunzip_fini(struct vdp_ctx *vdc, void **priv) { struct vgz *vg; - (void)vdc; - CAST_OBJ_NOTNULL(vg, *priv, VGZ_MAGIC); + CHECK_OBJ_NOTNULL(vdc, VDP_CTX_MAGIC); + TAKE_OBJ_NOTNULL(vg, priv, VGZ_MAGIC); AN(vg->m_buf); (void)VGZ_Destroy(&vg); - *priv = NULL; return (0); } @@ -403,6 +403,168 @@ const struct vdp VDP_gunzip = { .fini = vdp_gunzip_fini, }; +/*-------------------------------------------------------------------- + * VDP for BREACH mitigation + */ + +#define BREACH_PAD 256U + +struct breach { + unsigned magic; +#define BREACH_MAGIC 0xafe85758 + unsigned skip; + const char *comm; +}; + +static const unsigned char breach_hdr[] = { + 0x1f, 0x8b, /* magic markers ID1 and ID2 */ + 0x08, /* deflate compression */ + 1 << 4, /* FCOMMENT flag */ + 0x00, 0x00, 0x00, 0x00, /* no mtime */ + 0x02, /* slowest compression */ + 0xff, /* unknown OS */ +}; + +static const gz_header breach_gz_header = { + .os = 0xff, /* unknown OS */ +}; + +static const char * +breach_comment(struct ws *ws, size_t *plen) +{ + static const char *alpha = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789!{}$%/&*()_+-=[]"; + char buf[BREACH_PAD], *c, *e; + size_t len; + + CHECK_OBJ_NOTNULL(ws, WS_MAGIC); + len = 1 + VRND_RandomTestable() % BREACH_PAD; + e = buf + len - 1; + for (c = buf; c < e; c++) + *c = alpha[VRND_RandomTestable() % sizeof alpha]; + *c = '\0'; + if (plen != NULL) + *plen = len; + return (WS_Copy(ws, buf, len)); +} + +static int +breach_bytes(struct breach *br, struct vdp_ctx *vdc) +{ + int ret; + + if (VDP_bytes(vdc, VDP_NULL, breach_hdr, sizeof breach_hdr)) + return (vdc->retval); + + ret = VDP_bytes(vdc, VDP_NULL, br->comm, strlen(br->comm) + 1); + br->comm = NULL; + return (ret); +} + +static int v_matchproto_(vdp_init_f) +vdp_gzip_breach_init(VRT_CTX, struct vdp_ctx *vdc, void **priv, + struct objcore *oc) +{ + struct breach *br; + struct boc *boc; + struct req *req; + enum boc_state_e bos; + const char *p, *comm; + size_t start_bit, len; + ssize_t dl; + + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + CHECK_OBJ_NOTNULL(vdc, VDP_CTX_MAGIC); + CHECK_OBJ_ORNULL(oc, OBJCORE_MAGIC); + req = vdc->req; + CHECK_OBJ_NOTNULL(req, REQ_MAGIC); + + if (req->esi_level > 0 || oc == NULL) + return (1); + + boc = HSH_RefBoc(oc); + if (boc != NULL) { + CHECK_OBJ(boc, BOC_MAGIC); + bos = boc->state; + HSH_DerefBoc(vdc->wrk, oc); + if (bos < BOS_FINISHED) + return (1); /* OA_GZIPBITS is not stable yet */ + } + + p = ObjGetAttr(vdc->wrk, oc, OA_GZIPBITS, &dl); + if (p == NULL || dl != 32) + return (1); + + start_bit = vbe64dec(p); + assert(start_bit >= 80); + AZ(start_bit & 7); + + /* NB: a gzip header comment is null-terminated. */ + br = WS_Alloc(ctx->ws, sizeof *br); + comm = breach_comment(ctx->ws, &len); + if (br == NULL || comm == NULL) { + VSLb(ctx->vsl, SLT_Error, "gzip_breach: Out of workspace"); + return (VFP_ERROR); + } + + INIT_OBJ(br, BREACH_MAGIC); + br->skip = start_bit / 8; + br->comm = comm; + + if (req->resp_len > 0) + req->resp_len += sizeof breach_hdr + len - br->skip; + + *priv = br; + return (0); +} + +static int v_matchproto_(vdp_fini_f) +vdp_gzip_breach_fini(struct vdp_ctx *vdc, void **priv) +{ + struct breach *br; + + CHECK_OBJ_NOTNULL(vdc, VDP_CTX_MAGIC); + TAKE_OBJ_NOTNULL(br, priv, BREACH_MAGIC); + FINI_OBJ(br); + return (0); +} + +static int v_matchproto_(vdp_bytes_f) +vdp_gzip_breach_bytes(struct vdp_ctx *vdc, enum vdp_action act, void **priv, + const void *ptr, ssize_t len) +{ + struct breach *br; + + CHECK_OBJ_NOTNULL(vdc, VDP_CTX_MAGIC); + CAST_OBJ_NOTNULL(br, *priv, BREACH_MAGIC); + + if (len <= 0) + return (len); + + if (br->skip > len) { + br->skip -= len; + return (0); + } + + ptr = (const char *)ptr + br->skip; + len -= br->skip; + br->skip = 0; + + if (br->comm != NULL && breach_bytes(br, vdc)) + return (vdc->retval); + + return (VDP_bytes(vdc, act, ptr, len)); +} + +const struct vdp VDP_gzip_breach = { + .name = "gzip_breach", + .init = vdp_gzip_breach_init, + .bytes = vdp_gzip_breach_bytes, + .fini = vdp_gzip_breach_fini, +}; + /*--------------------------------------------------------------------*/ void @@ -479,13 +641,15 @@ static enum vfp_status v_matchproto_(vfp_init_f) vfp_gzip_init(VRT_CTX, struct vfp_ctx *vc, struct vfp_entry *vfe) { struct vgz *vg; + gz_header *hdr; + const char *comm; CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); CHECK_OBJ_NOTNULL(vc, VFP_CTX_MAGIC); CHECK_OBJ_NOTNULL(vfe, VFP_ENTRY_MAGIC); /* - * G(un)zip makes no sence on partial responses, but since + * G(un)zip makes no sense on partial responses, but since * it is an pure 1:1 transform, we can just ignore it. */ if (http_GetStatus(vc->resp) == 206) @@ -496,6 +660,18 @@ vfp_gzip_init(VRT_CTX, struct vfp_ctx *vc, struct vfp_entry *vfe) return (VFP_NULL); vg = VGZ_NewGzip(vc->wrk->vsl, vfe->vfp->priv1); vc->obj_flags |= OF_GZIPED | OF_CHGCE; + if (FEATURE(FEATURE_GZIP_BREACH)) { + comm = breach_comment(ctx->ws, NULL); + hdr = WS_Copy(ctx->ws, &breach_gz_header, + sizeof breach_gz_header); + if (comm == NULL || hdr == NULL) { + VSLb(vc->wrk->vsl, SLT_FetchError, + "gzip_breach: Out of workspace"); + return (VFP_ERROR); + } + hdr->comment = TRUST_ME(comm); + assert(deflateSetHeader(&vg->vz, hdr) == Z_OK); + } } else { if (!http_HdrIs(vc->resp, H_Content_Encoding, "gzip")) return (VFP_NULL); diff --git a/bin/varnishd/cache/cache_varnishd.h b/bin/varnishd/cache/cache_varnishd.h index 2469cdcecf9..1aaefab9c1e 100644 --- a/bin/varnishd/cache/cache_varnishd.h +++ b/bin/varnishd/cache/cache_varnishd.h @@ -196,6 +196,7 @@ int VDP_Push(VRT_CTX, struct vdp_ctx *, struct ws *, const struct vdp *, void *priv); int VDP_DeliverObj(struct vdp_ctx *vdc, struct objcore *oc); extern const struct vdp VDP_gunzip; +extern const struct vdp VDP_gzip_breach; extern const struct vdp VDP_esi; extern const struct vdp VDP_range; diff --git a/bin/varnishd/cache/cache_vrt_filter.c b/bin/varnishd/cache/cache_vrt_filter.c index e0ca1832162..3c11c1c1fc2 100644 --- a/bin/varnishd/cache/cache_vrt_filter.c +++ b/bin/varnishd/cache/cache_vrt_filter.c @@ -288,6 +288,7 @@ VCL_VRT_Init(void) AZ(vrt_addfilter(NULL, &VFP_esi_gzip, NULL)); AZ(vrt_addfilter(NULL, NULL, &VDP_esi)); AZ(vrt_addfilter(NULL, NULL, &VDP_gunzip)); + AZ(vrt_addfilter(NULL, NULL, &VDP_gzip_breach)); AZ(vrt_addfilter(NULL, NULL, &VDP_range)); } @@ -403,9 +404,12 @@ resp_default_filter_list(void *arg, struct vsb *vsb) if (cache_param->http_gzip_support && req->objcore != NULL && - ObjCheckFlag(req->wrk, req->objcore, OF_GZIPED) && - !RFC2616_Req_Gzip(req->http)) - VSB_cat(vsb, " gunzip"); + ObjCheckFlag(req->wrk, req->objcore, OF_GZIPED)) { + if (!RFC2616_Req_Gzip(req->http)) + VSB_cat(vsb, " gunzip"); + else if (FEATURE(FEATURE_GZIP_BREACH)) + VSB_cat(vsb, " gzip_breach"); + } if (cache_param->http_range_support && http_GetStatus(req->resp) == 200 && diff --git a/bin/varnishtest/tests/b00085.vtc b/bin/varnishtest/tests/b00085.vtc new file mode 100644 index 00000000000..3992c07f2b0 --- /dev/null +++ b/bin/varnishtest/tests/b00085.vtc @@ -0,0 +1,46 @@ +varnishtest "Heal-the-BREACH mitigation during delivery" + +server s1 { + rxreq + txresp -gziplen 4001 +} -start + +varnish v1 -cliok "param.set feature +gzip_breach" +varnish v1 -vcl+backend "" -start + +varnish v1 -cliok "debug.srandom" + +client c1 { + txreq -hdr "accept-encoding: gzip" + rxresp + expect resp.bodylen == 4119 + gunzip + expect resp.bodylen == 4001 + + txreq -hdr "accept-encoding: gzip" + rxresp + expect resp.bodylen == 4243 + gunzip + expect resp.bodylen == 4001 + + txreq -hdr "accept-encoding: gzip" + rxresp + expect resp.bodylen == 4347 + gunzip + expect resp.bodylen == 4001 + + txreq -hdr "accept-encoding: gzip" + rxresp + expect resp.bodylen == 4292 + gunzip + expect resp.bodylen == 4001 + + txreq -hdr "accept-encoding: gzip" + rxresp + expect resp.bodylen == 4139 + gunzip + expect resp.bodylen == 4001 +} -run + +varnish v1 -expect cache_miss == 1 +varnish v1 -expect cache_hit == 4 diff --git a/bin/varnishtest/tests/b00086.vtc b/bin/varnishtest/tests/b00086.vtc new file mode 100644 index 00000000000..db6fbd9330d --- /dev/null +++ b/bin/varnishtest/tests/b00086.vtc @@ -0,0 +1,50 @@ +varnishtest "Heal-the-BREACH mitigation during fetch" + +server s0 { + rxreq + txresp -bodylen 4001 +} -dispatch + +varnish v1 -cliok "param.set feature +gzip_breach" +varnish v1 -vcl+backend { + sub vcl_recv { + return (pass); + } + sub vcl_backend_response { + set beresp.do_gzip = true; + } +} -start + +varnish v1 -cliok "debug.srandom" + +client c1 { + txreq -hdr "accept-encoding: gzip" + rxresp + expect resp.bodylen == 381 + gunzip + expect resp.bodylen == 4001 + + txreq -hdr "accept-encoding: gzip" + rxresp + expect resp.bodylen == 485 + gunzip + expect resp.bodylen == 4001 + + txreq -hdr "accept-encoding: gzip" + rxresp + expect resp.bodylen == 430 + gunzip + expect resp.bodylen == 4001 + + txreq -hdr "accept-encoding: gzip" + rxresp + expect resp.bodylen == 277 + gunzip + expect resp.bodylen == 4001 + + txreq -hdr "accept-encoding: gzip" + rxresp + expect resp.bodylen == 414 + gunzip + expect resp.bodylen == 4001 +} -run diff --git a/include/tbl/feature_bits.h b/include/tbl/feature_bits.h index 808efe9174c..6769ff0f865 100644 --- a/include/tbl/feature_bits.h +++ b/include/tbl/feature_bits.h @@ -96,6 +96,10 @@ FEATURE_BIT(VCL_REQ_RESET, vcl_req_reset, "When this happens MAIN.req_reset is incremented." ) +FEATURE_BIT(GZIP_BREACH, gzip_breach, + "Heal-the-BREACH mitigation." +) + #undef FEATURE_BIT /*lint -restore */ diff --git a/lib/libvgz/deflate.c b/lib/libvgz/deflate.c index 05a2d451713..d50c525d2e5 100644 --- a/lib/libvgz/deflate.c +++ b/lib/libvgz/deflate.c @@ -715,8 +715,6 @@ int ZEXPORT deflateReset(z_streamp strm) { return ret; } -#ifdef NOVGZ - /* ========================================================================= */ int ZEXPORT deflateSetHeader(z_streamp strm, gz_headerp head) { if (deflateStateCheck(strm) || strm->state->wrap != 2) @@ -725,6 +723,8 @@ int ZEXPORT deflateSetHeader(z_streamp strm, gz_headerp head) { return Z_OK; } +#ifdef NOVGZ + /* ========================================================================= */ int ZEXPORT deflatePending(z_streamp strm, unsigned *pending, int *bits) { if (deflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -1082,7 +1082,6 @@ int ZEXPORT deflate(z_streamp strm, int flush) { } } else { -#ifdef NOVGZ put_byte(s, (s->gzhead->text ? 1 : 0) + (s->gzhead->hcrc ? 2 : 0) + (s->gzhead->extra == Z_NULL ? 0 : 4) + @@ -1200,11 +1199,6 @@ int ZEXPORT deflate(z_streamp strm, int flush) { return Z_OK; } } -#else /* !NOVGZ */ - abort(); - } - } -#endif /* NOVGZ */ #endif if (strm->start_bit == 0)