aboutsummaryrefslogtreecommitdiffstats
path: root/epan
diff options
context:
space:
mode:
authorTatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>2016-02-05 23:41:57 +0900
committerGerald Combs <gerald@wireshark.org>2016-02-22 18:30:39 +0000
commit3fe16e13b5c6c198a3d7675a3adeb707b61fda9e (patch)
treed381dc005e1f2201f61d0b7c5e0102761f12f876 /epan
parent614c7fb9f4f42d3faefb76a3a6a5fc47c8136b17 (diff)
HTTP2: Reduce resource usage.
Cache decoded header fields in order to conserve memory. If we try to decompress more than 256 KB or find 200 or more headers stop decompressing and add an expert item. Note that we might want to make the maximum values configurable via preferences. Bug:12077 Change-Id: Idf7cb1046c96cf87e1b53af6c56e19b4abad1dfb Reviewed-on: https://code.wireshark.org/review/13746 Reviewed-by: Gerald Combs <gerald@wireshark.org>
Diffstat (limited to 'epan')
-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);