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:
Nikita Popov 2020-09-01 10:52:53 +02:00
parent d2eccd4405
commit cd4243dde9
15 changed files with 228 additions and 94 deletions

View file

@ -2811,6 +2811,7 @@ static inline int accel_find_sapi(void)
"apache2handler",
"litespeed",
"uwsgi",
"fuzzer",
NULL
};
const char **sapi_name;

View file

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

View file

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

View file

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

View 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;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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