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

h2: Rapid reset mitigations (7.3) #4011

Merged
merged 29 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b9074c6
Bump cli_limit to fit param.show -j with more parameters coming
nigoroll Oct 16, 2023
5cdffa1
h2: Add a rate limit facility for h/2 RST handling
daghf Oct 12, 2023
54146bc
Introduce RAPID_RESET as a sess_close reason
daghf Oct 17, 2023
74bbb48
Add param h2_rapid_reset
daghf Oct 17, 2023
de3a875
Polish h2_rapid_reset docs
nigoroll Oct 17, 2023
d241f13
Flexelinting
bsdphk Oct 17, 2023
874a7cc
slinkified dridi-polish
nigoroll Oct 17, 2023
2eac591
vtc: Avoid cycling the barrier in t02014
dridi Oct 12, 2023
467a4db
transport: New poll method
dridi Oct 11, 2023
fa83fee
vcl_vrt: Skip VCL execution if the client is gone
dridi Oct 11, 2023
a1501a6
http2_session: Implement transport polling
dridi Oct 11, 2023
84b67dc
vtc: Stabilize r3996 and increase coverage
dridi Oct 18, 2023
ae7b0b5
vtc: Missing synchronization in t02025
dridi Oct 18, 2023
f5f8f27
Copy rapid reset parameters to the h2 session
nigoroll Oct 17, 2023
c21f9b2
Add vmod_h2 to control rapid_reset parameters per session
nigoroll Oct 16, 2023
53a1cd5
Start with a reasonable default for h2_rapid_reset_limit
nigoroll Oct 18, 2023
f064f64
Adjust test case to previous commit
nigoroll Oct 18, 2023
f28e5da
Flexelinting
nigoroll Oct 18, 2023
941d3fd
vmod_h2: VRT_fail if called outside of client context
daghf Oct 18, 2023
2e361de
Redo H2 field validation
bsdphk Aug 22, 2023
fec46c8
Slightly more coverage & consistency
bsdphk Aug 22, 2023
5e5a8cc
hpack: Turn header validation state into an enum
walid-git Sep 8, 2023
34dde81
hpack: Check illegal header blanks with vct_issp()
walid-git Sep 8, 2023
5db4965
hpack: Validate header values with vct_ishdrval()
walid-git Sep 8, 2023
01059ea
hpack: Remove redundant/incorrect header validation
walid-git Sep 8, 2023
2478263
vtc: More HPACK header validation coverage
walid-git Sep 8, 2023
05123d4
vtc: Coverage for h2 empty header in t02023
dridi Sep 14, 2023
2df7705
vmod_h2: Polish manual
dridi Oct 24, 2023
990bf81
build: Generate and install man/vmod_h2.3
dridi Oct 24, 2023
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_transport.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ typedef void vtr_sess_panic_f (struct vsb *, const struct sess *);
typedef void vtr_req_panic_f (struct vsb *, const struct req *);
typedef void vtr_req_fail_f (struct req *, stream_close_t);
typedef void vtr_reembark_f (struct worker *, struct req *);
typedef int vtr_poll_f (struct req *);
typedef int vtr_minimal_response_f (struct req *, uint16_t status);

