diff options
Diffstat (limited to 'tests/fuzz/curl_fuzzer.cc')
-rw-r--r-- | tests/fuzz/curl_fuzzer.cc | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/tests/fuzz/curl_fuzzer.cc b/tests/fuzz/curl_fuzzer.cc new file mode 100644 index 000000000..92bedf92e --- /dev/null +++ b/tests/fuzz/curl_fuzzer.cc @@ -0,0 +1,343 @@ +/*************************************************************************** + * _ _ ____ _ + * 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. */ + fuzz_parse_tlv(&fuzz, &tlv); + } + + if(tlv_rc != TLV_RC_NO_MORE_TLVS) { + /* A TLV call failed. Can't continue. */ + goto EXIT_LABEL; + } + + /* Do the CURL stuff! */ + 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)); + + /* Can enable verbose mode */ + /* FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_VERBOSE, 1L)); */ + + /* 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); + + 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; +} + +/** + * 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; + + switch(tlv->type) { + case TLV_TYPE_URL: + FCHECK(fuzz->url == NULL); + fuzz->url = fuzz_tlv_to_string(tlv); + FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_URL, fuzz->url)); + break; + + 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_USERNAME: + FCHECK(fuzz->username == NULL); + fuzz->username = fuzz_tlv_to_string(tlv); + FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_USERNAME, fuzz->username)); + break; + + case TLV_TYPE_PASSWORD: + FCHECK(fuzz->password == NULL); + fuzz->password = fuzz_tlv_to_string(tlv); + FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_PASSWORD, fuzz->password)); + break; + + case TLV_TYPE_POSTFIELDS: + FCHECK(fuzz->postfields == NULL); + fuzz->postfields = fuzz_tlv_to_string(tlv); + FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_POSTFIELDS, fuzz->postfields)); + break; + + default: + /* The fuzzer generates lots of unknown TLVs, so don't do anything if + the TLV isn't known. */ + 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; +} |