Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions test/common/http/http2/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ licenses(["notice"]) # Apache 2

load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_fuzz_test",
"envoy_cc_test",
"envoy_cc_test_library",
"envoy_package",
Expand Down Expand Up @@ -55,6 +56,35 @@ envoy_cc_test(
],
)

envoy_cc_test_library(
name = "frame_replay_lib",
srcs = ["frame_replay.cc"],
hdrs = ["frame_replay.h"],
deps = [
"//source/common/common:hex_lib",
"//source/common/common:macros",
"//source/common/http/http2:codec_lib",
"//test/common/http:common_lib",
"//test/common/http/http2:codec_impl_test_util",
"//test/mocks/http:http_mocks",
"//test/mocks/network:network_mocks",
"//test/test_common:environment_lib",
"//test/test_common:utility_lib",
],
)

envoy_cc_test(
name = "frame_replay_test",
srcs = ["frame_replay_test.cc"],
data = [
"request_header_corpus/simple_example_huffman",
"request_header_corpus/simple_example_plain",
"response_header_corpus/simple_example_huffman",
"response_header_corpus/simple_example_plain",
],
deps = [":frame_replay_lib"],
)

envoy_cc_test(
name = "metadata_encoder_decoder_test",
srcs = ["metadata_encoder_decoder_test.cc"],
Expand All @@ -69,3 +99,17 @@ envoy_cc_test(
"//test/test_common:utility_lib",
],
)

envoy_cc_fuzz_test(
name = "response_header_fuzz_test",
srcs = ["response_header_fuzz_test.cc"],
corpus = "response_header_corpus",
deps = [":frame_replay_lib"],
)

envoy_cc_fuzz_test(
name = "request_header_fuzz_test",
srcs = ["request_header_fuzz_test.cc"],
corpus = "request_header_corpus",
deps = [":frame_replay_lib"],
)
56 changes: 0 additions & 56 deletions test/common/http/http2/codec_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,6 @@ namespace Http2 {
using Http2SettingsTuple = ::testing::tuple<uint32_t, uint32_t, uint32_t, uint32_t>;
using Http2SettingsTestParam = ::testing::tuple<Http2SettingsTuple, Http2SettingsTuple>;

constexpr Http2SettingsTuple
DefaultHttp2SettingsTuple(Http2Settings::DEFAULT_HPACK_TABLE_SIZE,
Http2Settings::DEFAULT_MAX_CONCURRENT_STREAMS,
Http2Settings::DEFAULT_MAX_CONCURRENT_STREAMS,
Http2Settings::DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE);

class Http2CodecImplTestFixture {
public:
struct ConnectionWrapper {
Expand Down Expand Up @@ -89,9 +83,6 @@ class Http2CodecImplTestFixture {
if (corrupt_metadata_frame_) {
corruptMetadataFramePayload(data);
}
if (corrupt_at_offset_ >= 0) {
corruptAtOffset(data, corrupt_at_offset_, corrupt_with_char_);
}
server_wrapper_.dispatch(data, *server_);
}));
ON_CALL(server_connection_, write(_, _))
Expand Down Expand Up @@ -148,9 +139,6 @@ class Http2CodecImplTestFixture {
MockStreamCallbacks server_stream_callbacks_;
// Corrupt a metadata frame payload.
bool corrupt_metadata_frame_ = false;
// Corrupt frame at a given offset (if positive).
ssize_t corrupt_at_offset_{-1};
char corrupt_with_char_{'\0'};

uint32_t max_request_headers_kb_ = Http::DEFAULT_MAX_REQUEST_HEADERS_KB;
};
Expand Down Expand Up @@ -1041,50 +1029,6 @@ TEST_P(Http2CodecImplTest, TestCodecHeaderCompression) {
}
}

// Validate that nghttp2 rejects NUL/CR/LF as per
// https://httpwg.org/specs/rfc7540.html#rfc.section.10.3.
// TEST_P(Http2CodecImplTest, InvalidHeaderChars) {
// TODO(htuch): Write me. Http2CodecImplMutationTest basically covers this,
// but we could be a bit more specific and add a captured H2 HEADERS frame
// here and inject it with mutation of just the header value, ensuring we get
// the expected codec exception.
// }

class Http2CodecImplMutationTest : public ::testing::TestWithParam<::testing::tuple<char, int>>,
protected Http2CodecImplTestFixture {
public:
Http2CodecImplMutationTest()
: Http2CodecImplTestFixture(DefaultHttp2SettingsTuple, DefaultHttp2SettingsTuple) {}

void initialize() override {
corrupt_with_char_ = ::testing::get<0>(GetParam());
corrupt_at_offset_ = ::testing::get<1>(GetParam());
Http2CodecImplTestFixture::initialize();
}
};