struct transport {
Expand All @@ -64,6 +65,7 @@ struct transport {
vtr_sess_panic_f *sess_panic;
vtr_req_panic_f *req_panic;
vtr_reembark_f *reembark;
vtr_poll_f *poll;
vtr_minimal_response_f *minimal_response;

VTAILQ_ENTRY(transport) list;
Expand Down
37 changes: 37 additions & 0 deletions bin/varnishd/cache/cache_vrt_vcl.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "vbm.h"

#include "cache_director.h"
#include "cache_transport.h"
#include "cache_vcl.h"
#include "vcc_interface.h"

Expand Down Expand Up @@ -521,6 +522,40 @@ VRT_VCL_Allow_Discard(struct vclref **refp)
FREE_OBJ(ref);
}

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

static int
req_poll(struct worker *wrk, struct req *req)
{
struct req *top;

/* NB: Since a fail transition leads to vcl_synth, the request may be
* short-circuited twice.
*/
if (req->req_reset) {
wrk->vpi->handling = VCL_RET_FAIL;
return (-1);
}

top = req->top->topreq;
CHECK_OBJ_NOTNULL(top, REQ_MAGIC);
CHECK_OBJ_NOTNULL(top->transport, TRANSPORT_MAGIC);

if (!FEATURE(FEATURE_VCL_REQ_RESET))
return (0);
if (top->transport->poll == NULL)
return (0);
if (top->transport->poll(top) >= 0)
return (0);

VSLb_ts_req(req, "Reset", W_TIM_real(wrk));
wrk->stats->req_reset++;
wrk->vpi->handling = VCL_RET_FAIL;
req->req_reset = 1;
return (-1);
}

/*--------------------------------------------------------------------
* Method functions to call into VCL programs.
*
Expand Down Expand Up @@ -552,6 +587,8 @@ vcl_call_method(struct worker *wrk, struct req *req, struct busyobj *bo,
CHECK_OBJ_NOTNULL(req->sp, SESS_MAGIC);
CHECK_OBJ_NOTNULL(req->vcl, VCL_MAGIC);
CHECK_OBJ_NOTNULL(req->top, REQTOP_MAGIC);
if (req_poll(wrk, req))
return;
VCL_Req2Ctx(&ctx, req);
}
assert(ctx.now != 0);
Expand Down
10 changes: 10 additions & 0 deletions bin/varnishd/http2/cache_http2.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ struct h2_error_s {

typedef const struct h2_error_s *h2_error;

#define H2_CUSTOM_ERRORS
#define H2EC1(U,v,r,d) extern const struct h2_error_s H2CE_##U[1];
#define H2EC2(U,v,r,d) extern const struct h2_error_s H2SE_##U[1];
#define H2EC3(U,v,r,d) H2EC1(U,v,r,d) H2EC2(U,v,r,d)
Expand Down Expand Up @@ -193,6 +194,15 @@ struct h2_sess {
VTAILQ_HEAD(,h2_req) txqueue;

h2_error error;

// rst rate limit parameters, copied from h2_* parameters
vtim_dur rapid_reset;
int64_t rapid_reset_limit;
vtim_dur rapid_reset_period;

// rst rate limit state
double rst_budget;
vtim_real last_rst;
};

#define ASSERT_RXTHR(h2) do {assert(h2->rxthr == pthread_self());} while(0)
Expand Down
41 changes: 40 additions & 1 deletion bin/varnishd/http2/cache_http2_proto.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "vtcp.h"
#include "vtim.h"

#define H2_CUSTOM_ERRORS
#define H2EC1(U,v,r,d) const struct h2_error_s H2CE_##U[1] = {{#U,d,v,0,1,r}};
#define H2EC2(U,v,r,d) const struct h2_error_s H2SE_##U[1] = {{#U,d,v,1,0,r}};
#define H2EC3(U,v,r,d) H2EC1(U,v,r,d) H2EC2(U,v,r,d)
Expand Down Expand Up @@ -317,9 +318,46 @@ h2_rx_push_promise(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
/**********************************************************************
*/

static h2_error
h2_rapid_reset(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
{
vtim_real now;
vtim_dur d;

CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
ASSERT_RXTHR(h2);
CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);

if (h2->rapid_reset_limit == 0)
return (0);

now = VTIM_real();
CHECK_OBJ_NOTNULL(r2->req, REQ_MAGIC);
AN(r2->req->t_first);
if (now - r2->req->t_first > h2->rapid_reset)
return (0);

d = now - h2->last_rst;
h2->rst_budget += h2->rapid_reset_limit * d /
h2->rapid_reset_period;
h2->rst_budget = vmin_t(double, h2->rst_budget,
h2->rapid_reset_limit);
h2->last_rst = now;

if (h2->rst_budget < 1.0) {
Lck_Lock(&h2->sess->mtx);
VSLb(h2->vsl, SLT_Error, "H2: Hit RST limit. Closing session.");
Lck_Unlock(&h2->sess->mtx);
return (H2CE_RAPID_RESET);
}
h2->rst_budget -= 1.0;
return (0);
}

static h2_error v_matchproto_(h2_rxframe_f)
h2_rx_rst_stream(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
{
h2_error h2e;

CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
ASSERT_RXTHR(h2);
Expand All @@ -329,8 +367,9 @@ h2_rx_rst_stream(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
return (H2CE_FRAME_SIZE_ERROR);
if (r2 == NULL)
return (0);
h2e = h2_rapid_reset(wrk, h2, r2);
h2_kill_req(wrk, h2, r2, h2_streamerror(vbe32dec(h2->rxf_data)));
return (0);
return (h2e);
}

/**********************************************************************
Expand Down
19 changes: 19 additions & 0 deletions bin/varnishd/http2/cache_http2_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ h2_init_sess(struct sess *sp,
h2->remote_settings = H2_proto_settings;
h2->decode = decode;

h2->rapid_reset = cache_param->h2_rapid_reset;
h2->rapid_reset_limit = cache_param->h2_rapid_reset_limit;
h2->rapid_reset_period = cache_param->h2_rapid_reset_period;

h2->rst_budget = h2->rapid_reset_limit;
h2->last_rst = sp->t_open;
AZ(isnan(h2->last_rst));

AZ(VHT_Init(h2->dectbl, h2->local_settings.header_table_size));

*up = (uintptr_t)h2;
Expand Down Expand Up @@ -438,6 +446,16 @@ h2_new_session(struct worker *wrk, void *arg)
wrk->vsl = NULL;
}

static int v_matchproto_(vtr_poll_f)
h2_poll(struct req *req)
{
struct h2_req *r2;

CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
CAST_OBJ_NOTNULL(r2, req->transport_priv, H2_REQ_MAGIC);
return (r2->error ? -1 : 1);
}

struct transport HTTP2_transport = {
.name = "HTTP/2",
.magic = TRANSPORT_MAGIC,
Expand All @@ -447,4 +465,5 @@ struct transport HTTP2_transport = {
.req_body = h2_req_body,
.req_fail = h2_req_fail,
.sess_panic = h2_sess_panic,
.poll = h2_poll,
};
90 changes: 90 additions & 0 deletions bin/varnishtest/tests/r03996.vtc
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
varnishtest "h2 rapid reset"

barrier b1 sock 2 -cyclic
barrier b2 sock 5 -cyclic

server s1 {
rxreq
txresp
} -start

varnish v1 -cliok "param.set feature +http2"
varnish v1 -cliok "param.set debug +syncvsl"
varnish v1 -cliok "param.set h2_rapid_reset_limit 3"
varnish v1 -cliok "param.set h2_rapid_reset 5"

varnish v1 -vcl+backend {
import vtc;

sub vcl_recv {
if (req.http.barrier) {
vtc.barrier_sync(req.http.barrier);
}
vtc.barrier_sync("${b2_sock}");
}

} -start

client c1 {
stream 0 {
rxgoaway
expect goaway.err == ENHANCE_YOUR_CALM
} -start

stream 1 {
txreq -hdr barrier ${b1_sock}
barrier b1 sync
txrst
} -run
stream 3 {
txreq -hdr barrier ${b1_sock}
barrier b1 sync
txrst
} -run
stream 5 {
txreq -hdr barrier ${b1_sock}
barrier b1 sync
txrst
} -run
stream 7 {
txreq -hdr barrier ${b1_sock}
barrier b1 sync
txrst
} -run

barrier b2 sync
stream 0 -wait
} -run

varnish v1 -expect sc_rapid_reset == 1

varnish v1 -cliok "param.set feature -vcl_req_reset"

client c2 {
stream 0 {
rxgoaway
expect goaway.err == ENHANCE_YOUR_CALM
} -start

stream 1 {
txreq
txrst
} -run
stream 3 {
txreq
txrst
} -run
stream 5 {
txreq
txrst
} -run
stream 7 {
txreq
txrst
} -run

barrier b2 sync
stream 0 -wait
} -run

varnish v1 -expect sc_rapid_reset == 2
Loading