Merge commit 'c992cb4e42cc223f67ede0e48d7ff3f4947af0c6' as 'test/compatibility/C/uWebSockets'

This commit is contained in:
Benjamin Sergeant
2020-01-04 15:41:03 -08:00
68 changed files with 9564 additions and 0 deletions

View File

@ -0,0 +1,39 @@
/* This is a fuzz test of the websocket extensions parser */
#define WIN32_EXPORT
#include <cstdio>
#include <string>
/* We test the websocket extensions parser */
#include "../src/WebSocketExtensions.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
{
uWS::ExtensionsNegotiator<true> extensionsNegotiator(uWS::Options::PERMESSAGE_DEFLATE);
extensionsNegotiator.readOffer({(char *) data, size});
extensionsNegotiator.generateOffer();
extensionsNegotiator.getNegotiatedOptions();
}
{
uWS::ExtensionsNegotiator<true> extensionsNegotiator(uWS::Options::NO_OPTIONS);
extensionsNegotiator.readOffer({(char *) data, size});
extensionsNegotiator.generateOffer();
extensionsNegotiator.getNegotiatedOptions();
}
{
uWS::ExtensionsNegotiator<true> extensionsNegotiator(uWS::Options::CLIENT_NO_CONTEXT_TAKEOVER);
extensionsNegotiator.readOffer({(char *) data, size});
extensionsNegotiator.generateOffer();
extensionsNegotiator.getNegotiatedOptions();
}
return 0;
}

View File

@ -0,0 +1,20 @@
/* This is a fuzz test of the websocket handshake generator */
#define WIN32_EXPORT
#include <cstdio>
#include <string>
/* We test the websocket handshake generator */
#include "../src/WebSocketHandshake.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char output[28];
if (size >= 24) {
uWS::WebSocketHandshake::generate((char *) data, output);
}
return 0;
}

View File

@ -0,0 +1,124 @@
/* This is a fuzz test of the http parser */
#define WIN32_EXPORT
#include "helpers.h"
/* We test the websocket parser */
#include "../src/HttpParser.h"
/* And the router */
#include "../src/HttpRouter.h"
struct StaticData {
struct RouterData {
};
uWS::HttpRouter<RouterData> router;
StaticData() {
router.add({"get"}, "/:hello/:hi", [](auto *h) mutable {
auto [paramsTop, params] = h->getParameters();
/* Something is horribly wrong */
if (paramsTop != 1 || !params[0].length() || !params[1].length()) {
exit(-1);
}
/* This route did handle it */
return true;
});
router.add({"post"}, "/:hello/:hi/*", [](auto *h) mutable {
auto [paramsTop, params] = h->getParameters();
/* Something is horribly wrong */
if (paramsTop != 1 || !params[0].length() || !params[1].length()) {
exit(-1);
}
/* This route did handle it */
return true;
});
router.add({"get"}, "/*", [](auto *h) mutable {
auto [paramsTop, params] = h->getParameters();
/* Something is horribly wrong */
if (paramsTop != -1) {
exit(-1);
}
/* This route did not handle it */
return false;
});
router.add({"get"}, "/hi", [](auto *h) mutable {
auto [paramsTop, params] = h->getParameters();
/* Something is horribly wrong */
if (paramsTop != -1) {
exit(-1);
}
/* This route did handle it */
return true;
});
}
} staticData;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
/* Create parser */
uWS::HttpParser httpParser;
/* User data */
void *user = (void *) 13;
/* Iterate the padded fuzz as chunks */
makeChunked(makePadded(data, size), size, [&httpParser, user](const uint8_t *data, size_t size) {
/* We need at least 1 byte post padding */
if (size) {
size--;
} else {
/* We might be given zero length chunks */
return;
}
/* Parse it */
httpParser.consumePostPadded((char *) data, size, user, [](void *s, uWS::HttpRequest *httpRequest) -> void * {
readBytes(httpRequest->getHeader(httpRequest->getUrl()));
readBytes(httpRequest->getMethod());
readBytes(httpRequest->getQuery());
/* Route the method and URL in two passes */
staticData.router.getUserData() = {};
if (!staticData.router.route(httpRequest->getMethod(), httpRequest->getUrl())) {
/* It was not handled */
return nullptr;
}
for (auto p : *httpRequest) {
}
/* Return ok */
return s;
}, [](void *user, std::string_view data, bool fin) -> void * {
/* Return ok */
return user;
}, [](void *user) {
/* Return break */
return nullptr;
});
});
return 0;
}

View File

