diff options
author | Max Dymond <cmeister2@gmail.com> | 2017-08-27 15:57:05 +0100 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2017-09-01 11:22:51 +0200 |
commit | efeb4a317616b0437a26277945bd300eaffe96d7 (patch) | |
tree | 86f46482f6f9a4eb98fcad25eeb06ea0027b01dc /tests/fuzz/curl_fuzzer.c | |
parent | 222e65fd783bec974b99345e0d618d4e627304de (diff) |
ossfuzz: moving towards the ideal integration
- Start with the basic code from the ossfuzz project.
- Rewrite fuzz corpora to be binary files full of Type-Length-Value
data, and write a glue layer in the fuzzing function to convert
corpora into CURL options.
- Have supporting functions to generate corpora from existing tests
- Integrate with Makefile.am
Diffstat (limited to 'tests/fuzz/curl_fuzzer.c')
-rw-r--r-- | tests/fuzz/curl_fuzzer.c | 402 |
1 files changed, 307 insertions, 95 deletions
diff --git a/tests/fuzz/curl_fuzzer.c b/tests/fuzz/curl_fuzzer.c index 2ccf1b36e..f4a4ec6f9 100644 --- a/tests/fuzz/curl_fuzzer.c +++ b/tests/fuzz/curl_fuzzer.c @@ -1,128 +1,340 @@ -/* -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -*/ +/*************************************************************************** + * _ _ ____ _ + * 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 <errno.h> -#include <fcntl.h> -#include <netinet/in.h> -#include <stddef.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/select.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <sys/types.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. + */ +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + int rc = 0; + int tlv_rc; + FUZZ_DATA fuzz; + TLV tlv; + + if(size < sizeof(TLV_RAW)) { + /* Not enough data */ + goto EXIT_LABEL; + } -static const void *cur_data; -static int cur_size = -1; -static int server_fd = -1; -static int client_fd = -1; -static int wrote = 0; + /* 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; +} -static void fail(const char *why) { - perror(why); - exit(1); +/** + * 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; } -static curl_socket_t open_sock(void *ctx, curlsocktype purpose, - struct curl_sockaddr *address) { - if(cur_size == -1) { - fail("not fuzzing"); +/** + * 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(server_fd != -1 || client_fd != -1) { - fail("already connected"); +} + +/** + * 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)) { - fail("socketpair"); + /* Failed to create a pair of sockets. */ + return CURL_SOCKET_BAD; } + server_fd = fds[0]; client_fd = fds[1]; - if(write(server_fd, cur_data, cur_size) != cur_size) { - fail("write"); + + /* 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)) { - fail("shutdown"); + return CURL_SOCKET_BAD; } + return client_fd; } -static int set_opt(void *ctx, curl_socket_t curlfd, curlsocktype purpose) { +/** + * 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; } -static size_t write_callback(char *ptr, size_t size, size_t n, void *ctx) { - return size * n; +/** + * 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); } -static size_t read_callback(char *buf, size_t size, size_t n, void *ctx) { - if(wrote || size * n == 0) { - return 0; +/** + * 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; } - wrote = 1; - buf[0] = 'a'; - return 1; + + return fuzz_get_tlv_comn(fuzz, tlv); } -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - cur_data = Data; - cur_size = Size; - wrote = 0; - CURL *curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); - curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); - curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, open_sock); - curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, set_opt); -#if defined(FUZZER_FTP) - curl_easy_setopt(curl, CURLOPT_URL, "ftp://user@localhost/file.txt"); -#elif defined(FUZZER_IMAP) - curl_easy_setopt(curl, CURLOPT_USERNAME, "user"); - curl_easy_setopt(curl, CURLOPT_PASSWORD, "secret"); - curl_easy_setopt(curl, CURLOPT_URL, "imap://localhost"); -#elif defined(FUZZER_POP3) - curl_easy_setopt(curl, CURLOPT_USERNAME, "user"); - curl_easy_setopt(curl, CURLOPT_PASSWORD, "secret"); - curl_easy_setopt(curl, CURLOPT_URL, "pop3://localhost"); -#elif defined(FUZZER_HTTP_UPLOAD) - curl_easy_setopt(curl, CURLOPT_URL, "http://localhost/"); - curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); -#elif defined(FUZZER_HTTP2) - curl_easy_setopt(curl, CURLOPT_URL, "http://localhost/"); - /* use non-TLS HTTP/2 without HTTP/1.1 Upgrade: */ - curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE); -#else - curl_easy_setopt(curl, CURLOPT_URL, "http://localhost/"); - curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); -#endif - curl_easy_perform(curl); - curl_easy_cleanup(curl); - close(server_fd); - close(client_fd); - server_fd = -1; - client_fd = -1; - cur_data = NULL; - cur_size = -1; - return 0; +/** + * 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 = malloc(tlv->length + 1); + + if(tlvstr != NULL) { + memcpy(tlvstr, tlv->value, tlv->length); + tlvstr[tlv->length] = 0; + } + + return tlvstr; } |