MINOR: h2: add a generic frame checker
The new function h2_frame_check() checks the protocol limits for the
received frame (length, ID, direction) and returns a verdict made of
a connection error code. The purpose is to be able to validate any
frame regardless of the state and the ability to call the frame handler,
and to emit a GOAWAY early in this case.
diff --git a/include/common/h2.h b/include/common/h2.h
index 71be8ce..c434ed9 100644
--- a/include/common/h2.h
+++ b/include/common/h2.h
@@ -178,6 +178,25 @@
#define H2_MSGF_BODY_CL 0x0002 // content-length is present
#define H2_MSGF_BODY_TUNNEL 0x0004 // a tunnel is in use (CONNECT)
+#define H2_MAX_STREAM_ID ((1U << 31) - 1)
+#define H2_MAX_FRAME_LEN ((1U << 24) - 1)
+#define H2_DIR_REQ 1
+#define H2_DIR_RES 2
+#define H2_DIR_BOTH 3
+
+/* constraints imposed by the protocol on each frame type, in terms of stream
+ * ID values, frame sizes, and direction so that most connection-level checks
+ * can be centralized regardless of the frame's acceptance.
+ */
+struct h2_frame_definition {
+ int32_t dir; /* 0=none, 1=request, 2=response, 3=both */
+ int32_t min_id; /* minimum allowed stream ID */
+ int32_t max_id; /* maximum allowed stream ID */
+ int32_t min_len; /* minimum frame length */
+ int32_t max_len; /* maximum frame length */
+};
+
+extern struct h2_frame_definition h2_frame_definition[H2_FT_ENTRIES];
/* various protocol processing functions */
@@ -215,6 +234,41 @@
}
}
+/* Returns an error code if the frame is valid protocol-wise, otherwise 0. <ft>
+ * is the frame type (H2_FT_*), <dir> is the direction (1=req, 2=res), <id> is
+ * the stream ID from the frame header, <len> is the frame length from the
+ * header. The purpose is to be able to quickly return a PROTOCOL_ERROR or
+ * FRAME_SIZE_ERROR connection error even for situations where the frame will
+ * be ignored. <mfs> must be the max frame size currently in place for the
+ * protocol.
+ */
+static inline int h2_frame_check(enum h2_ft ft, int dir, int32_t id, int32_t len, int32_t mfs)
+{
+ struct h2_frame_definition *fd;
+
+ if (ft >= H2_FT_ENTRIES)
+ return H2_ERR_NO_ERROR; // ignore unhandled frame types
+
+ fd = &h2_frame_definition[ft];
+
+ if (!(dir & fd->dir))
+ return H2_ERR_PROTOCOL_ERROR;
+
+ if (id < fd->min_id || id > fd->max_id)
+ return H2_ERR_PROTOCOL_ERROR;
+
+ if (len < fd->min_len || len > fd->max_len)
+ return H2_ERR_FRAME_SIZE_ERROR;
+
+ if (len > mfs)
+ return H2_ERR_FRAME_SIZE_ERROR;
+
+ if (ft == H2_FT_SETTINGS && (len % 6) != 0)
+ return H2_ERR_FRAME_SIZE_ERROR; // RFC7540#6.5
+
+ return H2_ERR_NO_ERROR;
+}
+
/* returns the pseudo-header <str> corresponds to among H2_PHDR_IDX_*, 0 if not a
* pseudo-header, or -1 if not a valid pseudo-header.
*/
diff --git a/src/h2.c b/src/h2.c
index 1a103e4..6a1debf 100644
--- a/src/h2.c
+++ b/src/h2.c
@@ -31,6 +31,18 @@
#include <common/http-hdr.h>
#include <common/ist.h>
+struct h2_frame_definition h2_frame_definition[H2_FT_ENTRIES] = {
+ [H2_FT_DATA ] = { .dir = 3, .min_id = 1, .max_id = H2_MAX_STREAM_ID, .min_len = 0, .max_len = H2_MAX_FRAME_LEN, },
+ [H2_FT_HEADERS ] = { .dir = 3, .min_id = 1, .max_id = H2_MAX_STREAM_ID, .min_len = 1, .max_len = H2_MAX_FRAME_LEN, },
+ [H2_FT_PRIORITY ] = { .dir = 3, .min_id = 1, .max_id = H2_MAX_STREAM_ID, .min_len = 5, .max_len = 5, },
+ [H2_FT_RST_STREAM ] = { .dir = 3, .min_id = 1, .max_id = H2_MAX_STREAM_ID, .min_len = 4, .max_len = 4, },
+ [H2_FT_SETTINGS ] = { .dir = 3, .min_id = 0, .max_id = 0, .min_len = 0, .max_len = H2_MAX_FRAME_LEN, },
+ [H2_FT_PUSH_PROMISE ] = { .dir = 0, .min_id = 1, .max_id = H2_MAX_STREAM_ID, .min_len = 4, .max_len = H2_MAX_FRAME_LEN, },
+ [H2_FT_PING ] = { .dir = 3, .min_id = 0, .max_id = 0, .min_len = 8, .max_len = 8, },
+ [H2_FT_GOAWAY ] = { .dir = 3, .min_id = 0, .max_id = 0, .min_len = 8, .max_len = H2_MAX_FRAME_LEN, },
+ [H2_FT_WINDOW_UPDATE] = { .dir = 3, .min_id = 0, .max_id = H2_MAX_STREAM_ID, .min_len = 4, .max_len = 4, },
+ [H2_FT_CONTINUATION ] = { .dir = 3, .min_id = 1, .max_id = H2_MAX_STREAM_ID, .min_len = 0, .max_len = H2_MAX_FRAME_LEN, },
+};
/* Prepare the request line into <*ptr> (stopping at <end>) from pseudo headers
* stored in <phdr[]>. <fields> indicates what was found so far. This should be