@ -0,0 +1,21 @@
# You can select which sanitizer to use by setting this
SANITIZER ?= address
# These are set by OSS-Fuzz, we default to AddressSanitizer
CXXFLAGS ?= -DLIBUS_NO_SSL -fsanitize=$(SANITIZER),fuzzer
CFLAGS ?= -DLIBUS_NO_SSL
OUT ?= .
oss-fuzz:
# "Unit tests"
$(CXX) $(CXXFLAGS) -std=c++17 -O3 WebSocket.cpp -o $(OUT)/WebSocket $(LIB_FUZZING_ENGINE)
$(CXX) $(CXXFLAGS) -std=c++17 -O3 Http.cpp -o $(OUT)/Http $(LIB_FUZZING_ENGINE)
$(CXX) $(CXXFLAGS) -std=c++17 -O3 PerMessageDeflate.cpp -o $(OUT)/PerMessageDeflate $(LIB_FUZZING_ENGINE) -lz
# "Integration tests"
$(CC) $(CFLAGS) -DLIBUS_NO_SSL -c -O3 uSocketsMock.c
$(CXX) $(CXXFLAGS) -std=c++17 -O3 -DLIBUS_NO_SSL -I../src -I../uSockets/src MockedHelloWorld.cpp uSocketsMock.o -lz -o $(OUT)/MockedHelloWorld $(LIB_FUZZING_ENGINE)
$(CXX) $(CXXFLAGS) -std=c++17 -O3 -DLIBUS_NO_SSL -I../src -I../uSockets/src MockedEchoServer.cpp uSocketsMock.o -lz -o $(OUT)/MockedEchoServer $(LIB_FUZZING_ENGINE)
broken:
# Too small tests, failing coverage test
$(CXX) $(CXXFLAGS) -std=c++17 -O3 Extensions.cpp -o $(OUT)/Extensions $(LIB_FUZZING_ENGINE)
$(CXX) $(CXXFLAGS) -std=c++17 -O3 Handshake.cpp -o $(OUT)/Handshake $(LIB_FUZZING_ENGINE)

View File

@ -0,0 +1,75 @@
#include "App.h"
#include "helpers.h"
/* This function pushes data to the uSockets mock */
extern "C" void us_loop_read_mocked_data(struct us_loop *loop, char *data, unsigned int size);
uWS::TemplatedApp<false> *app;
us_listen_socket_t *listenSocket;
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
/* ws->getUserData returns one of these */
struct PerSocketData {
int nothing;
};
/* Very simple WebSocket echo server */
app = new uWS::TemplatedApp<false>(uWS::App().ws<PerSocketData>("/*", {
/* Settings */
.compression = uWS::SHARED_COMPRESSOR,
/* We want this to be low so that we can hit it, yet bigger than 256 */
.maxPayloadLength = 300,
.idleTimeout = 10,
/* Handlers */
.open = [](auto *ws, auto *req) {
if (req->getHeader("close_me").length()) {
ws->close();
} else if (req->getHeader("end_me").length()) {
ws->end(1006);
}
},
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
if (message.length() > 300) {
/* Inform the sanitizer of the fault */
fprintf(stderr, "Too long message passed\n");
free((void *) -1);
}
if (message.length() && message[0] == 'C') {
ws->close();
} else if (message.length() && message[0] == 'E') {
ws->end(1006);
} else {
ws->send(message, opCode, true);
}
},
.drain = [](auto *ws) {
/* Check getBufferedAmount here */
},
.ping = [](auto *ws) {
},
.pong = [](auto *ws) {
},
.close = [](auto *ws, int code, std::string_view message) {
}
}).listen(9001, [](us_listen_socket_t *listenSocket) {
if (listenSocket) {
std::cout << "Listening on port " << 9001 << std::endl;
::listenSocket = listenSocket;
}
}));
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
us_loop_read_mocked_data((struct us_loop *) uWS::Loop::get(), (char *) makePadded(data, size), size);
return 0;
}

View File

@ -0,0 +1,47 @@
#include "App.h"
#include "helpers.h"
/* This function pushes data to the uSockets mock */
extern "C" void us_loop_read_mocked_data(struct us_loop *loop, char *data, unsigned int size);
uWS::TemplatedApp<false> *app;
us_listen_socket_t *listenSocket;
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
app = new uWS::TemplatedApp<false>(uWS::App().get("/*", [](auto *res, auto *req) {
if (req->getHeader("use_write").length()) {
res->writeStatus("200 OK")->writeHeader("write", "true")->write("Hello");
res->write(" world!");
res->end();
} else if (req->getQuery().length()) {
res->close();
} else {
res->end("Hello world!");
}
})/*.post("/*", [](auto *res, auto *req) {
res->onAborted([]() {
});
res->onData([res](std::string_view chunk, bool isEnd) {
if (isEnd) {
res->end(chunk);
}
});
})*/.listen(9001, [](us_listen_socket_t *listenSocket) {
if (listenSocket) {
std::cout << "Listening on port " << 9001 << std::endl;
::listenSocket = listenSocket;
}
}));
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
us_loop_read_mocked_data((struct us_loop *) uWS::Loop::get(), (char *) makePadded(data, size), size);
return 0;
}

