fuzzer: move to using external curl-fuzzer

Use the external curl-fuzzer repository for fuzzing.

Closes #1923
This commit is contained in:
Max Dymond 2017-09-25 10:59:18 +01:00 committed by Daniel Stenberg
parent 120d963a64
commit 4f38db1d28
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
32 changed files with 20 additions and 1145 deletions

View File

@ -156,18 +156,15 @@ script:
fi
- |
if [ "$T" = "fuzzer" ]; then
export CC=clang
export CXX=clang++
export CFLAGS="-fsanitize=address"
# Download the fuzzer to a temporary folder
./tests/fuzz/download_fuzzer.sh /tmp/curl_fuzzer
# Specifically use libstdc++ for travis as libc++ is not installed.
# This is ok because we're not compiling against libFuzzer.
export CXXFLAGS="-fsanitize=address -stdlib=libstdc++"
./configure --disable-shared --enable-debug --enable-maintainer-mode
make
cd tests/fuzz
make clean
make check
export CURLSRC=$PWD
# Run the mainline fuzzer test
pushd /tmp/curl_fuzzer
./mainline.sh ${CURLSRC}
popd
fi
# whitelist branches to avoid testing feature branches twice (as branch and as pull request)

View File

@ -210,9 +210,6 @@ test-am:
endif
fuzzer:
@(cd tests/fuzz; $(MAKE) all)
examples:
@(cd docs/examples; $(MAKE) check)

View File

