Coverage Report

Created: 2025-07-23 06:33

/src/php-src/sapi/fuzzer/fuzzer-execute-common.h
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: Nikita Popov <nikic@php.net>                                |
14
   +----------------------------------------------------------------------+
15
 */
16
17
#include <main/php.h>
18
19
#if defined(__FreeBSD__)
20
# include <sys/sysctl.h>
21
#endif
22
23
#include "fuzzer.h"
24
#include "fuzzer-sapi.h"
25
#include "zend_exceptions.h"
26
27
260k
#define FILE_NAME "/tmp/fuzzer.php"
28
317k
#define MAX_STEPS 1000
29
1.11M
#define MAX_SIZE (8 * 1024)
30
13.6M
#define ZEND_VM_ENTER_BIT 1ULL
31
32
static uint32_t steps_left;
33
static bool bailed_out = false;
34
35
/* Because the fuzzer is always compiled with clang,
36
 * we can assume that we don't use global registers / hybrid VM. */
37
typedef zend_op *(ZEND_FASTCALL *opcode_handler_t)(zend_execute_data *, const zend_op *);
38
39
18.5k
static zend_always_inline void fuzzer_bailout(void) {
40
18.5k
  bailed_out = true;
41
18.5k
  zend_bailout();
42
18.5k
}
fuzzer-tracing-jit.c:fuzzer_bailout
Line
Count
Source
39
6.95k
static zend_always_inline void fuzzer_bailout(void) {
40
6.95k
  bailed_out = true;
41
6.95k
  zend_bailout();
42
6.95k
}
fuzzer-function-jit.c:fuzzer_bailout
Line
Count
Source
39
7.84k
static zend_always_inline void fuzzer_bailout(void) {
40
7.84k
  bailed_out = true;
41
7.84k
  zend_bailout();
42
7.84k
}
fuzzer-execute.c:fuzzer_bailout
Line
Count
Source
39
3.75k
static zend_always_inline void fuzzer_bailout(void) {
40
3.75k
  bailed_out = true;
41
3.75k
  zend_bailout();
42
3.75k
}
43
44
14.8M
static zend_always_inline void fuzzer_step(void) {
45
14.8M
  if (--steps_left == 0) {
46
    /* Reset steps before bailing out, so code running after bailout (e.g. in
47
     * destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
48
7.09k
    steps_left = MAX_STEPS;
49
7.09k
    fuzzer_bailout();
50
7.09k
  }
51
14.8M
}
fuzzer-tracing-jit.c:fuzzer_step
Line
Count
Source
44
5.73M
static zend_always_inline void fuzzer_step(void) {
45
5.73M
  if (--steps_left == 0) {
46
    /* Reset steps before bailing out, so code running after bailout (e.g. in
47
     * destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
48
2.35k
    steps_left = MAX_STEPS;
49
2.35k
    fuzzer_bailout();
50
2.35k
  }
51
5.73M
}
fuzzer-function-jit.c:fuzzer_step
Line
Count
Source
44
6.38M
static zend_always_inline void fuzzer_step(void) {
45
6.38M
  if (--steps_left == 0) {
46
    /* Reset steps before bailing out, so code running after bailout (e.g. in
47
     * destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
48
2.68k
    steps_left = MAX_STEPS;
49
2.68k
    fuzzer_bailout();
50
2.68k
  }
51
6.38M
}
fuzzer-execute.c:fuzzer_step
Line
Count
Source
44
2.73M
static zend_always_inline void fuzzer_step(void) {
45
2.73M
  if (--steps_left == 0) {
46
    /* Reset steps before bailing out, so code running after bailout (e.g. in
47
     * destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
48
2.05k
    steps_left = MAX_STEPS;
49
2.05k
    fuzzer_bailout();
50
2.05k
  }
51
2.73M
}
52
53
static void (*orig_execute_ex)(zend_execute_data *execute_data);
54
55
433k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
56
433k
  const zend_op *opline = EX(opline);
57
13.9M
  while (1) {
58
13.5M
    fuzzer_step();
59
13.5M
    opline = ((opcode_handler_t) opline->handler)(execute_data, opline);
60
13.5M
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
61
81.8k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
62
81.8k
      if (opline) {
63
0
        execute_data = EG(current_execute_data);
64
81.8k
      } else {
65
81.8k
        return;
66
81.8k
      }
67
81.8k
    }
68
13.5M
  }
69
433k
}
fuzzer-tracing-jit.c:fuzzer_execute_ex
Line
Count
Source
55
157k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
56
157k
  const zend_op *opline = EX(opline);
57
5.29M
  while (1) {
58
5.17M
    fuzzer_step();
59
5.17M
    opline = ((opcode_handler_t) opline->handler)(execute_data, opline);
60
5.17M
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
61
35.6k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
62
35.6k
      if (opline) {
63
0
        execute_data = EG(current_execute_data);
64
35.6k
      } else {
65
35.6k
        return;
66
35.6k
      }
67
35.6k
    }
68
5.17M
  }
69
157k
}
fuzzer-function-jit.c:fuzzer_execute_ex
Line
Count
Source
55
170k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
56
170k
  const zend_op *opline = EX(opline);
57
5.94M
  while (1) {
58
5.81M
    fuzzer_step();
59
5.81M
    opline = ((opcode_handler_t) opline->handler)(execute_data, opline);
60
5.81M
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
61
43.0k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
62
43.0k
      if (opline) {
63
0
        execute_data = EG(current_execute_data);
64
43.0k
      } else {
65
43.0k
        return;
66
43.0k
      }
67
43.0k
    }
68
5.81M
  }
69
170k
}
fuzzer-execute.c:fuzzer_execute_ex
Line
Count
Source
55
105k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
56
105k
  const zend_op *opline = EX(opline);
57
2.70M
  while (1) {
58
2.60M
    fuzzer_step();
59
2.60M
    opline = ((opcode_handler_t) opline->handler)(execute_data, opline);
60
2.60M
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
61
3.11k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
62
3.11k
      if (opline) {
63
0
        execute_data = EG(current_execute_data);
64
3.11k
      } else {
65
3.11k
        return;
66
3.11k
      }
67
3.11k
    }
68
2.60M
  }
69
105k
}
70
71
static zend_op_array *(*orig_compile_string)(
72
    zend_string *source_string, const char *filename, zend_compile_position position);
73
74
static zend_op_array *fuzzer_compile_string(
75
4.23k
    zend_string *str, const char *filename, zend_compile_position position) {
76
4.23k
  if (ZSTR_LEN(str) > MAX_SIZE) {
77
    /* Avoid compiling huge inputs via eval(). */
78
8
    fuzzer_bailout();
79
8
  }
80
81
4.23k
  return orig_compile_string(str, filename, position);
82
4.23k
}
fuzzer-tracing-jit.c:fuzzer_compile_string
Line
Count
Source
75
2.19k
    zend_string *str, const char *filename, zend_compile_position position) {
76
2.19k
  if (ZSTR_LEN(str) > MAX_SIZE) {
77
    /* Avoid compiling huge inputs via eval(). */
78
2
    fuzzer_bailout();
79
2
  }
80
81
2.19k
  return orig_compile_string(str, filename, position);
82
2.19k
}
fuzzer-function-jit.c:fuzzer_compile_string
Line
Count
Source
75
1.98k
    zend_string *str, const char *filename, zend_compile_position position) {
76
1.98k
  if (ZSTR_LEN(str) > MAX_SIZE) {
77
    /* Avoid compiling huge inputs via eval(). */
78
6
    fuzzer_bailout();
79
6
  }
80
81
1.98k
  return orig_compile_string(str, filename, position);
82
1.98k
}
fuzzer-execute.c:fuzzer_compile_string
Line
Count
Source
75
60
    zend_string *str, const char *filename, zend_compile_position position) {
76
60
  if (ZSTR_LEN(str) > MAX_SIZE) {
77
    /* Avoid compiling huge inputs via eval(). */
78
0
    fuzzer_bailout();
79
0
  }
80
81
60
  return orig_compile_string(str, filename, position);
82
60
}
83
84
static void (*orig_execute_internal)(zend_execute_data *execute_data, zval *return_value);
85
86
1.25M
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
87
1.25M
  fuzzer_step();