INSTANTIATE_TEST_SUITE_P(Http2CodecImplMutationTest, Http2CodecImplMutationTest,
::testing::Combine(::testing::ValuesIn({'\0', '\r', '\n'}),
::testing::Range(0, 128)));

// Mutate an arbitrary offset in the HEADERS frame with NUL/CR/LF. This should
// either throw an exception or continue, but we shouldn't crash due to
// validHeaderString() ASSERTs.
TEST_P(Http2CodecImplMutationTest, HandleInvalidChars) {
initialize();

TestHeaderMapImpl request_headers;
request_headers.addCopy("foo", "barbaz");
HttpTestUtility::addDefaultHeaders(request_headers);
EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(AnyNumber());
EXPECT_CALL(client_callbacks_, onGoAway()).Times(AnyNumber());
try {
request_encoder_->encodeHeaders(request_headers, true);
} catch (const CodecProtocolException& e) {
ENVOY_LOG_MISC(trace, "CodecProtocolException: {}", e.what());
}
}

} // namespace Http2
} // namespace Http
} // namespace Envoy
118 changes: 118 additions & 0 deletions test/common/http/http2/frame_replay.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include "test/common/http/http2/frame_replay.h"

#include "common/common/hex.h"
#include "common/common/macros.h"

#include "test/common/http/common.h"
#include "test/test_common/environment.h"

namespace Envoy {
namespace Http {
namespace Http2 {

FileFrame::FileFrame(absl::string_view path) : api_(Api::createApiForTest()) {
const std::string contents = api_->fileSystem().fileReadToEnd(
TestEnvironment::runfilesPath("test/common/http/http2/" + std::string(path)));
frame_.resize(contents.size());
contents.copy(reinterpret_cast<char*>(frame_.data()), frame_.size());
}

std::unique_ptr<std::istream> FileFrame::istream() {
const std::string frame_string{reinterpret_cast<char*>(frame_.data()), frame_.size()};
return std::make_unique<std::istringstream>(frame_string);
}

const Frame& WellKnownFrames::clientConnectionPrefaceFrame() {
CONSTRUCT_ON_FIRST_USE(std::vector<uint8_t>,
{0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,
0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a});
}

const Frame& WellKnownFrames::defaultSettingsFrame() {
CONSTRUCT_ON_FIRST_USE(std::vector<uint8_t>,
{0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
0x7f, 0xff, 0xff, 0xff, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00});
}

const Frame& WellKnownFrames::initialWindowUpdateFrame() {
CONSTRUCT_ON_FIRST_USE(std::vector<uint8_t>, {0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0f, 0xff, 0x00, 0x01});
}

void FrameUtils::fixupHeaders(Frame& frame) {
constexpr size_t frame_header_len = 9; // from RFC 7540
while (frame.size() < frame_header_len) {
frame.emplace_back(0x00);
}
size_t headers_len = frame.size() - frame_header_len;
frame[2] = headers_len & 0xff;
headers_len >>= 8;
frame[1] = headers_len & 0xff;
headers_len >>= 8;
frame[0] = headers_len & 0xff;
// HEADERS frame with END_STREAM | END_HEADERS for stream 1.
size_t offset = 3;
for (const uint8_t b : {0x01, 0x05, 0x00, 0x00, 0x00, 0x01}) {
frame[offset++] = b;
}
}

CodecFrameInjector::CodecFrameInjector(const std::string& injector_name)
: injector_name_(injector_name) {
settings_.hpack_table_size_ = Http2Settings::DEFAULT_HPACK_TABLE_SIZE;
settings_.max_concurrent_streams_ = Http2Settings::DEFAULT_MAX_CONCURRENT_STREAMS;
settings_.initial_stream_window_size_ = Http2Settings::DEFAULT_INITIAL_STREAM_WINDOW_SIZE;
settings_.initial_connection_window_size_ = Http2Settings::DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE;
settings_.allow_metadata_ = false;
}

ClientCodecFrameInjector::ClientCodecFrameInjector() : CodecFrameInjector("server") {
auto client = std::make_unique<TestClientConnectionImpl>(client_connection_, client_callbacks_,
stats_store_, settings_,
Http::DEFAULT_MAX_REQUEST_HEADERS_KB);
request_encoder_ = &client->newStream(response_decoder_);
connection_ = std::move(client);
ON_CALL(client_connection_, write(_, _))
.WillByDefault(Invoke([&](Buffer::Instance& data, bool) -> void {
ENVOY_LOG_MISC(
trace, "client write: {}",
Hex::encode(static_cast<uint8_t*>(data.linearize(data.length())), data.length()));
data.drain(data.length());
}));
request_encoder_->getStream().addCallbacks(client_stream_callbacks_);
// Setup a single stream to inject frames as a reply to.
TestHeaderMapImpl request_headers;
HttpTestUtility::addDefaultHeaders(request_headers);
request_encoder_->encodeHeaders(request_headers, true);
}

ServerCodecFrameInjector::ServerCodecFrameInjector() : CodecFrameInjector("client") {
connection_ = std::make_unique<TestServerConnectionImpl>(server_connection_, server_callbacks_,
stats_store_, settings_,
Http::DEFAULT_MAX_REQUEST_HEADERS_KB);
EXPECT_CALL(server_callbacks_, newStream(_, _))
.WillRepeatedly(Invoke([&](StreamEncoder& encoder, bool) -> StreamDecoder& {
encoder.getStream().addCallbacks(server_stream_callbacks_);
return request_decoder_;
}));
ON_CALL(server_connection_, write(_, _))
.WillByDefault(Invoke([&](Buffer::Instance& data, bool) -> void {
ENVOY_LOG_MISC(
trace, "server write: {}",
Hex::encode(static_cast<uint8_t*>(data.linearize(data.length())), data.length()));
data.drain(data.length());
}));
}

void CodecFrameInjector::write(const Frame& frame) {
Buffer::OwnedImpl buffer;
buffer.add(frame.data(), frame.size());
ENVOY_LOG_MISC(trace, "{} write: {}", injector_name_, Hex::encode(frame.data(), frame.size()));
while (buffer.length() > 0) {
connection_->dispatch(buffer);
}
}

} // namespace Http2
} // namespace Http
} // namespace Envoy
91 changes: 91 additions & 0 deletions test/common/http/http2/frame_replay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#include <cstdint>
#include <memory>
#include <vector>

