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