88
89
1.25M
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
90
3.03M
  for (uint32_t i = 0; i < num_args; i++) {
91
    /* Some internal functions like preg_replace() may be slow on large inputs.
92
     * Limit the maximum size of string inputs. */
93
1.77M
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
94
1.77M
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
95
11.4k
      fuzzer_bailout();
96
11.4k
    }
97
1.77M
  }
98
99
1.25M
  orig_execute_internal(execute_data, return_value);
100
1.25M
}
fuzzer-tracing-jit.c:fuzzer_execute_internal
Line
Count
Source
86
563k
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
87
563k
  fuzzer_step();
88
89
563k
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
90
1.33M
  for (uint32_t i = 0; i < num_args; i++) {
91
    /* Some internal functions like preg_replace() may be slow on large inputs.
92
     * Limit the maximum size of string inputs. */
93
769k
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
94
769k
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
95
4.60k
      fuzzer_bailout();
96
4.60k
    }
97
769k
  }
98
99
563k
  orig_execute_internal(execute_data, return_value);
100
563k
}
fuzzer-function-jit.c:fuzzer_execute_internal
Line
Count
Source
86
565k
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
87
565k
  fuzzer_step();
88
89
565k
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
90
1.32M
  for (uint32_t i = 0; i < num_args; i++) {
91
    /* Some internal functions like preg_replace() may be slow on large inputs.
92
     * Limit the maximum size of string inputs. */
93
762k
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
94
762k
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
95
5.15k
      fuzzer_bailout();
96
5.15k
    }