#include "common/stats/isolated_store_impl.h"

#include "test/common/http/http2/codec_impl_test_util.h"
#include "test/mocks/http/mocks.h"
#include "test/mocks/network/mocks.h"
#include "test/test_common/utility.h"

#include "absl/strings/string_view.h"
#include "gmock/gmock.h"

namespace Envoy {
namespace Http {
namespace Http2 {

// A byte vector representation of an HTTP/2 frame.
typedef std::vector<uint8_t> Frame;

// An HTTP/2 frame derived from a file location.
class FileFrame {
public:
FileFrame(absl::string_view path);

Frame& frame() { return frame_; }
std::unique_ptr<std::istream> istream();

Frame frame_;
Api::ApiPtr api_;
};

// Some standards HTTP/2 frames for setting up a connection. The contents for these and the seed
// corpus were captured via logging the hex bytes in codec_impl_test's write() connection mocks in
// setupDefaultConnectionMocks().
class WellKnownFrames {
public:
static const Frame& clientConnectionPrefaceFrame();
static const Frame& defaultSettingsFrame();
static const Frame& initialWindowUpdateFrame();
};

class FrameUtils {
public:
// Modify a given frame so that it has the HTTP/2 frame header for a valid
// HEADERS frame.
static void fixupHeaders(Frame& frame);
};

class CodecFrameInjector {
public:
CodecFrameInjector(const std::string& injector_name);

void write(const Frame& frame);

Http2Settings settings_;
std::unique_ptr<Http::Connection> connection_;
Stats::IsolatedStoreImpl stats_store_;
const std::string injector_name_;
};

// Wrapper for HTTP/2 client codec supporting injection of frames and expecting on
// the behaviors of callbacks and the request decoder.
class ClientCodecFrameInjector : public CodecFrameInjector {
public:
ClientCodecFrameInjector();

::testing::NiceMock<Network::MockConnection> client_connection_;
MockConnectionCallbacks client_callbacks_;
MockStreamDecoder response_decoder_;
StreamEncoder* request_encoder_;
MockStreamCallbacks client_stream_callbacks_;
};

// Wrapper for HTTP/2 server codec supporting injection of frames and expecting on
// the behaviors of callbacks and the request decoder.
class ServerCodecFrameInjector : public CodecFrameInjector {
public:
ServerCodecFrameInjector();

::testing::NiceMock<Network::MockConnection> server_connection_;
MockServerConnectionCallbacks server_callbacks_;
std::unique_ptr<TestServerConnectionImpl> server_;
MockStreamDecoder request_decoder_;
MockStreamCallbacks server_stream_callbacks_;
};

} // namespace Http2
} // namespace Http
} // namespace Envoy
Loading