aboutsummaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-http2.c
diff options
context:
space:
mode:
Diffstat (limited to 'epan/dissectors/packet-http2.c')
-rw-r--r--epan/dissectors/packet-http2.c159
1 files changed, 141 insertions, 18 deletions
diff --git a/epan/dissectors/packet-http2.c b/epan/dissectors/packet-http2.c
index 88b75e286f..747e70356c 100644
--- a/epan/dissectors/packet-http2.c
+++ b/epan/dissectors/packet-http2.c
@@ -39,6 +39,7 @@
#include "config.h"
#include <epan/packet.h>
+#include <epan/expert.h>
#include <epan/prefs.h>
#include <epan/proto_data.h>
@@ -103,8 +104,14 @@ typedef struct {
http2_header_t */
wmem_list_t *header_list;
/* This points to the list frame containing current decompressed
- header for dessecting later. */
+ header for dissecting later. */
wmem_list_frame_t *current;
+ /* Bytes decompressed if we exceeded MAX_HTTP2_HEADER_SIZE */
+ guint header_size_reached;
+ /* Bytes decompressed if we had not exceeded MAX_HTTP2_HEADER_SIZE */
+ guint header_size_attempted;
+ /* TRUE if we found >= MAX_HTTP2_HEADER_LINES */
+ gboolean header_lines_exceeded;
} http2_header_data_t;
/* In-flight SETTINGS data. */
@@ -191,6 +198,7 @@ static int hf_http2_headers = -1;
static int hf_http2_headers_padding = -1;
static int hf_http2_header = -1;
static int hf_http2_header_length = -1;
+static int hf_http2_header_count = -1;
static int hf_http2_header_name_length = -1;
static int hf_http2_header_name = -1;
static int hf_http2_header_value_length = -1;
@@ -240,6 +248,24 @@ static int hf_http2_altsvc_host = -1;
static int hf_http2_altsvc_origin = -1;
/* Blocked */
+/*
+ * These values *should* be large enough to handle most use cases while
+ * keeping hostile traffic from consuming too many resources. If that's
+ * not the case we can convert them to preferences. Current (Feb 2016)
+ * client and server limits:
+ *
+ * Apache: 8K (LimitRequestFieldSize), 100 lines (LimitRequestFields)
+ * Chrome: 256K?
+ * Firefox: Unknown
+ * IIS: 16K (MaxRequestBytes)
+ * Nginx: 8K (large_client_header_buffers)
+ * Safari: Unknown
+ * Tomcat: 8K (maxHttpHeaderSize)
+ */
+#define MAX_HTTP2_HEADER_SIZE (256 * 1024)
+#define MAX_HTTP2_HEADER_LINES 200
+static expert_field ei_http2_header_size = EI_INIT;
+static expert_field ei_http2_header_lines = EI_INIT;
static gint ett_http2 = -1;
static gint ett_http2_header = -1;
@@ -247,6 +273,17 @@ static gint ett_http2_headers = -1;
static gint ett_http2_flags = -1;
static gint ett_http2_settings = -1;
+/* Due to HPACK compression, we may get lots of relatively large
+ header fields (e.g., 4KiB). Allocating each of them requires lots
+ of memory. The maximum compression is achieved in HPACK by
+ referencing header field stored in dynamic table by one or two
+ bytes. We reduce memory usage by caching header field in this
+ wmem_map_t to reuse its memory region when we see the same header
+ field next time. */
+static wmem_map_t *http2_hdrcache_map = NULL;
+/* Header name_length + name + value_length + value */
+static char *http2_header_pstr = NULL;
+
static dissector_handle_t data_handle;
static dissector_handle_t http2_handle;
@@ -375,6 +412,8 @@ static gboolean
hd_inflate_del_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_, void *user_data)
{
nghttp2_hd_inflate_del((nghttp2_hd_inflater*)user_data);
+ http2_hdrcache_map = NULL;
+ http2_header_pstr = NULL;
return FALSE;
}
@@ -612,6 +651,32 @@ process_http2_header_repr_info(wmem_array_t *headers,
return start;
}
+static size_t http2_hdrcache_length(gconstpointer vv)
+{
+ const guint8 *v = (const guint8 *)vv;
+ guint32 namelen, valuelen;
+
+ namelen = pntoh32(v);
+ valuelen = pntoh32(v + sizeof(namelen) + namelen);
+
+ return namelen + valuelen + sizeof(namelen) + sizeof(valuelen);
+}
+
+static guint http2_hdrcache_hash(gconstpointer key)
+{
+ return wmem_strong_hash((const guint8 *)key, http2_hdrcache_length(key));
+}
+
+static gboolean http2_hdrcache_equal(gconstpointer lhs, gconstpointer rhs)
+{
+ const guint8 *a = (const guint8 *)lhs;
+ const guint8 *b = (const guint8 *)rhs;
+ size_t alen = http2_hdrcache_length(a);
+ size_t blen = http2_hdrcache_length(b);
+
+ return alen == blen && memcmp(a, b, alen) == 0;
+}
+
static void
inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
proto_tree *tree, size_t headlen,
@@ -637,6 +702,10 @@ inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
wmem_array_t *headers;
guint i;
+ if (!http2_hdrcache_map) {
+ http2_hdrcache_map = wmem_map_new(wmem_file_scope(), http2_hdrcache_hash, http2_hdrcache_equal);
+ }
+
header_data = (http2_header_data_t*)p_get_proto_data(wmem_file_scope(), pinfo, proto_http2, 0);
header_list = header_data->header_list;
@@ -647,6 +716,7 @@ inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
cache, already processed data will be fed into decompressor
again and again since dissector will be called randomly.
This makes context out-of-sync. */
+ int decompressed_bytes = 0;
headbuf = (guint8*)wmem_alloc(wmem_packet_scope(), headlen);
tvb_memcpy(tvb, headbuf, offset, headlen);
@@ -663,6 +733,11 @@ inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
nghttp2_nv nv;
int inflate_flags = 0;
+ if (wmem_array_get_count(headers) >= MAX_HTTP2_HEADER_LINES) {
+ header_data->header_lines_exceeded = TRUE;
+ break;
+ }
+
rv = (int)nghttp2_hd_inflate_hd(hd_inflater, &nv,
&inflate_flags, headbuf, headlen, final);
@@ -676,17 +751,25 @@ inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
rv -= process_http2_header_repr_info(headers, header_repr_info, headbuf - rv, rv);
if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
- char *str;
+ char *cached_pstr;
guint32 len;
+ guint datalen = (guint)(4 + nv.namelen + 4 + nv.valuelen);
http2_header_t *out;
+ if (decompressed_bytes + datalen >= MAX_HTTP2_HEADER_SIZE) {
+ header_data->header_size_reached = decompressed_bytes;
+ header_data->header_size_attempted = decompressed_bytes + datalen;
+ break;
+ }
+
out = wmem_new(wmem_file_scope(), http2_header_t);
out->type = header_repr_info->type;
out->length = rv;
out->table.data.idx = header_repr_info->integer;
- out->table.data.datalen = (guint)(4 + nv.namelen + 4 + nv.valuelen);
+ out->table.data.datalen = datalen;
+ decompressed_bytes += datalen;
/* Prepare buffer... with the following format
name length (uint32)
@@ -694,20 +777,27 @@ inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
value length (uint32)
value (string)
*/
- str = wmem_alloc_array(wmem_file_scope(), char, out->table.data.datalen);
+ http2_header_pstr = (char *)wmem_realloc(wmem_file_scope(), http2_header_pstr, out->table.data.datalen);
/* nv.namelen and nv.valuelen are of size_t. In order
to get length in 4 bytes, we have to copy it to
guint32. */
len = (guint32)nv.namelen;
- phton32(&str[0], len);
- memcpy(&str[4], nv.name, nv.namelen);
+ phton32(&http2_header_pstr[0], len);
+ memcpy(&http2_header_pstr[4], nv.name, nv.namelen);
len = (guint32)nv.valuelen;
- phton32(&str[4 + nv.namelen], len);
- memcpy(&str[4 + nv.namelen + 4], nv.value, nv.valuelen);
+ phton32(&http2_header_pstr[4 + nv.namelen], len);
+ memcpy(&http2_header_pstr[4 + nv.namelen + 4], nv.value, nv.valuelen);
- out->table.data.data = str;
+ cached_pstr = (char *)wmem_map_lookup(http2_hdrcache_map, http2_header_pstr);
+ if (cached_pstr) {
+ out->table.data.data = cached_pstr;
+ } else {
+ wmem_map_insert(http2_hdrcache_map, http2_header_pstr, http2_header_pstr);
+ out->table.data.data = http2_header_pstr;
+ http2_header_pstr = NULL;
+ }
wmem_array_append(headers, out, 1);
@@ -746,7 +836,6 @@ inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
for(i = 0; i < wmem_array_get_count(headers); ++i) {
http2_header_t *in;
tvbuff_t *next_tvb;
- char *str;
in = (http2_header_t*)wmem_array_index(headers, i);
@@ -754,14 +843,10 @@ inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
continue;
}
- str = (char *)g_malloc(in->table.data.datalen);
- memcpy(str, in->table.data.data, in->table.data.datalen);
-
header_len += in->table.data.datalen;
/* Now setup the tvb buffer to have the new data */
- next_tvb = tvb_new_child_real_data(tvb, str, in->table.data.datalen, in->table.data.datalen);
- tvb_set_free_cb(next_tvb, g_free);
+ next_tvb = tvb_new_child_real_data(tvb, in->table.data.data, in->table.data.datalen, in->table.data.datalen);
tvb_composite_append(header_tvb, next_tvb);
}
@@ -771,6 +856,20 @@ inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
ti = proto_tree_add_uint(tree, hf_http2_header_length, header_tvb, hoffset, 1, header_len);
PROTO_ITEM_SET_GENERATED(ti);
+ if (header_data->header_size_attempted > 0) {
+ expert_add_info_format(pinfo, ti, &ei_http2_header_size,
+ "Decompression stopped after %u bytes (%u attempted).",
+ header_data->header_size_reached,
+ header_data->header_size_attempted);
+ }
+
+ ti = proto_tree_add_uint(tree, hf_http2_header_count, header_tvb, hoffset, 1, wmem_array_get_count(headers));
+ PROTO_ITEM_SET_GENERATED(ti);
+
+ if (header_data->header_lines_exceeded) {
+ expert_add_info(pinfo, ti, &ei_http2_header_lines);
+ }
+
for(i = 0; i < wmem_array_get_count(headers); ++i) {
http2_header_t *in = (http2_header_t*)wmem_array_index(headers, i);
@@ -954,7 +1053,7 @@ dissect_http2_data(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree
/* Headers */
static int
-dissect_http2_headers(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree,
+dissect_http2_headers(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http2_tree,
guint offset, guint8 flags)
{
guint16 padding;
@@ -1248,9 +1347,8 @@ dissect_http2_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* dat
if(!p_get_proto_data(wmem_file_scope(), pinfo, proto_http2, 0)) {
http2_header_data_t *header_data;
- header_data = wmem_new(wmem_file_scope(), http2_header_data_t);
+ header_data = wmem_new0(wmem_file_scope(), http2_header_data_t);
header_data->header_list = wmem_list_new(wmem_file_scope());
- header_data->current = NULL;
p_add_proto_data(wmem_file_scope(), pinfo, proto_http2, 0, header_data);
}
@@ -1629,6 +1727,11 @@ proto_register_http2(void)
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
+ { &hf_http2_header_count,
+ { "Header Count", "http2.header.count",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ NULL, HFILL }
+ },
{ &hf_http2_header_name_length,
{ "Name Length", "http2.header.name.length",
FT_UINT32, BASE_DEC, NULL, 0x0,
@@ -1853,7 +1956,24 @@ proto_register_http2(void)
&ett_http2_settings
};
+ /* Setup protocol expert items */
+ /*
+ * Excessive header size or lines could mean a decompression bomb. Should
+ * these be PI_SECURITY instead?
+ */
+ static ei_register_info ei[] = {
+ { &ei_http2_header_size,
+ { "http2.header_size_exceeded", PI_UNDECODED, PI_ERROR,
+ "Decompression stopped.", EXPFILL }
+ },
+ { &ei_http2_header_lines,
+ { "http2.header_lines_exceeded", PI_UNDECODED, PI_ERROR,
+ "Decompression stopped after " G_STRINGIFY(MAX_HTTP2_HEADER_LINES) " header lines.", EXPFILL }
+ }
+ };
+
module_t *http2_module;
+ expert_module_t *expert_http2;
proto_http2 = proto_register_protocol("HyperText Transfer Protocol 2", "HTTP2", "http2");
@@ -1862,6 +1982,9 @@ proto_register_http2(void)
http2_module = prefs_register_protocol(proto_http2, NULL);
+ expert_http2 = expert_register_protocol(proto_http2);
+ expert_register_field_array(expert_http2, ei, array_length(ei));
+
prefs_register_obsolete_preference(http2_module, "heuristic_http2");
http2_handle = register_dissector("http2", dissect_http2, proto_http2);