97
762k
  }
98
99
565k
  orig_execute_internal(execute_data, return_value);
100
565k
}
fuzzer-execute.c:fuzzer_execute_internal
Line
Count
Source
86
129k
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
87
129k
  fuzzer_step();
88
89
129k
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
90
376k
  for (uint32_t i = 0; i < num_args; i++) {
91
    /* Some internal functions like preg_replace() may be slow on large inputs.
92
     * Limit the maximum size of string inputs. */
93
247k
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
94
247k
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
95
1.69k
      fuzzer_bailout();
96
1.69k
    }
97
247k
  }
98
99
129k
  orig_execute_internal(execute_data, return_value);
100
129k
}
101
102
6
static void fuzzer_init_php_for_execute(const char *extra_ini) {
103
  /* Compilation will often trigger fatal errors.
104
   * Use tracked allocation mode to avoid leaks in that case. */
105
6
  putenv("USE_TRACKED_ALLOC=1");
106
107
  /* Just like other SAPIs, ignore SIGPIPEs. */
108
6
  signal(SIGPIPE, SIG_IGN);
109
110
6
  fuzzer_init_php(extra_ini);
111
112
6
  orig_execute_ex = zend_execute_ex;
113
6
  zend_execute_ex = fuzzer_execute_ex;
114
6
  orig_execute_internal = zend_execute_internal ? zend_execute_internal : execute_internal;
115
6
  zend_execute_internal = fuzzer_execute_internal;
116
6
  orig_compile_string = zend_compile_string;
117
6
  zend_compile_string = fuzzer_compile_string;
118
6
}
fuzzer-tracing-jit.c:fuzzer_init_php_for_execute
Line
Count
Source
102
2
static void fuzzer_init_php_for_execute(const char *extra_ini) {
103
  /* Compilation will often trigger fatal errors.
104
   * Use tracked allocation mode to avoid leaks in that case. */
105
2
  putenv("USE_TRACKED_ALLOC=1");
106
107
  /* Just like other SAPIs, ignore SIGPIPEs. */
108
2
  signal(SIGPIPE, SIG_IGN);
109
110
2
  fuzzer_init_php(extra_ini);
111
112
2
  orig_execute_ex = zend_execute_ex;
113
2
  zend_execute_ex = fuzzer_execute_ex;
114
2
  orig_execute_internal = zend_execute_internal ? zend_execute_internal : execute_internal;
115
2
  zend_execute_internal = fuzzer_execute_internal;
116
2
  orig_compile_string = zend_compile_string;
117
2
  zend_compile_string = fuzzer_compile_string;
118
2
}
fuzzer-function-jit.c:fuzzer_init_php_for_execute
Line
Count
Source
102
2
static void fuzzer_init_php_for_execute(const char *extra_ini) {
103
  /* Compilation will often trigger fatal errors.
104
   * Use tracked allocation mode to avoid leaks in that case. */
105
2
  putenv("USE_TRACKED_ALLOC=1");
106
107
  /* Just like other SAPIs, ignore SIGPIPEs. */
108
2
  signal(SIGPIPE, SIG_IGN);
109
110
2
  fuzzer_init_php(extra_ini);
111
112
2
  orig_execute_ex = zend_execute_ex;
113
2
  zend_execute_ex = fuzzer_execute_ex;
114
2
  orig_execute_internal = zend_execute_internal ? zend_execute_internal : execute_internal;
115
2
  zend_execute_internal = fuzzer_execute_internal;
116
2
  orig_compile_string = zend_compile_string;
117
2
  zend_compile_string = fuzzer_compile_string;
118
2
}
fuzzer-execute.c:fuzzer_init_php_for_execute
Line
Count
Source
102
2
static void fuzzer_init_php_for_execute(const char *extra_ini) {
103
  /* Compilation will often trigger fatal errors.
104
   * Use tracked allocation mode to avoid leaks in that case. */
105
2
  putenv("USE_TRACKED_ALLOC=1");
106
107
  /* Just like other SAPIs, ignore SIGPIPEs. */
108
2
  signal(SIGPIPE, SIG_IGN);
109
110
2
  fuzzer_init_php(extra_ini);
111
112
2
  orig_execute_ex = zend_execute_ex;
113
2
  zend_execute_ex = fuzzer_execute_ex;
114
2
  orig_execute_internal = zend_execute_internal ? zend_execute_internal : execute_internal;
115
2
  zend_execute_internal = fuzzer_execute_internal;
116
2
  orig_compile_string = zend_compile_string;
117
2
  zend_compile_string = fuzzer_compile_string;
118
2
}
119
120
4
ZEND_ATTRIBUTE_UNUSED static void create_file(void) {
121
  /* For opcache_invalidate() to work, the dummy file name used for fuzzing needs to
122
   * actually exist. */
123
4
  FILE *f = fopen(FILE_NAME, "w");
124
4
  fclose(f);
125
4
}
fuzzer-tracing-jit.c:create_file
Line
Count
Source
120
2
ZEND_ATTRIBUTE_UNUSED static void create_file(void) {
121
  /* For opcache_invalidate() to work, the dummy file name used for fuzzing needs to
122
   * actually exist. */
123
2
  FILE *f = fopen(FILE_NAME, "w");
124
2
  fclose(f);
125
2
}
fuzzer-function-jit.c:create_file
Line
Count
Source
120
2
ZEND_ATTRIBUTE_UNUSED static void create_file(void) {
121
  /* For opcache_invalidate() to work, the dummy file name used for fuzzing needs to
122
   * actually exist. */
123
2
  FILE *f = fopen(FILE_NAME, "w");
124
2
  fclose(f);
125
2
}
Unexecuted instantiation: fuzzer-execute.c:create_file
126
127
86.0k
ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) {
128
86.0k
  steps_left = MAX_STEPS;
129
86.0k
  zend_exception_save();
130
86.0k
  zval retval, args[2];
131
86.0k
  zend_function *fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("opcache_invalidate"));
