xref: /PHP-8.1/sapi/fuzzer/fuzzer-execute.c (revision 93a88a1d)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Nikita Popov <nikic@php.net>                                |
14    +----------------------------------------------------------------------+
15  */
16 
17 #include <main/php.h>
18 
19 #include "fuzzer.h"
20 #include "fuzzer-sapi.h"
21 
22 #define MAX_STEPS 1000
23 #define MAX_SIZE (8 * 1024)
24 static uint32_t steps_left;
25 
26 /* Because the fuzzer is always compiled with clang,
27  * we can assume that we don't use global registers / hybrid VM. */
28 typedef int (ZEND_FASTCALL *opcode_handler_t)(zend_execute_data *);
29 
fuzzer_step(void)30 static zend_always_inline void fuzzer_step(void) {
31 	if (--steps_left == 0) {
32 		/* Reset steps before bailing out, so code running after bailout (e.g. in
33 		 * destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
34 		steps_left = MAX_STEPS;
35 		zend_bailout();
36 	}
37 }
38 
fuzzer_execute_ex(zend_execute_data * execute_data)39 static void fuzzer_execute_ex(zend_execute_data *execute_data) {
40 	while (1) {
41 		int ret;
42 		fuzzer_step();
43 		if ((ret = ((opcode_handler_t) EX(opline)->handler)(execute_data)) != 0) {
44 			if (ret > 0) {
45 				execute_data = EG(current_execute_data);
46 			} else {
47 				return;
48 			}
49 		}
50 	}
51 }
52 
53 static zend_op_array *(*orig_compile_string)(zend_string *source_string, const char *filename);
54 
fuzzer_compile_string(zend_string * str,const char * filename)55 static zend_op_array *fuzzer_compile_string(zend_string *str, const char *filename) {
56 	if (ZSTR_LEN(str) > MAX_SIZE) {
57 		/* Avoid compiling huge inputs via eval(). */
58 		zend_bailout();
59 	}
60 
61 	return orig_compile_string(str, filename);
62 }
63 
64 static void (*orig_execute_internal)(zend_execute_data *execute_data, zval *return_value);
65 
fuzzer_execute_internal(zend_execute_data * execute_data,zval * return_value)66 static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
67 	fuzzer_step();
68 
69 	uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
70 	for (uint32_t i = 0; i < num_args; i++) {
71 		/* Some internal functions like preg_replace() may be slow on large inputs.
72 		 * Limit the maximum size of string inputs. */
73 		zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
74 		if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
75 			zend_bailout();
76 		}
77 	}
78 
79 	orig_execute_internal(execute_data, return_value);
80 }
81 
LLVMFuzzerTestOneInput(const uint8_t * Data,size_t Size)82 int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
83 	if (Size > MAX_SIZE) {
84 		/* Large inputs have a large impact on fuzzer performance,
85 		 * but are unlikely to be necessary to reach new codepaths. */
86 		return 0;
87 	}
88 
89 	steps_left = MAX_STEPS;
90 	fuzzer_do_request_from_buffer("/fuzzer.php", (const char *) Data, Size, /* execute */ 1);
91 
92 	return 0;
93 }
94 
LLVMFuzzerInitialize(int * argc,char *** argv)95 int LLVMFuzzerInitialize(int *argc, char ***argv) {
96 	/* Compilation will often trigger fatal errors.
97 	 * Use tracked allocation mode to avoid leaks in that case. */
98 	putenv("USE_TRACKED_ALLOC=1");
99 
100 	/* Just like other SAPIs, ignore SIGPIPEs. */
101 	signal(SIGPIPE, SIG_IGN);
102 
103 	fuzzer_init_php();
104 
105 	zend_execute_ex = fuzzer_execute_ex;
106 	orig_execute_internal = zend_execute_internal ? zend_execute_internal : execute_internal;
107 	zend_execute_internal = fuzzer_execute_internal;
108 	orig_compile_string = zend_compile_string;
109 	zend_compile_string = fuzzer_compile_string;
110 
111 	/* fuzzer_shutdown_php(); */
112 	return 0;
113 }
114