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

gzip: Heal-the-BREACH mitigation #4131

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
184 changes: 180 additions & 4 deletions bin/varnishd/cache/cache_gzip.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "vend.h"

#include "vgz.h"
#include "vrnd.h"

struct vgz {
unsigned magic;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this not be >= ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

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
Expand Down Expand Up @@ -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)
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions bin/varnishd/cache/cache_varnishd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
10 changes: 7 additions & 3 deletions bin/varnishd/cache/cache_vrt_filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down Expand Up @@ -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 &&
Expand Down
46 changes: 46 additions & 0 deletions bin/varnishtest/tests/b00085.vtc
Original file line number Diff line number Diff line change
@@ -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
50 changes: 50 additions & 0 deletions bin/varnishtest/tests/b00086.vtc
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions include/tbl/feature_bits.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Loading