132
86.0k
  ZEND_ASSERT(fn != NULL);
133
134
86.0k
  ZVAL_STRING(&args[0], FILE_NAME);
135
86.0k
  ZVAL_TRUE(&args[1]);
136
86.0k
  zend_call_known_function(fn, NULL, NULL, &retval, 2, args, NULL);
137
86.0k
  ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE);
138
86.0k
  zval_ptr_dtor(&args[0]);
139
86.0k
  zval_ptr_dtor(&retval);
140
86.0k
  zend_exception_restore();
141
86.0k
}
fuzzer-tracing-jit.c:opcache_invalidate
Line
Count
Source
127
42.6k
ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) {
128
42.6k
  steps_left = MAX_STEPS;
129
42.6k
  zend_exception_save();
130
42.6k
  zval retval, args[2];
131
42.6k
  zend_function *fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("opcache_invalidate"));
132
42.6k
  ZEND_ASSERT(fn != NULL);
133
134
42.6k
  ZVAL_STRING(&args[0], FILE_NAME);
135
42.6k
  ZVAL_TRUE(&args[1]);
136
42.6k
  zend_call_known_function(fn, NULL, NULL, &retval, 2, args, NULL);
137
42.6k
  ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE);
138
42.6k
  zval_ptr_dtor(&args[0]);
