php-src/sapi/fuzzer/fuzzer-sapi.c
Nikita Popov 2f95af996f Disable InfiniteIterator class while fuzzing
The combination of LimitIterator and InfiniteIterator can cause
effectively infinite loops that bypass the executor step limit.
2020-09-04 10:59:55 +02:00

305 lines
8.0 KiB
C

/*
+----------------------------------------------------------------------+
| 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: |
| http://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: Johannes Schlüter <johanes@php.net> |
| Stanislav Malyshev <stas@php.net> |
+----------------------------------------------------------------------+
*/
#include <main/php.h>
#include <main/php_main.h>
#include <main/SAPI.h>
#include <ext/standard/info.h>
#include <ext/standard/php_var.h>
#include <main/php_variables.h>
#include <zend_exceptions.h>
#ifdef __SANITIZE_ADDRESS__
# include "sanitizer/lsan_interface.h"
#endif
#include "fuzzer.h"
#include "fuzzer-sapi.h"
const char HARDCODED_INI[] =
"html_errors=0\n"
"implicit_flush=1\n"
"output_buffering=0\n"
"error_reporting=0\n"
/* Let the timeout be enforced by libfuzzer, not PHP. */
"max_execution_time=0\n"
/* Reduce oniguruma limits to speed up fuzzing */
"mbstring.regex_stack_limit=10000\n"
"mbstring.regex_retry_limit=10000\n"
/* For the "execute" fuzzer disable some functions that are likely to have
* undesirable consequences (shell execution, file system writes). */
"allow_url_include=0\n"
"allow_url_fopen=0\n"
"open_basedir=/tmp\n"
"disable_functions=dl,mail,mb_send_mail"
",shell_exec,exec,system,proc_open,popen,passthru,pcntl_exec"
",chgrp,chmod,chown,copy,file_put_contents,lchgrp,lchown,link,mkdir"
",move_uploaded_file,rename,rmdir,symlink,tempname,touch,unlink,fopen"
/* Networking code likes to wait and wait. */
",fsockopen,pfsockopen"
",stream_socket_pair,stream_socket_client,stream_socket_server"
/* crypt() can be very slow. */
",crypt"
/* openlog() has a known memory-management issue. */
",openlog"
/* Can cause long loops that bypass the executor step limit. */
"\ndisable_classes=InfiniteIterator"
;
static int startup(sapi_module_struct *sapi_module)
{
if (php_module_startup(sapi_module, NULL, 0)==FAILURE) {
return FAILURE;
}
return SUCCESS;
}
static size_t ub_write(const char *str, size_t str_length)
{
/* quiet */
return str_length;
}
static void fuzzer_flush(void *server_context)
{
/* quiet */
}
static void send_header(sapi_header_struct *sapi_header, void *server_context)
{
}
static char* read_cookies()
{
/* TODO: fuzz these! */
return NULL;
}
static void register_variables(zval *track_vars_array)
{
php_import_environment_variables(track_vars_array);
}
static void log_message(const char *message, int level)
{
}
static sapi_module_struct fuzzer_module = {
"fuzzer", /* name */
"clang fuzzer", /* pretty name */
startup, /* startup */
php_module_shutdown_wrapper, /* shutdown */
NULL, /* activate */
NULL, /* deactivate */
ub_write, /* unbuffered write */
fuzzer_flush, /* flush */
NULL, /* get uid */
NULL, /* getenv */
php_error, /* error handler */
NULL, /* header handler */
NULL, /* send headers handler */
send_header, /* send header handler */
NULL, /* read POST data */
read_cookies, /* read Cookies */
register_variables, /* register server variables */
log_message, /* Log message */
NULL, /* Get request time */
NULL, /* Child terminate */
STANDARD_SAPI_MODULE_PROPERTIES
};
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;
fuzzer_module.ini_entries = malloc(sizeof(HARDCODED_INI));
memcpy(fuzzer_module.ini_entries, HARDCODED_INI, sizeof(HARDCODED_INI));
/*
* TODO: we might want to test both Zend and malloc MM, but testing with malloc
* is more likely to find bugs, so use that for now.
*/
putenv("USE_ZEND_ALLOC=0");
if (fuzzer_module.startup(&fuzzer_module)==FAILURE) {
return FAILURE;
}
#ifdef __SANITIZE_ADDRESS__
__lsan_enable();
#endif
return SUCCESS;
}
int fuzzer_request_startup()
{
if (php_request_startup() == FAILURE) {
php_module_shutdown();
return FAILURE;
}
#ifdef ZEND_SIGNALS
/* Some signal handlers will be overridden,
* don't complain about them during shutdown. */
SIGG(check) = 0;
#endif
return SUCCESS;
}
void fuzzer_request_shutdown()
{
zend_try {
/* Destroy thrown exceptions. This does not happen as part of request shutdown. */
if (EG(exception)) {
zend_object_release(EG(exception));
EG(exception) = NULL;
}
/* Some fuzzers (like unserialize) may create circular structures. Make sure we free them.
* Two calls are performed to handle objects with destructors. */
zend_gc_collect_cycles();
zend_gc_collect_cycles();
} zend_end_try();
php_request_shutdown(NULL);
}
/* Set up a dummy stack frame so that exceptions may be thrown. */
void fuzzer_setup_dummy_frame()
{
static zend_execute_data execute_data;
static zend_function func;
memset(&execute_data, 0, sizeof(zend_execute_data));
memset(&func, 0, sizeof(zend_function));
func.type = ZEND_INTERNAL_FUNCTION;
func.common.function_name = ZSTR_EMPTY_ALLOC();
execute_data.func = &func;
EG(current_execute_data) = &execute_data;
}
void fuzzer_set_ini_file(const char *file)
{
if (fuzzer_module.php_ini_path_override) {
free(fuzzer_module.php_ini_path_override);
}
fuzzer_module.php_ini_path_override = strdup(file);
}
int fuzzer_shutdown_php()
{
php_module_shutdown();
sapi_shutdown();
free(fuzzer_module.ini_entries);
return SUCCESS;
}
int fuzzer_do_request_from_buffer(
char *filename, const char *data, size_t data_len, zend_bool execute)
{
int retval = FAILURE; /* failure by default */
SG(options) |= SAPI_OPTION_NO_CHDIR;
SG(request_info).argc=0;
SG(request_info).argv=NULL;
if (fuzzer_request_startup() == FAILURE) {
return FAILURE;
}
// Commented out to avoid leaking the header callback.
//SG(headers_sent) = 1;
//SG(request_info).no_headers = 1;
php_register_variable("PHP_SELF", filename, NULL);
zend_first_try {
zend_file_handle file_handle;
zend_stream_init_filename(&file_handle, filename);
file_handle.buf = estrndup(data, data_len);
file_handle.len = data_len;
zend_op_array *op_array = zend_compile_file(&file_handle, ZEND_REQUIRE);
if (op_array) {
if (execute) {
zend_execute(op_array, NULL);
}
destroy_op_array(op_array);
efree(op_array);
}
} zend_end_try();
CG(compiled_filename) = NULL; /* ??? */
fuzzer_request_shutdown();
return (retval == SUCCESS) ? SUCCESS : FAILURE;
}
// 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;
ZVAL_STRING(&func, func_name);
ZVAL_UNDEF(&retval);
call_user_function(CG(function_table), NULL, &func, &retval, nargs, args);
// TODO: check result?
/* to ensure retval is not broken */
php_var_dump(&retval, 0);
/* cleanup */
zval_ptr_dtor(&retval);
zval_ptr_dtor(&func);
}
// Call named PHP function with N string arguments
void fuzzer_call_php_func(const char *func_name, int nargs, char **params) {
zval args[nargs];
int i;
for(i=0;i<nargs;i++) {
ZVAL_STRING(&args[i], params[i]);
}
fuzzer_call_php_func_zval(func_name, nargs, args);
for(i=0;i<nargs;i++) {
zval_ptr_dtor(&args[i]);
ZVAL_UNDEF(&args[i]);
}
}