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],
+++ /dev/null
-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)
--- /dev/null
+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
+```
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
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"
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
--- /dev/null
+<?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);
+}
--- /dev/null
+<?php
+
+$dict = "";
+foreach (get_declared_classes() as $class) {
+ $len = strlen($class);
+ $dict .= "\"$len:\\\"$class\\\"\"\n";
+}
+
+file_put_contents(__DIR__ . "/unserialize.dict", $dict);
--- /dev/null
+"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__"
--- /dev/null
+O:13:"ArrayIterator":2:{i:0;i:0;s:1:"x";R:2;}
--- /dev/null
+C:11:"ArrayObject":11:{x:i:0;r:3;X}
\ No newline at end of file
--- /dev/null
+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
--- /dev/null
+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}
--- /dev/null
+C:11:"ArrayObject":34:{x:i:1;O:8:"stdClass":1:{};m:a:0:{}}
\ No newline at end of file
--- /dev/null
+O:8:"00000000":
--- /dev/null
+O:9:"Exception":799999999999999999999999999997:0i:0;a:0:{}i:2;i:0;i:0;R:2;
--- /dev/null
+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;}}
--- /dev/null
+O:8:"stdClass":00000000
--- /dev/null
+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;}
--- /dev/null
+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
#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;
}
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) {
memcpy(data, Data, Size);
data[Size] = '\0';
- if (php_request_startup()==FAILURE) {
- php_module_shutdown();
+ if (fuzzer_request_startup() == FAILURE) {
return 0;
}
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);
memcpy(data, Data, Size);
data[Size] = '\0';
- if (php_request_startup()==FAILURE) {
- php_module_shutdown();
+ if (fuzzer_request_startup() == FAILURE) {
return 0;
}
#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(); */
#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"
"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)
{
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;
/* 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)
{
}
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;
*/
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;
}
int fuzzer_shutdown_php()
{
- TSRMLS_FETCH();
-
- php_module_shutdown(TSRMLS_C);
+ php_module_shutdown();
sapi_shutdown();
free(fuzzer_module.ini_entries);
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);
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;
// 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 */
*/
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);
#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;
}