139
42.6k
  zval_ptr_dtor(&retval);
140
42.6k
  zend_exception_restore();
141
42.6k
}
fuzzer-function-jit.c:opcache_invalidate
Line
Count
Source
127
43.3k
ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) {
128
43.3k
  steps_left = MAX_STEPS;
129
43.3k
  zend_exception_save();
130
43.3k
  zval retval, args[2];
131
43.3k
  zend_function *fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("opcache_invalidate"));
132
43.3k
  ZEND_ASSERT(fn != NULL);
133
134
43.3k
  ZVAL_STRING(&args[0], FILE_NAME);
135
43.3k
  ZVAL_TRUE(&args[1]);
136
43.3k
  zend_call_known_function(fn, NULL, NULL, &retval, 2, args, NULL);
137
43.3k
  ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE);
138
43.3k
  zval_ptr_dtor(&args[0]);
139
43.3k
  zval_ptr_dtor(&retval);
140
43.3k
  zend_exception_restore();
141
43.3k
}
Unexecuted instantiation: fuzzer-execute.c:opcache_invalidate
142
143
4
ZEND_ATTRIBUTE_UNUSED char *get_opcache_path(void) {
144
  /* Try relative to cwd. */
145
4
  char *p = realpath("modules/opcache.so", NULL);
146
4
  if (p) {
147
4
    return p;
148
4
  }
149
150
  /* Try relative to binary location. */
151
0
  char path[MAXPATHLEN];
152
#if defined(__FreeBSD__)
153
  size_t pathlen = sizeof(path);
154
  int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
155
  if (sysctl(mib, 4, path, &pathlen, NULL, 0) < 0) {
156
#else
157
0
  if (readlink("/proc/self/exe", path, sizeof(path)) < 0) {
158
0
#endif
159
0
    ZEND_ASSERT(0 && "Failed to get binary path");
160
0
    return NULL;
161
0
  }
162
163
  /* Get basename. */
164
0
  char *last_sep = strrchr(path, '/');
165
0
  if (last_sep) {
166
0
    *last_sep = '\0';
167
0
  }
168
169
0
  strlcat(path, "/modules/opcache.so", sizeof(path));
170
0
  return realpath(path, NULL);
171
0
}