Add fuzzer for tracing jit

This commit is contained in:
Nikita Popov 2021-09-21 15:26:04 +02:00
parent e69fb48ea3
commit 06a25c774d
6 changed files with 117 additions and 27 deletions

View file

@ -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 $@

View file

@ -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

View file

@ -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)

View file

@ -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);
}

View file

@ -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);

View file

@ -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 <nikic@php.net> |
+----------------------------------------------------------------------+
*/
#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;
}