diff options
Diffstat (limited to 'epan/dissectors/packet-http2.c')
-rw-r--r-- | epan/dissectors/packet-http2.c | 159 |
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); |