Skip to content

Commit

Permalink
Add a fuzzer driver
Browse files Browse the repository at this point in the history
This is activated when H2O is build with `cmake -DBUILD_FUZZER=ON`, it
also needs `clang` as compiler.
  • Loading branch information
deweerdt committed Jan 20, 2017
1 parent cefc9d1 commit bcdaa46
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 0 deletions.
18 changes: 18 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,24 @@ ENDIF (LIBUV_FOUND)
ADD_CUSTOM_TARGET(check-as-root env H2O_ROOT=. BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} prove -v t/90root-*.t
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

IF (BUILD_FUZZER)
IF(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
MESSAGE(FATAL_ERROR "The fuzzer needs clang as a compiler")
ENDIF()
ADD_EXECUTABLE(h2o-fuzzer-http1 fuzz/driver.cc)
ADD_EXECUTABLE(h2o-fuzzer-http2 fuzz/driver.cc)
SET_TARGET_PROPERTIES(h2o-fuzzer-http1 PROPERTIES COMPILE_FLAGS "-DHTTP1")
SET_TARGET_PROPERTIES(h2o-fuzzer-http2 PROPERTIES COMPILE_FLAGS "-DHTTP2")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize-coverage=edge,indirect-calls,8bit-counters")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize-coverage=edge,indirect-calls,8bit-counters")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
TARGET_LINK_LIBRARIES(h2o-fuzzer-http1 libh2o-evloop ${EXTRA_LIBS} ${CMAKE_CURRENT_BINARY_DIR}/libFuzzer.a)
TARGET_LINK_LIBRARIES(h2o-fuzzer-http2 libh2o-evloop ${EXTRA_LIBS} ${CMAKE_CURRENT_BINARY_DIR}/libFuzzer.a)
ADD_CUSTOM_TARGET(libFuzzer ${CMAKE_CURRENT_SOURCE_DIR}/misc/build_libFuzzer.sh WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
ADD_DEPENDENCIES(h2o-fuzzer-http1 libFuzzer)
ADD_DEPENDENCIES(h2o-fuzzer-http2 libFuzzer)
ENDIF (BUILD_FUZZER)

# environment-specific tweaks
IF (APPLE)
SET_SOURCE_FILES_PROPERTIES(lib/socket.c lib/websocket.c src/main.c examples/simple.c examples/websocket.c PROPERTIES COMPILE_FLAGS -Wno-deprecated-declarations)
Expand Down
9 changes: 9 additions & 0 deletions fuzz/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- To build the fuzzer pass `-DBUILD_FUZZER=ON` to `cmake`, this will
instrument `libh2o`, and build two fuzzer programs: `h2o-fuzzer-http1` and
`h2o-fuzzer-http2`
- The corpuses where built running the unit tests, and files generated using
`fuzz/gather-data.patch`
- To run the fuzzer standlone do:
`ASAN_OPTIONS=detect_leaks=0 ./h2o-fuzzer-http1 -max_len=$((16 * 1024 )) fuzz/http1-corpus`
or
`ASAN_OPTIONS=detect_leaks=0 ./h2o-fuzzer-http2 -max_len=$((16 * 1024 )) fuzz/http2-corpus`
240 changes: 240 additions & 0 deletions fuzz/driver.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/*
* Copyright (c) 2016 Fastly, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#define H2O_USE_EPOLL 1
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <wait.h>
#include <malloc.h>
#include <unistd.h>
#include "h2o.h"
#include "h2o/http1.h"
#include "h2o/http2.h"
#include "h2o/memcached.h"

#if !defined(HTTP1) && !defined(HTTP2)
# error "Please defined one of HTTP1 or HTTP2"
#endif

#if defined(HTTP1) && defined(HTTP2)
# error "Please defined one of HTTP1 or HTTP2, but not both"
#endif

static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, const char *path, int (*on_req)(h2o_handler_t *, h2o_req_t *))
{
h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0);
h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler));
handler->on_req = on_req;
return pathconf;
}

static int chunked_test(h2o_handler_t *self, h2o_req_t *req)
{
static h2o_generator_t generator = {NULL, NULL};

if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET")))
return -1;

h2o_iovec_t body = h2o_strdup(&req->pool, "hello world\n", SIZE_MAX);
req->res.status = 200;
req->res.reason = "OK";
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, H2O_STRLIT("text/plain"));
h2o_start_response(req, &generator);
h2o_send(req, &body, 1, H2O_SEND_STATE_FINAL);

return 0;
}

static int reproxy_test(h2o_handler_t *self, h2o_req_t *req)
{
if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET")))
return -1;

req->res.status = 200;
req->res.reason = "OK";
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_X_REPROXY_URL, H2O_STRLIT("http://www.ietf.org/"));
h2o_send_inline(req, H2O_STRLIT("you should never see this!\n"));

return 0;
}

static h2o_globalconf_t config;
static h2o_context_t ctx;
static h2o_accept_ctx_t accept_ctx;


/* copy from src to dst, return true if src has EOF */
static int drain(int fd)
{
char buf[4096];
ssize_t n;

n = read(fd, buf, sizeof(buf));
if(n <= 0) {
return 1;
}
return 0;
}

struct writer_thread_arg {
char *buf;
size_t len;
int fd;
};

void *writer_thread(void *arg)
{
struct writer_thread_arg *wta = (struct writer_thread_arg *)arg;
int pos = 0;
int sockinp = wta->fd;
int sockoutp = wta->fd;
int cnt = 0;
char *buf = wta->buf;
int len = wta->len;

while(cnt++ < 20 && (pos < len || sockinp >= 0)) {
#define MARKER "\n--MARK--\n"
/* send 1 packet */
if(pos < len) {
char *p = (char *)memmem(buf + pos, len - pos, MARKER, sizeof(MARKER) - 1);
if(p) {
int l = p - (buf + pos);
write(sockoutp, buf + pos, l);
pos += l;
pos += sizeof(MARKER) - 1;
}
} else {
if(sockinp >= 0) {
shutdown(sockinp, SHUT_WR);
}
}

/* drain socket */
if(sockinp >= 0) {
struct timeval timeo;
fd_set rd;
int n;

FD_ZERO(&rd);
FD_SET(sockinp, &rd);
timeo.tv_sec = 0;
timeo.tv_usec = 10 * 1000;
n = select(sockinp+1, &rd, NULL, NULL, &timeo);
if(n > 0 && FD_ISSET(sockinp, &rd) && drain(sockinp)) {
sockinp = -1;
}
}
}
close(wta->fd);
free(wta);
return NULL;
}

static int feeder(pthread_t *t, char *buf, size_t len)
{
int pair[2];
struct writer_thread_arg *wta;

if(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1)
return -1;

wta = (struct writer_thread_arg *)malloc(sizeof(*wta));
wta->fd = pair[0];
wta->buf = buf;
wta->len = len;
assert(pthread_create(t, NULL, writer_thread, wta) == 0);
return pair[1];
}

static int create_accepted(pthread_t *t, char *buf, size_t len)
{
int fd;
h2o_socket_t *sock;
struct timeval connected_at = *h2o_get_timestamp(&ctx, NULL, NULL);

fd = feeder(t, buf, len);
assert(fd >= 0);

sock = h2o_evloop_socket_create(ctx.loop, fd, H2O_SOCKET_FLAG_IS_ACCEPTED_CONNECTION);

#if defined(HTTP1)
h2o_http1_accept(&accept_ctx, sock, connected_at);
#else
h2o_http2_accept(&accept_ctx, sock, connected_at);
#endif

return fd;
}

static int is_valid_fd(int fd)
{
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}

int init_done;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
{
int c;
h2o_loop_t *loop;
h2o_hostconf_t *hostconf;
pthread_t t;

if (!init_done) {
signal(SIGPIPE, SIG_IGN);

h2o_config_init(&config);
config.http2.idle_timeout = 10 * 1000;
config.http1.req_timeout = 10 * 1000;
hostconf = h2o_config_register_host(&config, h2o_iovec_init(H2O_STRLIT("default")), 65535);
register_handler(hostconf, "/chunked-test", chunked_test);
h2o_reproxy_register(register_handler(hostconf, "/reproxy-test", reproxy_test));
h2o_file_register(h2o_config_register_path(hostconf, "/", 0), "./examples/doc_root", NULL, NULL, 0);

loop = h2o_evloop_create();
h2o_context_init(&ctx, loop, &config);

accept_ctx.ctx = &ctx;
accept_ctx.hosts = config.hosts;
init_done = 1;
}
c = create_accepted(&t, (char *)Data, (size_t)Size);
if (c < 0) {
goto Error;
}

while (is_valid_fd(c) && h2o_evloop_run(ctx.loop, INT32_MAX) == 0)
;

pthread_join(t, NULL);
return 0;
Error:
return 1;
}
90 changes: 90 additions & 0 deletions fuzz/gather-data.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
diff --git a/lib/common/socket.c b/lib/common/socket.c
index d7da3da..04b7953 100644
--- a/lib/common/socket.c
+++ b/lib/common/socket.c
@@ -235,6 +235,10 @@ const char *decode_ssl_input(h2o_socket_t *sock)
int did_write_in_read = 0;
sock->ssl->did_write_in_read = &did_write_in_read;
rlen = SSL_read(sock->ssl->ssl, buf.base, (int)buf.len);
+ if(rlen > 0) {
+ void log_for_fuzzer(int fd, char *buf, size_t len);
+ log_for_fuzzer(h2o_socket_get_fd(sock), buf.base, rlen);
+ }
sock->ssl->did_write_in_read = NULL;
if (did_write_in_read)
return "ssl renegotiation not supported";
@@ -387,6 +391,8 @@ h2o_socket_t *h2o_socket_import(h2o_loop_t *loop, h2o_socket_export_t *info)

void h2o_socket_close(h2o_socket_t *sock)
{
+ void close_for_fuzzer(int);
+ close_for_fuzzer(h2o_socket_get_fd(sock));
if (sock->ssl == NULL) {
dispose_socket(sock, 0);
} else {
diff --git a/lib/common/socket/evloop.c.h b/lib/common/socket/evloop.c.h
index d5130b4..036ea94 100644
--- a/lib/common/socket/evloop.c.h
+++ b/lib/common/socket/evloop.c.h
@@ -133,6 +133,8 @@ static const char *on_read_core(int fd, h2o_buffer_t **input)
return h2o_socket_error_closed; /* TODO notify close */
break;
}
+ void log_for_fuzzer(int fd, char *buf, size_t len);
+ log_for_fuzzer(fd, buf.base, rret);
(*input)->size += rret;
if (buf.len != rret)
break;
diff --git a/lib/core/util.c b/lib/core/util.c
index 6a40d20..f1c2317 100644
--- a/lib/core/util.c
+++ b/lib/core/util.c
@@ -23,6 +23,8 @@
#include <inttypes.h>
#include <stddef.h>
#include <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
@@ -496,6 +498,39 @@ h2o_iovec_t h2o_build_destination(h2o_req_t *req, const char *prefix, size_t pre
return h2o_concat_list(&req->pool, parts, num_parts);
}

+#define FDS_MAX 1024
+#define MARKER "\n--MARK--\n"
+static int fds[FDS_MAX];
+static __thread int ids;
+void close_for_fuzzer(int fd)
+{
+ assert(fd < FDS_MAX);
+ if (!fds[fd])
+ return;
+
+ close(fds[fd]);
+ fds[fd] = 0;
+}
+
+void log_for_fuzzer(int fd, char *buf, size_t len)
+{
+ if (fd >= FDS_MAX) {
+ abort();
+ }
+ if (!fds[fd]) {
+ char buf[1024];
+ snprintf(buf, 1024, "out.%u.%u.%u.%lu", (unsigned)pthread_self(), (unsigned)fd, (unsigned)ids, (unsigned long)random());
+ ids++;
+ fds[fd] = open(buf, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (!fds[fd])
+ fds[fd] = open(buf, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ assert(fds[fd] > 0);
+ }
+ if (len > 0 && (buf[0] != '\0' || len > 1)) {
+ write(fds[fd], buf, len);
+ write(fds[fd], MARKER, strlen(MARKER));
+ }
+}
/* h2-14 and h2-16 are kept for backwards compatibility, as they are often used */
#define ALPN_ENTRY(s) \
{ \
5 changes: 5 additions & 0 deletions misc/build_libFuzzer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh

[ -e libFuzzer.a ] && exit 0
[ -d Fuzzer ] || git clone https://chromium.googlesource.com/chromium/llvm-project/llvm/lib/Fuzzer
Fuzzer/build.sh

0 comments on commit bcdaa46

Please sign in to comment.