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