X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=fuzz.c;fp=fuzz.c;h=0e59a80f5f34c0f26b3722324d1b2392c2675917;hp=0000000000000000000000000000000000000000;hb=f67b3326138f428863c21c7738e0c8db87fa6f5c;hpb=a1649d1c0992d9b1459d2b92cc02f7b723baf5e0 diff --git a/fuzz.c b/fuzz.c new file mode 100644 index 00000000..0e59a80f --- /dev/null +++ b/fuzz.c @@ -0,0 +1,628 @@ +/********************************************************************* + * + * File : $Source:$ + * + * Purpose : Fuzz-related functions for Privoxy. + * + * Copyright : Written by and Copyright (C) 2014-16 by + * Fabian Keil + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * The GNU General Public License should be included with + * this file. If not, you can view it at + * http://www.gnu.org/copyleft/gpl.html + * or write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + *********************************************************************/ + + +#include "config.h" + +#include +#include +#include +#include + +#include "project.h" +#include "filters.h" +#include "loaders.h" +#include "parsers.h" +#include "miscutil.h" +#include "errlog.h" +#include "actions.h" +#include "cgi.h" +#include "loadcfg.h" +#include "urlmatch.h" +#include "filters.h" +#include "jbsockets.h" +#include "gateway.h" +#include "jcc.h" +#include "list.h" + + +#ifdef FUZZ +static int fuzz_action(struct client_state *csp, char *fuzz_input_file); +static int fuzz_client_header(struct client_state *csp, char *fuzz_input_file); +static int fuzz_deflate(struct client_state *csp, char *fuzz_input_file); +static int fuzz_filter(struct client_state *csp, char *fuzz_input_file); +static int fuzz_gif(struct client_state *csp, char *fuzz_input_file); +static int fuzz_gzip(struct client_state *csp, char *fuzz_input_file); +#ifdef FUZZ_SOCKS +static int fuzz_socks(struct client_state *csp, char *fuzz_input_file); +#endif +static int fuzz_pcrs_substitute(struct client_state *csp, char *fuzz_input_file); +static int fuzz_server_header(struct client_state *csp, char *fuzz_input_file); + +struct fuzz_mode +{ + const char *name; + const char *expected_input; + const int stdin_support; + int (* const handler)(struct client_state *csp, char *input_file); +}; + +static const struct fuzz_mode fuzz_modes[] = { + { "action", "Text to parse as action file.", 0, fuzz_action }, + { "client-request", "Client request to parse. Currently incomplete", 1, fuzz_client_request }, + { "client-header", "Client header to parse.", 1, fuzz_client_header }, + { "chunked-transfer-encoding", "Chunk-encoded data to dechunk.", 1, fuzz_chunked_transfer_encoding }, + { "deflate", "deflate-compressed data to decompress.", 1, fuzz_deflate }, + { "filter", "Text to parse as filter file.", 0, fuzz_filter }, + { "gif", "gif to deanimate.", 1, fuzz_gif }, + { "gzip", "gzip-compressed data to decompress.", 1, fuzz_gzip }, + { "pcrs-substitute", "A pcrs-substitute to compile. Not a whole pcrs job! Example: Bla $1 bla \x43 $3 blah.", 1, fuzz_pcrs_substitute }, + { "server-header", "Server header to parse.", 1, fuzz_server_header }, + { "server-response", "Server response to parse.", 1, fuzz_server_response }, +#ifdef FUZZ_SOCKS + { "socks", "A socks server response. Only reads from stdin!", 1, fuzz_socks }, +#endif +}; + +/********************************************************************* + * + * Function : load_fuzz_input_from_stdin + * + * Description : Loads stdin into a buffer. + * + * Parameters : + * 1 : csp = Used to store the data. + * + * Returns : JB_ERR_OK in case of success, + * + *********************************************************************/ +static jb_err load_fuzz_input_from_stdin(struct client_state *csp) +{ + static char buf[BUFFER_SIZE]; + int ret; + + while (0 < (ret = read_socket(0, buf, sizeof(buf)))) + { + log_error(LOG_LEVEL_INFO, + "Got %d bytes from stdin: %E. They look like this: %N", + ret, ret, buf); + + if (add_to_iob(csp->iob, csp->config->buffer_limit, buf, ret)) + { + log_error(LOG_LEVEL_FATAL, "Failed to buffer them."); + } + } + + log_error(LOG_LEVEL_INFO, "Read %d bytes from stdin", + csp->iob->eod -csp->iob->cur); + + return JB_ERR_OK; +} + +/********************************************************************* + * + * Function : load_fuzz_input_from_file + * + * Description : Loads file content into a buffer. + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : filename = Name of the file to be loaded. + * + * Returns : JB_ERR_OK in case of success, + * + *********************************************************************/ +static jb_err load_fuzz_input_from_file(struct client_state *csp, const char *filename) +{ + FILE *fp; + size_t length; + long ret; + + fp = fopen(filename, "rb"); + if (NULL == fp) + { + log_error(LOG_LEVEL_FATAL, "Failed to open %s: %E", filename); + } + + /* Get file length */ + if (fseek(fp, 0, SEEK_END)) + { + log_error(LOG_LEVEL_FATAL, + "Unexpected error while fseek()ing to the end of %s: %E", + filename); + } + ret = ftell(fp); + if (-1 == ret) + { + log_error(LOG_LEVEL_FATAL, + "Unexpected ftell() error while loading %s: %E", + filename); + } + length = (size_t)ret; + + /* Go back to the beginning. */ + if (fseek(fp, 0, SEEK_SET)) + { + log_error(LOG_LEVEL_FATAL, + "Unexpected error while fseek()ing to the beginning of %s: %E", + filename); + } + + csp->iob->size = length + 1; + + csp->iob->buf = malloc_or_die(csp->iob->size); + csp->iob->cur = csp->iob->buf; + csp->iob->eod = csp->iob->buf + length; + + if (1 != fread(csp->iob->cur, length, 1, fp)) + { + /* + * May theoretically happen if the file size changes between + * fseek() and fread() because it's edited in-place. Privoxy + * and common text editors don't do that, thus we just fail. + */ + log_error(LOG_LEVEL_FATAL, + "Couldn't completely read file %s.", filename); + } + *csp->iob->eod = '\0'; + + fclose(fp); + + return JB_ERR_OK; + +} + +/********************************************************************* + * + * Function : load_fuzz_input + * + * Description : Loads a file into a buffer. XXX: Reverse argument order + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : filename = Name of the file to be loaded. + * + * Returns : JB_ERR_OK in case of success, + * + *********************************************************************/ +jb_err load_fuzz_input(struct client_state *csp, const char *filename) +{ + if (strcmp(filename, "-") == 0) + { + return load_fuzz_input_from_stdin(csp); + } + + return load_fuzz_input_from_file(csp, filename); +} + + +/********************************************************************* + * + * Function : remove_forbidden_bytes + * + * Description : Sanitizes fuzzed data to decrease the likelihood of + * premature parse abortions. + * + * Parameters : + * 1 : csp = Used to store the data. + * + * Returns : N/A + * + *********************************************************************/ +static void remove_forbidden_bytes(struct client_state *csp) +{ + char *p = csp->iob->cur; + char first_valid_byte = ' '; + + while (p < csp->iob->eod) + { + if (*p != '\0') + { + first_valid_byte = *p; + break; + } + p++; + } + + p = csp->iob->cur; + while (p < csp->iob->eod) + { + if (*p == '\0') + { + *p = first_valid_byte; + } + p++; + } +} + + +/********************************************************************* + * + * Function : fuzz_action + * + * Description : Treat the fuzzed input as action file. + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Result of fuzzed function + * + *********************************************************************/ +int fuzz_action(struct client_state *csp, char *fuzz_input_file) +{ + csp->config->actions_file[0] = fuzz_input_file; + + return(load_action_files(csp)); +} + + +/********************************************************************* + * + * Function : fuzz_client_header + * + * Description : Treat the fuzzed input as a client header. + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Result of fuzzed function + * + *********************************************************************/ +int fuzz_client_header(struct client_state *csp, char *fuzz_input_file) +{ + char *header; + + header = get_header(csp->iob); + + if (NULL == header) + { + return 1; + } + if (JB_ERR_OK != enlist(csp->headers, header)) + { + return 1; + } + + /* + * Silence an insightful client_host_adder() warning + * about ignored weirdness. + */ + csp->flags |= CSP_FLAG_HOST_HEADER_IS_SET; + /* Adding headers doesn't depend on the fuzzed input */ + csp->flags |= CSP_FLAG_CLIENT_CONNECTION_HEADER_SET; + + /* +hide-if-modified-since{+60} */ + csp->action->flags |= ACTION_HIDE_IF_MODIFIED_SINCE; + csp->action->string[ACTION_STRING_IF_MODIFIED_SINCE] = "+60"; + + /* XXX: Enable more actions. */ + + return(sed(csp, FILTER_CLIENT_HEADERS)); +} + + +/********************************************************************* + * + * Function : fuzz_filter + * + * Description : Treat the fuzzed input as filter file. + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Result of fuzzed function + * + *********************************************************************/ +int fuzz_filter(struct client_state *csp, char *fuzz_input_file) +{ + csp->config->re_filterfile[0] = fuzz_input_file; + return (load_one_re_filterfile(csp, 0)); +} + + +/********************************************************************* + * + * Function : fuzz_deflate + * + * Description : Treat the fuzzed input as data to deflate. + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Result of fuzzed function + * + *********************************************************************/ +static int fuzz_deflate(struct client_state *csp, char *fuzz_input_file) +{ + csp->content_type = CT_DEFLATE; + return(JB_ERR_OK == decompress_iob(csp)); +} + + +/********************************************************************* + * + * Function : fuzz_gif + * + * Description : Treat the fuzzed input as a gif to deanimate. + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Result of fuzzed function + * + *********************************************************************/ +static int fuzz_gif(struct client_state *csp, char *fuzz_input_file) +{ + char *deanimated_gif; + + if (6 < csp->iob->size) + { + /* Why yes of course, officer, this is a gif. */ + memcpy(csp->iob->cur, "GIF87a", 6); + } + + /* Using the last image requires parsing of all images */ + csp->action->string[ACTION_STRING_DEANIMATE] = "last"; + deanimated_gif = gif_deanimate_response(csp); + if (NULL != deanimated_gif) + { + free(deanimated_gif); + return 0; + } + + return 1; + +} + + +/********************************************************************* + * + * Function : fuzz_gzip + * + * Description : Treat the fuzzed input as data to unzip + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Result of fuzzed function + * + *********************************************************************/ +static int fuzz_gzip(struct client_state *csp, char *fuzz_input_file) +{ + csp->content_type = CT_GZIP; + + return(JB_ERR_OK == decompress_iob(csp)); + +} + + +#ifdef FUZZ_SOCKS +/********************************************************************* + * + * Function : fuzz_socks + * + * Description : Treat the fuzzed input as a socks response. + * XXX: This is pretty useless as parsing socks repsonse + * is trivial. + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Result of fuzzed function + * + *********************************************************************/ +static int fuzz_socks(struct client_state *csp, char *fuzz_input_file) +{ + return(JB_ERR_OK == socks_fuzz(csp)); +} +#endif + + +/********************************************************************* + * + * Function : fuzz_pcrs_substitute + * + * Description : Treat the fuzzed input as a pcrs substitute. + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Result of fuzzed function + * + *********************************************************************/ +static int fuzz_pcrs_substitute(struct client_state *csp, char *fuzz_input_file) +{ + static pcrs_substitute *result; + int err; + + remove_forbidden_bytes(csp); + result = pcrs_compile_fuzzed_replacement(csp->iob->cur, &err); + if (NULL == result) + { + log_error(LOG_LEVEL_ERROR, + "Failed to compile pcrs replacement. Error: %s", pcrs_strerror(err)); + return 1; + } + log_error(LOG_LEVEL_INFO, "%s", pcrs_strerror(err)); + free(result->text); + freez(result); + return 0; +} + + +/********************************************************************* + * + * Function : fuzz_server_header + * + * Description : Treat the fuzzed input as a server header. + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Result of fuzzed function + * + *********************************************************************/ +int fuzz_server_header(struct client_state *csp, char *fuzz_input_file) +{ + char *header; + + header = get_header(csp->iob); + + if (NULL == header) + { + return 1; + } + if (JB_ERR_OK != enlist(csp->headers, header)) + { + return 1; + } + + /* Adding headers doesn't depend on the fuzzed input */ + csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE; + csp->flags |= CSP_FLAG_SERVER_CONNECTION_HEADER_SET; + + /* +overwrite-last-modified{randomize} */ + csp->action->flags |= ACTION_OVERWRITE_LAST_MODIFIED; + csp->action->string[ACTION_STRING_LAST_MODIFIED] = "randomize"; + + /* +limit-cookie-lifetime{60} */ + csp->action->flags |= ACTION_LIMIT_COOKIE_LIFETIME; + csp->action->string[ACTION_STRING_LIMIT_COOKIE_LIFETIME] = "60"; + + /* XXX: Enable more actions. */ + + return(sed(csp, FILTER_SERVER_HEADERS)); +} + +/********************************************************************* + * + * Function : process_fuzzed_input + * + * Description : Process the fuzzed input in a specified file treating + * it like the input type specified. + * + * XXX: Does not check malloc succcess. + * + * Parameters : + * 1 : fuzz_input_type = Type of input. + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Return value of the fuzzed function + * + *********************************************************************/ +int process_fuzzed_input(char *fuzz_input_type, char *fuzz_input_file) +{ + static struct client_state csp_stack_storage; + static struct configuration_spec config_stack_storage; + struct client_state *csp; + int i; + + csp = &csp_stack_storage; + csp->config = &config_stack_storage; + csp->config->buffer_limit = 4096 * 1024; + /* In --stfu mode, these will be ignored ... */ + set_debug_level(LOG_LEVEL_ACTIONS|LOG_LEVEL_CONNECT|LOG_LEVEL_DEANIMATE|LOG_LEVEL_INFO|LOG_LEVEL_ERROR|LOG_LEVEL_RE_FILTER|LOG_LEVEL_HEADER|LOG_LEVEL_WRITING|LOG_LEVEL_RECEIVED); + + csp->flags |= CSP_FLAG_FUZZED_INPUT; + csp->config->feature_flags |= RUNTIME_FEATURE_ACCEPT_INTERCEPTED_REQUESTS; + +#ifdef FEATURE_CLIENT_TAGS + csp->config->trust_x_forwarded_for = 1; +#endif + + for (i = 0; i < SZ(fuzz_modes); i++) + { + if (strcmp(fuzz_modes[i].name, fuzz_input_type) == 0) + { + if (fuzz_modes[i].stdin_support && + (strcmp(fuzz_input_type, "client-request") != 0) && + (strcmp(fuzz_input_type, "server-response") != 0) && + (strcmp(fuzz_input_type, "socks") != 0)) + { + load_fuzz_input(csp, fuzz_input_file); + } + return (fuzz_modes[i].handler(csp, fuzz_input_file)); + } + } + + log_error(LOG_LEVEL_FATAL, + "Unrecognized fuzz type %s for input file %s. You may need --help.", + fuzz_input_type, fuzz_input_file); + + /* Not reached. */ + return 1; + +} + + +/********************************************************************* + * + * Function : show_fuzz_usage + * + * Description : Shows the --fuzz usage. D'oh. + * + * Parameters : Pointer to argv[0] for identifying ourselves + * + * Returns : void + * + *********************************************************************/ +void show_fuzz_usage(const char *name) +{ + int i; + + printf("%s%s --fuzz fuzz-mode ./path/to/fuzzed/input [--stfu]\n\n", + " ", name); + + printf("Supported fuzz modes and the expected input:\n"); + for (i = 0; i < SZ(fuzz_modes); i++) + { + printf(" %s: %s\n", fuzz_modes[i].name, fuzz_modes[i].expected_input); + } + printf("\n"); + + printf("The following fuzz modes read data from stdin if the 'file' is '-'\n"); + for (i = 0; i < SZ(fuzz_modes); i++) + { + if (fuzz_modes[i].stdin_support) + { + printf(" %s\n", fuzz_modes[i].name); + } + } + printf("\n"); +} +#endif