/********************************************************************* * * 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