diff --git a/test/common/network/BUILD b/test/common/network/BUILD index e5949d401931..e38391834b07 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -393,6 +393,30 @@ envoy_cc_test( ], ) +envoy_cc_fuzz_test( + name = "udp_fuzz", + srcs = ["udp_fuzz.cc"], + corpus = "udp_corpus", + deps = [ + "udp_listener_impl_test_base_lib", + "//source/common/event:dispatcher_lib", + "//source/common/network:address_lib", + "//source/common/network:listener_lib", + "//source/common/network:socket_option_lib", + "//source/common/network:udp_packet_writer_handler_lib", + "//source/common/network:utility_lib", + "//source/common/stats:stats_lib", + "//test/common/network:listener_impl_test_base_lib", + "//test/mocks/network:network_mocks", + "//test/mocks/server:server_mocks", + "//test/test_common:environment_lib", + "//test/test_common:network_utility_lib", + "//test/test_common:threadsafe_singleton_injector_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + envoy_cc_fuzz_test( name = "utility_fuzz_test", srcs = ["utility_fuzz_test.cc"], diff --git a/test/common/network/udp_corpus/seed1.txt b/test/common/network/udp_corpus/seed1.txt new file mode 100644 index 000000000000..5fb47689faf3 --- /dev/null +++ b/test/common/network/udp_corpus/seed1.txt @@ -0,0 +1 @@ +udp-seed1 diff --git a/test/common/network/udp_fuzz.cc b/test/common/network/udp_fuzz.cc new file mode 100644 index 000000000000..792f1f93286b --- /dev/null +++ b/test/common/network/udp_fuzz.cc @@ -0,0 +1,183 @@ +#include + +#include "envoy/config/core/v3/base.pb.h" + +#include "common/api/os_sys_calls_impl.h" +#include "common/network/address_impl.h" +#include "common/network/socket_option_factory.h" +#include "common/network/socket_option_impl.h" +#include "common/network/udp_listener_impl.h" +#include "common/network/udp_packet_writer_handler_impl.h" +#include "common/network/utility.h" + +#include "test/common/network/udp_listener_impl_test_base.h" +#include "test/fuzz/fuzz_runner.h" +#include "test/mocks/api/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/test_common/environment.h" +#include "test/test_common/network_utility.h" +#include "test/test_common/threadsafe_singleton_injector.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace { + +class OverrideOsSysCallsImpl : public Api::OsSysCallsImpl { +public: + MOCK_METHOD(bool, supportsUdpGro, (), (const)); + MOCK_METHOD(bool, supportsMmsg, (), (const)); +}; + +class UdpFuzz; + +class FuzzUdpListenerCallbacks : public Network::UdpListenerCallbacks { +public: + FuzzUdpListenerCallbacks(UdpFuzz* upf) : my_upf_(upf) {} + ~FuzzUdpListenerCallbacks() override = default; + void onData(Network::UdpRecvData&& data) override; + void onReadReady() override; + void onWriteReady(const Network::Socket& socket) override; + void onReceiveError(Api::IoError::IoErrorCode error_code) override; + void onDataWorker(Network::UdpRecvData&& data) override; + void post(Network::UdpRecvData&& data) override; + void onDatagramsDropped(uint32_t dropped) override; + uint32_t workerIndex() const override; + Network::UdpPacketWriter& udpPacketWriter() override; + +private: + UdpFuzz* my_upf_; +}; + +class UdpFuzz { +public: + UdpFuzz(const uint8_t* buf, size_t len) { + // Prepare environment + api_ = Api::createApiForTest(); + dispatcher_ = api_->allocateDispatcher("test_thread"); + ip_version_ = TestEnvironment::getIpVersionsForTest()[0]; + + server_socket_ = createServerSocket(true, ip_version_); + server_socket_->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); + server_socket_->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); + + // Create packet writer + udp_packet_writer_ = std::make_unique(server_socket_->ioHandle()); + + // Set up callbacks + FuzzUdpListenerCallbacks fuzzCallbacks(this); + + // Create listener with default config + envoy::config::core::v3::UdpSocketConfig config; + + FuzzedDataProvider provider(buf, len); + uint16_t SocketType = provider.ConsumeIntegralInRange(0, 2); + if (SocketType == 0) { + config.mutable_prefer_gro()->set_value(true); + ON_CALL(override_syscall_, supportsUdpGro()).WillByDefault(Return(true)); + } else if (SocketType == 1) { + ON_CALL(override_syscall_, supportsMmsg()).WillByDefault(Return(true)); + } else { + ON_CALL(override_syscall_, supportsMmsg()).WillByDefault(Return(false)); + ON_CALL(override_syscall_, supportsUdpGro()).WillByDefault(Return(false)); + } + + std::unique_ptr listener_ = + std::make_unique(dispatcherImpl(), server_socket_, fuzzCallbacks, + dispatcherImpl().timeSource(), config); + + Network::Address::Instance* send_to_addr_ = new Network::Address::Ipv4Instance( + "127.0.0.1", server_socket_->addressProvider().localAddress()->ip()->port()); + + // Now do all of the fuzzing + static const int MaxPackets = 15; + total_packets_ = provider.ConsumeIntegralInRange(1, MaxPackets); + Network::Test::UdpSyncPeer client_(ip_version_); + for (uint16_t i = 0; i < total_packets_; i++) { + std::string packet_ = + provider.ConsumeBytesAsString(provider.ConsumeIntegralInRange(1, 3000)); + if (packet_.empty()) { + packet_ = "EMPTY_PACKET"; + } + client_.write(packet_, *send_to_addr_); + } + dispatcher_->run(Event::Dispatcher::RunType::Block); + + // cleanup + delete send_to_addr_; + } + + Event::DispatcherImpl& dispatcherImpl() { + // We need access to the concrete impl type in order to instantiate a + // Test[Udp]Listener, which instantiates a [Udp]ListenerImpl, which requires + // a DispatcherImpl to access DispatcherImpl::base_, which is not part of + // the Dispatcher API. + Event::DispatcherImpl* impl = dynamic_cast(dispatcher_.get()); + return *impl; + } + + Network::SocketSharedPtr createServerSocket(bool bind, Network::Address::IpVersion version) { + // Set IP_FREEBIND to allow sendmsg to send with non-local IPv6 source + // address. + return std::make_shared( + Network::Test::getCanonicalLoopbackAddress(version), +#ifdef IP_FREEBIND + Network::SocketOptionFactory::buildIpFreebindOptions(), +#else + nullptr, +#endif + bind); + } + + Network::SocketSharedPtr server_socket_; + Event::DispatcherPtr dispatcher_; + Api::ApiPtr api_; + Network::UdpPacketWriterPtr udp_packet_writer_; + uint16_t sent_packets_ = 0; + uint16_t total_packets_; + Network::Address::IpVersion ip_version_; + NiceMock override_syscall_; + TestThreadsafeSingletonInjector os_calls{&override_syscall_}; +}; + +void FuzzUdpListenerCallbacks::onData(Network::UdpRecvData&& data) { + my_upf_->sent_packets_++; + if (my_upf_->sent_packets_ == my_upf_->total_packets_) { + my_upf_->dispatcher_->exit(); + } + UNREFERENCED_PARAMETER(data); +} + +void FuzzUdpListenerCallbacks::onReadReady() {} + +void FuzzUdpListenerCallbacks::onWriteReady(const Network::Socket& socket) { + UNREFERENCED_PARAMETER(socket); +} + +void FuzzUdpListenerCallbacks::onReceiveError(Api::IoError::IoErrorCode error_code) { + my_upf_->sent_packets_++; + if (my_upf_->sent_packets_ == my_upf_->total_packets_) { + my_upf_->dispatcher_->exit(); + } + UNREFERENCED_PARAMETER(error_code); +} +Network::UdpPacketWriter& FuzzUdpListenerCallbacks::udpPacketWriter() { + return *my_upf_->udp_packet_writer_; +} +uint32_t FuzzUdpListenerCallbacks::workerIndex() const { return 0; } +void FuzzUdpListenerCallbacks::onDataWorker(Network::UdpRecvData&& data) { + UNREFERENCED_PARAMETER(data); +} +void FuzzUdpListenerCallbacks::post(Network::UdpRecvData&& data) { UNREFERENCED_PARAMETER(data); } + +void FuzzUdpListenerCallbacks::onDatagramsDropped(uint32_t dropped) { + my_upf_->sent_packets_++; + if (my_upf_->sent_packets_ == my_upf_->total_packets_) { + my_upf_->dispatcher_->exit(); + } + UNREFERENCED_PARAMETER(dropped); +} + +DEFINE_FUZZER(const uint8_t* buf, size_t len) { UdpFuzz udp_instance(buf, len); } +} // namespace +} // namespace Envoy