Coverage Report

Created: 2025-06-13 06:43

/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
253k
#define FILE_NAME "/tmp/fuzzer.php"
28
308k
#define MAX_STEPS 1000
29
1.16M
#define MAX_SIZE (8 * 1024)
30
14.2M
#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.9k
static zend_always_inline void fuzzer_bailout(void) {
40
18.9k
  bailed_out = true;
41
18.9k
  zend_bailout();
42
18.9k
}
fuzzer-tracing-jit.c:fuzzer_bailout
Line
Count
Source
39
6.86k
static zend_always_inline void fuzzer_bailout(void) {
40
6.86k
  bailed_out = true;
41
6.86k
  zend_bailout();
42
6.86k
}
fuzzer-function-jit.c:fuzzer_bailout
Line
Count
Source
39
9.02k
static zend_always_inline void fuzzer_bailout(void) {
40
9.02k
  bailed_out = true;
41
9.02k
  zend_bailout();
42
9.02k
}
fuzzer-execute.c:fuzzer_bailout
Line
Count
Source
39
3.05k
static zend_always_inline void fuzzer_bailout(void) {
40
3.05k
  bailed_out = true;
41
3.05k
  zend_bailout();
42
3.05k
}
43
44
15.5M
static zend_always_inline void fuzzer_step(void) {
45
15.5M
  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.17k
    steps_left = MAX_STEPS;
49
7.17k
    fuzzer_bailout();
50
7.17k
  }
51
15.5M
}
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.20k
    steps_left = MAX_STEPS;
49
2.20k
    fuzzer_bailout();
50
2.20k
  }
51
5.73M
}
fuzzer-function-jit.c:fuzzer_step
Line
Count
Source
44
7.23M
static zend_always_inline void fuzzer_step(void) {
45
7.23M
  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.88k
    steps_left = MAX_STEPS;
49
2.88k
    fuzzer_bailout();
50
2.88k
  }
51
7.23M
}
fuzzer-execute.c:fuzzer_step
Line
Count
Source
44
2.59M
static zend_always_inline void fuzzer_step(void) {
45
2.59M
  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.08k
    steps_left = MAX_STEPS;
49
2.08k
    fuzzer_bailout();
50
2.08k
  }
51
2.59M
}
52
53
static void (*orig_execute_ex)(zend_execute_data *execute_data);
54
55
448k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
56
448k
  const zend_op *opline = EX(opline);
57
14.5M
  while (1) {
58
14.2M
    fuzzer_step();
59
14.2M
    opline = ((opcode_handler_t) opline->handler)(execute_data, opline);
60
14.2M
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
61
79.2k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
62
79.2k
      if (opline) {
63
0
        execute_data = EG(current_execute_data);
64
79.2k
      } else {
65
79.2k
        return;
66
79.2k
      }
67
79.2k
    }
68
14.2M
  }
69
448k
}
fuzzer-tracing-jit.c:fuzzer_execute_ex
Line
Count
Source
55
159k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
56
159k
  const zend_op *opline = EX(opline);
57
5.28M
  while (1) {
58
5.15M
    fuzzer_step();
59
5.15M
    opline = ((opcode_handler_t) opline->handler)(execute_data, opline);
60
5.15M
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
61
36.2k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
62
36.2k
      if (opline) {
63
0
        execute_data = EG(current_execute_data);
64
36.2k
      } else {
65
36.2k
        return;
66
36.2k
      }
67
36.2k
    }
68
5.15M
  }
69
159k
}
fuzzer-function-jit.c:fuzzer_execute_ex
Line
Count
Source
55
187k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
56
187k
  const zend_op *opline = EX(opline);
57
6.73M
  while (1) {
58
6.58M
    fuzzer_step();
59
6.58M
    opline = ((opcode_handler_t) opline->handler)(execute_data, opline);
60
6.58M
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
61
39.8k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
62
39.8k
      if (opline) {
63
0
        execute_data = EG(current_execute_data);
64
39.8k
      } else {
65
39.8k
        return;
66
39.8k
      }
67
39.8k
    }