View File

@ -0,0 +1,38 @@
/* This is a fuzz test of the permessage-deflate module */
#define WIN32_EXPORT
#include <cstdio>
#include <string>
/* We test the permessage deflate module */
#include "../src/PerMessageDeflate.h"
#include "helpers.h"
struct StaticData {
uWS::ZlibContext zlibContext;
uWS::InflationStream inflationStream;
uWS::DeflationStream deflationStream;
} staticData;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
/* Why is this padded? */
makeChunked(makePadded(data, size), size, [](const uint8_t *data, size_t size) {
std::string_view inflation = staticData.inflationStream.inflate(&staticData.zlibContext, std::string_view((char *) data, size), 256);
if (inflation.length() > 256) {
/* Cause ASAN to freak out */
delete (int *) (void *) 1;
}
});
makeChunked(makePadded(data, size), size, [](const uint8_t *data, size_t size) {
/* Always reset */
staticData.deflationStream.deflate(&staticData.zlibContext, std::string_view((char *) data, size), true);
});
return 0;
}

View File

@ -0,0 +1,29 @@
# Fuzz-testing of various parsers and mocked examples
A secure web server must be capable of receiving mass amount of malicious input without misbehaving or performing illegal actions, such as stepping outside of a memory block or otherwise spilling the beans.
### Continuous fuzzing under various sanitizers is done as part of the [Google OSS-Fuzz](https://github.com/google/oss-fuzz#oss-fuzz---continuous-fuzzing-for-open-source-software) project:
* UndefinedBehaviorSanitizer
* AddressSanitizer
* MemorySanitizer
### Currently the following parts are individually fuzzed:
* WebSocket handshake generator
* WebSocket message parser
* WebSocket extensions parser & negotiator
* WebSocket permessage-deflate compression/inflation helper
* Http parser
* Http method/url router
### While entire (mocked) examples are fuzzed:
* HelloWorld
* EchoServer
No defects or issues are left unfixed, covered up or otherwise neglected. In fact we **cannot** cover up security issues as OSS-Fuzz automatically and publicly reports security issues as they happen.
Currently we are at ~80% total fuzz coverage and OSS-Fuzz is reporting **zero** issues whatsoever. The goal is to approach 90% total coverage.
### Security awards
Google have sent us thousands of USD for the integration with OSS-Fuzz - we continue working on bettering the testing with every new release.

View File

@ -0,0 +1,59 @@
/* This is a fuzz test of the websocket parser */
#define WIN32_EXPORT
#include "helpers.h"
/* We test the websocket parser */
#include "../src/WebSocketProtocol.h"
struct Impl {
static bool refusePayloadLength(uint64_t length, uWS::WebSocketState<true> *wState, void *s) {
/* We need a limit */
if (length > 16000) {
return true;
}
/* Return ok */
return false;
}
static bool setCompressed(uWS::WebSocketState<true> *wState, void *s) {
/* We support it */
return true;
}
static void forceClose(uWS::WebSocketState<true> *wState, void *s) {
}
static bool handleFragment(char *data, size_t length, unsigned int remainingBytes, int opCode, bool fin, uWS::WebSocketState<true> *webSocketState, void *s) {
if (opCode == uWS::TEXT) {
if (!uWS::protocol::isValidUtf8((unsigned char *)data, length)) {
/* Return break */
return true;
}
} else if (opCode == uWS::CLOSE) {
uWS::protocol::parseClosePayload((char *)data, length);
}
/* Return ok */
return false;
}
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
/* Create the parser state */
uWS::WebSocketState<true> state;
makeChunked(makePadded(data, size), size, [&state](const uint8_t *data, size_t size) {
/* Parse it */
uWS::WebSocketProtocol<true, Impl>::consume((char *) data, size, &state, nullptr);
});
return 0;
}

View File

