summaryrefslogtreecommitdiffstats
path: root/src/mux_h2.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/mux_h2.c165
1 files changed, 155 insertions, 10 deletions
diff --git a/src/mux_h2.c b/src/mux_h2.c
index 273e1f5..7ce0e6e 100644
--- a/src/mux_h2.c
+++ b/src/mux_h2.c
@@ -78,11 +78,13 @@ struct h2c {
int timeout; /* idle timeout duration in ticks */
int shut_timeout; /* idle timeout duration in ticks after GOAWAY was sent */
int idle_start; /* date of the last time the connection went idle (no stream + empty mbuf), or the start of current http req */
- /* 32-bit hole here */
+
unsigned int nb_streams; /* number of streams in the tree */
unsigned int nb_sc; /* number of attached stream connectors */
unsigned int nb_reserved; /* number of reserved streams */
unsigned int stream_cnt; /* total number of streams seen */
+ int glitches; /* total number of glitches on this connection */
+
struct proxy *proxy; /* the proxy this connection was created for */
struct task *task; /* timeout management task */
struct h2_counters *px_counters; /* h2 counters attached to proxy */
@@ -408,6 +410,8 @@ static int h2_settings_header_table_size = 4096; /* initial value */
static int h2_settings_initial_window_size = 65536; /* default initial value */
static int h2_be_settings_initial_window_size = 0; /* backend's default initial value */
static int h2_fe_settings_initial_window_size = 0; /* frontend's default initial value */
+static int h2_be_glitches_threshold = 0; /* backend's max glitches: unlimited */
+static int h2_fe_glitches_threshold = 0; /* frontend's max glitches: unlimited */
static unsigned int h2_settings_max_concurrent_streams = 100; /* default value */
static unsigned int h2_be_settings_max_concurrent_streams = 0; /* backend value */
static unsigned int h2_fe_settings_max_concurrent_streams = 0; /* frontend value */
@@ -510,6 +514,9 @@ static void h2_trace(enum trace_level level, uint64_t mask, const struct trace_s
if (h2c->errcode)
chunk_appendf(&trace_buf, " err=%s/%02x", h2_err_str(h2c->errcode), h2c->errcode);
+ if (h2c->glitches)
+ chunk_appendf(&trace_buf, " glitches=%d", h2c->glitches);
+
if (h2c->flags & H2_CF_DEM_IN_PROGRESS && // frame processing has started, type and length are valid
(mask & (H2_EV_RX_FRAME|H2_EV_RX_FHDR)) == (H2_EV_RX_FRAME|H2_EV_RX_FHDR)) {
chunk_appendf(&trace_buf, " dft=%s/%02x dfl=%d", h2_ft_str(h2c->dft), h2c->dff, h2c->dfl);
@@ -599,7 +606,6 @@ static inline int h2c_max_concurrent_streams(const struct h2c *h2c)
return ret;
}
-
/* update h2c timeout if needed */
static void h2c_update_timeout(struct h2c *h2c)
{
@@ -1046,6 +1052,7 @@ static int h2_init(struct connection *conn, struct proxy *prx, struct session *s
h2c->nb_sc = 0;
h2c->nb_reserved = 0;
h2c->stream_cnt = 0;
+ h2c->glitches = 0;
h2c->dbuf = *input;
h2c->dsi = -1;
@@ -1297,6 +1304,25 @@ static void __maybe_unused h2s_alert(struct h2s *h2s)
TRACE_LEAVE(H2_EV_H2S_WAKE, h2s->h2c->conn, h2s);
}
+/* report one or more glitches on the connection. That is any unexpected event
+ * that may occasionally happen but if repeated a bit too much, might indicate
+ * a misbehaving or completely bogus peer. It normally returns zero, unless the
+ * glitch limit was reached, in which case an error is also reported on the
+ * connection.
+ */
+static inline int h2c_report_glitch(struct h2c *h2c, int increment)
+{
+ int thres = (h2c->flags & H2_CF_IS_BACK) ?
+ h2_be_glitches_threshold : h2_fe_glitches_threshold;
+
+ h2c->glitches += increment;
+ if (thres && h2c->glitches >= thres) {
+ h2c_error(h2c, H2_ERR_ENHANCE_YOUR_CALM);
+ return 1;
+ }
+ return 0;
+}
+
/* writes the 24-bit frame size <len> at address <frame> */
static inline __maybe_unused void h2_set_frame_size(void *frame, uint32_t len)
{
@@ -1586,6 +1612,7 @@ static struct h2s *h2c_frt_stream_new(struct h2c *h2c, int id, struct buffer *in
BUG_ON(conn_reverse_in_preconnect(h2c->conn));
if (h2c->nb_streams >= h2c_max_concurrent_streams(h2c)) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("HEADERS frame causing MAX_CONCURRENT_STREAMS to be exceeded", H2_EV_H2S_NEW|H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn);
session_inc_http_req_ctr(sess);
session_inc_http_err_ctr(sess);
@@ -1835,6 +1862,7 @@ static int h2c_frt_recv_preface(struct h2c *h2c)
if (!ret1)
h2c->flags |= H2_CF_DEM_SHORT_READ;
if (ret1 < 0 || (h2c->flags & H2_CF_RCVD_SHUT)) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("I/O error or short read", H2_EV_RX_FRAME|H2_EV_RX_PREFACE, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
if (b_data(&h2c->dbuf) ||
@@ -2298,12 +2326,26 @@ static int h2c_handle_settings(struct h2c *h2c)
*/
if (arg < 0) { // RFC7540#6.5.2
error = H2_ERR_FLOW_CONTROL_ERROR;
+ h2c_report_glitch(h2c, 1);
goto fail;
}
+ /* Let's count a glitch here in case of a reduction
+ * after H2_CS_SETTINGS1 because while it's not
+ * fundamentally invalid from a protocol's perspective,
+ * it's often suspicious.
+ */
+ if (h2c->st0 != H2_CS_SETTINGS1 && arg < h2c->miw)
+ if (h2c_report_glitch(h2c, 1)) {
+ error = H2_ERR_ENHANCE_YOUR_CALM;
+ TRACE_STATE("glitch limit reached on SETTINGS frame", H2_EV_RX_FRAME|H2_EV_RX_SETTINGS|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
+ goto fail;
+ }
+
h2c->miw = arg;
break;
case H2_SETTINGS_MAX_FRAME_SIZE:
if (arg < 16384 || arg > 16777215) { // RFC7540#6.5.2
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("MAX_FRAME_SIZE out of range", H2_EV_RX_FRAME|H2_EV_RX_SETTINGS, h2c->conn);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -2316,6 +2358,7 @@ static int h2c_handle_settings(struct h2c *h2c)
break;
case H2_SETTINGS_ENABLE_PUSH:
if (arg < 0 || arg > 1) { // RFC7540#6.5.2
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("ENABLE_PUSH out of range", H2_EV_RX_FRAME|H2_EV_RX_SETTINGS, h2c->conn);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -2578,13 +2621,22 @@ static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s)
goto done;
if (!inc) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("stream WINDOW_UPDATE inc=0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn, h2s);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err);
goto strm_err;
}
+ /* WT: it would be tempting to count a glitch here for very small
+ * increments (less than a few tens of bytes), but that might be
+ * perfectly valid for many short streams, so better instead
+ * count the number of WU per frame maybe. That would be better
+ * dealt with using scores per frame.
+ */
+
if (h2s_mws(h2s) >= 0 && h2s_mws(h2s) + inc < 0) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("stream WINDOW_UPDATE inc<0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn, h2s);
error = H2_ERR_FLOW_CONTROL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err);
@@ -2603,6 +2655,7 @@ static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s)
else {
/* connection window update */
if (!inc) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("conn WINDOW_UPDATE inc=0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -2610,6 +2663,7 @@ static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s)
}
if (h2c->mws >= 0 && h2c->mws + inc < 0) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("conn WINDOW_UPDATE inc<0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn);
error = H2_ERR_FLOW_CONTROL_ERROR;
goto conn_err;
@@ -2679,6 +2733,7 @@ static int h2c_handle_priority(struct h2c *h2c)
if (h2_get_n32(&h2c->dbuf, 0) == h2c->dsi) {
/* 7540#5.3 : can't depend on itself */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("PRIORITY depends on itself", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -2792,6 +2847,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
else if (h2c->dsi <= h2c->max_id || !(h2c->dsi & 1)) {
/* RFC7540#5.1.1 stream id > prev ones, and must be odd here */
error = H2_ERR_PROTOCOL_ERROR;
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("HEADERS on invalid stream ID", H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
sess_log(h2c->conn->owner);
@@ -2809,6 +2865,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
* stop processing its requests for real.
*/
error = H2_ERR_ENHANCE_YOUR_CALM;
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("Stream limit violated", H2_EV_STRM_SHUT, h2c->conn);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
sess_log(h2c->conn->owner);
@@ -2977,6 +3034,7 @@ static struct h2s *h2c_bck_handle_headers(struct h2c *h2c, struct h2s *h2s)
if (h2s->st != H2_SS_OPEN && h2s->st != H2_SS_HLOC) {
/* RFC7540#5.1 */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("response HEADERS in invalid state", H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn, h2s);
h2s_error(h2s, H2_ERR_STREAM_CLOSED);
h2c->st0 = H2_CS_FRAME_E;
@@ -3067,22 +3125,24 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s)
if (h2s->st != H2_SS_OPEN && h2s->st != H2_SS_HLOC) {
/* RFC7540#6.1 */
error = H2_ERR_STREAM_CLOSED;
- goto strm_err;
+ goto strm_err_wu;
}
if (!(h2s->flags & H2_SF_HEADERS_RCVD)) {
/* RFC9113#8.1: The header section must be received before the message content */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("Unexpected DATA frame before the message headers", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err);
- goto strm_err;
+ goto strm_err_wu;
}
if ((h2s->flags & H2_SF_DATA_CLEN) && (h2c->dfl - h2c->dpl) > h2s->body_len) {
/* RFC7540#8.1.2 */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("DATA frame larger than content-length", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err);
- goto strm_err;
+ goto strm_err_wu;
}
if (!(h2c->flags & H2_CF_IS_BACK) &&
(h2s->flags & (H2_SF_TUNNEL_ABRT|H2_SF_ES_SENT)) == (H2_SF_TUNNEL_ABRT|H2_SF_ES_SENT) &&
@@ -3095,7 +3155,7 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s)
*/
TRACE_ERROR("Request DATA frame for aborted tunnel", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s);
error = H2_ERR_CANCEL;
- goto strm_err;
+ goto strm_err_wu;
}
if (!h2_frt_transfer_data(h2s))
@@ -3137,6 +3197,7 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s)
if (h2s->flags & H2_SF_DATA_CLEN && h2s->body_len) {
/* RFC7540#8.1.2 */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("ES on DATA frame before content-length", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err);
@@ -3156,6 +3217,12 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s)
TRACE_LEAVE(H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s);
return 1;
+ strm_err_wu:
+ /* stream error before the frame was taken into account, we're
+ * going to kill the stream but must still update the connection's
+ * window.
+ */
+ h2c->rcvd_c += h2c->dfl - h2c->dpl;
strm_err:
h2s_error(h2s, error);
h2c->st0 = H2_CS_FRAME_E;
@@ -3179,6 +3246,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
/* RFC7540#5.1: any frame other than HEADERS or PRIORITY in
* this state MUST be treated as a connection error
*/
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid frame type for IDLE state", H2_EV_RX_FRAME|H2_EV_RX_FHDR, h2c->conn, h2s);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
if (!h2c->nb_streams && !(h2c->flags & H2_CF_IS_BACK)) {
@@ -3192,6 +3260,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
if (h2s->st == H2_SS_IDLE && (h2c->flags & H2_CF_IS_BACK)) {
/* only PUSH_PROMISE would be permitted here */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid frame type for IDLE state (back)", H2_EV_RX_FRAME|H2_EV_RX_FHDR, h2c->conn, h2s);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -3207,11 +3276,13 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
* PUSH_PROMISE/CONTINUATION cause connection errors.
*/
if (h2_ft_bit(h2c->dft) & H2_FT_HDR_MASK) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid frame type for HREM state", H2_EV_RX_FRAME|H2_EV_RX_FHDR, h2c->conn, h2s);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
}
else {
+ h2c_report_glitch(h2c, 1);
h2s_error(h2s, H2_ERR_STREAM_CLOSED);
}
TRACE_DEVEL("leaving in error (hrem&!wu&!rst&!prio)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s);
@@ -3241,6 +3312,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
* receives an unexpected stream identifier
* MUST respond with a connection error.
*/
+ h2c_report_glitch(h2c, 1);
h2c_error(h2c, H2_ERR_STREAM_CLOSED);
TRACE_DEVEL("leaving in error (closed&hdrmask)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s);
return 0;
@@ -3267,6 +3339,14 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
* RST_RCVD bit, we don't want to accidentally catch valid
* frames for a closed stream, i.e. RST/PRIO/WU.
*/
+ if (h2c->dft == H2_FT_DATA) {
+ /* even if we reject out-of-stream DATA, it must
+ * still count against the connection's flow control.
+ */
+ h2c->rcvd_c += h2c->dfl - h2c->dpl;
+ }
+
+ h2c_report_glitch(h2c, 1);
h2s_error(h2s, H2_ERR_STREAM_CLOSED);
h2c->st0 = H2_CS_FRAME_E;
TRACE_DEVEL("leaving in error (rst_rcvd&!hdrmask)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s);
@@ -3291,6 +3371,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
if (h2c->dft != H2_FT_RST_STREAM &&
h2c->dft != H2_FT_PRIORITY &&
h2c->dft != H2_FT_WINDOW_UPDATE) {
+ h2c_report_glitch(h2c, 1);
h2c_error(h2c, H2_ERR_STREAM_CLOSED);
TRACE_DEVEL("leaving in error (rst_sent&!rst&!prio&!wu)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s);
return 0;
@@ -3426,6 +3507,10 @@ static void h2_process_demux(struct h2c *h2c)
if (unlikely(h2c_frt_recv_preface(h2c) <= 0)) {
/* RFC7540#3.5: a GOAWAY frame MAY be omitted */
if (h2c->st0 == H2_CS_ERROR) {
+ if (b_data(&h2c->dbuf) ||
+ !(((const struct session *)h2c->conn->owner)->fe->options & (PR_O_NULLNOLOG|PR_O_IGNORE_PRB)))
+ h2c_report_glitch(h2c, 1);
+
TRACE_PROTO("failed to receive preface", H2_EV_RX_PREFACE|H2_EV_PROTO_ERR, h2c->conn);
h2c->st0 = H2_CS_ERROR2;
if (b_data(&h2c->dbuf) ||
@@ -3450,6 +3535,7 @@ static void h2_process_demux(struct h2c *h2c)
/* RFC7540#3.5: a GOAWAY frame MAY be omitted */
h2c->flags |= H2_CF_DEM_SHORT_READ;
if (h2c->st0 == H2_CS_ERROR) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("failed to receive settings", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_SETTINGS|H2_EV_PROTO_ERR, h2c->conn);
h2c->st0 = H2_CS_ERROR2;
if (!(h2c->flags & H2_CF_IS_BACK))
@@ -3460,6 +3546,7 @@ static void h2_process_demux(struct h2c *h2c)
if (hdr.sid || hdr.ft != H2_FT_SETTINGS || hdr.ff & H2_F_SETTINGS_ACK) {
/* RFC7540#3.5: a GOAWAY frame MAY be omitted */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("unexpected frame type or flags", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_SETTINGS|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
h2c->st0 = H2_CS_ERROR2;
@@ -3471,6 +3558,7 @@ static void h2_process_demux(struct h2c *h2c)
if ((int)hdr.len < 0 || (int)hdr.len > global.tune.bufsize) {
/* RFC7540#3.5: a GOAWAY frame MAY be omitted */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid settings frame length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_SETTINGS|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
h2c->st0 = H2_CS_ERROR2;
@@ -3512,6 +3600,7 @@ static void h2_process_demux(struct h2c *h2c)
}
if ((int)hdr.len < 0 || (int)hdr.len > global.tune.bufsize) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid H2 frame length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
if (!h2c->nb_streams && !(h2c->flags & H2_CF_IS_BACK)) {
@@ -3542,6 +3631,7 @@ static void h2_process_demux(struct h2c *h2c)
* padlen in the flow control, so it must be adjusted.
*/
if (hdr.len < 1) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid H2 padded frame length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
if (!(h2c->flags & H2_CF_IS_BACK))
@@ -3559,6 +3649,7 @@ static void h2_process_demux(struct h2c *h2c)
padlen = *(uint8_t *)b_peek(&h2c->dbuf, 9);
if (padlen > hdr.len) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid H2 padding length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn);
/* RFC7540#6.1 : pad length = length of
* frame payload or greater => error.
@@ -3591,6 +3682,7 @@ static void h2_process_demux(struct h2c *h2c)
/* check for minimum basic frame format validity */
ret = h2_frame_check(h2c->dft, 1, h2c->dsi, h2c->dfl, global.tune.bufsize);
if (ret != H2_ERR_NO_ERROR) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("received invalid H2 frame header", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, ret);
if (!(h2c->flags & H2_CF_IS_BACK))
@@ -3679,6 +3771,7 @@ static void h2_process_demux(struct h2c *h2c)
* frames' parsers consume all following CONTINUATION
* frames so this one is out of sequence.
*/
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("received unexpected H2 CONTINUATION frame", H2_EV_RX_FRAME|H2_EV_RX_CONT|H2_EV_H2C_ERR, h2c->conn, h2s);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
if (!(h2c->flags & H2_CF_IS_BACK))
@@ -4568,6 +4661,9 @@ static int h2_ctl(struct connection *conn, enum mux_ctl_type mux_ctl, void *outp
tasklet_wakeup(h2c->wait_event.tasklet);
return 0;
+ case MUX_CTL_GET_GLITCHES:
+ return h2c->glitches;
+
default:
return -1;
}
@@ -5004,7 +5100,8 @@ static void h2_shutw(struct stconn *sc, enum co_shw_mode mode)
*
* The H2_SF_HEADERS_RCVD flag is also looked at in the <flags> field prior to
* decoding, in order to detect if we're dealing with a headers or a trailers
- * block (the trailers block appears after H2_SF_HEADERS_RCVD was seen).
+ * block (the trailers block appears after H2_SF_HEADERS_RCVD was seen). The
+ * function takes care of counting glitches.
*/
static int h2c_dec_hdrs(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags, unsigned long long *body_len, char *upgrade_protocol)
{
@@ -5014,7 +5111,8 @@ static int h2c_dec_hdrs(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags,
struct buffer *copy = NULL;
unsigned int msgf;
struct htx *htx = NULL;
- int flen; // header frame len
+ int flen = 0; // header frame len
+ int fragments = 0;
int hole = 0;
int ret = 0;
int outlen;
@@ -5048,6 +5146,7 @@ next_frame:
if (hdr.ft != H2_FT_CONTINUATION) {
/* RFC7540#6.10: frame of unexpected type */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("not continuation!", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_HDR|H2_EV_RX_CONT|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -5056,6 +5155,7 @@ next_frame:
if (hdr.sid != h2c->dsi) {
/* RFC7540#6.10: frame of different stream */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("different stream ID!", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_HDR|H2_EV_RX_CONT|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -5064,6 +5164,7 @@ next_frame:
if ((unsigned)hdr.len > (unsigned)global.tune.bufsize) {
/* RFC7540#4.2: invalid frame length */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("too large frame!", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_HDR|H2_EV_RX_CONT|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
goto fail;
@@ -5086,6 +5187,7 @@ next_frame:
hole += h2c->dpl + 9;
h2c->dpl = 0;
TRACE_STATE("waiting for next continuation frame", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_CONT|H2_EV_RX_HDR, h2c->conn);
+ fragments++;
goto next_frame;
}
@@ -5109,6 +5211,7 @@ next_frame:
if (h2c->dff & H2_F_HEADERS_PRIORITY) {
if (read_n32(hdrs) == h2c->dsi) {
/* RFC7540#5.3.1 : stream dep may not depend on itself */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("invalid stream dependency!", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -5116,6 +5219,7 @@ next_frame:
}
if (flen < 5) {
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("frame too short for priority!", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
goto fail;
@@ -5167,6 +5271,7 @@ next_frame:
}
if (outlen < 0) {
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("failed to decompress HPACK", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_COMPRESSION_ERROR);
goto fail;
@@ -5199,6 +5304,7 @@ next_frame:
if (outlen < 0 || htx_free_space(htx) < global.tune.maxrewrite) {
/* too large headers? this is a stream error only */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("message headers too large or invalid", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2S_ERR|H2_EV_PROTO_ERR, h2c->conn);
htx->flags |= HTX_FL_PARSING_ERROR;
goto fail;
@@ -5234,6 +5340,7 @@ next_frame:
if (h2c->dff & H2_F_HEADERS_END_STREAM) {
if (msgf & H2_MSGF_RSP_1XX) {
/* RFC9113#8.1 : HEADERS frame with the ES flag set that carries an informational status code is malformed */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("invalid interim response with ES flag!", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
goto fail;
}
@@ -5269,6 +5376,20 @@ next_frame:
htx_to_buf(htx, rxbuf);
free_trash_chunk(copy);
TRACE_LEAVE(H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn);
+
+ /* Check for abuse of CONTINUATION: more than 4 fragments and less than
+ * 1kB per fragment is clearly unusual and suspicious enough to count
+ * one glitch per 1kB fragment in a 16kB buffer, which means that an
+ * abuser sending 1600 1-byte frames in a 16kB buffer would increment
+ * its counter by 100.
+ */
+ if (unlikely(fragments > 4) && fragments > flen / 1024 && ret != 0) {
+ if (h2c_report_glitch(h2c, (fragments + 15) / 16)) {
+ TRACE_STATE("glitch limit reached on CONTINUATION frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
+ ret = -1;
+ }
+ }
+
return ret;
fail:
@@ -5279,6 +5400,7 @@ next_frame:
/* This is the last HEADERS frame hence a trailer */
if (!(h2c->dff & H2_F_HEADERS_END_STREAM)) {
/* It's a trailer but it's missing ES flag */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("missing EH on trailers frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -7249,9 +7371,9 @@ static int h2_dump_h2c_info(struct buffer *msg, struct h2c *h2c, const char *pfx
hmbuf = br_head(h2c->mbuf);
tmbuf = br_tail(h2c->mbuf);
chunk_appendf(msg, " h2c.st0=%s .err=%d .maxid=%d .lastid=%d .flg=0x%04x"
- " .nbst=%u .nbsc=%u",
+ " .nbst=%u .nbsc=%u, .glitches=%d",
h2c_st_to_str(h2c->st0), h2c->errcode, h2c->max_id, h2c->last_sid, h2c->flags,
- h2c->nb_streams, h2c->nb_sc);
+ h2c->nb_streams, h2c->nb_sc, h2c->glitches);
if (pfx)
chunk_appendf(msg, "\n%s", pfx);
@@ -7407,6 +7529,27 @@ static int h2_takeover(struct connection *conn, int orig_tid)
/* functions below are dedicated to the config parsers */
/*******************************************************/
+/* config parser for global "tune.h2.{fe,be}.glitches-threshold" */
+static int h2_parse_glitches_threshold(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ int *vptr;
+
+ if (too_many_args(1, args, err, NULL))
+ return -1;
+
+ /* backend/frontend */
+ vptr = (args[0][8] == 'b') ? &h2_be_glitches_threshold : &h2_fe_glitches_threshold;
+
+ *vptr = atoi(args[1]);
+ if (*vptr < 0) {
+ memprintf(err, "'%s' expects a positive numeric value.", args[0]);
+ return -1;
+ }
+ return 0;
+}
+
/* config parser for global "tune.h2.header-table-size" */
static int h2_parse_header_table_size(char **args, int section_type, struct proxy *curpx,
const struct proxy *defpx, const char *file, int line,
@@ -7565,8 +7708,10 @@ INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_h2);
/* config keyword parsers */
static struct cfg_kw_list cfg_kws = {ILH, {
+ { CFG_GLOBAL, "tune.h2.be.glitches-threshold", h2_parse_glitches_threshold },
{ CFG_GLOBAL, "tune.h2.be.initial-window-size", h2_parse_initial_window_size },
{ CFG_GLOBAL, "tune.h2.be.max-concurrent-streams", h2_parse_max_concurrent_streams },
+ { CFG_GLOBAL, "tune.h2.fe.glitches-threshold", h2_parse_glitches_threshold },
{ CFG_GLOBAL, "tune.h2.fe.initial-window-size", h2_parse_initial_window_size },
{ CFG_GLOBAL, "tune.h2.fe.max-concurrent-streams", h2_parse_max_concurrent_streams },
{ CFG_GLOBAL, "tune.h2.fe.max-total-streams", h2_parse_max_total_streams },