diff --git a/test/common/http/http2/hpack_corpus/use_after_free b/test/common/http/http2/hpack_corpus/use_after_free new file mode 100644 index 0000000000000..0d0ddd84ec635 --- /dev/null +++ b/test/common/http/http2/hpack_corpus/use_after_free @@ -0,0 +1 @@ +headers { headers { key: ":path" value: " " } headers { key: " " e: " " } } \ No newline at end of file diff --git a/test/common/http/http2/hpack_corpus/whitespace b/test/common/http/http2/hpack_corpus/whitespace new file mode 100644 index 0000000000000..64b25dbdeae30 --- /dev/null +++ b/test/common/http/http2/hpack_corpus/whitespace @@ -0,0 +1,26 @@ +headers { + headers { + key: ":path" + value: ":path" + } + headers { + key: "GET" + value: "\364\214\214\214\364\214\214\214\364\214\214\214\364\214\214\214\364\214\214\214tqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\361\214\214\214\364\214\214\214\354\214\214" + } + headers { + key: "YYYYYYYYYYYYYYY" + value: "htvp" + } + headers { + key: "x-envoy" + value: " " + } + headers { + key: ":status" + value: "a " + } + headers { + key: "header" + value: " a" + } +} \ No newline at end of file diff --git a/test/common/http/http2/hpack_fuzz_test.cc b/test/common/http/http2/hpack_fuzz_test.cc index 6cab23df21f47..49fa83db3dbc8 100644 --- a/test/common/http/http2/hpack_fuzz_test.cc +++ b/test/common/http/http2/hpack_fuzz_test.cc @@ -8,6 +8,7 @@ #include "test/test_common/utility.h" #include "absl/container/fixed_array.h" +#include "absl/strings/escaping.h" #include "nghttp2/nghttp2.h" namespace Envoy { @@ -25,6 +26,8 @@ std::vector createNameValueArray(const test::fuzz::Headers& input) { for (const auto& header : input.headers()) { // TODO(asraa): Consider adding flags in fuzzed input. const uint8_t flags = 0; + ENVOY_LOG_MISC(trace, "encoding: {} {}", absl::CEscape(header.key()), + absl::CEscape(header.value())); nva[i++] = {const_cast(reinterpret_cast(header.key().data())), const_cast(reinterpret_cast(header.value().data())), header.key().size(), header.value().size(), flags}; @@ -56,18 +59,18 @@ Buffer::OwnedImpl encodeHeaders(nghttp2_hd_deflater* deflater, return payload; } -std::vector decodeHeaders(nghttp2_hd_inflater* inflater, - const Buffer::OwnedImpl& payload, bool end_headers) { +std::vector> +decodeHeaders(nghttp2_hd_inflater* inflater, const Buffer::OwnedImpl& payload, bool end_headers) { // Decode using nghttp2 Buffer::RawSliceVector slices = payload.getRawSlices(); const int num_slices = slices.size(); ASSERT(num_slices == 1, absl::StrCat("number of slices ", num_slices)); - std::vector decoded_headers; + std::vector> decoded_headers; int inflate_flags = 0; - nghttp2_nv decoded_nv; + nghttp2_nv nv; while (slices[0].len_ > 0) { - ssize_t result = nghttp2_hd_inflate_hd2(inflater, &decoded_nv, &inflate_flags, + ssize_t result = nghttp2_hd_inflate_hd2(inflater, &nv, &inflate_flags, reinterpret_cast(slices[0].mem_), slices[0].len_, end_headers); // Decoding should not fail and data should not be left in slice. @@ -78,7 +81,10 @@ std::vector decodeHeaders(nghttp2_hd_inflater* inflater, if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { // One header key value pair has been successfully decoded. - decoded_headers.push_back(decoded_nv); + decoded_headers.push_back({std::string(reinterpret_cast(nv.name), nv.namelen), + std::string(reinterpret_cast(nv.value), nv.valuelen)}); + ENVOY_LOG_MISC(trace, "decoded: {}, {}", absl::CEscape(decoded_headers.back().first), + absl::CEscape(decoded_headers.back().second)); } } @@ -90,10 +96,15 @@ std::vector decodeHeaders(nghttp2_hd_inflater* inflater, } struct NvComparator { - inline bool operator()(const nghttp2_nv& a, const nghttp2_nv& b) { - absl::string_view a_str(reinterpret_cast(a.name), a.namelen); - absl::string_view b_str(reinterpret_cast(b.name), b.namelen); - return a_str.compare(b_str); + inline bool operator()(const nghttp2_nv& a, const nghttp2_nv& b) const { + absl::string_view a_name(reinterpret_cast(a.name), a.namelen); + absl::string_view b_name(reinterpret_cast(b.name), b.namelen); + if (a_name != b_name) { + return a_name < b_name; + } + absl::string_view a_val(reinterpret_cast(a.value), a.valuelen); + absl::string_view b_val(reinterpret_cast(b.value), b.valuelen); + return a_val < b_val; } }; @@ -108,8 +119,8 @@ DEFINE_PROTO_FUZZER(const test::common::http::http2::HpackTestCase& input) { // Create name value pairs from headers. std::vector input_nv = createNameValueArray(input.headers()); - // Skip encoding empty headers. nghttp2 will throw a nullptr error on runtime if it receives a - // nullptr input. + // Skip encoding an empty header map. nghttp2 will throw a nullptr error on runtime if it receives + // a nullptr input. if (!input_nv.data()) { return; } @@ -127,18 +138,19 @@ DEFINE_PROTO_FUZZER(const test::common::http::http2::HpackTestCase& input) { ASSERT(!payload.getRawSlices().empty()); // Decode headers with nghttp2 - std::vector output_nv = decodeHeaders(inflater, payload, input.end_headers()); + std::vector> output_nv = + decodeHeaders(inflater, payload, input.end_headers()); // Verify that decoded == encoded. ASSERT(input_nv.size() == output_nv.size()); std::sort(input_nv.begin(), input_nv.end(), NvComparator()); - std::sort(output_nv.begin(), output_nv.end(), NvComparator()); + std::sort(output_nv.begin(), output_nv.end()); + for (size_t i = 0; i < input_nv.size(); i++) { - absl::string_view in_name = {reinterpret_cast(input_nv[i].name), input_nv[i].namelen}; - absl::string_view out_name = {reinterpret_cast(output_nv[i].name), output_nv[i].namelen}; - absl::string_view in_val = {reinterpret_cast(input_nv[i].value), input_nv[i].valuelen}; - absl::string_view out_val = {reinterpret_cast(output_nv[i].value), - output_nv[i].valuelen}; + std::string in_name = {reinterpret_cast(input_nv[i].name), input_nv[i].namelen}; + std::string out_name = output_nv[i].first; + std::string in_val = {reinterpret_cast(input_nv[i].value), input_nv[i].valuelen}; + std::string out_val = output_nv[i].second; ASSERT(in_name == out_name); ASSERT(in_val == out_val); }