diff --git a/sapi/fuzzer/Makefile.frag b/sapi/fuzzer/Makefile.frag index fc8fa8410cb..2f15ad92f89 100644 --- a/sapi/fuzzer/Makefile.frag +++ b/sapi/fuzzer/Makefile.frag @@ -8,8 +8,11 @@ $(SAPI_FUZZER_PATH)/php-fuzz-parser: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_F $(SAPI_FUZZER_PATH)/php-fuzz-execute: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_FUZZER_EXECUTE_OBJS) $(FUZZER_BUILD) $(PHP_FUZZER_EXECUTE_OBJS) -o $@ -$(SAPI_FUZZER_PATH)/php-fuzz-jit: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_FUZZER_JIT_OBJS) - $(FUZZER_BUILD) $(PHP_FUZZER_JIT_OBJS) -o $@ +$(SAPI_FUZZER_PATH)/php-fuzz-function-jit: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_FUZZER_FUNCTION_JIT_OBJS) + $(FUZZER_BUILD) $(PHP_FUZZER_FUNCTION_JIT_OBJS) -o $@ + +$(SAPI_FUZZER_PATH)/php-fuzz-tracing-jit: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_FUZZER_TRACING_JIT_OBJS) + $(FUZZER_BUILD) $(PHP_FUZZER_TRACING_JIT_OBJS) -o $@ $(SAPI_FUZZER_PATH)/php-fuzz-unserialize: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_FUZZER_UNSERIALIZE_OBJS) $(FUZZER_BUILD) $(PHP_FUZZER_UNSERIALIZE_OBJS) -o $@ diff --git a/sapi/fuzzer/README.md b/sapi/fuzzer/README.md index e8f60ac40ad..2099a34bfbf 100644 --- a/sapi/fuzzer/README.md +++ b/sapi/fuzzer/README.md @@ -30,7 +30,8 @@ When running `make` it creates these binaries in `sapi/fuzzer/`: * `php-fuzz-exif`: Fuzzing `exif_read_data()` function (requires --enable-exif) * `php-fuzz-mbstring`: Fuzzing `mb_ereg[i]()` (requires --enable-mbstring) * `php-fuzz-execute`: Fuzzing the executor -* `php-fuzz-jit`: Fuzzing the function JIT (requires --enable-opcache) +* `php-fuzz-function-jit`: Fuzzing the function JIT (requires --enable-opcache) +* `php-fuzz-tracing-jit`: Fuzzing the tracing JIT (requires --enable-opcache) Some fuzzers have a seed corpus in `sapi/fuzzer/corpus`. You can use it as follows: @@ -64,6 +65,13 @@ sapi/fuzzer/php-fuzz-parser -merge=1 ./my-parser-corpus sapi/fuzzer/corpus/parse sapi/fuzzer/php-fuzz-parser -only_ascii=1 ./my-parser-corpus ``` +For the execute, function-jit and tracing-jit fuzzers, a corpus may be generated from any set of test files: + +```sh +sapi/cli/php sapi/fuzzer/generate_execute_corpus.php ./execute-corpus Zend/tests ext/opcache/tests/jit +sapi/fuzzer/php-fuzzer-function-jit ./execute-corpus +``` + For the mbstring fuzzer, you may want to build the libonig dependency with instrumentation. At this time, libonig is not clean under ubsan, so only the fuzzer and address sanitizers may be used. ```sh diff --git a/sapi/fuzzer/config.m4 b/sapi/fuzzer/config.m4 index f7125365d0e..72953a8295d 100644 --- a/sapi/fuzzer/config.m4 +++ b/sapi/fuzzer/config.m4 @@ -54,7 +54,8 @@ if test "$PHP_FUZZER" != "no"; then PHP_FUZZER_TARGET([parser], PHP_FUZZER_PARSER_OBJS) PHP_FUZZER_TARGET([execute], PHP_FUZZER_EXECUTE_OBJS) - PHP_FUZZER_TARGET([jit], PHP_FUZZER_JIT_OBJS) + PHP_FUZZER_TARGET([function-jit], PHP_FUZZER_FUNCTION_JIT_OBJS) + PHP_FUZZER_TARGET([tracing-jit], PHP_FUZZER_TRACING_JIT_OBJS) PHP_FUZZER_TARGET([unserialize], PHP_FUZZER_UNSERIALIZE_OBJS) PHP_FUZZER_TARGET([unserializehash], PHP_FUZZER_UNSERIALIZEHASH_OBJS) PHP_FUZZER_TARGET([json], PHP_FUZZER_JSON_OBJS) diff --git a/sapi/fuzzer/fuzzer-execute-common.h b/sapi/fuzzer/fuzzer-execute-common.h index 092a662c42f..d0e80b8bb44 100644 --- a/sapi/fuzzer/fuzzer-execute-common.h +++ b/sapi/fuzzer/fuzzer-execute-common.h @@ -18,6 +18,7 @@ #include "fuzzer.h" #include "fuzzer-sapi.h" +#include "zend_exceptions.h" #define MAX_STEPS 1000 #define MAX_SIZE (8 * 1024) @@ -38,6 +39,8 @@ static zend_always_inline void fuzzer_step(void) { } } +static void (*orig_execute_ex)(zend_execute_data *execute_data); + static void fuzzer_execute_ex(zend_execute_data *execute_data) { while (1) { int ret; @@ -91,9 +94,31 @@ static void fuzzer_init_php_for_execute(const char *extra_ini) { fuzzer_init_php(extra_ini); + orig_execute_ex = zend_execute_ex; zend_execute_ex = fuzzer_execute_ex; orig_execute_internal = zend_execute_internal ? zend_execute_internal : execute_internal; zend_execute_internal = fuzzer_execute_internal; orig_compile_string = zend_compile_string; zend_compile_string = fuzzer_compile_string; } + +ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) { + steps_left = MAX_STEPS; + zend_exception_save(); + zval retval, func, args[2]; + ZVAL_STRING(&func, "opcache_invalidate"); + ZVAL_STRING(&args[0], "/fuzzer.php"); + ZVAL_TRUE(&args[1]); + call_user_function(CG(function_table), NULL, &func, &retval, 2, args); + ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE); + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&retval); + zval_ptr_dtor(&func); + zend_exception_restore(); +} + +ZEND_ATTRIBUTE_UNUSED char *get_opcache_path(void) { + // TODO: Make this more general. + char *opcache_path = "modules/opcache.so"; + return realpath(opcache_path, NULL); +} diff --git a/sapi/fuzzer/fuzzer-jit.c b/sapi/fuzzer/fuzzer-function-jit.c similarity index 79% rename from sapi/fuzzer/fuzzer-jit.c rename to sapi/fuzzer/fuzzer-function-jit.c index 04313bf9b74..4bebc4ce927 100644 --- a/sapi/fuzzer/fuzzer-jit.c +++ b/sapi/fuzzer/fuzzer-function-jit.c @@ -15,22 +15,6 @@ */ #include "fuzzer-execute-common.h" -#include "zend_exceptions.h" - -static void opcache_invalidate(void) { - steps_left = MAX_STEPS; - zend_exception_save(); - zval retval, func, args[2]; - ZVAL_STRING(&func, "opcache_invalidate"); - ZVAL_STRING(&args[0], "/fuzzer.php"); - ZVAL_TRUE(&args[1]); - call_user_function(CG(function_table), NULL, &func, &retval, 2, args); - ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE); - zval_ptr_dtor(&args[0]); - zval_ptr_dtor(&retval); - zval_ptr_dtor(&func); - zend_exception_restore(); -} int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { if (Size > MAX_SIZE) { @@ -63,12 +47,6 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { return 0; } -char *get_opcache_path(void) { - // TODO: Make this more general. - char *opcache_path = "modules/opcache.so"; - return realpath(opcache_path, NULL); -} - int LLVMFuzzerInitialize(int *argc, char ***argv) { char *opcache_path = get_opcache_path(); assert(opcache_path && "Failed to determine opcache path"); @@ -78,7 +56,7 @@ int LLVMFuzzerInitialize(int *argc, char ***argv) { "zend_extension=%s\n" "opcache.validate_timestamps=0\n" "opcache.file_update_protection=0\n" - "opcache.jit_buffer_size=512M", + "opcache.jit_buffer_size=256M", opcache_path); free(opcache_path); fuzzer_init_php_for_execute(ini_buf); diff --git a/sapi/fuzzer/fuzzer-tracing-jit.c b/sapi/fuzzer/fuzzer-tracing-jit.c new file mode 100644 index 00000000000..f4387dc1f9b --- /dev/null +++ b/sapi/fuzzer/fuzzer-tracing-jit.c @@ -0,0 +1,75 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Nikita Popov | + +----------------------------------------------------------------------+ + */ + +#include "fuzzer-execute-common.h" + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (Size > MAX_SIZE) { + /* Large inputs have a large impact on fuzzer performance, + * but are unlikely to be necessary to reach new codepaths. */ + return 0; + } + + zend_string *jit_option = zend_string_init("opcache.jit", sizeof("opcache.jit") - 1, 1); + + /* First run without JIT to determine whether we bail out. We should not run JITed code if + * we bail out here, as the JIT code may loop infinitely. */ + steps_left = MAX_STEPS; + bailed_out = false; + zend_alter_ini_entry_chars( + jit_option, "off", sizeof("off")-1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + fuzzer_do_request_from_buffer( + "/fuzzer.php", (const char *) Data, Size, /* execute */ 1, opcache_invalidate); + + if (!bailed_out) { + steps_left = MAX_STEPS; + zend_alter_ini_entry_chars(jit_option, + "tracing", sizeof("tracing")-1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_execute_ex = orig_execute_ex; + /* Trace & compile */ + fuzzer_do_request_from_buffer( + "/fuzzer.php", (const char *) Data, Size, /* execute */ 1, NULL); + /* Execute trace */ + fuzzer_do_request_from_buffer( + "/fuzzer.php", (const char *) Data, Size, /* execute */ 1, opcache_invalidate); + zend_execute_ex = fuzzer_execute_ex; + } + + zend_string_release(jit_option); + + return 0; +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) { + char *opcache_path = get_opcache_path(); + assert(opcache_path && "Failed to determine opcache path"); + + char ini_buf[512]; + snprintf(ini_buf, sizeof(ini_buf), + "zend_extension=%s\n" + "opcache.validate_timestamps=0\n" + "opcache.file_update_protection=0\n" + "opcache.jit_buffer_size=256M\n" + "opcache.jit_hot_func=1\n" + "opcache.jit_hot_loop=1\n" + "opcache.jit_hot_return=1\n" + "opcache.jit_hot_side_exit=1\n" + "opcache.jit_max_root_traces=32768", + opcache_path); + free(opcache_path); + fuzzer_init_php_for_execute(ini_buf); + return 0; +}