]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: h2: add a generic frame checker
authorWilly Tarreau <w@1wt.eu>
Wed, 30 Jan 2019 14:09:21 +0000 (15:09 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 30 Jan 2019 18:37:20 +0000 (19:37 +0100)
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.

include/common/h2.h
src/h2.c

index 71be8ce78568b09f32bbdf3f3c2dcd0be1d81a50..c434ed9fb02a6dc6afc703d5c6d3d8fa8aebd447 100644 (file)
@@ -178,6 +178,25 @@ enum h2_err {
 #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 @@ static inline const char *h2_ft_str(int type)
        }
 }
 
+/* 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.
  */
index 1a103e49e52b864d00590705da0f051725eba844..6a1debf957ea1349c196a44cf4414d3988a6c2ca 100644 (file)
--- a/src/h2.c
+++ b/src/h2.c
 #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