xref: /PHP-8.3/sapi/fuzzer/fuzzer-sapi.c (revision b45e7a61)
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: Johannes Schlüter <johanes@php.net>                         |
14    |          Stanislav Malyshev <stas@php.net>                           |
15    +----------------------------------------------------------------------+
16  */
17 
18 #include <main/php.h>
19 #include <main/php_main.h>
20 #include <main/SAPI.h>
21 #include <ext/standard/info.h>
22 #include <ext/standard/php_var.h>
23 #include <main/php_variables.h>
24 #include <zend_exceptions.h>
25 
26 #ifdef __SANITIZE_ADDRESS__
27 # include "sanitizer/lsan_interface.h"
28 #endif
29 
30 #include "fuzzer.h"
31 #include "fuzzer-sapi.h"
32 
33 const char HARDCODED_INI[] =
34 	"html_errors=0\n"
35 	"implicit_flush=1\n"
36 	"output_buffering=0\n"
37 	"error_reporting=0\n"
38 	/* Let the timeout be enforced by libfuzzer, not PHP. */
39 	"max_execution_time=0\n"
40 	/* Reduce oniguruma limits to speed up fuzzing */
41 	"mbstring.regex_stack_limit=10000\n"
42 	"mbstring.regex_retry_limit=10000\n"
43 	/* For the "execute" fuzzer disable some functions that are likely to have
44 	 * undesirable consequences (shell execution, file system writes). */
45 	"allow_url_include=0\n"
46 	"allow_url_fopen=0\n"
47 	"open_basedir=/tmp\n"
48 	"disable_functions=dl,mail,mb_send_mail"
49 	",shell_exec,exec,system,proc_open,popen,passthru,pcntl_exec"
50 	",chdir,chgrp,chmod,chown,copy,file_put_contents,lchgrp,lchown,link,mkdir"
51 	",move_uploaded_file,rename,rmdir,symlink,tempname,touch,unlink,fopen"
52 	/* Networking code likes to wait and wait. */
53 	",fsockopen,pfsockopen"
54 	",stream_socket_pair,stream_socket_client,stream_socket_server"
55 	/* crypt() can be very slow. */
56 	",crypt"
57 	/* openlog() has a known memory-management issue. */
58 	",openlog"
59 	/* Can cause long loops that bypass the executor step limit. */
60 	"\ndisable_classes=InfiniteIterator"
61 ;
62 
startup(sapi_module_struct * sapi_module)63 static int startup(sapi_module_struct *sapi_module)
64 {
65 	return php_module_startup(sapi_module, NULL);
66 }
67 
ub_write(const char * str,size_t str_length)68 static size_t ub_write(const char *str, size_t str_length)
69 {
70 	/* quiet */
71 	return str_length;
72 }
73 
fuzzer_flush(void * server_context)74 static void fuzzer_flush(void *server_context)
75 {
76 	/* quiet */
77 }
78 
send_header(sapi_header_struct * sapi_header,void * server_context)79 static void send_header(sapi_header_struct *sapi_header, void *server_context)
80 {
81 }
82 
read_cookies(void)83 static char* read_cookies(void)
84 {
85 	/* TODO: fuzz these! */
86 	return NULL;
87 }
88 
register_variables(zval * track_vars_array)89 static void register_variables(zval *track_vars_array)
90 {
91 	php_import_environment_variables(track_vars_array);
92 }
93 
log_message(const char * message,int level)94 static void log_message(const char *message, int level)
95 {
96 }
97 
98 
99 static sapi_module_struct fuzzer_module = {
100 	"fuzzer",               /* name */
101 	"clang fuzzer", /* pretty name */
102 
103 	startup,             /* startup */
104 	php_module_shutdown_wrapper,   /* shutdown */
105 
106 	NULL,                          /* activate */
107 	NULL,                          /* deactivate */
108 
109 	ub_write,            /* unbuffered write */
110 	fuzzer_flush,               /* flush */
111 	NULL,                          /* get uid */
112 	NULL,                          /* getenv */
113 
114 	php_error,                     /* error handler */
115 
116 	NULL,                          /* header handler */
117 	NULL,                          /* send headers handler */
118 	send_header,         /* send header handler */
119 
120 	NULL,                          /* read POST data */
121 	read_cookies,        /* read Cookies */
122 
123 	register_variables,  /* register server variables */
124 	log_message,         /* Log message */
125 	NULL,                          /* Get request time */
126 	NULL,                          /* Child terminate */
127 
128 	STANDARD_SAPI_MODULE_PROPERTIES
129 };
130 
fuzzer_init_php(const char * extra_ini)131 int fuzzer_init_php(const char *extra_ini)
132 {
133 #ifdef __SANITIZE_ADDRESS__
134 	/* We're going to leak all the memory allocated during startup,
135 	 * so disable lsan temporarily. */
136 	__lsan_disable();
137 #endif
138 
139 	sapi_startup(&fuzzer_module);
140 	fuzzer_module.phpinfo_as_text = 1;
141 
142 	size_t ini_len = sizeof(HARDCODED_INI);
143 	size_t extra_ini_len = extra_ini ? strlen(extra_ini) : 0;
144 	if (extra_ini) {
145 		ini_len += extra_ini_len + 1;
146 	}
147 	char *p = malloc(ini_len + 1);
148 	fuzzer_module.ini_entries = p;
149 	memcpy(p, HARDCODED_INI, sizeof(HARDCODED_INI) - 1);
150 	p += sizeof(HARDCODED_INI) - 1;
151 	if (extra_ini) {
152 		*p++ = '\n';
153 		memcpy(p, extra_ini, extra_ini_len);
154 		p += extra_ini_len;
155 	}
156 	*p = '\0';
157 
158 	/*
159 	 * TODO: we might want to test both Zend and malloc MM, but testing with malloc
160 	 * is more likely to find bugs, so use that for now.
161 	 */
162 	putenv("USE_ZEND_ALLOC=0");
163 
164 	if (fuzzer_module.startup(&fuzzer_module)==FAILURE) {
165 		return FAILURE;
166 	}
167 
168 #ifdef __SANITIZE_ADDRESS__
169 	__lsan_enable();
170 #endif
171 
172 	return SUCCESS;
173 }
174 
fuzzer_request_startup(void)175 int fuzzer_request_startup(void)
176 {
177 	if (php_request_startup() == FAILURE) {
178 		php_module_shutdown();
179 		return FAILURE;
180 	}
181 
182 #ifdef ZEND_SIGNALS
183 	/* Some signal handlers will be overridden,
184 	 * don't complain about them during shutdown. */
185 	SIGG(check) = 0;
186 #endif
187 
188 	return SUCCESS;
189 }
190 
fuzzer_request_shutdown(void)191 void fuzzer_request_shutdown(void)
192 {
193 	zend_try {
194 		/* Destroy thrown exceptions. This does not happen as part of request shutdown. */
195 		if (EG(exception)) {
196 			zend_object_release(EG(exception));
197 			EG(exception) = NULL;
198 		}
199 
200 		/* Some fuzzers (like unserialize) may create circular structures. Make sure we free them.
201 		 * Two calls are performed to handle objects with destructors. */
202 		zend_gc_collect_cycles();
203 		zend_gc_collect_cycles();
204 	} zend_end_try();
205 
206 	php_request_shutdown(NULL);
207 }
208 
209 /* Set up a dummy stack frame so that exceptions may be thrown. */
fuzzer_setup_dummy_frame(void)210 void fuzzer_setup_dummy_frame(void)
211 {
212 	static zend_execute_data execute_data;
213 	static zend_function func;
214 
215 	memset(&execute_data, 0, sizeof(zend_execute_data));
216 	memset(&func, 0, sizeof(zend_function));
217 
218 	func.type = ZEND_INTERNAL_FUNCTION;
219 	func.common.function_name = ZSTR_EMPTY_ALLOC();
220 	execute_data.func = &func;
221 	EG(current_execute_data) = &execute_data;
222 }
223 
fuzzer_set_ini_file(const char * file)224 void fuzzer_set_ini_file(const char *file)
225 {
226 	if (fuzzer_module.php_ini_path_override) {
227 		free(fuzzer_module.php_ini_path_override);
228 	}
229 	fuzzer_module.php_ini_path_override = strdup(file);
230 }
231 
232 
fuzzer_shutdown_php(void)233 int fuzzer_shutdown_php(void)
234 {
235 	php_module_shutdown();
236 	sapi_shutdown();
237 
238 	free((void *)fuzzer_module.ini_entries);
239 	return SUCCESS;
240 }
241 
fuzzer_do_request_from_buffer(char * filename,const char * data,size_t data_len,bool execute,void (* before_shutdown)(void))242 int fuzzer_do_request_from_buffer(
243 		char *filename, const char *data, size_t data_len, bool execute,
244 		void (*before_shutdown)(void))
245 {
246 	int retval = FAILURE; /* failure by default */
247 
248 	SG(options) |= SAPI_OPTION_NO_CHDIR;
249 	SG(request_info).argc=0;
250 	SG(request_info).argv=NULL;
251 
252 	if (fuzzer_request_startup() == FAILURE) {
253 		return FAILURE;
254 	}
255 
256 	// Commented out to avoid leaking the header callback.
257 	//SG(headers_sent) = 1;
258 	//SG(request_info).no_headers = 1;
259 	php_register_variable("PHP_SELF", filename, NULL);
260 
261 	zend_first_try {
262 		zend_file_handle file_handle;
263 		zend_stream_init_filename(&file_handle, filename);
264 		file_handle.primary_script = 1;
265 		file_handle.buf = emalloc(data_len + ZEND_MMAP_AHEAD);
266 		memcpy(file_handle.buf, data, data_len);
267 		memset(file_handle.buf + data_len, 0, ZEND_MMAP_AHEAD);
268 		file_handle.len = data_len;
269 		/* Avoid ZEND_HANDLE_FILENAME for opcache. */
270 		file_handle.type = ZEND_HANDLE_STREAM;
271 
272 		zend_op_array *op_array = zend_compile_file(&file_handle, ZEND_REQUIRE);
273 		zend_destroy_file_handle(&file_handle);
274 		if (op_array) {
275 			if (execute) {
276 				zend_execute(op_array, NULL);
277 			}
278 			zend_destroy_static_vars(op_array);
279 			destroy_op_array(op_array);
280 			efree(op_array);
281 		}
282 	} zend_end_try();
283 
284 	CG(compiled_filename) = NULL; /* ??? */
285 	if (before_shutdown) {
286 		zend_try {
287 			before_shutdown();
288 		} zend_end_try();
289 	}
290 	fuzzer_request_shutdown();
291 
292 	return (retval == SUCCESS) ? SUCCESS : FAILURE;
293 }
294 
295 // Call named PHP function with N zval arguments
fuzzer_call_php_func_zval(const char * func_name,int nargs,zval * args)296 void fuzzer_call_php_func_zval(const char *func_name, int nargs, zval *args) {
297 	zval retval, func;
298 
299 	ZVAL_STRING(&func, func_name);
300 	ZVAL_UNDEF(&retval);
301 	call_user_function(CG(function_table), NULL, &func, &retval, nargs, args);
302 
303 	// TODO: check result?
304 	/* to ensure retval is not broken */
305 	php_var_dump(&retval, 0);
306 
307 	/* cleanup */
308 	zval_ptr_dtor(&retval);
309 	zval_ptr_dtor(&func);
310 }
311 
312 // Call named PHP function with N string arguments
fuzzer_call_php_func(const char * func_name,int nargs,char ** params)313 void fuzzer_call_php_func(const char *func_name, int nargs, char **params) {
314 	zval args[nargs];
315 	int i;
316 
317 	for(i=0;i<nargs;i++) {
318 		ZVAL_STRING(&args[i], params[i]);
319 	}
320 
321 	fuzzer_call_php_func_zval(func_name, nargs, args);
322 
323 	for(i=0;i<nargs;i++) {
324 		zval_ptr_dtor(&args[i]);
325 		ZVAL_UNDEF(&args[i]);
326 	}
327 }
328