fuzzer-sapi.c 8.2 KB

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