]> granicus.if.org Git - php/commitdiff
Various improvements to fuzzer SAPIs
authorNikita Popov <nikita.ppv@gmail.com>
Fri, 13 Sep 2019 13:15:46 +0000 (15:15 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Mon, 16 Sep 2019 14:04:10 +0000 (16:04 +0200)
25 files changed:
configure.ac
sapi/fuzzer/README [deleted file]
sapi/fuzzer/README.md [new file with mode: 0644]
sapi/fuzzer/config.m4
sapi/fuzzer/corpus/generate_parser_corpus.php [new file with mode: 0644]
sapi/fuzzer/corpus/generate_unserialize_dict.php [new file with mode: 0644]
sapi/fuzzer/corpus/parser.dict [new file with mode: 0644]
sapi/fuzzer/corpus/unserialize/__serialize_007 [new file with mode: 0644]
sapi/fuzzer/corpus/unserialize/bug7131 [new file with mode: 0644]
sapi/fuzzer/corpus/unserialize/bug71313 [new file with mode: 0644]
sapi/fuzzer/corpus/unserialize/bug73144_1 [new file with mode: 0644]
sapi/fuzzer/corpus/unserialize/bug73144_2 [new file with mode: 0644]
sapi/fuzzer/corpus/unserialize/bug73825 [new file with mode: 0644]
sapi/fuzzer/corpus/unserialize/bug74101 [new file with mode: 0644]
sapi/fuzzer/corpus/unserialize/bug74103 [new file with mode: 0644]
sapi/fuzzer/corpus/unserialize/bug74111 [new file with mode: 0644]
sapi/fuzzer/corpus/unserialize/bug74614 [new file with mode: 0644]
sapi/fuzzer/corpus/unserialize/bug75054 [new file with mode: 0644]
sapi/fuzzer/fuzzer-exif.c
sapi/fuzzer/fuzzer-json.c
sapi/fuzzer/fuzzer-mbstring.c
sapi/fuzzer/fuzzer-parser.c
sapi/fuzzer/fuzzer-sapi.c
sapi/fuzzer/fuzzer-sapi.h
sapi/fuzzer/fuzzer-unserialize.c

index e15b83ca25296749783d288559262894775b1733..48756f3c6c9a8868f26e0966f34868394023ec04 100644 (file)
@@ -897,6 +897,18 @@ else
   ZEND_DEBUG=no
 fi
 
+PHP_ARG_ENABLE([debug-assertions],
+  [whether to enable debug assertions in release mode],
+  [AS_HELP_STRING([--enable-debug-assertions],
+    [Compile with debug assertions even in release mode])],
+  [no],
+  [no])
+
+if test "$PHP_DEBUG_ASSERTIONS" = "yes"; then
+  PHP_DEBUG=1
+  ZEND_DEBUG=yes
+fi
+
 PHP_ARG_ENABLE([rtld-now],
   [whether to dlopen extensions with RTLD_NOW instead of RTLD_LAZY],
   [AS_HELP_STRING([--enable-rtld-now],
diff --git a/sapi/fuzzer/README b/sapi/fuzzer/README
deleted file mode 100644 (file)
index e0aafca..0000000
+++ /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 (file)
index 0000000..2cdbb1c
--- /dev/null
@@ -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
+```
index 12cf99bf8653c6527d0213602bfcaf1487ed28a3..bf08123bfb0893c6ec94a31a87ead89d254ad115 100644 (file)
@@ -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 (file)
index 0000000..7d9cdf9
--- /dev/null
@@ -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 (file)
index 0000000..4c20ed7
--- /dev/null
@@ -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 (file)
index 0000000..8b382af
--- /dev/null
@@ -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 (file)
index 0000000..6709aca
--- /dev/null
@@ -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 (file)
index 0000000..1ba49d8
--- /dev/null
@@ -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 (file)
index 0000000..4163b03
--- /dev/null
@@ -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 (file)
index 0000000..0d6d600
--- /dev/null
@@ -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 (file)
index 0000000..d114506
--- /dev/null
@@ -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 (file)
index 0000000..dc9b0e4
--- /dev/null
@@ -0,0 +1 @@
+O:8:"00000000":
diff --git a/sapi/fuzzer/corpus/unserialize/bug74101 b/sapi/fuzzer/corpus/unserialize/bug74101
new file mode 100644 (file)
index 0000000..b8d2fd2
--- /dev/null
@@ -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 (file)
index 0000000..ab2f702
--- /dev/null
@@ -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 (file)
index 0000000..c1196ee
--- /dev/null
@@ -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 (file)
index 0000000..c717489
--- /dev/null
@@ -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 (file)
index 0000000..866e5b8
--- /dev/null
@@ -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:"\ 6000";R:2;s:4:"0000";d:0;s:4:"0000";a:9:{s:4:"0000";
\ No newline at end of file
index 574f3393d22a76e8c9fce711d16ae16e22568fa9..5217ebf14d60d8448c21ab0effb4ca1f6285134b 100644 (file)
 #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) {
index 0c619a22b9b439d2fccc6d3291ec1da6a2375075..4ebc9317275203e00b5abe9beb0f895fef07b15f 100644 (file)
@@ -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);
index aaeef1ce8a68b43a249cee043f088b5f730ec5a8..9837d3420512cfc2453b01418b2bbf3b0822a708 100644 (file)
@@ -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;
        }
 
index eb1e03b6e8a61124c4ce172f6d8d6bd420089d52..ac2a1a8f2b72fc8c3bc93c85e57e6aaf8851528d 100644 (file)
 #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(); */
index dd26c3c103fc056310d87be5328d01df1f902ccd..da1df3794348d4a4f32ff38350226c90c5a1fd08 100644 (file)
 #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 */
index 92ce95b86ad05eae1b86c619238d7a515d510589..cce8080b2c3bd7f1163694372b7680243c5c5266 100644 (file)
@@ -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);
index 9b843f25fe16ba90485def3c41184f25888e8ae6..9cf040944aaba24c721bbf643566fc540758f711 100644 (file)
 #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;
 }