@ -0,0 +1,51 @@
#ifndef HELPERS_H
#define HELPERS_H
/* Common helpers for fuzzing */
#include <functional>
#include <string_view>
#include <cstring>
/* We use this to pad the fuzz */
static inline const uint8_t *makePadded(const uint8_t *data, size_t size) {
static int paddedLength = 512 * 1024;
static char *padded = new char[128 + paddedLength + 128];
/* Increase landing area if required */
if (paddedLength < size) {
delete [] padded;
paddedLength = size;
padded = new char [128 + paddedLength + 128];
}
memcpy(padded + 128, data, size);
return (uint8_t *) padded + 128;
}
/* Splits the fuzz data in one or many chunks */
static inline void makeChunked(const uint8_t *data, size_t size, std::function<void(const uint8_t *data, size_t size)> cb) {
/* First byte determines chunk size; 0 is all that remains, 1-255 is small chunk */
for (int i = 0; i < size; ) {
unsigned int chunkSize = data[i++];
if (!chunkSize) {
chunkSize = size - i;
} else {
chunkSize = std::min<int>(chunkSize, size - i);
}
cb(data + i, chunkSize);
i += chunkSize;
}
}
/* Reads all bytes to trigger invalid reads */
static inline void readBytes(std::string_view s) {
volatile int sum = 0;
for (int i = 0; i < s.size(); i++) {
sum += s[i];
}
}
#endif

View File

