aboutsummaryrefslogtreecommitdiff
path: root/tests/fuzz/curl_fuzzer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tests/fuzz/curl_fuzzer.cc')
-rw-r--r--tests/fuzz/curl_fuzzer.cc343
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;
+}