-
Notifications
You must be signed in to change notification settings - Fork 5.3k
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 all 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,86 @@ | ||
| #include "test/extensions/filters/listener/common/fuzz/listener_filter_fakes.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Extensions { | ||
| namespace ListenerFilters { | ||
|
|
||
| Network::IoHandle& FakeConnectionSocket::ioHandle() { return *io_handle_; } | ||
|
|
||
| const Network::IoHandle& FakeConnectionSocket::ioHandle() const { return *io_handle_; } | ||
|
|
||
| void FakeConnectionSocket::setLocalAddress( | ||
| const Network::Address::InstanceConstSharedPtr& local_address) { | ||
| local_address_ = local_address; | ||
| if (local_address_ != nullptr) { | ||
| addr_type_ = local_address_->type(); | ||
| } | ||
| } | ||
|
|
||
| void FakeConnectionSocket::setRemoteAddress( | ||
| const Network::Address::InstanceConstSharedPtr& remote_address) { | ||
| remote_address_ = remote_address; | ||
| } | ||
|
|
||
| const Network::Address::InstanceConstSharedPtr& FakeConnectionSocket::localAddress() const { | ||
| return local_address_; | ||
| } | ||
|
|
||
| const Network::Address::InstanceConstSharedPtr& FakeConnectionSocket::remoteAddress() const { | ||
| return remote_address_; | ||
| } | ||
|
|
||
| Network::Address::Type FakeConnectionSocket::addressType() const { return addr_type_; } | ||
|
|
||
| absl::optional<Network::Address::IpVersion> FakeConnectionSocket::ipVersion() const { | ||
| if (local_address_ == nullptr || addr_type_ != Network::Address::Type::Ip) { | ||
| return absl::nullopt; | ||
| } | ||
|
|
||
| return local_address_->ip()->version(); | ||
| } | ||
|
|
||
| void FakeConnectionSocket::setDetectedTransportProtocol(absl::string_view protocol) { | ||
| transport_protocol_ = std::string(protocol); | ||
| } | ||
|
|
||
| absl::string_view FakeConnectionSocket::detectedTransportProtocol() const { | ||
| return transport_protocol_; | ||
| } | ||
|
|
||
| void FakeConnectionSocket::setRequestedApplicationProtocols( | ||
| const std::vector<absl::string_view>& protocols) { | ||
| application_protocols_.clear(); | ||
| for (const auto& protocol : protocols) { | ||
| application_protocols_.emplace_back(protocol); | ||
| } | ||
| } | ||
|
|
||
| const std::vector<std::string>& FakeConnectionSocket::requestedApplicationProtocols() const { | ||
| return application_protocols_; | ||
| } | ||
|
|
||
| void FakeConnectionSocket::setRequestedServerName(absl::string_view server_name) { | ||
| server_name_ = std::string(server_name); | ||
| } | ||
|
|
||
| absl::string_view FakeConnectionSocket::requestedServerName() const { return server_name_; } | ||
|
|
||
| Api::SysCallIntResult FakeConnectionSocket::getSocketOption(int level, int, void* optval, | ||
| socklen_t*) const { | ||
| 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}; | ||
| } | ||
|
|
||
| } // namespace ListenerFilters | ||
| } // namespace Extensions | ||
| } // namespace Envoy |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| #include "common/api/os_sys_calls_impl.h" | ||
| #include "common/network/io_socket_handle_impl.h" | ||
|
|
||
| #include "test/mocks/network/mocks.h" | ||
|
|
||
| #include "gmock/gmock.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Extensions { | ||
| namespace ListenerFilters { | ||
|
|
||
| static constexpr int kFakeSocketFd = 42; | ||
|
|
||
| class FakeConnectionSocket : public Network::MockConnectionSocket { | ||
| public: | ||
| FakeConnectionSocket() | ||
| : io_handle_(std::make_unique<Network::IoSocketHandleImpl>(kFakeSocketFd)), | ||
| local_address_(nullptr), remote_address_(nullptr) {} | ||
|
|
||
| ~FakeConnectionSocket() override { io_handle_->close(); } | ||
|
|
||
| Network::IoHandle& ioHandle() override; | ||
|
|
||
| const Network::IoHandle& ioHandle() const override; | ||
|
|
||
| void setLocalAddress(const Network::Address::InstanceConstSharedPtr& local_address) override; | ||
|
|
||
| void setRemoteAddress(const Network::Address::InstanceConstSharedPtr& remote_address) override; | ||
|
|
||
| const Network::Address::InstanceConstSharedPtr& localAddress() const override; | ||
|
|
||
| const Network::Address::InstanceConstSharedPtr& remoteAddress() const override; | ||
|
|
||
| Network::Address::Type addressType() const override; | ||
|
|
||
| absl::optional<Network::Address::IpVersion> ipVersion() const override; | ||
|
|
||
| void setRequestedApplicationProtocols(const std::vector<absl::string_view>& protocols) override; | ||
|
|
||
| const std::vector<std::string>& requestedApplicationProtocols() const override; | ||
|
|
||
| void setDetectedTransportProtocol(absl::string_view protocol) override; | ||
|
|
||
| absl::string_view detectedTransportProtocol() const override; | ||
|
|
||
| void setRequestedServerName(absl::string_view server_name) override; | ||
|
|
||
| absl::string_view requestedServerName() const override; | ||
|
|
||
| Api::SysCallIntResult getSocketOption(int level, int, void* optval, socklen_t*) const override; | ||
|
|
||
| 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.
Show resolved
Hide resolved
|
||
| std::vector<std::string> application_protocols_; | ||
| std::string transport_protocol_; | ||
| std::string server_name_; | ||
| }; | ||
|
|
||
| // TODO: Move over to Fake (name is confusing) | ||
| class FakeOsSysCalls : public Api::OsSysCallsImpl { | ||
arthuryan-k marked this conversation as resolved.
Show resolved
Hide 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,21 +8,92 @@ 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) { | ||
| ENVOY_LOG_MISC(debug, "EnvoyException: {}", e.what()); | ||
| return; | ||
| // Socket's local address will be nullptr by default if fuzzed local address is malformed | ||
| // or missing - local address field in proto is optional | ||
| } | ||
| try { | ||
| socket_.setRemoteAddress(Network::Utility::resolveUrl(input.sock().remote_address())); | ||
| } catch (const EnvoyException& e) { | ||
| // Socket's remote address will be nullptr by default if fuzzed remote address is malformed | ||
| // or missing - remote address field in proto is optional | ||
| } | ||
|
|
||
| FuzzedHeader header(input); | ||
|
|
||
| if (!header.empty()) { | ||
| ON_CALL(os_sys_calls_, recv(kFakeSocketFd, _, _, MSG_PEEK)) | ||
| .WillByDefault(testing::Return(Api::SysCallSizeResult{static_cast<ssize_t>(0), 0})); | ||
|
|
||
| ON_CALL(dispatcher_, | ||
| createFileEvent_(_, _, Event::FileTriggerType::Edge, | ||
| Event::FileReadyType::Read | Event::FileReadyType::Closed)) | ||
| .WillByDefault(testing::DoAll(testing::SaveArg<1>(&file_event_callback_), | ||
| testing::ReturnNew<NiceMock<Event::MockFileEvent>>())); | ||
| } | ||
|
|
||
| filter.onAccept(cb_); | ||
|
|
||
| if (file_event_callback_ == nullptr) { | ||
| // If filter does not call createFileEvent (i.e. original_dst and original_src) | ||
| return; | ||
| } | ||
|
|
||
| if (!header.empty()) { | ||
| { | ||
| testing::InSequence s; | ||
|
|
||
| EXPECT_CALL(os_sys_calls_, recv(kFakeSocketFd, _, _, MSG_PEEK)) | ||
| .Times(testing::AnyNumber()) | ||
| .WillRepeatedly(Invoke( | ||
| [&header](os_fd_t, void* buffer, size_t length, int) -> Api::SysCallSizeResult { | ||
| return header.next(buffer, length); | ||
| })); | ||
| } | ||
|
|
||
| bool got_continue = false; | ||
|
|
||
| ON_CALL(cb_, continueFilterChain(true)) | ||
asraa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .WillByDefault(testing::InvokeWithoutArgs([&got_continue]() { got_continue = true; })); | ||
|
|
||
| while (!got_continue) { | ||
| if (header.done()) { // End of stream reached but not done | ||
| 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 :) |
||
| } else { | ||
| file_event_callback_(Event::FileReadyType::Read); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| 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())); | ||
| FuzzedHeader::FuzzedHeader(const test::extensions::filters::listener::FilterFuzzTestCase& input) | ||
| : nreads_(input.data_size()), nread_(0) { | ||
| size_t len = 0; | ||
| for (int i = 0; i < nreads_; i++) { | ||
| len += input.data(i).size(); | ||
| } | ||
|
|
||
| header_.reserve(len); | ||
|
|
||
| for (int i = 0; i < nreads_; i++) { | ||
| header_ += input.data(i); | ||
| indices_.push_back(header_.size()); | ||
| } | ||
| } | ||
|
|
||
| Api::SysCallSizeResult FuzzedHeader::next(void* buffer, size_t length) { | ||
| if (done()) { // End of stream reached | ||
| nread_ = nreads_ - 1; // Decrement to avoid out-of-range for last recv() call | ||
| } | ||
| memcpy(buffer, header_.data(), std::min(indices_[nread_], length)); | ||
| return Api::SysCallSizeResult{static_cast<ssize_t>(indices_[nread_++]), 0}; | ||
| } | ||
|
|
||
| bool FuzzedHeader::done() { return nread_ >= nreads_; } | ||
|
|
||
| bool FuzzedHeader::empty() { return nreads_ == 0; } | ||
|
|
||
| } // namespace ListenerFilters | ||
| } // namespace Extensions | ||
| } // namespace Envoy | ||
| 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.
Uh oh!
There was an error while loading. Please reload this page.