Skip to content

Commit

Permalink
Adding an HTTP/2 integration fuzzer (envoyproxy#10321)
Browse files Browse the repository at this point in the history
Adding an HTTP/2 integration fuzzer that checks different kind of frames handling in both Downstream and Upstream

Risk Level: Low - a new test
Testing: It is a new fuzz test

Signed-off-by: Adi Suissa-Peleg <[email protected]>
Signed-off-by: yashwant121 <[email protected]>
  • Loading branch information
adisuissa authored and yashwant121 committed Jul 24, 2020
1 parent a9628f3 commit e65bcd5
Show file tree
Hide file tree
Showing 9 changed files with 707 additions and 4 deletions.
57 changes: 53 additions & 4 deletions test/common/http/http2/http2_frame.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@ const char Http2Frame::Preamble[25] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
void Http2Frame::setHeader(absl::string_view header) {
ASSERT(header.size() >= HeaderSize);
data_.assign(HeaderSize, 0);
// TODO(adisuissa): memcpy is discouraged as it may be unsafe. This should be
// use a safer memcpy alternative (example: https://abseil.io/tips/93)
memcpy(data_.data(), header.data(), HeaderSize);
data_.resize(HeaderSize + payloadSize());
}

void Http2Frame::setPayload(absl::string_view payload) {
ASSERT(payload.size() >= payloadSize());
ASSERT(data_.capacity() >= HeaderSize + payloadSize());
memcpy(&data_[HeaderSize], payload.data(), payloadSize());
}

Expand Down Expand Up @@ -116,6 +119,7 @@ Http2Frame Http2Frame::makePingFrame(absl::string_view data) {
static constexpr size_t kPingPayloadSize = 8;
Http2Frame frame;
frame.buildHeader(Type::Ping, kPingPayloadSize);
ASSERT(frame.data_.capacity() >= HeaderSize + std::min(kPingPayloadSize, data.size()));
if (!data.empty()) {
memcpy(&frame.data_[HeaderSize], data.data(), std::min(kPingPayloadSize, data.size()));
}
Expand Down Expand Up @@ -152,8 +156,46 @@ Http2Frame Http2Frame::makePriorityFrame(uint32_t stream_index, uint32_t depende
static constexpr size_t kPriorityPayloadSize = 5;
Http2Frame frame;
frame.buildHeader(Type::Priority, kPriorityPayloadSize, 0, makeRequestStreamId(stream_index));
uint32_t dependent_net = makeRequestStreamId(dependent_index);
memcpy(&frame.data_[HeaderSize], reinterpret_cast<void*>(&dependent_net), sizeof(uint32_t));
const uint32_t dependent_net = makeRequestStreamId(dependent_index);
ASSERT(frame.data_.capacity() >= HeaderSize + sizeof(uint32_t));
memcpy(&frame.data_[HeaderSize], reinterpret_cast<const void*>(&dependent_net), sizeof(uint32_t));
return frame;
}

Http2Frame Http2Frame::makeEmptyPushPromiseFrame(uint32_t stream_index,
uint32_t promised_stream_index,
HeadersFlags flags) {
static constexpr size_t kEmptyPushPromisePayloadSize = 4;
Http2Frame frame;
frame.buildHeader(Type::PushPromise, kEmptyPushPromisePayloadSize, static_cast<uint8_t>(flags),
makeRequestStreamId(stream_index));
const uint32_t promised_stream_id = makeRequestStreamId(promised_stream_index);
ASSERT(frame.data_.capacity() >= HeaderSize + sizeof(uint32_t));
memcpy(&frame.data_[HeaderSize], reinterpret_cast<const void*>(&promised_stream_id),
sizeof(uint32_t));
return frame;
}

Http2Frame Http2Frame::makeResetStreamFrame(uint32_t stream_index, ErrorCode error_code) {
static constexpr size_t kResetStreamPayloadSize = 4;
Http2Frame frame;
frame.buildHeader(Type::RstStream, kResetStreamPayloadSize, 0, makeRequestStreamId(stream_index));
const uint32_t error = static_cast<uint32_t>(error_code);
ASSERT(frame.data_.capacity() >= HeaderSize + sizeof(uint32_t));
memcpy(&frame.data_[HeaderSize], reinterpret_cast<const void*>(&error), sizeof(uint32_t));
return frame;
}

Http2Frame Http2Frame::makeEmptyGoAwayFrame(uint32_t last_stream_index, ErrorCode error_code) {
static constexpr size_t kEmptyGoAwayPayloadSize = 8;
Http2Frame frame;
frame.buildHeader(Type::GoAway, kEmptyGoAwayPayloadSize, 0, makeRequestStreamId(0));
const uint32_t last_stream_id = makeRequestStreamId(last_stream_index);
ASSERT(frame.data_.capacity() >= HeaderSize + 4 + sizeof(uint32_t));
memcpy(&frame.data_[HeaderSize], reinterpret_cast<const void*>(&last_stream_id),
sizeof(uint32_t));
const uint32_t error = static_cast<uint32_t>(error_code);
memcpy(&frame.data_[HeaderSize + 4], reinterpret_cast<const void*>(&error), sizeof(uint32_t));
return frame;
}

Expand All @@ -162,8 +204,9 @@ Http2Frame Http2Frame::makeWindowUpdateFrame(uint32_t stream_index, uint32_t inc
Http2Frame frame;
frame.buildHeader(Type::WindowUpdate, kWindowUpdatePayloadSize, 0,
makeRequestStreamId(stream_index));
uint32_t increment_net = htonl(increment);
memcpy(&frame.data_[HeaderSize], reinterpret_cast<void*>(&increment_net), sizeof(uint32_t));
const uint32_t increment_net = htonl(increment);
ASSERT(frame.data_.capacity() >= HeaderSize + sizeof(uint32_t));
memcpy(&frame.data_[HeaderSize], reinterpret_cast<const void*>(&increment_net), sizeof(uint32_t));
return frame;
}

Expand Down Expand Up @@ -218,6 +261,12 @@ Http2Frame Http2Frame::makePostRequest(uint32_t stream_index, absl::string_view
return frame;
}

Http2Frame Http2Frame::makeGenericFrame(absl::string_view contents) {
Http2Frame frame;
frame.appendData(contents);
return frame;
}

} // namespace Http2
} // namespace Http
} // namespace Envoy
30 changes: 30 additions & 0 deletions test/common/http/http2/http2_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,23 @@ class Http2Frame {
Host = 38,
};

