mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
Add fuzzer for function JIT
This is a basic fuzzer for the function JIT, which looks for crashes and sanitizer violations only, and does not try to detect differing behavior yet.
This commit is contained in:
parent
d2eccd4405
commit
cd4243dde9
15 changed files with 228 additions and 94 deletions
|
@ -2811,6 +2811,7 @@ static inline int accel_find_sapi(void)
|
|||
"apache2handler",
|
||||
"litespeed",
|
||||
"uwsgi",
|
||||
"fuzzer",
|
||||
NULL
|
||||
};
|
||||
const char **sapi_name;
|
||||
|
|
|
@ -8,6 +8,9 @@ $(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-unserialize: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_FUZZER_UNSERIALIZE_OBJS)
|
||||
$(FUZZER_BUILD) $(PHP_FUZZER_UNSERIALIZE_OBJS) -o $@
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ 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)
|
||||
|
||||
Some fuzzers have a seed corpus in `sapi/fuzzer/corpus`. You can use it as follows:
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ 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([unserialize], PHP_FUZZER_UNSERIALIZE_OBJS)
|
||||
PHP_FUZZER_TARGET([unserializehash], PHP_FUZZER_UNSERIALIZEHASH_OBJS)
|
||||
PHP_FUZZER_TARGET([json], PHP_FUZZER_JSON_OBJS)
|
||||
|
|
99
sapi/fuzzer/fuzzer-execute-common.h
Normal file
99
sapi/fuzzer/fuzzer-execute-common.h
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| 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 <main/php.h>
|
||||
|
||||
#include "fuzzer.h"
|
||||
#include "fuzzer-sapi.h"
|
||||
|
||||
#define MAX_STEPS 1000
|
||||
#define MAX_SIZE (8 * 1024)
|
||||
static uint32_t steps_left;
|
||||
static bool bailed_out = false;
|
||||
|
||||
/* Because the fuzzer is always compiled with clang,
|
||||
* we can assume that we don't use global registers / hybrid VM. */
|
||||
typedef int (ZEND_FASTCALL *opcode_handler_t)(zend_execute_data *);
|
||||
|
||||
static zend_always_inline void fuzzer_step(void) {
|
||||
if (--steps_left == 0) {
|
||||
/* Reset steps before bailing out, so code running after bailout (e.g. in
|
||||
* destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
|
||||
steps_left = MAX_STEPS;
|
||||
bailed_out = true;
|
||||
zend_bailout();
|
||||
}
|
||||
}
|
||||
|
||||
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
|
||||
while (1) {
|
||||
int ret;
|
||||
fuzzer_step();
|
||||
if ((ret = ((opcode_handler_t) EX(opline)->handler)(execute_data)) != 0) {
|
||||
if (ret > 0) {
|
||||
execute_data = EG(current_execute_data);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static zend_op_array *(*orig_compile_string)(zend_string *source_string, const char *filename);
|
||||
|
||||
static zend_op_array *fuzzer_compile_string(zend_string *str, const char *filename) {
|
||||
if (ZSTR_LEN(str) > MAX_SIZE) {
|
||||
/* Avoid compiling huge inputs via eval(). */
|
||||
zend_bailout();
|
||||
}
|
||||
|
||||
return orig_compile_string(str, filename);
|
||||
}
|
||||
|
||||
static void (*orig_execute_internal)(zend_execute_data *execute_data, zval *return_value);
|
||||
|
||||
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
|
||||
fuzzer_step();
|
||||
|
||||
uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
|
||||
for (uint32_t i = 0; i < num_args; i++) {
|
||||
/* Some internal functions like preg_replace() may be slow on large inputs.
|
||||
* Limit the maximum size of string inputs. */
|
||||
zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
|
||||
if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
|
||||
zend_bailout();
|
||||
}
|
||||
}
|
||||
|
||||
orig_execute_internal(execute_data, return_value);
|
||||
}
|
||||
|
||||
static void fuzzer_init_php_for_execute(const char *extra_ini) {
|
||||
/* Compilation will often trigger fatal errors.
|
||||
* Use tracked allocation mode to avoid leaks in that case. */
|
||||
putenv("USE_TRACKED_ALLOC=1");
|
||||
|
||||
/* Just like other SAPIs, ignore SIGPIPEs. */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
fuzzer_init_php(extra_ini);
|
||||
|
||||
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;
|
||||
}
|
|
@ -14,70 +14,7 @@
|
|||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#include <main/php.h>
|
||||
|
||||
#include "fuzzer.h"
|
||||
#include "fuzzer-sapi.h"
|
||||
|
||||
#define MAX_STEPS 1000
|
||||
#define MAX_SIZE (8 * 1024)
|
||||
static uint32_t steps_left;
|
||||
|
||||
/* Because the fuzzer is always compiled with clang,
|
||||
* we can assume that we don't use global registers / hybrid VM. */
|
||||
typedef int (ZEND_FASTCALL *opcode_handler_t)(zend_execute_data *);
|
||||
|
||||
static zend_always_inline void fuzzer_step(void) {
|
||||
if (--steps_left == 0) {
|
||||
/* Reset steps before bailing out, so code running after bailout (e.g. in
|
||||
* destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
|
||||
steps_left = MAX_STEPS;
|
||||
zend_bailout();
|
||||
}
|
||||
}
|
||||
|
||||
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
|
||||
while (1) {
|
||||
int ret;
|
||||
fuzzer_step();
|
||||
if ((ret = ((opcode_handler_t) EX(opline)->handler)(execute_data)) != 0) {
|
||||
if (ret > 0) {
|
||||
execute_data = EG(current_execute_data);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static zend_op_array *(*orig_compile_string)(zend_string *source_string, const char *filename);
|
||||
|
||||
static zend_op_array *fuzzer_compile_string(zend_string *str, const char *filename) {
|
||||
if (ZSTR_LEN(str) > MAX_SIZE) {
|
||||
/* Avoid compiling huge inputs via eval(). */
|
||||
zend_bailout();
|
||||
}
|
||||
|
||||
return orig_compile_string(str, filename);
|
||||
}
|
||||
|
||||
static void (*orig_execute_internal)(zend_execute_data *execute_data, zval *return_value);
|
||||
|
||||
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
|
||||
fuzzer_step();
|
||||
|
||||
uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
|
||||
for (uint32_t i = 0; i < num_args; i++) {
|
||||
/* Some internal functions like preg_replace() may be slow on large inputs.
|
||||
* Limit the maximum size of string inputs. */
|
||||
zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
|
||||
if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
|
||||
zend_bailout();
|
||||
}
|
||||
}
|
||||
|
||||
orig_execute_internal(execute_data, return_value);
|
||||
}
|
||||
#include "fuzzer-execute-common.h"
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
if (Size > MAX_SIZE) {
|
||||
|
@ -87,27 +24,13 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
|||
}
|
||||
|
||||
steps_left = MAX_STEPS;
|
||||
fuzzer_do_request_from_buffer("/fuzzer.php", (const char *) Data, Size, /* execute */ 1);
|
||||
fuzzer_do_request_from_buffer(
|
||||
"/fuzzer.php", (const char *) Data, Size, /* execute */ 1, /* before_shutdown */ NULL);
|
||||
|
||||
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");
|
||||
|
||||
/* Just like other SAPIs, ignore SIGPIPEs. */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
fuzzer_init_php();
|
||||
|
||||
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;
|
||||
|
||||
/* fuzzer_shutdown_php(); */
|
||||
fuzzer_init_php_for_execute(NULL);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
|||
}
|
||||
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv) {
|
||||
fuzzer_init_php();
|
||||
fuzzer_init_php(NULL);
|
||||
|
||||
/* fuzzer_shutdown_php(); */
|
||||
return 0;
|
||||
|
|
86
sapi/fuzzer/fuzzer-jit.c
Normal file
86
sapi/fuzzer/fuzzer-jit.c
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| 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"
|
||||
#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) {
|
||||
/* 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,
|
||||
"function", sizeof("function")-1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
|
||||
fuzzer_do_request_from_buffer(
|
||||
"/fuzzer.php", (const char *) Data, Size, /* execute */ 1, opcache_invalidate);
|
||||
}
|
||||
|
||||
zend_string_release(jit_option);
|
||||
|
||||
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");
|
||||
|
||||
char ini_buf[256];
|
||||
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=512M",
|
||||
opcache_path);
|
||||
free(opcache_path);
|
||||
fuzzer_init_php_for_execute(ini_buf);
|
||||
return 0;
|
||||
}
|
|
@ -55,7 +55,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
|||
}
|
||||
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv) {
|
||||
fuzzer_init_php();
|
||||
fuzzer_init_php(NULL);
|
||||
|
||||
/* fuzzer_shutdown_php(); */
|
||||
return 0;
|
||||
|
|
|
@ -68,7 +68,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
|||
}
|
||||
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv) {
|
||||
fuzzer_init_php();
|
||||
fuzzer_init_php(NULL);
|
||||
|
||||
/* The default parse depth limit allows stack overflows under asan. */
|
||||
onig_set_parse_depth_limit(512);
|
||||
|
|
|
@ -32,7 +32,8 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
fuzzer_do_request_from_buffer("fuzzer.php", (const char *) Data, Size, /* execute */ 0);
|
||||
fuzzer_do_request_from_buffer(
|
||||
"fuzzer.php", (const char *) Data, Size, /* execute */ 0, /* before_shutdown */ NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -42,7 +43,7 @@ int LLVMFuzzerInitialize(int *argc, char ***argv) {
|
|||
* Use tracked allocation mode to avoid leaks in that case. */
|
||||
putenv("USE_TRACKED_ALLOC=1");
|
||||
|
||||
fuzzer_init_php();
|
||||
fuzzer_init_php(NULL);
|
||||
|
||||
/* fuzzer_shutdown_php(); */
|
||||
return 0;
|
||||
|
|
|
@ -131,7 +131,7 @@ static sapi_module_struct fuzzer_module = {
|
|||
STANDARD_SAPI_MODULE_PROPERTIES
|
||||
};
|
||||
|
||||
int fuzzer_init_php()
|
||||
int fuzzer_init_php(const char *extra_ini)
|
||||
{
|
||||
#ifdef __SANITIZE_ADDRESS__
|
||||
/* We're going to leak all the memory allocated during startup,
|
||||
|
@ -142,8 +142,20 @@ int fuzzer_init_php()
|
|||
sapi_startup(&fuzzer_module);
|
||||
fuzzer_module.phpinfo_as_text = 1;
|
||||
|
||||
fuzzer_module.ini_entries = malloc(sizeof(HARDCODED_INI));
|
||||
memcpy(fuzzer_module.ini_entries, HARDCODED_INI, sizeof(HARDCODED_INI));
|
||||
size_t ini_len = sizeof(HARDCODED_INI);
|
||||
size_t extra_ini_len = extra_ini ? strlen(extra_ini) : 0;
|
||||
if (extra_ini) {
|
||||
ini_len += extra_ini_len + 1;
|
||||
}
|
||||
char *p = fuzzer_module.ini_entries = malloc(ini_len + 1);
|
||||
memcpy(p, HARDCODED_INI, sizeof(HARDCODED_INI) - 1);
|
||||
p += sizeof(HARDCODED_INI) - 1;
|
||||
if (extra_ini) {
|
||||
*p++ = '\n';
|
||||
memcpy(p, extra_ini, extra_ini_len);
|
||||
p += extra_ini_len;
|
||||
}
|
||||
*p = '\0';
|
||||
|
||||
/*
|
||||
* TODO: we might want to test both Zend and malloc MM, but testing with malloc
|
||||
|
@ -230,7 +242,8 @@ int fuzzer_shutdown_php(void)
|
|||
}
|
||||
|
||||
int fuzzer_do_request_from_buffer(
|
||||
char *filename, const char *data, size_t data_len, bool execute)
|
||||
char *filename, const char *data, size_t data_len, bool execute,
|
||||
void (*before_shutdown)(void))
|
||||
{
|
||||
int retval = FAILURE; /* failure by default */
|
||||
|
||||
|
@ -253,6 +266,8 @@ int fuzzer_do_request_from_buffer(
|
|||
file_handle.primary_script = 1;
|
||||
file_handle.buf = estrndup(data, data_len);
|
||||
file_handle.len = data_len;
|
||||
/* Avoid ZEND_HANDLE_FILENAME for opcache. */
|
||||
file_handle.type = ZEND_HANDLE_STREAM;
|
||||
|
||||
zend_op_array *op_array = zend_compile_file(&file_handle, ZEND_REQUIRE);
|
||||
zend_destroy_file_handle(&file_handle);
|
||||
|
@ -267,6 +282,9 @@ int fuzzer_do_request_from_buffer(
|
|||
} zend_end_try();
|
||||
|
||||
CG(compiled_filename) = NULL; /* ??? */
|
||||
if (before_shutdown) {
|
||||
before_shutdown();
|
||||
}
|
||||
fuzzer_request_shutdown();
|
||||
|
||||
return (retval == SUCCESS) ? SUCCESS : FAILURE;
|
||||
|
|
|
@ -15,11 +15,12 @@
|
|||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
int fuzzer_init_php(void);
|
||||
int fuzzer_init_php(const char *extra_ini);
|
||||
int fuzzer_request_startup(void);
|
||||
void fuzzer_request_shutdown(void);
|
||||
void fuzzer_setup_dummy_frame(void);
|
||||
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, const char *data, size_t data_len, bool execute);
|
||||
char *filename, const char *data, size_t data_len, bool execute,
|
||||
void (*before_shutdown)(void));
|
||||
|
|
|
@ -60,7 +60,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
|||
}
|
||||
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv) {
|
||||
fuzzer_init_php();
|
||||
fuzzer_init_php(NULL);
|
||||
|
||||
/* fuzzer_shutdown_php(); */
|
||||
return 0;
|
||||
|
|
|
@ -75,7 +75,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t FullSize) {
|
|||
}
|
||||
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv) {
|
||||
fuzzer_init_php();
|
||||
fuzzer_init_php(NULL);
|
||||
|
||||
/* fuzzer_shutdown_php(); */
|
||||
return 0;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue