Add a --fuzz option
[privoxy.git] / fuzz.c
diff --git a/fuzz.c b/fuzz.c
new file mode 100644 (file)
index 0000000..0e59a80
--- /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 <fk@fabiankeil.de>
+ *
+ *                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 <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#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