1 /*********************************************************************
5 * Purpose : Fuzz-related functions for Privoxy.
7 * Copyright : Written by and Copyright (C) 2014-16 by
8 * Fabian Keil <fk@fabiankeil.de>
10 * This program is free software; you can redistribute it
11 * and/or modify it under the terms of the GNU General
12 * Public License as published by the Free Software
13 * Foundation; either version 2 of the License, or (at
14 * your option) any later version.
16 * This program is distributed in the hope that it will
17 * be useful, but WITHOUT ANY WARRANTY; without even the
18 * implied warranty of MERCHANTABILITY or FITNESS FOR A
19 * PARTICULAR PURPOSE. See the GNU General Public
20 * License for more details.
22 * The GNU General Public License should be included with
23 * this file. If not, you can view it at
24 * http://www.gnu.org/copyleft/gpl.html
25 * or write to the Free Software Foundation, Inc., 59
26 * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 *********************************************************************/
35 #include <sys/types.h>
49 #include "jbsockets.h"
56 static int fuzz_action(struct client_state *csp, char *fuzz_input_file);
57 static int fuzz_client_header(struct client_state *csp, char *fuzz_input_file);
58 static int fuzz_deflate(struct client_state *csp, char *fuzz_input_file);
59 static int fuzz_filter(struct client_state *csp, char *fuzz_input_file);
60 static int fuzz_gif(struct client_state *csp, char *fuzz_input_file);
61 static int fuzz_gzip(struct client_state *csp, char *fuzz_input_file);
63 static int fuzz_socks(struct client_state *csp, char *fuzz_input_file);
65 static int fuzz_pcrs_substitute(struct client_state *csp, char *fuzz_input_file);
66 static int fuzz_server_header(struct client_state *csp, char *fuzz_input_file);
71 const char *expected_input;
72 const int stdin_support;
73 int (* const handler)(struct client_state *csp, char *input_file);
76 static const struct fuzz_mode fuzz_modes[] = {
77 { "action", "Text to parse as action file.", 0, fuzz_action },
78 { "client-request", "Client request to parse. Currently incomplete", 1, fuzz_client_request },
79 { "client-header", "Client header to parse.", 1, fuzz_client_header },
80 { "chunked-transfer-encoding", "Chunk-encoded data to dechunk.", 1, fuzz_chunked_transfer_encoding },
81 { "deflate", "deflate-compressed data to decompress.", 1, fuzz_deflate },
82 { "filter", "Text to parse as filter file.", 0, fuzz_filter },
83 { "gif", "gif to deanimate.", 1, fuzz_gif },
84 { "gzip", "gzip-compressed data to decompress.", 1, fuzz_gzip },
85 { "pcrs-substitute", "A pcrs-substitute to compile. Not a whole pcrs job! Example: Bla $1 bla \x43 $3 blah.", 1, fuzz_pcrs_substitute },
86 { "server-header", "Server header to parse.", 1, fuzz_server_header },
87 { "server-response", "Server response to parse.", 1, fuzz_server_response },
89 { "socks", "A socks server response. Only reads from stdin!", 1, fuzz_socks },
93 /*********************************************************************
95 * Function : load_fuzz_input_from_stdin
97 * Description : Loads stdin into a buffer.
100 * 1 : csp = Used to store the data.
102 * Returns : JB_ERR_OK in case of success,
104 *********************************************************************/
105 static jb_err load_fuzz_input_from_stdin(struct client_state *csp)
107 static char buf[BUFFER_SIZE];
110 while (0 < (ret = read_socket(0, buf, sizeof(buf))))
112 log_error(LOG_LEVEL_INFO,
113 "Got %d bytes from stdin: %E. They look like this: %N",
116 if (add_to_iob(csp->iob, csp->config->buffer_limit, buf, ret))
118 log_error(LOG_LEVEL_FATAL, "Failed to buffer them.");
122 log_error(LOG_LEVEL_INFO, "Read %d bytes from stdin",
123 csp->iob->eod -csp->iob->cur);
128 /*********************************************************************
130 * Function : load_fuzz_input_from_file
132 * Description : Loads file content into a buffer.
135 * 1 : csp = Used to store the data.
136 * 2 : filename = Name of the file to be loaded.
138 * Returns : JB_ERR_OK in case of success,
140 *********************************************************************/
141 static jb_err load_fuzz_input_from_file(struct client_state *csp, const char *filename)
147 fp = fopen(filename, "rb");
150 log_error(LOG_LEVEL_FATAL, "Failed to open %s: %E", filename);
153 /* Get file length */
154 if (fseek(fp, 0, SEEK_END))
156 log_error(LOG_LEVEL_FATAL,
157 "Unexpected error while fseek()ing to the end of %s: %E",
163 log_error(LOG_LEVEL_FATAL,
164 "Unexpected ftell() error while loading %s: %E",
167 length = (size_t)ret;
169 /* Go back to the beginning. */
170 if (fseek(fp, 0, SEEK_SET))
172 log_error(LOG_LEVEL_FATAL,
173 "Unexpected error while fseek()ing to the beginning of %s: %E",
177 csp->iob->size = length + 1;
179 csp->iob->buf = malloc_or_die(csp->iob->size);
180 csp->iob->cur = csp->iob->buf;
181 csp->iob->eod = csp->iob->buf + length;
183 if (1 != fread(csp->iob->cur, length, 1, fp))
186 * May theoretically happen if the file size changes between
187 * fseek() and fread() because it's edited in-place. Privoxy
188 * and common text editors don't do that, thus we just fail.
190 log_error(LOG_LEVEL_FATAL,
191 "Couldn't completely read file %s.", filename);
193 *csp->iob->eod = '\0';
201 /*********************************************************************
203 * Function : load_fuzz_input
205 * Description : Loads a file into a buffer. XXX: Reverse argument order
208 * 1 : csp = Used to store the data.
209 * 2 : filename = Name of the file to be loaded.
211 * Returns : JB_ERR_OK in case of success,
213 *********************************************************************/
214 jb_err load_fuzz_input(struct client_state *csp, const char *filename)
216 if (strcmp(filename, "-") == 0)
218 return load_fuzz_input_from_stdin(csp);
221 return load_fuzz_input_from_file(csp, filename);
225 /*********************************************************************
227 * Function : remove_forbidden_bytes
229 * Description : Sanitizes fuzzed data to decrease the likelihood of
230 * premature parse abortions.
233 * 1 : csp = Used to store the data.
237 *********************************************************************/
238 static void remove_forbidden_bytes(struct client_state *csp)
240 char *p = csp->iob->cur;
241 char first_valid_byte = ' ';
243 while (p < csp->iob->eod)
247 first_valid_byte = *p;
254 while (p < csp->iob->eod)
258 *p = first_valid_byte;
265 /*********************************************************************
267 * Function : fuzz_action
269 * Description : Treat the fuzzed input as action file.
272 * 1 : csp = Used to store the data.
273 * 2 : fuzz_input_file = File to read the input from.
275 * Returns : Result of fuzzed function
277 *********************************************************************/
278 int fuzz_action(struct client_state *csp, char *fuzz_input_file)
280 csp->config->actions_file[0] = fuzz_input_file;
282 return(load_action_files(csp));
286 /*********************************************************************
288 * Function : fuzz_client_header
290 * Description : Treat the fuzzed input as a client header.
293 * 1 : csp = Used to store the data.
294 * 2 : fuzz_input_file = File to read the input from.
296 * Returns : Result of fuzzed function
298 *********************************************************************/
299 int fuzz_client_header(struct client_state *csp, char *fuzz_input_file)
303 header = get_header(csp->iob);
309 if (JB_ERR_OK != enlist(csp->headers, header))
315 * Silence an insightful client_host_adder() warning
316 * about ignored weirdness.
318 csp->flags |= CSP_FLAG_HOST_HEADER_IS_SET;
319 /* Adding headers doesn't depend on the fuzzed input */
320 csp->flags |= CSP_FLAG_CLIENT_CONNECTION_HEADER_SET;
322 /* +hide-if-modified-since{+60} */
323 csp->action->flags |= ACTION_HIDE_IF_MODIFIED_SINCE;
324 csp->action->string[ACTION_STRING_IF_MODIFIED_SINCE] = "+60";
326 /* XXX: Enable more actions. */
328 return(sed(csp, FILTER_CLIENT_HEADERS));
332 /*********************************************************************
334 * Function : fuzz_filter
336 * Description : Treat the fuzzed input as filter file.
339 * 1 : csp = Used to store the data.
340 * 2 : fuzz_input_file = File to read the input from.
342 * Returns : Result of fuzzed function
344 *********************************************************************/
345 int fuzz_filter(struct client_state *csp, char *fuzz_input_file)
347 csp->config->re_filterfile[0] = fuzz_input_file;
348 return (load_one_re_filterfile(csp, 0));
352 /*********************************************************************
354 * Function : fuzz_deflate
356 * Description : Treat the fuzzed input as data to deflate.
359 * 1 : csp = Used to store the data.
360 * 2 : fuzz_input_file = File to read the input from.
362 * Returns : Result of fuzzed function
364 *********************************************************************/
365 static int fuzz_deflate(struct client_state *csp, char *fuzz_input_file)
367 csp->content_type = CT_DEFLATE;
368 return(JB_ERR_OK == decompress_iob(csp));
372 /*********************************************************************
374 * Function : fuzz_gif
376 * Description : Treat the fuzzed input as a gif to deanimate.
379 * 1 : csp = Used to store the data.
380 * 2 : fuzz_input_file = File to read the input from.
382 * Returns : Result of fuzzed function
384 *********************************************************************/
385 static int fuzz_gif(struct client_state *csp, char *fuzz_input_file)
387 char *deanimated_gif;
389 if (6 < csp->iob->size)
391 /* Why yes of course, officer, this is a gif. */
392 memcpy(csp->iob->cur, "GIF87a", 6);
395 /* Using the last image requires parsing of all images */
396 csp->action->string[ACTION_STRING_DEANIMATE] = "last";
397 deanimated_gif = gif_deanimate_response(csp);
398 if (NULL != deanimated_gif)
400 free(deanimated_gif);
409 /*********************************************************************
411 * Function : fuzz_gzip
413 * Description : Treat the fuzzed input as data to unzip
416 * 1 : csp = Used to store the data.
417 * 2 : fuzz_input_file = File to read the input from.
419 * Returns : Result of fuzzed function
421 *********************************************************************/
422 static int fuzz_gzip(struct client_state *csp, char *fuzz_input_file)
424 csp->content_type = CT_GZIP;
426 return(JB_ERR_OK == decompress_iob(csp));
432 /*********************************************************************
434 * Function : fuzz_socks
436 * Description : Treat the fuzzed input as a socks response.
437 * XXX: This is pretty useless as parsing socks repsonse
441 * 1 : csp = Used to store the data.
442 * 2 : fuzz_input_file = File to read the input from.
444 * Returns : Result of fuzzed function
446 *********************************************************************/
447 static int fuzz_socks(struct client_state *csp, char *fuzz_input_file)
449 return(JB_ERR_OK == socks_fuzz(csp));
454 /*********************************************************************
456 * Function : fuzz_pcrs_substitute
458 * Description : Treat the fuzzed input as a pcrs substitute.
461 * 1 : csp = Used to store the data.
462 * 2 : fuzz_input_file = File to read the input from.
464 * Returns : Result of fuzzed function
466 *********************************************************************/
467 static int fuzz_pcrs_substitute(struct client_state *csp, char *fuzz_input_file)
469 static pcrs_substitute *result;
472 remove_forbidden_bytes(csp);
473 result = pcrs_compile_fuzzed_replacement(csp->iob->cur, &err);
476 log_error(LOG_LEVEL_ERROR,
477 "Failed to compile pcrs replacement. Error: %s", pcrs_strerror(err));
480 log_error(LOG_LEVEL_INFO, "%s", pcrs_strerror(err));
487 /*********************************************************************
489 * Function : fuzz_server_header
491 * Description : Treat the fuzzed input as a server header.
494 * 1 : csp = Used to store the data.
495 * 2 : fuzz_input_file = File to read the input from.
497 * Returns : Result of fuzzed function
499 *********************************************************************/
500 int fuzz_server_header(struct client_state *csp, char *fuzz_input_file)
504 header = get_header(csp->iob);
510 if (JB_ERR_OK != enlist(csp->headers, header))
515 /* Adding headers doesn't depend on the fuzzed input */
516 csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE;
517 csp->flags |= CSP_FLAG_SERVER_CONNECTION_HEADER_SET;
519 /* +overwrite-last-modified{randomize} */
520 csp->action->flags |= ACTION_OVERWRITE_LAST_MODIFIED;
521 csp->action->string[ACTION_STRING_LAST_MODIFIED] = "randomize";
523 /* +limit-cookie-lifetime{60} */
524 csp->action->flags |= ACTION_LIMIT_COOKIE_LIFETIME;
525 csp->action->string[ACTION_STRING_LIMIT_COOKIE_LIFETIME] = "60";
527 /* XXX: Enable more actions. */
529 return(sed(csp, FILTER_SERVER_HEADERS));
532 /*********************************************************************
534 * Function : process_fuzzed_input
536 * Description : Process the fuzzed input in a specified file treating
537 * it like the input type specified.
539 * XXX: Does not check malloc succcess.
542 * 1 : fuzz_input_type = Type of input.
543 * 2 : fuzz_input_file = File to read the input from.
545 * Returns : Return value of the fuzzed function
547 *********************************************************************/
548 int process_fuzzed_input(char *fuzz_input_type, char *fuzz_input_file)
550 static struct client_state csp_stack_storage;
551 static struct configuration_spec config_stack_storage;
552 struct client_state *csp;
555 csp = &csp_stack_storage;
556 csp->config = &config_stack_storage;
557 csp->config->buffer_limit = 4096 * 1024;
558 /* In --stfu mode, these will be ignored ... */
559 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);
561 csp->flags |= CSP_FLAG_FUZZED_INPUT;
562 csp->config->feature_flags |= RUNTIME_FEATURE_ACCEPT_INTERCEPTED_REQUESTS;
564 #ifdef FEATURE_CLIENT_TAGS
565 csp->config->trust_x_forwarded_for = 1;
568 for (i = 0; i < SZ(fuzz_modes); i++)
570 if (strcmp(fuzz_modes[i].name, fuzz_input_type) == 0)
572 if (fuzz_modes[i].stdin_support &&
573 (strcmp(fuzz_input_type, "client-request") != 0) &&
574 (strcmp(fuzz_input_type, "server-response") != 0) &&
575 (strcmp(fuzz_input_type, "socks") != 0))
577 load_fuzz_input(csp, fuzz_input_file);
579 return (fuzz_modes[i].handler(csp, fuzz_input_file));
583 log_error(LOG_LEVEL_FATAL,
584 "Unrecognized fuzz type %s for input file %s. You may need --help.",
585 fuzz_input_type, fuzz_input_file);
593 /*********************************************************************
595 * Function : show_fuzz_usage
597 * Description : Shows the --fuzz usage. D'oh.
599 * Parameters : Pointer to argv[0] for identifying ourselves
603 *********************************************************************/
604 void show_fuzz_usage(const char *name)
608 printf("%s%s --fuzz fuzz-mode ./path/to/fuzzed/input [--stfu]\n\n",
611 printf("Supported fuzz modes and the expected input:\n");
612 for (i = 0; i < SZ(fuzz_modes); i++)
614 printf(" %s: %s\n", fuzz_modes[i].name, fuzz_modes[i].expected_input);
618 printf("The following fuzz modes read data from stdin if the 'file' is '-'\n");
619 for (i = 0; i < SZ(fuzz_modes); i++)
621 if (fuzz_modes[i].stdin_support)
623 printf(" %s\n", fuzz_modes[i].name);