68
6.58M
  }
69
187k
}
fuzzer-execute.c:fuzzer_execute_ex
Line
Count
Source
55
101k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
56
101k
  const zend_op *opline = EX(opline);
57
2.56M
  while (1) {
58
2.46M
    fuzzer_step();
59
2.46M
    opline = ((opcode_handler_t) opline->handler)(execute_data, opline);
60
2.46M
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
61
3.15k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
62
3.15k
      if (opline) {
63
0
        execute_data = EG(current_execute_data);
64
3.15k
      } else {
65
3.15k
        return;
66
3.15k
      }
67
3.15k
    }
68
2.46M
  }
69
101k
}
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.00k
    zend_string *str, const char *filename, zend_compile_position position) {
76
4.00k
  if (ZSTR_LEN(str) > MAX_SIZE) {
77
    /* Avoid compiling huge inputs via eval(). */
78
4
    fuzzer_bailout();
79
4
  }
80
81
4.00k
  return orig_compile_string(str, filename, position);
82
4.00k
}
fuzzer-tracing-jit.c:fuzzer_compile_string
Line
Count
Source
75
2.14k
    zend_string *str, const char *filename, zend_compile_position position) {
76
2.14k
  if (ZSTR_LEN(str) > MAX_SIZE) {
77
    /* Avoid compiling huge inputs via eval(). */
78
2
    fuzzer_bailout();
79
2
  }
80
81
2.14k
  return orig_compile_string(str, filename, position);
82
2.14k
}
fuzzer-function-jit.c:fuzzer_compile_string
Line
Count
Source
75
1.80k
    zend_string *str, const char *filename, zend_compile_position position) {
76
1.80k
  if (ZSTR_LEN(str) > MAX_SIZE) {
77
    /* Avoid compiling huge inputs via eval(). */
78
2
    fuzzer_bailout();
79
2
  }
80
81
1.80k
  return orig_compile_string(str, filename, position);
82
1.80k
}
fuzzer-execute.c:fuzzer_compile_string
Line
Count
Source
75
62
    zend_string *str, const char *filename, zend_compile_position position) {
76
62
  if (ZSTR_LEN(str) > MAX_SIZE) {
77
    /* Avoid compiling huge inputs via eval(). */
78
0
    fuzzer_bailout();
79
0
  }
80
81
62
  return orig_compile_string(str, filename, position);
82
62
}
83
84
static void (*orig_execute_internal)(zend_execute_data *execute_data, zval *return_value);
85
86
1.33M
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
87
1.33M
  fuzzer_step();
88
89
1.33M
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
90
3.20M
  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.86M
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
94
1.86M
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
95
11.7k
      fuzzer_bailout();
96
11.7k
    }
97
1.86M
  }
98
99
1.33M
  orig_execute_internal(execute_data, return_value);
100
1.33M
}
fuzzer-tracing-jit.c:fuzzer_execute_internal
Line
Count
Source
86
574k
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
87
574k
  fuzzer_step();
88
89
574k
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
90
1.35M
  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
779k
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
94
779k
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
95
4.65k
      fuzzer_bailout();
96
4.65k
    }
97
779k
  }
98
99
574k
  orig_execute_internal(execute_data, return_value);
100
574k
}
fuzzer-function-jit.c:fuzzer_execute_internal
Line
Count
Source
86
642k
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
87
642k
  fuzzer_step();
88
89
642k
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
90
1.49M
  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
854k
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
94
854k
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
95
6.14k
      fuzzer_bailout();
96
6.14k
    }
97
854k
  }
98
99
642k
  orig_execute_internal(execute_data, return_value);
100
642k
}
fuzzer-execute.c:fuzzer_execute_internal
Line
Count
Source
86
120k
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
87
120k
  fuzzer_step();
88
89
120k
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
90
351k
  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
