wsd: add HttpEcho fuzzer

This is a full round-trip http fuzzer.
It can achieve >1000 iterations per second
on a single 2 Ghz core, even while going
through the network loopback layer.
The advantage is that more networking code
is fuzzed this way, including not just
the http code, but also the sockets.

Change-Id: I75d21bd0e25221ee6621097a2605d62c4bb2ae4d
Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
pull/5163/head
Ashod Nakashian 2021-05-26 22:04:02 -04:00 committed by Ashod Nakashian
parent c9cb1f4e01
commit a87d1ae54f
4 changed files with 165 additions and 3 deletions

View File

@ -151,7 +151,8 @@ if ENABLE_LIBFUZZER
noinst_PROGRAMS += \
admin_fuzzer \
clientsession_fuzzer \
httpresponse_fuzzer
httpresponse_fuzzer \
httpecho_fuzzer
endif
connect_SOURCES = tools/Connect.cpp \
@ -210,6 +211,17 @@ httpresponse_fuzzer_SOURCES = \
fuzzer/HttpResponse.cpp
httpresponse_fuzzer_LDFLAGS = -fsanitize=fuzzer $(AM_LDFLAGS)
httpecho_fuzzer_CPPFLAGS = \
-DKIT_IN_PROCESS=1 \
$(AM_CPPFLAGS) \
-I${top_srcdir}/test
httpecho_fuzzer_SOURCES = \
$(loolwsd_sources) \
$(loolforkit_sources) \
$(shared_sources) \
fuzzer/HttpEcho.cpp
httpecho_fuzzer_LDFLAGS = -fsanitize=fuzzer $(AM_LDFLAGS)
endif # ENABLE_LIBFUZZER
clientnb_SOURCES = net/clientnb.cpp \

View File

@ -151,7 +151,7 @@ AC_ARG_ENABLE(fuzzers,
AS_HELP_STRING([--enable-fuzzers],
[Enables building libfuzzer targets for fuzz testing. It is useful to enable this switch
only in a separate build tree, since the switch disables the creation of a coolwsd
binary.])
binary. For best results: --with-sanitizer=address,undefined])
)
AC_ARG_ENABLE([androidapp],

149
fuzzer/HttpEcho.cpp 100644
View File

@ -0,0 +1,149 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <config.h>
#include <cstdlib>
#include <iostream>
#include "ConfigUtil.hpp"
#include "Socket.hpp"
#include <test/HttpTestServer.hpp>
#include <Poco/URI.h>
#include <chrono>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string>
#include <test/lokassert.hpp>
#if ENABLE_SSL
#include "Ssl.hpp"
#include <net/SslSocket.hpp>
#endif
#include <net/ServerSocket.hpp>
#include <net/DelaySocket.hpp>
#include <net/HttpRequest.hpp>
#include <FileUtil.hpp>
#include <Util.hpp>
class HttpRequestTests final
{
void testSimpleGetSync();
std::string _localUri;
SocketPoll _pollServerThread;
std::shared_ptr<ServerSocket> _socket;
std::shared_ptr<http::Session> _httpSession;
SocketPoll _poller;
bool _completed;
class ServerSocketFactory final : public SocketFactory
{
std::shared_ptr<Socket> create(const int physicalFd) override
{
return StreamSocket::create<StreamSocket>("localhost", physicalFd, false,
std::make_shared<ServerRequestHandler>());
}
};
public:
HttpRequestTests()
: _pollServerThread("HttpServerPoll")
, _poller("HttpSynReqPoll")
{
_poller.runOnClientThread();
std::map<std::string, std::string> logProperties;
const auto log_level = std::getenv("LOG_LEVEL");
if (log_level)
{
Log::initialize("fuz", log_level ? log_level : "error", isatty(fileno(stderr)), false,
logProperties);
}
std::shared_ptr<SocketFactory> factory = std::make_shared<ServerSocketFactory>();
int port = 9990;
for (int i = 0; i < 40; ++i, ++port)
{
// Try listening on this port.
_socket = ServerSocket::create(ServerSocket::Type::Local, port, Socket::Type::IPv4,
_pollServerThread, factory);
if (_socket)
break;
}
_localUri = "http://127.0.0.1:" + std::to_string(port);
_pollServerThread.startThread();
_pollServerThread.insertNewSocket(_socket);
_httpSession = http::Session::create(localUri());
if (!_httpSession)
throw std::runtime_error("Failed to create http::Session to " + localUri());
_httpSession->setTimeout(std::chrono::milliseconds(500));
_httpSession->setFinishedHandler(
[&](const std::shared_ptr<http::Session>&)
{
_completed = true;
return true;
});
}
~HttpRequestTests()
{
_pollServerThread.stop();
_socket.reset();
}
const std::string& localUri() const { return _localUri; }
std::shared_ptr<http::Session> session() const { return _httpSession; }
SocketPoll& poller() { return _poller; };
bool isCompleted() const { return _completed; };
void resetCompleted() { _completed = false; };
};
#define CHECK(X) \
do \
{ \
if (!(X)) \
{ \
assert(!(X)); \
__builtin_trap(); \
} \
} while (0)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
static HttpRequestTests test;
http::Request httpRequest("/inject/" + Util::bytesToHexString(data, size));
test.resetCompleted();
const std::shared_ptr<const http::Response> httpResponse =
test.session()->syncRequest(httpRequest, test.poller());
CHECK(httpResponse->done());
CHECK(test.isCompleted()); // The onFinished callback must always be called.
if (httpResponse->state() == http::Response::State::Complete)
{
CHECK(!httpResponse->statusLine().httpVersion().empty());
CHECK(!httpResponse->statusLine().reasonPhrase().empty());
CHECK(httpResponse->statusLine().statusCode() >= 100);
CHECK(httpResponse->statusLine().statusCode() < 600);
}
return 0;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View File

@ -1173,7 +1173,8 @@ private:
}
};
_response.reset(new Response(onFinished));
_response.reset();
_response = std::make_shared<Response>(onFinished);
_request = std::move(req);