@ -3974,7 +3974,6 @@ AC_CONFIG_FILES([Makefile \
tests/server/Makefile \
tests/libtest/Makefile \
tests/unit/Makefile \
tests/fuzz/Makefile \
packages/Makefile \
packages/Win32/Makefile \
packages/Win32/cygwin/Makefile \

View File

@ -53,7 +53,7 @@ DIST_UNIT = unit
endif
SUBDIRS = certs data server libtest $(BUILD_UNIT)
DIST_SUBDIRS = $(SUBDIRS) $(DIST_UNIT) fuzz
DIST_SUBDIRS = $(SUBDIRS) $(DIST_UNIT)
PERLFLAGS = -I$(srcdir)

View File

@ -1,56 +0,0 @@
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.haxx.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
###########################################################################
AUTOMAKE_OPTIONS = foreign nostdinc
# Specify our include paths here, and do it relative to $(top_srcdir) and
# $(top_builddir), to ensure that these paths which belong to the library
# being currently built and tested are searched before the library which
# might possibly already be installed in the system.
#
# $(top_srcdir)/include is for libcurl's external include files
# $(top_builddir)/lib is for libcurl's generated lib/curl_config.h file
# $(top_srcdir)/lib for libcurl's lib/curl_setup.h and other "borrowed" files
AM_CXXFLAGS = -I$(top_srcdir)/include \
-I$(top_builddir)/lib \
-I$(top_srcdir)/lib \
-I$(top_srcdir)/tests/fuzz
LIBS = -lpthread -lm
# Run e.g. "make all LIB_FUZZING_ENGINE=/path/to/libFuzzer.a"
# to link the fuzzer(s) against a real fuzzing engine.
#
# OSS-Fuzz will define its own value for LIB_FUZZING_ENGINE.
LIB_FUZZING_ENGINE ?= libstandaloneengine.a
LDADD = $(top_builddir)/lib/libcurl.la \
$(LIB_FUZZING_ENGINE) @LDFLAGS@ @LIBCURL_LIBS@
# Makefile.inc provides neat definitions
include Makefile.inc
checksrc:
@PERL@ $(top_srcdir)/lib/checksrc.pl $(srcdir)/*.cc
noinst_PROGRAMS = $(FUZZPROGS)
noinst_LIBRARIES = $(FUZZLIBS)

View File

@ -1,15 +0,0 @@
FUZZPROGS = curl_fuzzer
FUZZLIBS = libstandaloneengine.a
curl_fuzzer_SOURCES = curl_fuzzer.cc
curl_fuzzer_CXXFLAGS = $(AM_CXXFLAGS)
libstandaloneengine_a_SOURCES = standalone_fuzz_target_runner.cc
libstandaloneengine_a_CXXFLAGS = $(AM_CXXFLAGS)
# Some more targets.
zip:
zip -q -r curl_fuzzer_seed_corpus.zip curl_fuzz_data
check: all
./curl_fuzzer curl_fuzz_data/*

View File

@ -1,21 +1,8 @@
Fuzz tests
==========
The goal is to add tests for *ALL* protocols supported in libcurl.
The fuzzing tests for curl have been moved to a separate repository:
Building the fuzz target
========================
From the CURL root directory:
https://github.com/curl/curl-fuzzer
export CC=clang-5.0
export CXX=clang++-5.0
export CFLAGS="-fsanitize=address -fsanitize-address-use-after-scope -fsanitize-coverage=trace-pc-guard,trace-cmp"
export CXXFLAGS="-fsanitize=address -fsanitize-address-use-after-scope -fsanitize-coverage=trace-pc-guard,trace-cmp -stdlib=libc++"
./configure --disable-shared --enable-debug --enable-maintainer-mode
make -sj
cd tests/fuzz
(optional) export LIB_FUZZING_ENGINE=<path to libFuzzer.a>
make check
More information on how to get started with curl fuzz testing can be found there.

View File

@ -1,96 +0,0 @@
#!/usr/bin/env python
#
# Common corpus functions
import logging
import struct
log = logging.getLogger(__name__)
class BaseType(object):
TYPE_URL = 1
TYPE_RSP1 = 2
TYPE_USERNAME = 3
TYPE_PASSWORD = 4
TYPE_POSTFIELDS = 5
TYPE_HEADER = 6
TYPE_COOKIE = 7
TYPE_UPLOAD1 = 8
TYPE_RANGE = 9
TYPE_CUSTOMREQUEST = 10
TYPE_MAIL_RECIPIENT = 11
TYPE_MAIL_FROM = 12
class TLVEncoder(BaseType):
def __init__(self, output):
self.output = output
def write_string(self, tlv_type, wstring):
data = wstring.encode("utf-8")
self.write_tlv(tlv_type, len(data), data)
def write_bytes(self, tlv_type, bytedata):
self.write_tlv(tlv_type, len(bytedata), bytedata)
def maybe_write_string(self, tlv_type, wstring):
if wstring is not None:
self.write_string(tlv_type, wstring)
def write_tlv(self, tlv_type, tlv_length, tlv_data=None):
log.debug("Writing TLV %d, length %d, data %r",
tlv_type,
tlv_length,
tlv_data)
data = struct.pack("!H", tlv_type)
self.output.write(data)
data = struct.pack("!L", tlv_length)
self.output.write(data)
if tlv_data:
self.output.write(tlv_data)
class TLVDecoder(BaseType):
def __init__(self, inputdata):
self.inputdata = inputdata
self.pos = 0
self.tlv = None
def __iter__(self):
self.pos = 0
self.tlv = None
return self
def __next__(self):
if self.tlv:
self.pos += self.tlv.total_length()
if (self.pos + TLVHeader.TLV_DECODE_FMT_LEN) > len(self.inputdata):
raise StopIteration
# Get the next TLV
self.tlv = TLVHeader(self.inputdata[self.pos:])
return self.tlv
next = __next__
class TLVHeader(BaseType):
TLV_DECODE_FMT = "!HL"
TLV_DECODE_FMT_LEN = struct.calcsize(TLV_DECODE_FMT)
def __init__(self, data):
# Parse the data to populate the TLV fields
(self.type, self.length) = struct.unpack(self.TLV_DECODE_FMT, data[0:self.TLV_DECODE_FMT_LEN])
# Get the remaining data and store it.
self.data = data[self.TLV_DECODE_FMT_LEN:self.TLV_DECODE_FMT_LEN + self.length]
def __repr__(self):
return ("{self.__class__.__name__}(type={self.type!r}, length={self.length!r}, data={self.data!r})"
.format(self=self))
def total_length(self):
return self.TLV_DECODE_FMT_LEN + self.length

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,447 +0,0 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2017, Max Dymond, <cmeister2@gmail.com>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <curl/curl.h>
#include "curl_fuzzer.h"
/**
* Fuzzing entry point. This function is passed a buffer containing a test
* case. This test case should drive the CURL API into making a request.
*/
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
int rc = 0;
int tlv_rc;
FUZZ_DATA fuzz;
TLV tlv;
/* Have to set all fields to zero before getting to the terminate function */
memset(&fuzz, 0, sizeof(FUZZ_DATA));
if(size < sizeof(TLV_RAW)) {
/* Not enough data for a single TLV - don't continue */
goto EXIT_LABEL;
}
/* Try to initialize the fuzz data */
FTRY(fuzz_initialize_fuzz_data(&fuzz, data, size));
for(tlv_rc = fuzz_get_first_tlv(&fuzz, &tlv);
tlv_rc == 0;
tlv_rc = fuzz_get_next_tlv(&fuzz, &tlv)) {
/* Have the TLV in hand. Parse the TLV. */
rc = fuzz_parse_tlv(&fuzz, &tlv);
if(rc != 0) {
/* Failed to parse the TLV. Can't continue. */
goto EXIT_LABEL;
}
}
if(tlv_rc != TLV_RC_NO_MORE_TLVS) {
/* A TLV call failed. Can't continue. */
goto EXIT_LABEL;
}
/* Do the CURL stuff! */
if(fuzz.header_list != NULL) {
curl_easy_setopt(fuzz.easy, CURLOPT_HTTPHEADER, fuzz.header_list);
}
if(fuzz.mail_recipients_list != NULL) {
curl_easy_setopt(fuzz.easy, CURLOPT_MAIL_RCPT, fuzz.mail_recipients_list);
}
curl_easy_perform(fuzz.easy);
EXIT_LABEL:
fuzz_terminate_fuzz_data(&fuzz);
/* This function must always return 0. Non-zero codes are reserved. */
return 0;
}
/**
* Utility function to convert 4 bytes to a u32 predictably.
*/
uint32_t to_u32(uint8_t b[4])
{
uint32_t u;
u = (b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3];
return u;
}
/**
* Utility function to convert 2 bytes to a u16 predictably.
*/
uint16_t to_u16(uint8_t b[2])
{
uint16_t u;
u = (b[0] << 8) + b[1];
return u;
}
/**
* Initialize the local fuzz data structure.
*/
int fuzz_initialize_fuzz_data(FUZZ_DATA *fuzz,
const uint8_t *data,
size_t data_len)
{
int rc = 0;
/* Initialize the fuzz data. */
memset(fuzz, 0, sizeof(FUZZ_DATA));
/* Create an easy handle. This will have all of the settings configured on
it. */
fuzz->easy = curl_easy_init();
FCHECK(fuzz->easy != NULL);
/* Set some standard options on the CURL easy handle. We need to override the
socket function so that we create our own sockets to present to CURL. */
FTRY(curl_easy_setopt(fuzz->easy,
CURLOPT_OPENSOCKETFUNCTION,
fuzz_open_socket));
FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_OPENSOCKETDATA, fuzz));
/* In case something tries to set a socket option, intercept this. */
FTRY(curl_easy_setopt(fuzz->easy,
CURLOPT_SOCKOPTFUNCTION,
fuzz_sockopt_callback));
/* Set the standard read function callback. */
FTRY(curl_easy_setopt(fuzz->easy,
CURLOPT_READFUNCTION,
fuzz_read_callback));
FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_READDATA, fuzz));
/* Set the standard write function callback. */
FTRY(curl_easy_setopt(fuzz->easy,
CURLOPT_WRITEFUNCTION,
fuzz_write_callback));
FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_WRITEDATA, fuzz));
/* Can enable verbose mode by changing 0L to 1L */
FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_VERBOSE, 0L));
/* Set up the state parser */
fuzz->state.data = data;
fuzz->state.data_len = data_len;
EXIT_LABEL:
return rc;
}
/**
* Terminate the fuzz data structure, including freeing any allocated memory.
*/
void fuzz_terminate_fuzz_data(FUZZ_DATA *fuzz)
{
fuzz_free((void **)&fuzz->url);
fuzz_free((void **)&fuzz->username);
fuzz_free((void **)&fuzz->password);
fuzz_free((void **)&fuzz->postfields);
fuzz_free((void **)&fuzz->cookie);
fuzz_free((void **)&fuzz->range);
fuzz_free((void **)&fuzz->customrequest);
fuzz_free((void **)&fuzz->mail_from);
if(fuzz->header_list != NULL) {
curl_slist_free_all(fuzz->header_list);
fuzz->header_list = NULL;
}
if(fuzz->mail_recipients_list != NULL) {
curl_slist_free_all(fuzz->mail_recipients_list);
fuzz->mail_recipients_list = NULL;
}
if(fuzz->easy != NULL) {
curl_easy_cleanup(fuzz->easy);
fuzz->easy = NULL;
}
}
/**
* If a pointer has been allocated, free that pointer.
*/
void fuzz_free(void **ptr)
{
if(*ptr != NULL) {
free(*ptr);
*ptr = NULL;
}
}
/**
* Function for providing a socket to CURL already primed with data.
*/
static curl_socket_t fuzz_open_socket(void *ptr,
curlsocktype purpose,
struct curl_sockaddr *address)
{
FUZZ_DATA *fuzz = (FUZZ_DATA *)ptr;
int fds[2];
curl_socket_t server_fd;
curl_socket_t client_fd;
/* Handle unused parameters */
(void)purpose;
(void)address;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) {
/* Failed to create a pair of sockets. */
return CURL_SOCKET_BAD;
}
server_fd = fds[0];
client_fd = fds[1];
/* Try and write the response data to the server file descriptor so the
client can read it. */
if(write(server_fd,
fuzz->rsp1_data,
fuzz->rsp1_data_len) != (ssize_t)fuzz->rsp1_data_len) {
/* Failed to write the data. */
return CURL_SOCKET_BAD;
}
if(shutdown(server_fd, SHUT_WR)) {
return CURL_SOCKET_BAD;
}
return client_fd;
}
/**
* Callback function for setting socket options on the sockets created by
* fuzz_open_socket. In our testbed the sockets are "already connected".
*/
static int fuzz_sockopt_callback(void *ptr,
curl_socket_t curlfd,
curlsocktype purpose)
{
(void)ptr;
(void)curlfd;
(void)purpose;
return CURL_SOCKOPT_ALREADY_CONNECTED;
}
/**
* Callback function for doing data uploads.
*/
static size_t fuzz_read_callback(char *buffer,
size_t size,
size_t nitems,
void *ptr)
{
FUZZ_DATA *fuzz = (FUZZ_DATA *)ptr;
curl_off_t nread;
/* If no upload data has been specified, then return an error code. */
if(fuzz->upload1_data_len == 0) {
/* No data to upload */
return CURL_READFUNC_ABORT;
}
/* Send the upload data. */
memcpy(buffer,
fuzz->upload1_data,
fuzz->upload1_data_len);
return fuzz->upload1_data_len;
}
/**
* Callback function for handling data output quietly.
*/
static size_t fuzz_write_callback(void *contents,
size_t size,
size_t nmemb,
void *ptr)
{
size_t total = size * nmemb;
FUZZ_DATA *fuzz = (FUZZ_DATA *)ptr;
size_t copy_len = total;
/* Restrict copy_len to at most TEMP_WRITE_ARRAY_SIZE. */
if(copy_len > TEMP_WRITE_ARRAY_SIZE) {
copy_len = TEMP_WRITE_ARRAY_SIZE;
}
/* Copy bytes to the temp store just to ensure the parameters are
exercised. */
memcpy(fuzz->write_array, contents, copy_len);
return total;
}
/**
* TLV access function - gets the first TLV from a data stream.
*/
int fuzz_get_first_tlv(FUZZ_DATA *fuzz,
TLV *tlv)
{
/* Reset the cursor. */
fuzz->state.data_pos = 0;
return fuzz_get_tlv_comn(fuzz, tlv);
}
/**
* TLV access function - gets the next TLV from a data stream.
*/
int fuzz_get_next_tlv(FUZZ_DATA *fuzz,
TLV *tlv)
{
/* Advance the cursor by the full length of the previous TLV. */
fuzz->state.data_pos += sizeof(TLV_RAW) + tlv->length;
/* Work out if there's a TLV's worth of data to read */
if(fuzz->state.data_pos + sizeof(TLV_RAW) > fuzz->state.data_len) {
/* No more TLVs to parse */
return TLV_RC_NO_MORE_TLVS;
}
return fuzz_get_tlv_comn(fuzz, tlv);
}
/**
* Common TLV function for accessing TLVs in a data stream.
*/
int fuzz_get_tlv_comn(FUZZ_DATA *fuzz,
TLV *tlv)
{
int rc = 0;
size_t data_offset;
TLV_RAW *raw;
/* Start by casting the data stream to a TLV. */
raw = (TLV_RAW *)&fuzz->state.data[fuzz->state.data_pos];
data_offset = fuzz->state.data_pos + sizeof(TLV_RAW);
/* Set the TLV values. */
tlv->type = to_u16(raw->raw_type);
tlv->length = to_u32(raw->raw_length);
tlv->value = &fuzz->state.data[data_offset];
/* Sanity check that the TLV length is ok. */
if(data_offset + tlv->length > fuzz->state.data_len) {
rc = TLV_RC_SIZE_ERROR;
}
return rc;
}
/**
* Do different actions on the CURL handle for different received TLVs.
*/
int fuzz_parse_tlv(FUZZ_DATA *fuzz, TLV *tlv)
{
int rc;
char *tmp;
switch(tlv->type) {
case TLV_TYPE_RESPONSE1:
/* The pointers in the TLV will always be valid as long as the fuzz data
is in scope, which is the entirety of this file. */
fuzz->rsp1_data = tlv->value;
fuzz->rsp1_data_len = tlv->length;
break;
case TLV_TYPE_UPLOAD1:
/* The pointers in the TLV will always be valid as long as the fuzz data
is in scope, which is the entirety of this file. */
fuzz->upload1_data = tlv->value;
fuzz->upload1_data_len = tlv->length;
curl_easy_setopt(fuzz->easy, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(fuzz->easy,
CURLOPT_INFILESIZE_LARGE,
(curl_off_t)fuzz->upload1_data_len);
break;
case TLV_TYPE_HEADER:
tmp = fuzz_tlv_to_string(tlv);
fuzz->header_list = curl_slist_append(fuzz->header_list, tmp);
fuzz_free((void **)&tmp);
break;
case TLV_TYPE_MAIL_RECIPIENT:
tmp = fuzz_tlv_to_string(tlv);
fuzz->mail_recipients_list =
curl_slist_append(fuzz->mail_recipients_list, tmp);
fuzz_free((void **)&tmp);
break;
/* Define a set of singleton TLVs - they can only have their value set once
and all follow the same pattern. */
FSINGLETONTLV(TLV_TYPE_URL, url, CURLOPT_URL);
FSINGLETONTLV(TLV_TYPE_USERNAME, username, CURLOPT_USERNAME);
FSINGLETONTLV(TLV_TYPE_PASSWORD, password, CURLOPT_PASSWORD);
FSINGLETONTLV(TLV_TYPE_POSTFIELDS, postfields, CURLOPT_POSTFIELDS);
FSINGLETONTLV(TLV_TYPE_COOKIE, cookie, CURLOPT_COOKIE);
FSINGLETONTLV(TLV_TYPE_RANGE, range, CURLOPT_RANGE);
FSINGLETONTLV(TLV_TYPE_CUSTOMREQUEST, customrequest, CURLOPT_CUSTOMREQUEST);
FSINGLETONTLV(TLV_TYPE_MAIL_FROM, mail_from, CURLOPT_MAIL_FROM);
default:
/* The fuzzer generates lots of unknown TLVs - we don't want these in the
corpus so we reject any unknown TLVs. */
rc = 255;
goto EXIT_LABEL;
break;
}
rc = 0;
EXIT_LABEL:
return rc;
}
/**
* Converts a TLV data and length into an allocated string.
*/
char *fuzz_tlv_to_string(TLV *tlv)
{
char *tlvstr;
/* Allocate enough space, plus a null terminator */
tlvstr = (char *)malloc(tlv->length + 1);
if(tlvstr != NULL) {
memcpy(tlvstr, tlv->value, tlv->length);
tlvstr[tlv->length] = 0;
}
return tlvstr;
}

View File

@ -1,186 +0,0 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2017, Max Dymond, <cmeister2@gmail.com>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
#include <curl/curl.h>
#include <testinput.h>
/**
* TLV types.
*/
#define TLV_TYPE_URL 1
#define TLV_TYPE_RESPONSE1 2
#define TLV_TYPE_USERNAME 3
#define TLV_TYPE_PASSWORD 4
#define TLV_TYPE_POSTFIELDS 5
#define TLV_TYPE_HEADER 6
#define TLV_TYPE_COOKIE 7
#define TLV_TYPE_UPLOAD1 8
#define TLV_TYPE_RANGE 9
#define TLV_TYPE_CUSTOMREQUEST 10
#define TLV_TYPE_MAIL_RECIPIENT 11
#define TLV_TYPE_MAIL_FROM 12
/**
* TLV function return codes.
*/
#define TLV_RC_NO_ERROR 0
#define TLV_RC_NO_MORE_TLVS 1
#define TLV_RC_SIZE_ERROR 2
/* Temporary write array size */
#define TEMP_WRITE_ARRAY_SIZE 10
/**
* Byte stream representation of the TLV header. Casting the byte stream
* to a TLV_RAW allows us to examine the type and length.
*/
typedef struct tlv_raw
{
/* Type of the TLV - 16 bits. */
uint8_t raw_type[2];
/* Length of the TLV data - 32 bits. */
uint8_t raw_length[4];
} TLV_RAW;
typedef struct tlv
{
/* Type of the TLV */
uint16_t type;
/* Length of the TLV data */
uint32_t length;
/* Pointer to data if length > 0. */
const uint8_t *value;
} TLV;
/**
* Internal state when parsing a TLV data stream.
*/
typedef struct fuzz_parse_state
{
/* Data stream */
const uint8_t *data;
size_t data_len;
/* Current position of our "cursor" in processing the data stream. */
size_t data_pos;
} FUZZ_PARSE_STATE;
/**
* Data local to a fuzzing run.
*/
typedef struct fuzz_data
{
/* CURL easy object */
CURL *easy;
/* Parser state */
FUZZ_PARSE_STATE state;
/* Temporary writefunction state */
char write_array[TEMP_WRITE_ARRAY_SIZE];
/* Response data and length */
const uint8_t *rsp1_data;
size_t rsp1_data_len;
/* Upload data and length; */
const uint8_t *upload1_data;
size_t upload1_data_len;
/* Singleton string fields. */
char *url;
char *username;
char *password;
char *postfields;
char *cookie;
char *range;
char *customrequest;
char *mail_from;
/* List of headers */
struct curl_slist *header_list;
/* List of mail recipients */
struct curl_slist *mail_recipients_list;
} FUZZ_DATA;
/* Function prototypes */
uint32_t to_u32(uint8_t b[4]);
uint16_t to_u16(uint8_t b[2]);
int fuzz_initialize_fuzz_data(FUZZ_DATA *fuzz,
const uint8_t *data,
size_t data_len);
void fuzz_terminate_fuzz_data(FUZZ_DATA *fuzz);
void fuzz_free(void **ptr);
static curl_socket_t fuzz_open_socket(void *ptr,
curlsocktype purpose,
struct curl_sockaddr *address);
static int fuzz_sockopt_callback(void *ptr,
curl_socket_t curlfd,
curlsocktype purpose);
static size_t fuzz_read_callback(char *buffer,
size_t size,
size_t nitems,
void *ptr);
static size_t fuzz_write_callback(void *contents,
size_t size,
size_t nmemb,
void *ptr);
int fuzz_get_first_tlv(FUZZ_DATA *fuzz, TLV *tlv);
int fuzz_get_next_tlv(FUZZ_DATA *fuzz, TLV *tlv);
int fuzz_get_tlv_comn(FUZZ_DATA *fuzz, TLV *tlv);
int fuzz_parse_tlv(FUZZ_DATA *fuzz, TLV *tlv);
char *fuzz_tlv_to_string(TLV *tlv);
/* Macros */
#define FTRY(FUNC) \
{ \
int _func_rc = (FUNC); \
if (_func_rc) \
{ \
rc = _func_rc; \
goto EXIT_LABEL; \
} \
}
#define FCHECK(COND) \
{ \
if (!(COND)) \
{ \
rc = 255; \
goto EXIT_LABEL; \
} \
}
#define FSINGLETONTLV(TLVNAME, FIELDNAME, OPTNAME) \
case TLVNAME: \
FCHECK(fuzz->FIELDNAME == NULL); \
fuzz->FIELDNAME = fuzz_tlv_to_string(tlv); \
FTRY(curl_easy_setopt(fuzz->easy, OPTNAME, fuzz->FIELDNAME)); \
break

8
tests/fuzz/download_fuzzer.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
# If any commands fail, fail the script immediately.
set -ex
# Clone the curl-fuzzer repository to the specified directory.
git clone http://github.com/curl/curl-fuzzer $1

View File

@ -1,132 +0,0 @@
#!/usr/bin/env python
#
# Simple script which generates corpus files.
import argparse
import logging
import corpus
import sys
sys.path.append("..")
import curl_test_data
log = logging.getLogger(__name__)
def generate_corpus(options):
td = curl_test_data.TestData("../data")
with open(options.output, "wb") as f:
enc = corpus.TLVEncoder(f)
# Write the URL to the file.
enc.write_string(enc.TYPE_URL, options.url)
# Write the first response to the file.
if options.rsp1:
enc.write_bytes(enc.TYPE_RSP1, options.rsp1.encode("utf-8"))
elif options.rsp1file:
with open(options.rsp1file, "rb") as g:
enc.write_bytes(enc.TYPE_RSP1, g.read())
elif options.rsp1test:
wstring = td.get_test_data(options.rsp1test)
enc.write_bytes(enc.TYPE_RSP1, wstring.encode("utf-8"))
# Write other options to file.
enc.maybe_write_string(enc.TYPE_USERNAME, options.username)
enc.maybe_write_string(enc.TYPE_PASSWORD, options.password)
enc.maybe_write_string(enc.TYPE_POSTFIELDS, options.postfields)
enc.maybe_write_string(enc.TYPE_COOKIE, options.cookie)
enc.maybe_write_string(enc.TYPE_RANGE, options.range)
enc.maybe_write_string(enc.TYPE_CUSTOMREQUEST, options.customrequest)
enc.maybe_write_string(enc.TYPE_MAIL_FROM, options.mailfrom)
# Write the first upload to the file.
if options.upload1:
enc.write_bytes(enc.TYPE_UPLOAD1, options.upload1.encode("utf-8"))
elif options.upload1file:
with open(options.upload1file, "rb") as g:
enc.write_bytes(enc.TYPE_UPLOAD1, g.read())
# Write an array of headers to the file.
if options.header:
for header in options.header:
enc.write_string(enc.TYPE_HEADER, header)
# Write an array of headers to the file.
if options.mailrecipient:
for mailrecipient in options.mailrecipient:
enc.write_string(enc.TYPE_MAIL_RECIPIENT, mailrecipient)
return ScriptRC.SUCCESS
def get_options():
parser = argparse.ArgumentParser()
parser.add_argument("--output", required=True)
parser.add_argument("--url", required=True)
parser.add_argument("--username")
parser.add_argument("--password")
parser.add_argument("--postfields")
parser.add_argument("--header", action="append")
parser.add_argument("--cookie")
parser.add_argument("--range")
parser.add_argument("--customrequest")
parser.add_argument("--mailfrom")
parser.add_argument("--mailrecipient", action="append")
rsp1 = parser.add_mutually_exclusive_group(required=True)
rsp1.add_argument("--rsp1")
rsp1.add_argument("--rsp1file")
rsp1.add_argument("--rsp1test", type=int)
upload1 = parser.add_mutually_exclusive_group()
upload1.add_argument("--upload1")
upload1.add_argument("--upload1file")
return parser.parse_args()
def setup_logging():
"""
Set up logging from the command line options
"""
root_logger = logging.getLogger()
formatter = logging.Formatter("%(asctime)s %(levelname)-5.5s %(message)s")
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setFormatter(formatter)
stdout_handler.setLevel(logging.DEBUG)
root_logger.addHandler(stdout_handler)
root_logger.setLevel(logging.DEBUG)
class ScriptRC(object):
"""Enum for script return codes"""
SUCCESS = 0
FAILURE = 1
EXCEPTION = 2
class ScriptException(Exception):
pass
def main():
# Get the options from the user.
options = get_options()
setup_logging()
# Run main script.
try:
rc = generate_corpus(options)
except Exception as e:
log.exception(e)
rc = ScriptRC.EXCEPTION
log.info("Returning %d", rc)
return rc
if __name__ == '__main__':
sys.exit(main())

View File

@ -1,69 +0,0 @@
#!/usr/bin/env python
#
# Simple script which reads corpus files.
import argparse
import logging
import sys
import corpus
log = logging.getLogger(__name__)
def read_corpus(options):
with open(options.input, "rb") as f:
dec = corpus.TLVDecoder(f.read())
for tlv in dec:
print(tlv)
return ScriptRC.SUCCESS
def get_options():
parser = argparse.ArgumentParser()
parser.add_argument("--input", required=True)
return parser.parse_args()
def setup_logging():
"""
Set up logging from the command line options
"""
root_logger = logging.getLogger()
formatter = logging.Formatter("%(asctime)s %(levelname)-5.5s %(message)s")
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setFormatter(formatter)
stdout_handler.setLevel(logging.DEBUG)
root_logger.addHandler(stdout_handler)
root_logger.setLevel(logging.DEBUG)
class ScriptRC(object):
"""Enum for script return codes"""
SUCCESS = 0
FAILURE = 1
EXCEPTION = 2
class ScriptException(Exception):
pass
def main():
# Get the options from the user.
options = get_options()
setup_logging()
# Run main script.
try:
rc = read_corpus(options)
except Exception as e:
log.exception(e)
rc = ScriptRC.EXCEPTION
log.info("Returning %d", rc)
return rc
if __name__ == '__main__':
sys.exit(main())

View File

@ -1,89 +0,0 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2017, Max Dymond, <cmeister2@gmail.com>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "testinput.h"
/**
* Main procedure for standalone fuzzing engine.
*
* Reads filenames from the argument array. For each filename, read the file
* into memory and then call the fuzzing interface with the data.
*/
int main(int argc, char **argv)
{
int ii;
FILE *infile;
uint8_t *buffer = NULL;
size_t buffer_len;
for(ii = 1; ii < argc; ii++) {
/* Try and open the file. */
infile = fopen(argv[ii], "rb");
if(infile) {
printf("[%s] Open succeeded! \n", argv[ii]);
/* Get the length of the file. */
fseek(infile, 0L, SEEK_END);
buffer_len = ftell(infile);
/* Reset the file indicator to the beginning of the file. */
fseek(infile, 0L, SEEK_SET);
/* Allocate a buffer for the file contents. */
buffer = (uint8_t *)calloc(buffer_len, sizeof(uint8_t));
if(buffer) {
/* Read all the text from the file into the buffer. */
fread(buffer, sizeof(uint8_t), buffer_len, infile);
printf("[%s] Read %zu bytes, calling fuzzer\n", argv[ii], buffer_len);
/* Call the fuzzer with the data. */
LLVMFuzzerTestOneInput(buffer, buffer_len);
printf("[%s] Fuzzing complete\n", argv[ii]);
/* Free the buffer as it's no longer needed. */
free(buffer);
buffer = NULL;
}
else
{
fprintf(stderr,
"[%s] Failed to allocate %zu bytes \n",
argv[ii],
buffer_len);
}
/* Close the file as it's no longer needed. */
fclose(infile);
infile = NULL;
}
else
{
/* Failed to open the file. Maybe wrong name or wrong permissions? */
fprintf(stderr, "[%s] Open failed. \n", argv[ii]);
}
}
}

View File

@ -1,23 +0,0 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2017, Max Dymond, <cmeister2@gmail.com>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);