-
Notifications
You must be signed in to change notification settings - Fork 5.5k
fuzz: added fuzz test for listener filter http_inspector #12411
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
495ee4e
f781ded
c554392
28b2eeb
81bb3b3
c53f9db
a7a4135
ffc8f11
4966faf
561207d
be1750e
62ab061
b5c7e52
855c04d
4e34192
049bfc5
943d9e1
8ba4fd5
fa2e184
7b4b871
af4e649
11ccd21
110dfb6
b98406c
8e0c8d0
0766cca
656c96a
48f3598
a1c3cad
cafddca
bccd521
6bba706
37662dc
88f1a8d
734db04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| #include "common/api/os_sys_calls_impl.h" | ||
| #include "common/network/io_socket_handle_impl.h" | ||
| #include "common/network/utility.h" | ||
|
|
||
| #include "test/mocks/network/mocks.h" | ||
|
|
||
| #include "gmock/gmock.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Extensions { | ||
| namespace ListenerFilters { | ||
|
|
||
| class FakeConnectionSocket : public Network::MockConnectionSocket { | ||
|
arthuryan-k marked this conversation as resolved.
|
||
| public: | ||
| FakeConnectionSocket() | ||
| : io_handle_(std::make_unique<Network::IoSocketHandleImpl>(42)), local_address_(nullptr), | ||
|
arthuryan-k marked this conversation as resolved.
Outdated
|
||
| remote_address_(nullptr) {} | ||
|
|
||
| ~FakeConnectionSocket() override { io_handle_->close(); } | ||
|
|
||
| Network::IoHandle& ioHandle() override { return *io_handle_; } | ||
|
|
||
| const Network::IoHandle& ioHandle() const override { return *io_handle_; } | ||
|
|
||
| void setLocalAddress(const Network::Address::InstanceConstSharedPtr& local_address) override { | ||
| local_address_ = local_address; | ||
| if (local_address_ != nullptr) { | ||
| addr_type_ = local_address_->type(); | ||
| } | ||
|
arthuryan-k marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| void setRemoteAddress(const Network::Address::InstanceConstSharedPtr& remote_address) override { | ||
| remote_address_ = remote_address; | ||
| } | ||
|
|
||
| const Network::Address::InstanceConstSharedPtr& localAddress() const override { | ||
| return local_address_; | ||
| } | ||
|
|
||
| const Network::Address::InstanceConstSharedPtr& remoteAddress() const override { | ||
| return remote_address_; | ||
| } | ||
|
|
||
| Network::Address::Type addressType() const override { return addr_type_; } | ||
|
|
||
| absl::optional<Network::Address::IpVersion> ipVersion() const override { | ||
| if (addr_type_ != Network::Address::Type::Ip) { | ||
| return absl::nullopt; | ||
| } | ||
|
|
||
| return local_address_->ip()->version(); | ||
| } | ||
|
|
||
| void setRequestedApplicationProtocols(const std::vector<absl::string_view>& protocols) override { | ||
| application_protocols_.clear(); | ||
| for (const auto& protocol : protocols) { | ||
| application_protocols_.emplace_back(protocol); | ||
| } | ||
| } | ||
|
|
||
| const std::vector<std::string>& requestedApplicationProtocols() const override { | ||
| return application_protocols_; | ||
| } | ||
|
|
||
| Api::SysCallIntResult getSocketOption(int level, int, void* optval, socklen_t*) const override { | ||
| switch (level) { | ||
| case SOL_IPV6: | ||
| static_cast<sockaddr_storage*>(optval)->ss_family = AF_INET6; | ||
| break; | ||
| case SOL_IP: | ||
| static_cast<sockaddr_storage*>(optval)->ss_family = AF_INET; | ||
| break; | ||
| default: | ||
| NOT_REACHED_GCOVR_EXCL_LINE; | ||
| } | ||
|
|
||
| return Api::SysCallIntResult{0, 0}; | ||
| } | ||
|
|
||
| private: | ||
| const Network::IoHandlePtr io_handle_; | ||
| Network::Address::InstanceConstSharedPtr local_address_; | ||
| Network::Address::InstanceConstSharedPtr remote_address_; | ||
| Network::Address::Type addr_type_; | ||
|
arthuryan-k marked this conversation as resolved.
|
||
| std::vector<std::string> application_protocols_; | ||
| }; | ||
|
|
||
| class FakeOsSysCalls : public Api::OsSysCallsImpl { | ||
|
arthuryan-k marked this conversation as resolved.
|
||
| public: | ||
| MOCK_METHOD(Api::SysCallSizeResult, recv, (os_fd_t, void*, size_t, int)); | ||
| }; | ||
|
|
||
| } // namespace ListenerFilters | ||
| } // namespace Extensions | ||
| } // namespace Envoy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,5 @@ | ||
| #include "test/extensions/filters/listener/common/fuzz/listener_filter_fuzzer.h" | ||
|
|
||
| #include "common/network/utility.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Extensions { | ||
| namespace ListenerFilters { | ||
|
|
@@ -10,19 +8,85 @@ void ListenerFilterFuzzer::fuzz( | |
| Network::ListenerFilter& filter, | ||
| const test::extensions::filters::listener::FilterFuzzTestCase& input) { | ||
| try { | ||
| fuzzerSetup(input); | ||
| socket_.setLocalAddress(Network::Utility::resolveUrl(input.sock().local_address())); | ||
| } catch (const EnvoyException& e) { | ||
| // If fuzzed local address is malformed or missing, socket's local address will be nullptr | ||
|
arthuryan-k marked this conversation as resolved.
Outdated
|
||
| } | ||
| try { | ||
| socket_.setRemoteAddress(Network::Utility::resolveUrl(input.sock().remote_address())); | ||
| } catch (const EnvoyException& e) { | ||
| ENVOY_LOG_MISC(debug, "EnvoyException: {}", e.what()); | ||
| return; | ||
| // If fuzzed remote address is malformed or missing, socket's remote address will be nullptr | ||
| } | ||
|
|
||
| const int nreads = input.data_size(); // Number of reads | ||
|
|
||
| if (nreads > 0) { | ||
| EXPECT_CALL(socket_, detectedTransportProtocol()).WillRepeatedly(testing::Return("raw_buffer")); | ||
|
|
||
| EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) | ||
| .WillOnce(testing::Return(Api::SysCallSizeResult{static_cast<ssize_t>(0), 0})); | ||
|
|
||
| EXPECT_CALL(dispatcher_, | ||
| createFileEvent_(_, _, Event::FileTriggerType::Edge, | ||
| Event::FileReadyType::Read | Event::FileReadyType::Closed)) | ||
| .WillOnce(testing::DoAll(testing::SaveArg<1>(&file_event_callback_), | ||
| testing::ReturnNew<NiceMock<Event::MockFileEvent>>())); | ||
| } | ||
|
|
||
| filter.onAccept(cb_); | ||
| } | ||
|
|
||
| void ListenerFilterFuzzer::socketSetup( | ||
| const test::extensions::filters::listener::FilterFuzzTestCase& input) { | ||
| socket_.setLocalAddress(Network::Utility::resolveUrl(input.sock().local_address())); | ||
| socket_.setRemoteAddress(Network::Utility::resolveUrl(input.sock().remote_address())); | ||
| if (nreads > 0) { | ||
| // Construct header from single or multiple reads | ||
| std::string data = ""; | ||
| std::vector<size_t> indices; // Ending indices for each read | ||
|
|
||
| size_t curr = 0; | ||
|
|
||
| for (int i = 0; i < nreads; i++) { | ||
| data += input.data(i); | ||
| curr += input.data(i).size(); | ||
| indices.push_back(curr); | ||
|
arthuryan-k marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| absl::string_view header(data); | ||
|
arthuryan-k marked this conversation as resolved.
Outdated
|
||
|
|
||
| int nread = 0; // Counter of current read | ||
|
|
||
| { | ||
| testing::InSequence s; | ||
|
|
||
| if (nreads > 1) { | ||
| EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) | ||
| .WillOnce(testing::InvokeWithoutArgs([]() { | ||
| return Api::SysCallSizeResult{ssize_t(-1), SOCKET_ERROR_AGAIN}; | ||
| })); | ||
| } | ||
|
|
||
| EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) | ||
| .Times(testing::AnyNumber()) | ||
| .WillRepeatedly(Invoke([&header, &indices, &nread](os_fd_t, void* buffer, size_t length, | ||
| int) -> Api::SysCallSizeResult { | ||
| ASSERT(length >= indices[nread]); | ||
| memcpy(buffer, header.data(), indices[nread]); | ||
| return Api::SysCallSizeResult{ssize_t(indices[nread++]), 0}; | ||
| })); | ||
| } | ||
|
|
||
| bool got_continue = false; | ||
|
|
||
| ON_CALL(cb_, continueFilterChain(true)) | ||
|
asraa marked this conversation as resolved.
|
||
| .WillByDefault(testing::InvokeWithoutArgs([&got_continue]() { got_continue = true; })); | ||
|
|
||
| while (!got_continue) { | ||
| if (nread >= nreads) { // End of stream reached but not done | ||
| nread--; // Decrement to avoid out-of-range for last recv() call | ||
| file_event_callback_(Event::FileReadyType::Closed); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there is no more data to fuzz, should this be an early return? (Does "not done" mean that the input data has not been read completely, even if the filter will do a no-op?)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, "not done" should handle the case where all of the fuzzed input data has been read (end of stream reached), but the parser fails to determine http. In this case, the file event will fallback to non-http and call Edit: Actually, great catch! This works as-is for http_inspector due to how
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahhhhh! Gotcha -- thanks so much for the explanation though. Sounds good for fixing later :) |
||
| break; | ||
| } else { | ||
| file_event_callback_(Event::FileReadyType::Read); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| } // namespace ListenerFilters | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,4 +9,5 @@ message Socket { | |
|
|
||
| message FilterFuzzTestCase { | ||
| Socket sock = 1; | ||
| repeated string data = 2; | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| #include "extensions/filters/listener/http_inspector/http_inspector.h" | ||
|
|
||
| #include "test/extensions/filters/listener/common/fuzz/listener_filter_fuzzer.h" | ||
| #include "test/extensions/filters/listener/common/fuzz/listener_filter_fuzzer.pb.validate.h" | ||
| #include "test/fuzz/fuzz_runner.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Extensions { | ||
| namespace ListenerFilters { | ||
| namespace HttpInspector { | ||
|
|
||
| DEFINE_PROTO_FUZZER(const test::extensions::filters::listener::FilterFuzzTestCase& input) { | ||
|
|
||
| try { | ||
| TestUtility::validate(input); | ||
| } catch (const ProtoValidationException& e) { | ||
| ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what()); | ||
| return; | ||
| } | ||
|
|
||
| Stats::IsolatedStoreImpl store; | ||
| ConfigSharedPtr cfg = std::make_shared<Config>(store); | ||
| auto filter = std::make_unique<Filter>(cfg); | ||
|
|
||
| ListenerFilterFuzzer fuzzer; | ||
|
arthuryan-k marked this conversation as resolved.
|
||
| fuzzer.fuzz(*filter, input); | ||
| } | ||
|
|
||
| } // namespace HttpInspector | ||
| } // namespace ListenerFilters | ||
| } // namespace Extensions | ||
| } // namespace Envoy | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
This file was deleted.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.