@ -0,0 +1,285 @@
/* uSockets is entierly opaque so we can use the real header straight up */
#include "../uSockets/src/libusockets.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdalign.h>
#include <string.h>
struct us_loop_t {
/* We only support one listen socket */
alignas(16) struct us_listen_socket_t *listen_socket;
/* The list of closed sockets */
struct us_socket_t *close_list;
};
struct us_loop_t *us_create_loop(void *hint, void (*wakeup_cb)(struct us_loop_t *loop), void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop), unsigned int ext_size) {
struct us_loop_t *loop = (struct us_loop_t *) malloc(sizeof(struct us_loop_t) + ext_size);
loop->listen_socket = 0;
loop->close_list = 0;
return loop;
}
void us_loop_free(struct us_loop_t *loop) {
free(loop);
}
void *us_loop_ext(struct us_loop_t *loop) {
return loop + 1;
}
void us_loop_run(struct us_loop_t *loop) {
}
struct us_socket_context_t {
alignas(16) struct us_loop_t *loop;
struct us_socket_t *(*on_open)(struct us_socket_t *s, int is_client, char *ip, int ip_length);
struct us_socket_t *(*on_close)(struct us_socket_t *s);
struct us_socket_t *(*on_data)(struct us_socket_t *s, char *data, int length);
struct us_socket_t *(*on_writable)(struct us_socket_t *s);
struct us_socket_t *(*on_timeout)(struct us_socket_t *s);
struct us_socket_t *(*on_end)(struct us_socket_t *s);
};
struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t *loop, int ext_size, struct us_socket_context_options_t options) {
struct us_socket_context_t *socket_context = (struct us_socket_context_t *) malloc(sizeof(struct us_socket_context_t) + ext_size);
socket_context->loop = loop;
//printf("us_create_socket_context: %p\n", socket_context);
return socket_context;
}
void us_socket_context_free(int ssl, struct us_socket_context_t *context) {
//printf("us_socket_context_free: %p\n", context);
free(context);
}
void us_socket_context_on_open(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_open)(struct us_socket_t *s, int is_client, char *ip, int ip_length)) {
context->on_open = on_open;
}
void us_socket_context_on_close(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_close)(struct us_socket_t *s)) {
context->on_close = on_close;
}
void us_socket_context_on_data(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_data)(struct us_socket_t *s, char *data, int length)) {
context->on_data = on_data;
}
void us_socket_context_on_writable(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_writable)(struct us_socket_t *s)) {
context->on_writable = on_writable;
}
void us_socket_context_on_timeout(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_timeout)(struct us_socket_t *s)) {
context->on_timeout = on_timeout;
}
void us_socket_context_on_end(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_end)(struct us_socket_t *s)) {
context->on_end = on_end;
}
void *us_socket_context_ext(int ssl, struct us_socket_context_t *context) {
return context + 1;
}
struct us_listen_socket_t {
int socket_ext_size;
struct us_socket_context_t *context;
};
struct us_listen_socket_t *us_socket_context_listen(int ssl, struct us_socket_context_t *context, const char *host, int port, int options, int socket_ext_size) {
struct us_listen_socket_t *listen_socket = (struct us_listen_socket_t *) malloc(sizeof(struct us_listen_socket_t));
listen_socket->socket_ext_size = socket_ext_size;
listen_socket->context = context;
context->loop->listen_socket = listen_socket;
return listen_socket;
}
void us_listen_socket_close(int ssl, struct us_listen_socket_t *ls) {
free(ls);
}
struct us_socket_t {
alignas(16) struct us_socket_context_t *context;
int closed;
int shutdown;
int wants_writable;
//struct us_socket_t *next;
};
struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_t *context, const char *host, int port, int options, int socket_ext_size) {
//printf("us_socket_context_connect\n");
return 0;
}
struct us_loop_t *us_socket_context_loop(int ssl, struct us_socket_context_t *context) {
return context->loop;
}
struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s, int ext_size) {
struct us_socket_t *new_s = (struct us_socket_t *) realloc(s, sizeof(struct us_socket_t) + ext_size);
new_s->context = context;
return new_s;
}
struct us_socket_context_t *us_create_child_socket_context(int ssl, struct us_socket_context_t *context, int context_ext_size) {
/* We simply create a new context in this mock */
struct us_socket_context_options_t options = {};
struct us_socket_context_t *child_context = us_create_socket_context(ssl, context->loop, context_ext_size, options);
return child_context;
}
int us_socket_write(int ssl, struct us_socket_t *s, const char *data, int length, int msg_more) {
if (!length) {
return 0;
}
/* Last byte determines if we send everything or not, to stress the buffering mechanism */
if (data[length - 1] % 2 == 0) {
/* Send only half, but first set our outgoing flag */
s->wants_writable = 1;
return length / 2;
}
/* Send everything */
return length;
}
void us_socket_timeout(int ssl, struct us_socket_t *s, unsigned int seconds) {
}
void *us_socket_ext(int ssl, struct us_socket_t *s) {
return s + 1;
}
struct us_socket_context_t *us_socket_context(int ssl, struct us_socket_t *s) {
return s->context;
}
void us_socket_flush(int ssl, struct us_socket_t *s) {
}
void us_socket_shutdown(int ssl, struct us_socket_t *s) {
s->shutdown = 1;
}
int us_socket_is_shut_down(int ssl, struct us_socket_t *s) {
return s->shutdown;
}
int us_socket_is_closed(int ssl, struct us_socket_t *s) {
return s->closed;
}
struct us_socket_t *us_socket_close(int ssl, struct us_socket_t *s) {
if (!us_socket_is_closed(0, s)) {
/* Emit close event */
s = s->context->on_close(s);
}
/* We are now closed */
s->closed = 1;
/* Add us to the close list */
return s;
}
void us_socket_remote_address(int ssl, struct us_socket_t *s, char *buf, int *length) {
printf("us_socket_remote_address\n");
}
/* We expose this function to let fuzz targets push data to uSockets */
void us_loop_read_mocked_data(struct us_loop_t *loop, char *data, unsigned int size) {
/* We are unwound so let's free all closed polls here */
/* We have one listen socket */
int socket_ext_size = loop->listen_socket->socket_ext_size;
/* Create a socket with information from the listen socket */
struct us_socket_t *s = (struct us_socket_t *) malloc(sizeof(struct us_socket_t) + socket_ext_size);
s->context = loop->listen_socket->context;
s->closed = 0;
s->shutdown = 0;
s->wants_writable = 0;
/* Emit open event */
s = s->context->on_open(s, 0, 0, 0);
if (!us_socket_is_closed(0, s) && !us_socket_is_shut_down(0, s)) {
/* Trigger writable event if we want it */
if (s->wants_writable) {
s->wants_writable = 0;
s = s->context->on_writable(s);
/* Check if we closed inside of writable */
if (us_socket_is_closed(0, s) || us_socket_is_shut_down(0, s)) {
goto done;
}
}
/* Loop over the data, emitting it in chunks of 0-255 bytes */
for (int i = 0; i < size; ) {
unsigned char chunkLength = data[i++];
if (i + chunkLength > size) {
chunkLength = size - i;
}
/* Copy the data chunk to a properly padded buffer */
static char *paddedBuffer;
if (!paddedBuffer) {
paddedBuffer = malloc(128 + 255 + 128);
memset(paddedBuffer, 0, 128 + 255 + 128);
}
memcpy(paddedBuffer + 128, data + i, chunkLength);
/* Emit a bunch of data events here */
s = s->context->on_data(s, paddedBuffer + 128, chunkLength);
if (us_socket_is_closed(0, s) || us_socket_is_shut_down(0, s)) {
break;
}
/* Also trigger it here */
if (s->wants_writable) {
s->wants_writable = 0;
s = s->context->on_writable(s);
/* Check if we closed inside of writable */
if (us_socket_is_closed(0, s) || us_socket_is_shut_down(0, s)) {
goto done;
}
}
i += chunkLength;
}
}
done:
if (!us_socket_is_closed(0, s)) {
/* Emit close event */
s = s->context->on_close(s);
}
/* Free the socket */
free(s);
}