Coverage Report

Created: 2022-10-06 01:35

/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
   | http://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
25
#ifdef __SANITIZE_ADDRESS__
26
# include "sanitizer/lsan_interface.h"
27
#endif
28
29
#include "fuzzer.h"
30
#include "fuzzer-sapi.h"
31
32
const char HARDCODED_INI[] =
33
  "html_errors=0\n"
34
  "implicit_flush=1\n"
35
  "output_buffering=0\n"
36
  "error_reporting=0\n"
37
  /* Reduce oniguruma limits to speed up fuzzing */
38
  "mbstring.regex_stack_limit=10000\n"
39
  "mbstring.regex_retry_limit=10000";
40
41
static int startup(sapi_module_struct *sapi_module)
42
3.68k
{
43
3.68k
  if (php_module_startup(sapi_module, NULL, 0)==FAILURE) {
44
0
    return FAILURE;
45
0
  }
46
3.68k
  return SUCCESS;
47
3.68k
}
48
49
static size_t ub_write(const char *str, size_t str_length)
50
0
{
51
  /* quiet */
52
0
  return str_length;
53
0
}
54
55
static void fuzzer_flush(void *server_context)
56
0
{
57
  /* quiet */
58
0
}
59
60
static void send_header(sapi_header_struct *sapi_header, void *server_context)
61
0
{
62
0
}
63
64
static char* read_cookies()
65
0
{
66
  /* TODO: fuzz these! */
67
0
  return NULL;
68
0
}
69
70
static void register_variables(zval *track_vars_array)
71
375
{
72
375
  php_import_environment_variables(track_vars_array);
73
375
}
74
75
static void log_message(const char *message, int level)
76
0
{
77
0
}
78
79
80
static sapi_module_struct fuzzer_module = {
81
  "fuzzer",               /* name */
82
  "clang fuzzer", /* pretty name */
83
84
  startup,             /* startup */
85
  php_module_shutdown_wrapper,   /* shutdown */
86
87
  NULL,                          /* activate */
88
  NULL,                          /* deactivate */
89
90
  ub_write,            /* unbuffered write */
91
  fuzzer_flush,               /* flush */
92
  NULL,                          /* get uid */
93
  NULL,                          /* getenv */
94
95
  php_error,                     /* error handler */
96
97
  NULL,                          /* header handler */
98
  NULL,                          /* send headers handler */
99
  send_header,         /* send header handler */
100
101
  NULL,                          /* read POST data */
102
  read_cookies,        /* read Cookies */
103
104
  register_variables,  /* register server variables */
105
  log_message,         /* Log message */
106
  NULL,                          /* Get request time */
107
  NULL,                          /* Child terminate */
108
109
  STANDARD_SAPI_MODULE_PROPERTIES
110
};
111
112
int fuzzer_init_php()
113
3.68k
{
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
120
3.68k
  sapi_startup(&fuzzer_module);
121
3.68k
  fuzzer_module.phpinfo_as_text = 1;
122
123
3.68k
  fuzzer_module.ini_entries = malloc(sizeof(HARDCODED_INI));
124
3.68k
  memcpy(fuzzer_module.ini_entries, HARDCODED_INI, sizeof(HARDCODED_INI));
125
126
  /*
127
   * TODO: we might want to test both Zend and malloc MM, but testing with malloc
128
   * is more likely to find bugs, so use that for now.
129
   */
130
3.68k
  putenv("USE_ZEND_ALLOC=0");
131
132
3.68k
  if (fuzzer_module.startup(&fuzzer_module)==FAILURE) {
133
0
    return FAILURE;
134
0
  }
135
136
#ifdef __SANITIZE_ADDRESS__
137
  __lsan_enable();
138
#endif
139
140
3.68k
  return SUCCESS;
141
3.68k
}
142
143
int fuzzer_request_startup()
144
391k
{
145
391k
  if (php_request_startup() == FAILURE) {
146
0
    php_module_shutdown();
147
0
    return FAILURE;
148
0
  }
149
150
391k
#ifdef ZEND_SIGNALS
151
  /* Some signal handlers will be overridden,
152
   * don't complain about them during shutdown. */
153
391k
  SIGG(check) = 0;
154
391k
#endif
155
156
391k
  return SUCCESS;
157
391k
}
158
159
void fuzzer_request_shutdown()
160
0
{
161
  /* Destroy thrown exceptions. This does not happen as part of request shutdown. */
162
0
  if (EG(exception)) {
163
0
    zend_object_release(EG(exception));
164
0
    EG(exception) = NULL;
165
0
  }
166
167
  /* Some fuzzers (like unserialize) may create circular structures. Make sure we free them.
168
   * Two calls are performed to handle objects with destructors. */
169
0
  zend_gc_collect_cycles();
170
0
  zend_gc_collect_cycles();
171
172
0
  php_request_shutdown(NULL);
173
0
}
174
175
/* Set up a dummy stack frame so that exceptions may be thrown. */
176
void fuzzer_setup_dummy_frame()
177
0
{
178
0
  static zend_execute_data execute_data;
179
0
  static zend_function func;
180
181
0
  memset(&execute_data, 0, sizeof(zend_execute_data));
182
0
  memset(&func, 0, sizeof(zend_function));
183
184
0
  func.type = ZEND_INTERNAL_FUNCTION;
185
0
  func.common.function_name = ZSTR_EMPTY_ALLOC();
186
0
  execute_data.func = &func;
187
0
  EG(current_execute_data) = &execute_data;
188
0
}
189
190
void fuzzer_set_ini_file(const char *file)
191
0
{
192
0
  if (fuzzer_module.php_ini_path_override) {
193
0
    free(fuzzer_module.php_ini_path_override);
194
0
  }
195
0
  fuzzer_module.php_ini_path_override = strdup(file);
196
0
}
197
198
199
int fuzzer_shutdown_php()
200
0
{
201
0
  php_module_shutdown();
202
0
  sapi_shutdown();
203
204
0
  free(fuzzer_module.ini_entries);
205
0
  return SUCCESS;
206
0
}
207
208
int fuzzer_do_request(zend_file_handle *file_handle, char *filename)
209
391k
{
210
391k
  int retval = FAILURE; /* failure by default */
211
212
391k
  SG(options) |= SAPI_OPTION_NO_CHDIR;
213
391k
  SG(request_info).argc=0;
214
391k
  SG(request_info).argv=NULL;
215
216
391k
  if (fuzzer_request_startup() == FAILURE) {
217
0
    return FAILURE;
218
0
  }
219
220
391k
  SG(headers_sent) = 1;
221
391k
  SG(request_info).no_headers = 1;
222
391k
  php_register_variable("PHP_SELF", filename, NULL);
223
224
391k
  zend_first_try {
225
391k
    zend_op_array *op_array = zend_compile_file(file_handle, ZEND_REQUIRE);
226
391k
    if (op_array) {
227
140k
      destroy_op_array(op_array);
228
140k
      efree(op_array);
229
140k
    }
230
391k
    if (EG(exception)) {
231
233k
      zend_object_release(EG(exception));
232
233k
      EG(exception) = NULL;
233
233k
    }
234
    /*retval = php_execute_script(file_handle);*/
235
391k
  } zend_end_try();
236
237
391k
  php_request_shutdown((void *) 0);
238
239
391k
  return (retval == SUCCESS) ? SUCCESS : FAILURE;
240
391k
}
241
242
243
int fuzzer_do_request_f(char *filename)
244
0
{
245
0
  zend_file_handle file_handle;
246
0
  file_handle.type = ZEND_HANDLE_FILENAME;
247
0
  file_handle.filename = filename;
248
0
  file_handle.handle.fp = NULL;
249
0
  file_handle.opened_path = NULL;
250
251
0
  return fuzzer_do_request(&file_handle, filename);
252
0
}
253
254
int fuzzer_do_request_from_buffer(char *filename, char *data, size_t data_len)
255
391k
{
256
391k
  zend_file_handle file_handle;
257
391k
  file_handle.filename = filename;
258
391k
  file_handle.free_filename = 0;
259
391k
  file_handle.opened_path = NULL;
260
391k
  file_handle.handle.stream.handle = NULL;
261
391k
  file_handle.handle.stream.reader = (zend_stream_reader_t)_php_stream_read;
262
391k
  file_handle.handle.stream.fsizer = NULL;
263
391k
  file_handle.handle.stream.isatty = 0;
264
391k
  file_handle.handle.stream.closer   = NULL;
265
391k
  file_handle.buf = data;
266
391k
  file_handle.len = data_len;
267
391k
  file_handle.type = ZEND_HANDLE_STREAM;
268
269
391k
  return fuzzer_do_request(&file_handle, filename);
270
391k
}
271
272
// Call named PHP function with N zval arguments
273
0
void fuzzer_call_php_func_zval(const char *func_name, int nargs, zval *args) {
274
0
  zval retval, func;
275
276
0
  ZVAL_STRING(&func, func_name);
277
0
  ZVAL_UNDEF(&retval);
278
0
  call_user_function(CG(function_table), NULL, &func, &retval, nargs, args);
279
280
  // TODO: check result?
281
  /* to ensure retval is not broken */
282
0
  php_var_dump(&retval, 0);
283
284
  /* cleanup */
285
0
  zval_ptr_dtor(&retval);
286
0
  zval_ptr_dtor(&func);
287
0
}
288
289
// Call named PHP function with N string arguments
290
0
void fuzzer_call_php_func(const char *func_name, int nargs, char **params) {
291
0
  zval args[nargs];
292
0
  int i;
293
294
0
  for(i=0;i<nargs;i++) {
295
0
    ZVAL_STRING(&args[i], params[i]);
296
0
  }
297
298
0
  fuzzer_call_php_func_zval(func_name, nargs, args);
299
300
0
  for(i=0;i<nargs;i++) {
301
0
    zval_ptr_dtor(&args[i]);
302
0
    ZVAL_UNDEF(&args[i]);
303
0
  }
304
0
}