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()83 static char* read_cookies()
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 = fuzzer_module.ini_entries = malloc(ini_len + 1);
148 memcpy(p, HARDCODED_INI, sizeof(HARDCODED_INI) - 1);
149 p += sizeof(HARDCODED_INI) - 1;
150 if (extra_ini) {
151 *p++ = '\n';
152 memcpy(p, extra_ini, extra_ini_len);
153 p += extra_ini_len;
154 }
155 *p = '\0';
156
157 /*
158 * TODO: we might want to test both Zend and malloc MM, but testing with malloc
159 * is more likely to find bugs, so use that for now.
160 */
161 putenv("USE_ZEND_ALLOC=0");
162
163 if (fuzzer_module.startup(&fuzzer_module)==FAILURE) {
164 return FAILURE;
165 }
166
167 #ifdef __SANITIZE_ADDRESS__
168 __lsan_enable();
169 #endif
170
171 return SUCCESS;
172 }
173
fuzzer_request_startup()174 int fuzzer_request_startup()
175 {
176 if (php_request_startup() == FAILURE) {
177 php_module_shutdown();
178 return FAILURE;
179 }
180
181 #ifdef ZEND_SIGNALS
182 /* Some signal handlers will be overridden,
183 * don't complain about them during shutdown. */
184 SIGG(check) = 0;
185 #endif
186
187 return SUCCESS;
188 }
189
fuzzer_request_shutdown()190 void fuzzer_request_shutdown()
191 {
192 zend_try {
193 /* Destroy thrown exceptions. This does not happen as part of request shutdown. */
194 if (EG(exception)) {
195 zend_object_release(EG(exception));
196 EG(exception) = NULL;
197 }
198
199 /* Some fuzzers (like unserialize) may create circular structures. Make sure we free them.
200 * Two calls are performed to handle objects with destructors. */
201 zend_gc_collect_cycles();
202 zend_gc_collect_cycles();
203 } zend_end_try();
204
205 php_request_shutdown(NULL);
206 }
207
208 /* Set up a dummy stack frame so that exceptions may be thrown. */
fuzzer_setup_dummy_frame()209 void fuzzer_setup_dummy_frame()
210 {
211 static zend_execute_data execute_data;
212 static zend_function func;
213
214 memset(&execute_data, 0, sizeof(zend_execute_data));
215 memset(&func, 0, sizeof(zend_function));
216
217 func.type = ZEND_INTERNAL_FUNCTION;
218 func.common.function_name = ZSTR_EMPTY_ALLOC();
219 execute_data.func = &func;
220 EG(current_execute_data) = &execute_data;
221 }
222
fuzzer_set_ini_file(const char * file)223 void fuzzer_set_ini_file(const char *file)
224 {
225 if (fuzzer_module.php_ini_path_override) {
226 free(fuzzer_module.php_ini_path_override);
227 }
228 fuzzer_module.php_ini_path_override = strdup(file);
229 }
230
231
fuzzer_shutdown_php(void)232 int fuzzer_shutdown_php(void)
233 {
234 php_module_shutdown();
235 sapi_shutdown();
236
237 free(fuzzer_module.ini_entries);
238 return SUCCESS;
239 }
240
fuzzer_do_request_from_buffer(char * filename,const char * data,size_t data_len,bool execute,void (* before_shutdown)(void))241 int fuzzer_do_request_from_buffer(
242 char *filename, const char *data, size_t data_len, bool execute,
243 void (*before_shutdown)(void))
244 {
245 int retval = FAILURE; /* failure by default */
246
247 SG(options) |= SAPI_OPTION_NO_CHDIR;
248 SG(request_info).argc=0;
249 SG(request_info).argv=NULL;
250
251 if (fuzzer_request_startup() == FAILURE) {
252 return FAILURE;
253 }
254
255 // Commented out to avoid leaking the header callback.
256 //SG(headers_sent) = 1;
257 //SG(request_info).no_headers = 1;
258 php_register_variable("PHP_SELF", filename, NULL);
259
260 zend_first_try {
261 zend_file_handle file_handle;
262 zend_stream_init_filename(&file_handle, filename);
263 file_handle.primary_script = 1;
264 file_handle.buf = estrndup(data, data_len);
265 file_handle.len = data_len;
266 /* Avoid ZEND_HANDLE_FILENAME for opcache. */
267 file_handle.type = ZEND_HANDLE_STREAM;
268
269 zend_op_array *op_array = zend_compile_file(&file_handle, ZEND_REQUIRE);
270 zend_destroy_file_handle(&file_handle);
271 if (op_array) {
272 if (execute) {
273 zend_execute(op_array, NULL);
274 }
275 zend_destroy_static_vars(op_array);
276 destroy_op_array(op_array);
277 efree(op_array);
278 }
279 } zend_end_try();
280
281 CG(compiled_filename) = NULL; /* ??? */
282 if (before_shutdown) {
283 zend_try {
284 before_shutdown();
285 } zend_end_try();
286 }
287 fuzzer_request_shutdown();
288
289 return (retval == SUCCESS) ? SUCCESS : FAILURE;
290 }
291
292 // Call named PHP function with N zval arguments
fuzzer_call_php_func_zval(const char * func_name,int nargs,zval * args)293 void fuzzer_call_php_func_zval(const char *func_name, int nargs, zval *args) {
294 zval retval, func;
295
296 ZVAL_STRING(&func, func_name);
297 ZVAL_UNDEF(&retval);
298 call_user_function(CG(function_table), NULL, &func, &retval, nargs, args);
299
300 // TODO: check result?
301 /* to ensure retval is not broken */
302 php_var_dump(&retval, 0);
303
304 /* cleanup */
305 zval_ptr_dtor(&retval);
306 zval_ptr_dtor(&func);
307 }
308
309 // Call named PHP function with N string arguments
fuzzer_call_php_func(const char * func_name,int nargs,char ** params)310 void fuzzer_call_php_func(const char *func_name, int nargs, char **params) {
311 zval args[nargs];
312 int i;
313
314 for(i=0;i<nargs;i++) {
315 ZVAL_STRING(&args[i], params[i]);
316 }
317
318 fuzzer_call_php_func_zval(func_name, nargs, args);
319
320 for(i=0;i<nargs;i++) {
321 zval_ptr_dtor(&args[i]);
322 ZVAL_UNDEF(&args[i]);
323 }
324 }
325