enum class ErrorCode : uint8_t {
NoError = 0,
ProtocolError,
InternalError,
FlowControlError,
SettingsTimeout,
StreamClosed,
FrameSizeError,
RefusedStream,
Cancel,
CompressionError,
ConnectError,
EnhanceYourCalm,
InadequateSecurity,
Http11Required
};

enum class ResponseStatus { Unknown, Ok, NotFound };

// Methods for creating HTTP2 frames
Expand All @@ -77,6 +94,12 @@ class Http2Frame {
HeadersFlags flags = HeadersFlags::None);
static Http2Frame makeEmptyDataFrame(uint32_t stream_index, DataFlags flags = DataFlags::None);
static Http2Frame makePriorityFrame(uint32_t stream_index, uint32_t dependent_index);

static Http2Frame makeEmptyPushPromiseFrame(uint32_t stream_index, uint32_t promised_stream_index,
HeadersFlags flags = HeadersFlags::None);
static Http2Frame makeResetStreamFrame(uint32_t stream_index, ErrorCode error_code);
static Http2Frame makeEmptyGoAwayFrame(uint32_t last_stream_index, ErrorCode error_code);

static Http2Frame makeWindowUpdateFrame(uint32_t stream_index, uint32_t increment);
static Http2Frame makeMalformedRequest(uint32_t stream_index);
static Http2Frame makeMalformedRequestWithZerolenHeader(uint32_t stream_index,
Expand All @@ -86,6 +109,13 @@ class Http2Frame {
absl::string_view path);
static Http2Frame makePostRequest(uint32_t stream_index, absl::string_view host,
absl::string_view path);
/**
* Creates a frame with the given contents. This frame can be
* malformed/invalid depending on the given contents.
* @param contents the contents of the newly created frame.
* @return an Http2Frame that is comprised of the given contents.
*/
static Http2Frame makeGenericFrame(absl::string_view contents);

Type type() const { return static_cast<Type>(data_[3]); }
ResponseStatus responseStatus() const;
Expand Down
68 changes: 68 additions & 0 deletions test/integration/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ envoy_proto_library(
srcs = [":capture_fuzz.proto"],
)

envoy_proto_library(
name = "h2_capture_fuzz_proto",
srcs = [":h2_capture_fuzz.proto"],
)

envoy_cc_test(
name = "cds_integration_test",
srcs = ["cds_integration_test.cc"],
Expand Down Expand Up @@ -1110,6 +1115,69 @@ envoy_cc_fuzz_test(
],
)

H2_FUZZ_LIB_DEPS = [
":h2_capture_fuzz_proto_cc_proto",
":http_integration_lib",
"//source/common/common:assert_lib",
"//source/common/common:logger_lib",
"//test/common/http/http2:http2_frame",
"//test/fuzz:fuzz_runner_lib",
"//test/fuzz:utility_lib",
"//test/integration:integration_lib",
"//test/test_common:environment_lib",
]

envoy_cc_test_library(
name = "h2_fuzz_lib",
srcs = ["h2_fuzz.cc"],
hdrs = ["h2_fuzz.h"],
deps = H2_FUZZ_LIB_DEPS,
)

envoy_cc_test_library(
name = "h2_fuzz_persistent_lib",
srcs = ["h2_fuzz.cc"],
hdrs = ["h2_fuzz.h"],
copts = ["-DPERSISTENT_FUZZER"],
deps = H2_FUZZ_LIB_DEPS,
)

envoy_cc_fuzz_test(
name = "h2_capture_fuzz_test",
srcs = ["h2_capture_fuzz_test.cc"],
corpus = "h2_corpus",
deps = [":h2_fuzz_lib"],
)

envoy_cc_fuzz_test(
name = "h2_capture_persistent_fuzz_test",
srcs = ["h2_capture_fuzz_test.cc"],
copts = ["-DPERSISTENT_FUZZER"],
corpus = "h2_corpus",
deps = [":h2_fuzz_persistent_lib"],
)

envoy_cc_fuzz_test(
name = "h2_capture_direct_response_fuzz_test",
srcs = ["h2_capture_direct_response_fuzz_test.cc"],
corpus = "h2_corpus",
deps = [
":h2_fuzz_lib",
"@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto",
],
)

envoy_cc_fuzz_test(
name = "h2_capture_direct_response_persistent_fuzz_test",
srcs = ["h2_capture_direct_response_fuzz_test.cc"],
copts = ["-DPERSISTENT_FUZZER"],
corpus = "h2_corpus",
deps = [
":h2_fuzz_persistent_lib",
"@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto",
],
)

envoy_cc_test(
name = "scoped_rds_integration_test",
srcs = [
Expand Down
42 changes: 42 additions & 0 deletions test/integration/h2_capture_direct_response_fuzz_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h"

#include "test/integration/h2_fuzz.h"

namespace Envoy {

void H2FuzzIntegrationTest::initialize() {
const std::string body = "Response body";
const std::string file_path = TestEnvironment::writeStringToFileForTest("test_envoy", body);
const std::string prefix("/");
const Http::Code status(Http::Code::OK);

setDownstreamProtocol(Http::CodecClient::Type::HTTP2);
setUpstreamProtocol(FakeHttpConnection::Type::HTTP2);

config_helper_.addConfigModifier(
[&file_path, &prefix](
envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
hcm) -> void {
auto* route_config = hcm.mutable_route_config();
// adding direct response mode to the default route
auto* default_route =
hcm.mutable_route_config()->mutable_virtual_hosts(0)->mutable_routes(0);
default_route->mutable_match()->set_prefix(prefix);
default_route->mutable_direct_response()->set_status(static_cast<uint32_t>(status));
default_route->mutable_direct_response()->mutable_body()->set_filename(file_path);
// adding headers to the default route
auto* header_value_option = route_config->mutable_response_headers_to_add()->Add();
header_value_option->mutable_header()->set_value("direct-response-enabled");
header_value_option->mutable_header()->set_key("x-direct-response-header");
});
HttpIntegrationTest::initialize();
}

DEFINE_PROTO_FUZZER(const test::integration::H2CaptureFuzzTestCase& input) {
RELEASE_ASSERT(!TestEnvironment::getIpVersionsForTest().empty(), "");
const auto ip_version = TestEnvironment::getIpVersionsForTest()[0];
PERSISTENT_FUZZ_VAR H2FuzzIntegrationTest h2_fuzz_integration_test(ip_version);
h2_fuzz_integration_test.replay(input, true);
}

} // namespace Envoy
Loading

0 comments on commit e65bcd5

Please sign in to comment.