231k
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
94
231k
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
95
974
      fuzzer_bailout();
96
974
    }
97
231k
  }
98
99
120k
  orig_execute_internal(execute_data, return_value);
100
120k
}
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
81.0k
ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) {
128
81.0k
  steps_left = MAX_STEPS;
129
81.0k
  zend_exception_save();
130
81.0k
  zval retval, func, args[2];
131
81.0k
  ZVAL_STRING(&func, "opcache_invalidate");
132
81.0k
  ZVAL_STRING(&args[0], FILE_NAME);
133
81.0k
  ZVAL_TRUE(&args[1]);
134
81.0k
  call_user_function(CG(function_table), NULL, &func, &retval, 2, args);
135
81.0k
  ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE);
136
81.0k
  zval_ptr_dtor(&args[0]);
137
81.0k
  zval_ptr_dtor(&retval);
138
81.0k
  zval_ptr_dtor(&func);
139
81.0k
  zend_exception_restore();
140
81.0k
}
fuzzer-tracing-jit.c:opcache_invalidate
Line
Count
Source
127
41.3k
ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) {
128
41.3k
  steps_left = MAX_STEPS;
129
41.3k
  zend_exception_save();
130
41.3k
  zval retval, func, args[2];
131
41.3k
  ZVAL_STRING(&func, "opcache_invalidate");
132
41.3k
  ZVAL_STRING(&args[0], FILE_NAME);
133
41.3k
  ZVAL_TRUE(&args[1]);
134
41.3k
  call_user_function(CG(function_table), NULL, &func, &retval, 2, args);
135
41.3k
  ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE);
136
41.3k
  zval_ptr_dtor(&args[0]);
137
41.3k
  zval_ptr_dtor(&retval);
138
41.3k
  zval_ptr_dtor(&func);
139
41.3k
  zend_exception_restore();
140
41.3k
}
fuzzer-function-jit.c:opcache_invalidate
Line
Count
Source
127
39.7k
ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) {
128
39.7k
  steps_left = MAX_STEPS;
129
39.7k
  zend_exception_save();
130
39.7k
  zval retval, func, args[2];
131
39.7k
  ZVAL_STRING(&func, "opcache_invalidate");
132
39.7k
  ZVAL_STRING(&args[0], FILE_NAME);
133
39.7k
  ZVAL_TRUE(&args[1]);
134
39.7k
  call_user_function(CG(function_table), NULL, &func, &retval, 2, args);
135
39.7k
  ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE);
136
39.7k
  zval_ptr_dtor(&args[0]);
137
39.7k
  zval_ptr_dtor(&retval);
138
39.7k
  zval_ptr_dtor(&func);
139
39.7k
  zend_exception_restore();
140
39.7k
}
Unexecuted instantiation: fuzzer-execute.c:opcache_invalidate
141
142
4
ZEND_ATTRIBUTE_UNUSED char *get_opcache_path(void) {
143
  /* Try relative to cwd. */
144
4
  char *p = realpath("modules/opcache.so", NULL);
145
4
  if (p) {
146
4
    return p;
147
4
  }
148
149
  /* Try relative to binary location. */
150
0
  char path[MAXPATHLEN];
151
#if defined(__FreeBSD__)
152
  size_t pathlen = sizeof(path);
153
  int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
154
  if (sysctl(mib, 4, path, &pathlen, NULL, 0) < 0) {
155
#else
156
0
  if (readlink("/proc/self/exe", path, sizeof(path)) < 0) {
157
0
#endif
158
0
    ZEND_ASSERT(0 && "Failed to get binary path");
159
0
    return NULL;
160
0
  }
161
162
  /* Get basename. */
163
0
  char *last_sep = strrchr(path, '/');
164
0
  if (last_sep) {
165
0
    *last_sep = '\0';
166
0
  }
167
168
0
  strlcat(path, "/modules/opcache.so", sizeof(path));
169
0
  return realpath(path, NULL);
170
0
}