diff options
author | Nikita Popov <nikita.ppv@gmail.com> | 2019-09-13 15:15:46 +0200 |
---|---|---|
committer | Nikita Popov <nikita.ppv@gmail.com> | 2019-09-16 16:04:10 +0200 |
commit | c4e2ca607f49d37564aaf34f5a48c5e59aca12a6 (patch) | |
tree | 9efd9c787f0f37f77e9da6d8dac1dfdb275ec4af /sapi | |
parent | 41f45647f90a44514fd18b16fdcec5cceebf1635 (diff) | |
download | php-git-c4e2ca607f49d37564aaf34f5a48c5e59aca12a6.tar.gz |
Various improvements to fuzzer SAPIs
Diffstat (limited to 'sapi')
24 files changed, 308 insertions, 100 deletions
diff --git a/sapi/fuzzer/README b/sapi/fuzzer/README deleted file mode 100644 index e0aafcaadf..0000000000 --- a/sapi/fuzzer/README +++ /dev/null @@ -1,13 +0,0 @@ -Fuzzing SAPI for PHP - -Enable fuzzing targets with --enable-fuzzer switch. - -Your compiler should support -fsanitize=address and you need -to have Fuzzer library around. - -When running `make` it creates these binaries in `sapi/fuzzer/`: -* php-fuzz-parser - fuzzing language parser -* php-fuzz-unserialize - fuzzing unserialize() function -* php-fuzz-json - fuzzing JSON parser -* php-fuzz-exif - fuzzing exif_read_data() function (use --enable-exif) -* php-fuzz-mbstring - fuzzing mb_ereg[i] (requires --enable-mbstring) diff --git a/sapi/fuzzer/README.md b/sapi/fuzzer/README.md new file mode 100644 index 0000000000..2cdbb1c5a3 --- /dev/null +++ b/sapi/fuzzer/README.md @@ -0,0 +1,50 @@ +Fuzzing SAPI for PHP +-------------------- + +The following `./configure` options can be used to enable the fuzzing SAPI, as well as all availablefuzzers. If you don't build the exif/json/mbstring extensions, fuzzers for these extensions will not be built. + +```sh +./configure \ + --enable-fuzzer \ + --with-pic \ + --enable-debug-assertions \ + --enable-exif \ + --enable-json \ + --enable-mbstring +``` + +The `--with-pic` option is required to avoid a linking failure. The `--enable-debug-assertions` option can be used to enable debug assertions despite the use of a release build. + +You will need a recent version of clang that supports the `-fsanitize=fuzzer-no-link` option. + +When running `make` it creates these binaries in `sapi/fuzzer/`: + +* `php-fuzz-parser`: Fuzzing language parser and compiler +* `php-fuzz-unserialize`: Fuzzing unserialize() function +* `php-fuzz-json`: Fuzzing JSON parser (requires --enable-json) +* `php-fuzz-exif`: Fuzzing `exif_read_data()` function (requires --enable-exif) +* `php-fuzz-mbstring`: fuzzing `mb_ereg[i]()` (requires --enable-mbstring) + +Some fuzzers have a seed corpus in `sapi/fuzzer/corpus`. You can use it as follows: + +```sh +cp -r sapi/fuzzer/corpus/exif ./my-exif-corpus +sapi/fuzzer/php-fuzz-exif ./my-exif-corpus +``` + +For the unserialize fuzzer, a dictionary of internal classes should be generated first: + +```sh +sapi/cli/php sapi/fuzzer/corpus/generate_unserialize_dict.php +cp -r sapi/fuzzer/corpus/unserialize ./my-unserialize-corpus +sapi/fuzzer/php-fuzz-unserialize -dict=$PWD/sapi/fuzzer/corpus/unserialize.dict ./my-unserialize-corpus +``` + +For the parser fuzzer, a corpus may be generated from Zend test files: + +```sh +sapi/cli/php sapi/fuzzer/corpus/generate_parser_corpus.php +mkdir ./my-parser-corpus +sapi/fuzzer/php-fuzz-parser -merge=1 ./my-parser-corpus sapi/fuzzer/corpus/parser +sapi/fuzzer/php-fuzz-parser -only_ascii=1 ./my-parser-corpus +``` diff --git a/sapi/fuzzer/config.m4 b/sapi/fuzzer/config.m4 index 12cf99bf86..bf08123bfb 100644 --- a/sapi/fuzzer/config.m4 +++ b/sapi/fuzzer/config.m4 @@ -14,7 +14,8 @@ dnl AC_DEFUN([PHP_FUZZER_TARGET], [ PHP_FUZZER_BINARIES="$PHP_FUZZER_BINARIES $SAPI_FUZZER_PATH/php-fuzz-$1" PHP_SUBST($2) - PHP_ADD_SOURCES_X([sapi/fuzzer],[fuzzer-$1.c fuzzer-sapi.c],[],$2) + PHP_ADD_SOURCES_X([sapi/fuzzer],[fuzzer-$1.c],[],$2) + $2="[$]$2 $FUZZER_COMMON_OBJS" ]) if test "$PHP_FUZZER" != "no"; then @@ -24,14 +25,20 @@ if test "$PHP_FUZZER" != "no"; then SAPI_FUZZER_PATH=sapi/fuzzer PHP_SUBST(SAPI_FUZZER_PATH) if test -z "$LIB_FUZZING_ENGINE"; then - FUZZING_LIB="-lFuzzer" + FUZZING_LIB="-fsanitize=fuzzer" FUZZING_CC="$CC" - AX_CHECK_COMPILE_FLAG([-fsanitize=address], [ - CFLAGS="$CFLAGS -fsanitize=address" - CXXFLAGS="$CXXFLAGS -fsanitize=address" - LDFLAGS="$LDFLAGS -fsanitize=address" + dnl Don't include -fundefined in CXXFLAGS, because that would also require linking + dnl with a C++ compiler. + AX_CHECK_COMPILE_FLAG([-fsanitize=fuzzer-no-link], [ + CFLAGS="$CFLAGS -fsanitize=fuzzer-no-link,address" + dnl Disable object-size sanitizer, because it is incompatible with our zend_function + dnl union, and this can't be easily fixed. + dnl We need to specify -fno-sanitize-recover=undefined here, otherwise ubsan warnings + dnl will not be considered failures by the fuzzer. + CFLAGS="$CFLAGS -fsanitize=undefined -fno-sanitize=object-size -fno-sanitize-recover=undefined" + CXXFLAGS="$CXXFLAGS -fsanitize=fuzzer-no-link,address" ],[ - AC_MSG_ERROR(compiler doesn't support -fsanitize flags) + AC_MSG_ERROR(Compiler doesn't support -fsanitize=fuzzer-no-link) ]) else FUZZING_LIB="-lFuzzingEngine" @@ -44,15 +51,21 @@ if test "$PHP_FUZZER" != "no"; then PHP_ADD_BUILD_DIR([sapi/fuzzer]) PHP_FUZZER_BINARIES="" + PHP_BINARIES="$PHP_BINARIES fuzzer" PHP_INSTALLED_SAPIS="$PHP_INSTALLED_SAPIS fuzzer" + PHP_ADD_SOURCES_X([sapi/fuzzer], [fuzzer-sapi.c], [], FUZZER_COMMON_OBJS) + PHP_FUZZER_TARGET([parser], PHP_FUZZER_PARSER_OBJS) PHP_FUZZER_TARGET([unserialize], PHP_FUZZER_UNSERIALIZE_OBJS) - PHP_FUZZER_TARGET([exif], PHP_FUZZER_EXIF_OBJS) - if test -n "$enable_json" && test "$enable_json" != "no"; then + dnl json extension is enabled by default + if (test -n "$enable_json" && test "$enable_json" != "no") || test -z "$PHP_ENABLE_ALL"; then PHP_FUZZER_TARGET([json], PHP_FUZZER_JSON_OBJS) fi + if test -n "$enable_exif" && test "$enable_exif" != "no"; then + PHP_FUZZER_TARGET([exif], PHP_FUZZER_EXIF_OBJS) + fi if test -n "$enable_mbstring" && test "$enable_mbstring" != "no"; then PHP_FUZZER_TARGET([mbstring], PHP_FUZZER_MBSTRING_OBJS) fi diff --git a/sapi/fuzzer/corpus/generate_parser_corpus.php b/sapi/fuzzer/corpus/generate_parser_corpus.php new file mode 100644 index 0000000000..7d9cdf98d1 --- /dev/null +++ b/sapi/fuzzer/corpus/generate_parser_corpus.php @@ -0,0 +1,22 @@ +<?php + +$testsDir = __DIR__ . '/../../../Zend/tests/'; +$it = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($testsDir), + RecursiveIteratorIterator::LEAVES_ONLY +); + +$corpusDir = __DIR__ . '/parser'; +@mkdir($corpusDir); + +foreach ($it as $file) { + if (!preg_match('/\.phpt$/', $file)) continue; + $code = file_get_contents($file); + if (!preg_match('/--FILE--(.*)--EXPECT/s', $code, $matches)) continue; + $code = $matches[1]; + + $outFile = str_replace($testsDir, '', $file); + $outFile = str_replace('/', '_', $outFile); + $outFile = $corpusDir . '/' . $outFile; + file_put_contents($outFile, $code); +} diff --git a/sapi/fuzzer/corpus/generate_unserialize_dict.php b/sapi/fuzzer/corpus/generate_unserialize_dict.php new file mode 100644 index 0000000000..4c20ed7e93 --- /dev/null +++ b/sapi/fuzzer/corpus/generate_unserialize_dict.php @@ -0,0 +1,9 @@ +<?php + +$dict = ""; +foreach (get_declared_classes() as $class) { + $len = strlen($class); + $dict .= "\"$len:\\\"$class\\\"\"\n"; +} + +file_put_contents(__DIR__ . "/unserialize.dict", $dict); diff --git a/sapi/fuzzer/corpus/parser.dict b/sapi/fuzzer/corpus/parser.dict new file mode 100644 index 0000000000..8b382afac9 --- /dev/null +++ b/sapi/fuzzer/corpus/parser.dict @@ -0,0 +1,85 @@ +"exit" +"die" +"fn" +"function" +"const" +"return" +"yield" +"yield from" +"try" +"catch" +"finally" +"throw" +"if" +"elseif" +"endif" +"else" +"while" +"endwhile" +"do" +"for" +"endfor" +"foreach" +"endforeach" +"declare" +"enddeclare" +"instanceof" +"as" +"switch" +"endswitch" +"case" +"default" +"break" +"continue" +"goto" +"echo" +"print" +"class" +"interface" +"trait" +"extends" +"implements" +"new" +"clone" +"var" +"int" +"integer" +"float" +"double" +"real" +"string" +"binary" +"array" +"object" +"bool" +"boolean" +"unset" +"eval" +"include" +"include_once" +"require" +"require_once" +"namespace" +"use" +"insteadof" +"global" +"isset" +"empty" +"__halt_compiler" +"static" +"abstract" +"final" +"private" +"protected" +"public" +"unset" +"list" +"callable" +"__class__" +"__trait__" +"__function__" +"__method__" +"__line__" +"__file__" +"__dir__" +"__namespace__" diff --git a/sapi/fuzzer/corpus/unserialize/__serialize_007 b/sapi/fuzzer/corpus/unserialize/__serialize_007 new file mode 100644 index 0000000000..6709aca303 --- /dev/null +++ b/sapi/fuzzer/corpus/unserialize/__serialize_007 @@ -0,0 +1 @@ +O:13:"ArrayIterator":2:{i:0;i:0;s:1:"x";R:2;} diff --git a/sapi/fuzzer/corpus/unserialize/bug7131 b/sapi/fuzzer/corpus/unserialize/bug7131 new file mode 100644 index 0000000000..1ba49d8da1 --- /dev/null +++ b/sapi/fuzzer/corpus/unserialize/bug7131 @@ -0,0 +1 @@ +C:11:"ArrayObject":11:{x:i:0;r:3;X}
\ No newline at end of file diff --git a/sapi/fuzzer/corpus/unserialize/bug71313 b/sapi/fuzzer/corpus/unserialize/bug71313 new file mode 100644 index 0000000000..4163b0350a --- /dev/null +++ b/sapi/fuzzer/corpus/unserialize/bug71313 @@ -0,0 +1 @@ +C:16:"SplObjectStorage":113:{x:i:2;O:8:"stdClass":0:{},a:2:{s:4:"prev";i:2;s:4:"next";O:8:"stdClass":0:{}};r:7;,R:2;s:4:"next";;r:3;};m:a:0:{}}
\ No newline at end of file diff --git a/sapi/fuzzer/corpus/unserialize/bug73144_1 b/sapi/fuzzer/corpus/unserialize/bug73144_1 new file mode 100644 index 0000000000..0d6d600520 --- /dev/null +++ b/sapi/fuzzer/corpus/unserialize/bug73144_1 @@ -0,0 +1 @@ +a:2:{i:0;O:1:"0":2:0s:1:"0";i:0;s:1:"0";a:1:{i:0;C:11:"ArrayObject":7:{x:i:0;r} diff --git a/sapi/fuzzer/corpus/unserialize/bug73144_2 b/sapi/fuzzer/corpus/unserialize/bug73144_2 new file mode 100644 index 0000000000..d1145066c5 --- /dev/null +++ b/sapi/fuzzer/corpus/unserialize/bug73144_2 @@ -0,0 +1 @@ +C:11:"ArrayObject":34:{x:i:1;O:8:"stdClass":1:{};m:a:0:{}}
\ No newline at end of file diff --git a/sapi/fuzzer/corpus/unserialize/bug73825 b/sapi/fuzzer/corpus/unserialize/bug73825 new file mode 100644 index 0000000000..dc9b0e4334 --- /dev/null +++ b/sapi/fuzzer/corpus/unserialize/bug73825 @@ -0,0 +1 @@ +O:8:"00000000": diff --git a/sapi/fuzzer/corpus/unserialize/bug74101 b/sapi/fuzzer/corpus/unserialize/bug74101 new file mode 100644 index 0000000000..b8d2fd2308 --- /dev/null +++ b/sapi/fuzzer/corpus/unserialize/bug74101 @@ -0,0 +1 @@ +O:9:"Exception":799999999999999999999999999997:0i:0;a:0:{}i:2;i:0;i:0;R:2; diff --git a/sapi/fuzzer/corpus/unserialize/bug74103 b/sapi/fuzzer/corpus/unserialize/bug74103 new file mode 100644 index 0000000000..ab2f70239c --- /dev/null +++ b/sapi/fuzzer/corpus/unserialize/bug74103 @@ -0,0 +1 @@ +a:7:{i:0;i:04;s:1:"a";i:2;i:9617006;i:4;s:1:"a";i:4;s:1:"a";R:5;s:1:"7";R:3;s:1:"a";R:5;;s:18;}} diff --git a/sapi/fuzzer/corpus/unserialize/bug74111 b/sapi/fuzzer/corpus/unserialize/bug74111 new file mode 100644 index 0000000000..c1196ee7de --- /dev/null +++ b/sapi/fuzzer/corpus/unserialize/bug74111 @@ -0,0 +1 @@ +O:8:"stdClass":00000000 diff --git a/sapi/fuzzer/corpus/unserialize/bug74614 b/sapi/fuzzer/corpus/unserialize/bug74614 new file mode 100644 index 0000000000..c7174893fe --- /dev/null +++ b/sapi/fuzzer/corpus/unserialize/bug74614 @@ -0,0 +1 @@ +a:3020000000000000000000000000000001:{i:0;a:0:{}i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;R:2;} diff --git a/sapi/fuzzer/corpus/unserialize/bug75054 b/sapi/fuzzer/corpus/unserialize/bug75054 new file mode 100644 index 0000000000..866e5b8fca --- /dev/null +++ b/sapi/fuzzer/corpus/unserialize/bug75054 @@ -0,0 +1 @@ +a:9:{i:0;s:4:"0000";i:0;s:4:"0000";i:0;R:2;s:4:"5003";R:2;s:4:"0000";R:2;s:4:"0000";R:2;s:4:"000";R:2;s:4:"0000";d:0;s:4:"0000";a:9:{s:4:"0000";
\ No newline at end of file diff --git a/sapi/fuzzer/fuzzer-exif.c b/sapi/fuzzer/fuzzer-exif.c index 574f3393d2..5217ebf14d 100644 --- a/sapi/fuzzer/fuzzer-exif.c +++ b/sapi/fuzzer/fuzzer-exif.c @@ -33,11 +33,11 @@ #include "fuzzer-sapi.h" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { +#if HAVE_EXIF char *filename; int filedes; - if (php_request_startup()==FAILURE) { - php_module_shutdown(); + if (fuzzer_request_startup() == FAILURE) { return 0; } @@ -54,6 +54,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { php_request_shutdown(NULL); return 0; +#else + fprintf(stderr, "\n\nERROR:\nPHP built without EXIF, recompile with --enable-exif to use this fuzzer\n"); + exit(1); +#endif } int LLVMFuzzerInitialize(int *argc, char ***argv) { diff --git a/sapi/fuzzer/fuzzer-json.c b/sapi/fuzzer/fuzzer-json.c index 0c619a22b9..4ebc931727 100644 --- a/sapi/fuzzer/fuzzer-json.c +++ b/sapi/fuzzer/fuzzer-json.c @@ -41,8 +41,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { memcpy(data, Data, Size); data[Size] = '\0'; - if (php_request_startup()==FAILURE) { - php_module_shutdown(); + if (fuzzer_request_startup() == FAILURE) { return 0; } @@ -50,9 +49,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { zval result; php_json_parser parser; php_json_parser_init(&parser, &result, data, Size, option, 10); - php_json_yyparse(&parser); - - ZVAL_UNDEF(&result); + if (php_json_yyparse(&parser) == SUCCESS) { + zval_ptr_dtor(&result); + } } php_request_shutdown(NULL); diff --git a/sapi/fuzzer/fuzzer-mbstring.c b/sapi/fuzzer/fuzzer-mbstring.c index aaeef1ce8a..9837d34205 100644 --- a/sapi/fuzzer/fuzzer-mbstring.c +++ b/sapi/fuzzer/fuzzer-mbstring.c @@ -36,8 +36,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { memcpy(data, Data, Size); data[Size] = '\0'; - if (php_request_startup()==FAILURE) { - php_module_shutdown(); + if (fuzzer_request_startup() == FAILURE) { return 0; } diff --git a/sapi/fuzzer/fuzzer-parser.c b/sapi/fuzzer/fuzzer-parser.c index eb1e03b6e8..ac2a1a8f2b 100644 --- a/sapi/fuzzer/fuzzer-parser.c +++ b/sapi/fuzzer/fuzzer-parser.c @@ -23,56 +23,26 @@ #include <ext/standard/info.h> #include <ext/standard/php_var.h> #include <main/php_variables.h> -#ifdef JO0 -#include <ext/standard/php_smart_str.h> -#endif #include "fuzzer.h" - #include "fuzzer-sapi.h" -int fuzzer_do_parse(zend_file_handle *file_handle, char *filename) -{ - int retval = FAILURE; /* failure by default */ - - SG(options) |= SAPI_OPTION_NO_CHDIR; - SG(request_info).argc=0; - SG(request_info).argv=NULL; - - if (php_request_startup(TSRMLS_C)==FAILURE) { - php_module_shutdown(TSRMLS_C); - return FAILURE; - } - - SG(headers_sent) = 1; - SG(request_info).no_headers = 1; - php_register_variable("PHP_SELF", filename, NULL TSRMLS_CC); - - zend_first_try { - zend_compile_file(file_handle, ZEND_REQUIRE); - //retval = php_execute_script(file_handle TSRMLS_CC); - } zend_end_try(); - - php_request_shutdown((void *) 0); - - return (retval == SUCCESS) ? SUCCESS : FAILURE; -} - -int fuzzer_do_request_d(char *filename, char *data, size_t data_len); - int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { char *s = malloc(Size+1); memcpy(s, Data, Size); s[Size] = '\0'; - fuzzer_do_request_d("fuzzer.php", Data, Size); - //fuzzer_do_parse(&file_handle, "fuzzer.php"); + fuzzer_do_request_from_buffer("fuzzer.php", s, Size); - free(s); + /* Do not free s: fuzzer_do_request_from_buffer() takes ownership of the allocation. */ return 0; } int LLVMFuzzerInitialize(int *argc, char ***argv) { + /* Compilation will often trigger fatal errors. + * Use tracked allocation mode to avoid leaks in that case. */ + putenv("USE_TRACKED_ALLOC=1"); + fuzzer_init_php(); /* fuzzer_shutdown_php(); */ diff --git a/sapi/fuzzer/fuzzer-sapi.c b/sapi/fuzzer/fuzzer-sapi.c index dd26c3c103..da1df37943 100644 --- a/sapi/fuzzer/fuzzer-sapi.c +++ b/sapi/fuzzer/fuzzer-sapi.c @@ -24,6 +24,10 @@ #include <ext/standard/php_var.h> #include <main/php_variables.h> +#ifdef __SANITIZE_ADDRESS__ +# include "sanitizer/lsan_interface.h" +#endif + #include "fuzzer.h" #include "fuzzer-sapi.h" @@ -31,7 +35,8 @@ const char HARDCODED_INI[] = "html_errors=0\n" "implicit_flush=1\n" "max_execution_time=20\n" - "output_buffering=0\n"; + "output_buffering=0\n" + "error_reporting=0"; static int startup(sapi_module_struct *sapi_module) { @@ -41,7 +46,7 @@ static int startup(sapi_module_struct *sapi_module) return SUCCESS; } -static size_t ub_write(const char *str, size_t str_length TSRMLS_DC) +static size_t ub_write(const char *str, size_t str_length) { /* quiet */ return str_length; @@ -52,22 +57,22 @@ static void fuzzer_flush(void *server_context) /* quiet */ } -static void send_header(sapi_header_struct *sapi_header, void *server_context TSRMLS_DC) +static void send_header(sapi_header_struct *sapi_header, void *server_context) { } -static char* read_cookies(TSRMLS_D) +static char* read_cookies() { /* TODO: fuzz these! */ return NULL; } -static void register_variables(zval *track_vars_array TSRMLS_DC) +static void register_variables(zval *track_vars_array) { - php_import_environment_variables(track_vars_array TSRMLS_CC); + php_import_environment_variables(track_vars_array); } -static void log_message(char *message, int level TSRMLS_DC) +static void log_message(char *message, int level) { } @@ -106,6 +111,12 @@ static sapi_module_struct fuzzer_module = { int fuzzer_init_php() { +#ifdef __SANITIZE_ADDRESS__ + /* We're going to leak all the memory allocated during startup, + * so disable lsan temporarily. */ + __lsan_disable(); +#endif + sapi_startup(&fuzzer_module); fuzzer_module.phpinfo_as_text = 1; @@ -118,15 +129,30 @@ int fuzzer_init_php() */ putenv("USE_ZEND_ALLOC=0"); + if (fuzzer_module.startup(&fuzzer_module)==FAILURE) { + return FAILURE; + } + #ifdef __SANITIZE_ADDRESS__ - /* Not very interested in memory leak detection, since Zend MM does that */ - __lsan_disable(); + __lsan_enable(); #endif - if (fuzzer_module.startup(&fuzzer_module)==FAILURE) { + return SUCCESS; +} + +int fuzzer_request_startup() +{ + if (php_request_startup() == FAILURE) { + php_module_shutdown(); return FAILURE; } +#ifdef ZEND_SIGNALS + /* Some signal handlers will be overriden, + * don't complain about them during shutdown. */ + SIGG(check) = 0; +#endif + return SUCCESS; } @@ -141,9 +167,7 @@ void fuzzer_set_ini_file(const char *file) int fuzzer_shutdown_php() { - TSRMLS_FETCH(); - - php_module_shutdown(TSRMLS_C); + php_module_shutdown(); sapi_shutdown(); free(fuzzer_module.ini_entries); @@ -158,18 +182,25 @@ int fuzzer_do_request(zend_file_handle *file_handle, char *filename) SG(request_info).argc=0; SG(request_info).argv=NULL; - if (php_request_startup(TSRMLS_C)==FAILURE) { - php_module_shutdown(TSRMLS_C); + if (fuzzer_request_startup() == FAILURE) { return FAILURE; } SG(headers_sent) = 1; SG(request_info).no_headers = 1; - php_register_variable("PHP_SELF", filename, NULL TSRMLS_CC); + php_register_variable("PHP_SELF", filename, NULL); zend_first_try { - zend_compile_file(file_handle, ZEND_REQUIRE); - /*retval = php_execute_script(file_handle TSRMLS_CC);*/ + zend_op_array *op_array = zend_compile_file(file_handle, ZEND_REQUIRE); + if (op_array) { + destroy_op_array(op_array); + efree(op_array); + } + if (EG(exception)) { + zend_object_release(EG(exception)); + EG(exception) = NULL; + } + /*retval = php_execute_script(file_handle);*/ } zend_end_try(); php_request_shutdown((void *) 0); @@ -189,10 +220,11 @@ int fuzzer_do_request_f(char *filename) return fuzzer_do_request(&file_handle, filename); } -int fuzzer_do_request_d(char *filename, char *data, size_t data_len) +int fuzzer_do_request_from_buffer(char *filename, char *data, size_t data_len) { zend_file_handle file_handle; file_handle.filename = filename; + file_handle.free_filename = 0; file_handle.opened_path = NULL; file_handle.handle.stream.handle = NULL; file_handle.handle.stream.reader = (zend_stream_reader_t)_php_stream_read; @@ -209,11 +241,10 @@ int fuzzer_do_request_d(char *filename, char *data, size_t data_len) // Call named PHP function with N zval arguments void fuzzer_call_php_func_zval(const char *func_name, int nargs, zval *args) { zval retval, func; - int result; ZVAL_STRING(&func, func_name); ZVAL_UNDEF(&retval); - result = call_user_function(CG(function_table), NULL, &func, &retval, nargs, args); + call_user_function(CG(function_table), NULL, &func, &retval, nargs, args); // TODO: check result? /* to ensure retval is not broken */ diff --git a/sapi/fuzzer/fuzzer-sapi.h b/sapi/fuzzer/fuzzer-sapi.h index 92ce95b86a..cce8080b2c 100644 --- a/sapi/fuzzer/fuzzer-sapi.h +++ b/sapi/fuzzer/fuzzer-sapi.h @@ -18,5 +18,7 @@ */ int fuzzer_init_php(); +int fuzzer_request_startup(); void fuzzer_call_php_func(const char *func_name, int nargs, char **params); void fuzzer_call_php_func_zval(const char *func_name, int nargs, zval *args); +int fuzzer_do_request_from_buffer(char *filename, char *data, size_t data_len); diff --git a/sapi/fuzzer/fuzzer-unserialize.c b/sapi/fuzzer/fuzzer-unserialize.c index 9b843f25fe..9cf040944a 100644 --- a/sapi/fuzzer/fuzzer-unserialize.c +++ b/sapi/fuzzer/fuzzer-unserialize.c @@ -32,28 +32,54 @@ #include "ext/standard/php_var.h" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - unsigned char *data = malloc(Size+1); + unsigned char *orig_data = malloc(Size+1); + zend_execute_data execute_data; + zend_function func; - memcpy(data, Data, Size); - data[Size] = '\0'; + memcpy(orig_data, Data, Size); + orig_data[Size] = '\0'; - if (php_request_startup()==FAILURE) { - php_module_shutdown(); + if (fuzzer_request_startup()==FAILURE) { return 0; } - zval result; + /* Set up a dummy stack frame so that exceptions may be thrown. */ + { + memset(&execute_data, 0, sizeof(zend_execute_data)); + memset(&func, 0, sizeof(zend_function)); - php_unserialize_data_t var_hash; - PHP_VAR_UNSERIALIZE_INIT(var_hash); - php_var_unserialize(&result, &data, data + Size, &var_hash); - PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + func.type = ZEND_INTERNAL_FUNCTION; + func.common.function_name = ZSTR_EMPTY_ALLOC(); + execute_data.func = &func; + EG(current_execute_data) = &execute_data; + } + + { + const unsigned char *data = orig_data; + zval result; + ZVAL_UNDEF(&result); + + php_unserialize_data_t var_hash; + PHP_VAR_UNSERIALIZE_INIT(var_hash); + php_var_unserialize(&result, (const unsigned char **) &data, data + Size, &var_hash); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); - zval_ptr_dtor(&result); + zval_ptr_dtor(&result); + + /* Destroy any thrown exception. */ + if (EG(exception)) { + zend_object_release(EG(exception)); + EG(exception) = NULL; + } + } + /* Unserialize may create circular structure. Make sure we free them. + * Two calls are performed to handle objects with destructors. */ + zend_gc_collect_cycles(); + zend_gc_collect_cycles(); php_request_shutdown(NULL); - free(data); + free(orig_data); return 0; } |