Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/sapi/fuzzer/fuzzer-execute-common.h
Line
Count
Source
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
#include "zend_vm.h"
27
28
183k
#define FILE_NAME "/tmp/fuzzer.php"
29
218k
#define MAX_STEPS 1000
30
756k
#define MAX_SIZE (8 * 1024)
31
8.97M
#define ZEND_VM_ENTER_BIT 1ULL
32
33
static uint32_t steps_left;
34
static bool bailed_out = false;
35
36
12.1k
static zend_always_inline void fuzzer_bailout(void) {
37
12.1k
  bailed_out = true;
38
12.1k
  zend_bailout();
39
12.1k
}
fuzzer-tracing-jit.c:fuzzer_bailout
Line
Count
Source
36
5.63k
static zend_always_inline void fuzzer_bailout(void) {
37
5.63k
  bailed_out = true;
38
5.63k
  zend_bailout();
39
5.63k
}
fuzzer-function-jit.c:fuzzer_bailout
Line
Count
Source
36
6.31k
static zend_always_inline void fuzzer_bailout(void) {
37
6.31k
  bailed_out = true;
38
6.31k
  zend_bailout();
39
6.31k
}
fuzzer-execute.c:fuzzer_bailout
Line
Count
Source
36
245
static zend_always_inline void fuzzer_bailout(void) {
37
245
  bailed_out = true;
38
245
  zend_bailout();
39
245
}
40
41
9.79M
static zend_always_inline void fuzzer_step(void) {
42
9.79M
  if (--steps_left == 0) {
43
    /* Reset steps before bailing out, so code running after bailout (e.g. in
44
     * destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
45
3.12k
    steps_left = MAX_STEPS;
46
3.12k
    fuzzer_bailout();
47
3.12k
  }
48
9.79M
}
fuzzer-tracing-jit.c:fuzzer_step
Line
Count
Source
41
4.53M
static zend_always_inline void fuzzer_step(void) {
42
4.53M
  if (--steps_left == 0) {
43
    /* Reset steps before bailing out, so code running after bailout (e.g. in
44
     * destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
45
1.27k
    steps_left = MAX_STEPS;
46
1.27k
    fuzzer_bailout();
47
1.27k
  }
48
4.53M
}
fuzzer-function-jit.c:fuzzer_step
Line
Count
Source
41
4.94M
static zend_always_inline void fuzzer_step(void) {
42
4.94M
  if (--steps_left == 0) {
43
    /* Reset steps before bailing out, so code running after bailout (e.g. in
44
     * destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
45
1.61k
    steps_left = MAX_STEPS;
46
1.61k
    fuzzer_bailout();
47
1.61k
  }
48
4.94M
}
fuzzer-execute.c:fuzzer_step
Line
Count
Source
41
310k
static zend_always_inline void fuzzer_step(void) {
42
310k
  if (--steps_left == 0) {
43
    /* Reset steps before bailing out, so code running after bailout (e.g. in
44
     * destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
45
228
    steps_left = MAX_STEPS;
46
228
    fuzzer_bailout();
47
228
  }
48
310k
}
49
50
static void (*orig_execute_ex)(zend_execute_data *execute_data);
51
52
228k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
53
54
228k
#ifdef ZEND_CHECK_STACK_LIMIT
55
228k
  if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
56
0
    zend_call_stack_size_error();
57
    /* No opline was executed before exception */
58
0
    EG(opline_before_exception) = NULL;
59
    /* Fall through to handle exception below. */
60
0
  }
61
228k
#endif /* ZEND_CHECK_STACK_LIMIT */
62
63
228k
  const zend_op *opline = EX(opline);
64
65
9.00M
  while (1) {
66
8.87M
    fuzzer_step();
67
8.87M
    opline = ((zend_vm_opcode_handler_func_t) zend_get_opcode_handler_func(opline))(execute_data, opline);
68
8.87M
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
69
96.7k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
70
96.7k
      if (opline) {
71
0
        execute_data = EG(current_execute_data);
72
96.7k
      } else {
73
96.7k
        return;
74
96.7k
      }
75
96.7k
    }
76
8.87M
  }
77
228k
}
fuzzer-tracing-jit.c:fuzzer_execute_ex
Line
Count
Source
52
96.9k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
53
54
96.9k
#ifdef ZEND_CHECK_STACK_LIMIT
55
96.9k
  if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
56
0
    zend_call_stack_size_error();
57
    /* No opline was executed before exception */
58
0
    EG(opline_before_exception) = NULL;
59
    /* Fall through to handle exception below. */
60
0
  }
61
96.9k
#endif /* ZEND_CHECK_STACK_LIMIT */
62
63
96.9k
  const zend_op *opline = EX(opline);
64
65
4.14M
  while (1) {
66
4.07M
    fuzzer_step();
67
4.07M
    opline = ((zend_vm_opcode_handler_func_t) zend_get_opcode_handler_func(opline))(execute_data, opline);
68
4.07M
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
69
32.5k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
70
32.5k
      if (opline) {
71
0
        execute_data = EG(current_execute_data);
72
32.5k
      } else {
73
32.5k
        return;
74
32.5k
      }
75
32.5k
    }
76
4.07M
  }
77
96.9k
}
fuzzer-function-jit.c:fuzzer_execute_ex
Line
Count
Source
52
95.9k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
53
54
95.9k
#ifdef ZEND_CHECK_STACK_LIMIT
55
95.9k
  if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
56
0
    zend_call_stack_size_error();
57
    /* No opline was executed before exception */
58
0
    EG(opline_before_exception) = NULL;
59
    /* Fall through to handle exception below. */
60
0
  }
61
95.9k
#endif /* ZEND_CHECK_STACK_LIMIT */
62
63
95.9k
  const zend_op *opline = EX(opline);
64
65
4.57M
  while (1) {
66
4.51M
    fuzzer_step();
67
4.51M
    opline = ((zend_vm_opcode_handler_func_t) zend_get_opcode_handler_func(opline))(execute_data, opline);
68
4.51M
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
69
36.2k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
70
36.2k
      if (opline) {
71
0
        execute_data = EG(current_execute_data);
72
36.2k
      } else {
73
36.2k
        return;
74
36.2k
      }
75
36.2k
    }
76
4.51M
  }
77
95.9k
}
fuzzer-execute.c:fuzzer_execute_ex
Line
Count
Source
52
35.3k
static void fuzzer_execute_ex(zend_execute_data *execute_data) {
53
54
35.3k
#ifdef ZEND_CHECK_STACK_LIMIT
55
35.3k
  if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
56
0
    zend_call_stack_size_error();
57
    /* No opline was executed before exception */
58
0
    EG(opline_before_exception) = NULL;
59
    /* Fall through to handle exception below. */
60
0
  }
61
35.3k
#endif /* ZEND_CHECK_STACK_LIMIT */
62
63
35.3k
  const zend_op *opline = EX(opline);
64
65
292k
  while (1) {
66
284k
    fuzzer_step();
67
284k
    opline = ((zend_vm_opcode_handler_func_t) zend_get_opcode_handler_func(opline))(execute_data, opline);
68
284k
    if ((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
69
27.9k
      opline = (const zend_op *) ((uintptr_t) opline & ~ZEND_VM_ENTER_BIT);
70
27.9k
      if (opline) {
71
0
        execute_data = EG(current_execute_data);
72
27.9k
      } else {
73
27.9k
        return;
74
27.9k
      }
75
27.9k
    }
76
284k
  }
77
35.3k
}
78
79
static zend_op_array *(*orig_compile_string)(
80
    zend_string *source_string, const char *filename, zend_compile_position position);
81
82
static zend_op_array *fuzzer_compile_string(
83
4.01k
    zend_string *str, const char *filename, zend_compile_position position) {
84
4.01k
  if (ZSTR_LEN(str) > MAX_SIZE) {
85
    /* Avoid compiling huge inputs via eval(). */
86
5
    fuzzer_bailout();
87
5
  }
88
89
4.01k
  return orig_compile_string(str, filename, position);
90
4.01k
}
fuzzer-tracing-jit.c:fuzzer_compile_string
Line
Count
Source
83
2.35k
    zend_string *str, const char *filename, zend_compile_position position) {
84
2.35k
  if (ZSTR_LEN(str) > MAX_SIZE) {
85
    /* Avoid compiling huge inputs via eval(). */
86
2
    fuzzer_bailout();
87
2
  }
88
89
2.35k
  return orig_compile_string(str, filename, position);
90
2.35k
}
fuzzer-function-jit.c:fuzzer_compile_string
Line
Count
Source
83
1.61k
    zend_string *str, const char *filename, zend_compile_position position) {
84
1.61k
  if (ZSTR_LEN(str) > MAX_SIZE) {
85
    /* Avoid compiling huge inputs via eval(). */
86
3
    fuzzer_bailout();
87
3
  }
88
89
1.61k
  return orig_compile_string(str, filename, position);
90
1.61k
}
fuzzer-execute.c:fuzzer_compile_string
Line
Count
Source
83
39
    zend_string *str, const char *filename, zend_compile_position position) {
84
39
  if (ZSTR_LEN(str) > MAX_SIZE) {
85
    /* Avoid compiling huge inputs via eval(). */
86
0
    fuzzer_bailout();
87
0
  }
88
89
39
  return orig_compile_string(str, filename, position);
90
39
}
91
92
static void (*orig_execute_internal)(zend_execute_data *execute_data, zval *return_value);
93
94
918k
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
95
918k
  fuzzer_step();
96
97
918k
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
98
2.07M
  for (uint32_t i = 0; i < num_args; i++) {
99
    /* Some internal functions like preg_replace() may be slow on large inputs.
100
     * Limit the maximum size of string inputs. */
101
1.15M
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
102
1.15M
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
103
9.07k
      fuzzer_bailout();
104
9.07k
    }
105
1.15M
  }
106
107
918k
  orig_execute_internal(execute_data, return_value);
108
918k
}
fuzzer-tracing-jit.c:fuzzer_execute_internal
Line
Count
Source
94
461k
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
95
461k
  fuzzer_step();
96
97
461k
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
98
1.05M
  for (uint32_t i = 0; i < num_args; i++) {
99
    /* Some internal functions like preg_replace() may be slow on large inputs.
100
     * Limit the maximum size of string inputs. */
101
593k
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
102
593k
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
103
4.35k
      fuzzer_bailout();
104
4.35k
    }
105
593k
  }
106
107
461k
  orig_execute_internal(execute_data, return_value);
108
461k
}
fuzzer-function-jit.c:fuzzer_execute_internal
Line
Count
Source
94
432k
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
95
432k
  fuzzer_step();
96
97
432k
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
98
967k
  for (uint32_t i = 0; i < num_args; i++) {
99
    /* Some internal functions like preg_replace() may be slow on large inputs.
100
     * Limit the maximum size of string inputs. */
101
534k
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
102
534k
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
103
4.69k
      fuzzer_bailout();
104
4.69k
    }
105
534k
  }
106
107
432k
  orig_execute_internal(execute_data, return_value);
108
432k
}
fuzzer-execute.c:fuzzer_execute_internal
Line
Count
Source
94
25.2k
static void fuzzer_execute_internal(zend_execute_data *execute_data, zval *return_value) {
95
25.2k
  fuzzer_step();
96
97
25.2k
  uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data);
98
48.2k
  for (uint32_t i = 0; i < num_args; i++) {
99
    /* Some internal functions like preg_replace() may be slow on large inputs.
100
     * Limit the maximum size of string inputs. */
101
22.9k
    zval *arg = ZEND_CALL_VAR_NUM(execute_data, i);
102
22.9k
    if (Z_TYPE_P(arg) == IS_STRING && Z_STRLEN_P(arg) > MAX_SIZE) {
103
17
      fuzzer_bailout();
104
17
    }
105
22.9k
  }
106
107
25.2k
  orig_execute_internal(execute_data, return_value);
108
25.2k
}
109
110
6
static void fuzzer_init_php_for_execute(const char *extra_ini) {
111
  /* Compilation will often trigger fatal errors.
112
   * Use tracked allocation mode to avoid leaks in that case. */
113
6
  putenv("USE_TRACKED_ALLOC=1");
114
115
  /* Just like other SAPIs, ignore SIGPIPEs. */
116
6
  signal(SIGPIPE, SIG_IGN);
117
118
6
  fuzzer_init_php(extra_ini);
119
120
6
  orig_execute_ex = zend_execute_ex;
121
6
  zend_execute_ex = fuzzer_execute_ex;
122
6
  orig_execute_internal = zend_execute_internal ? zend_execute_internal : execute_internal;
123
6
  zend_execute_internal = fuzzer_execute_internal;
124
6
  orig_compile_string = zend_compile_string;
125
6
  zend_compile_string = fuzzer_compile_string;
126
6
}
fuzzer-tracing-jit.c:fuzzer_init_php_for_execute
Line
Count
Source
110
2
static void fuzzer_init_php_for_execute(const char *extra_ini) {
111
  /* Compilation will often trigger fatal errors.
112
   * Use tracked allocation mode to avoid leaks in that case. */
113
2
  putenv("USE_TRACKED_ALLOC=1");
114
115
  /* Just like other SAPIs, ignore SIGPIPEs. */
116
2
  signal(SIGPIPE, SIG_IGN);
117
118
2
  fuzzer_init_php(extra_ini);
119
120
2
  orig_execute_ex = zend_execute_ex;
121
2
  zend_execute_ex = fuzzer_execute_ex;
122
2
  orig_execute_internal = zend_execute_internal ? zend_execute_internal : execute_internal;
123
2
  zend_execute_internal = fuzzer_execute_internal;
124
2
  orig_compile_string = zend_compile_string;
125
2
  zend_compile_string = fuzzer_compile_string;
126
2
}
fuzzer-function-jit.c:fuzzer_init_php_for_execute
Line
Count
Source
110
2
static void fuzzer_init_php_for_execute(const char *extra_ini) {
111
  /* Compilation will often trigger fatal errors.
112
   * Use tracked allocation mode to avoid leaks in that case. */
113
2
  putenv("USE_TRACKED_ALLOC=1");
114
115
  /* Just like other SAPIs, ignore SIGPIPEs. */
116
2
  signal(SIGPIPE, SIG_IGN);
117
118
2
  fuzzer_init_php(extra_ini);
119
120
2
  orig_execute_ex = zend_execute_ex;
121
2
  zend_execute_ex = fuzzer_execute_ex;
122
2
  orig_execute_internal = zend_execute_internal ? zend_execute_internal : execute_internal;
123
2
  zend_execute_internal = fuzzer_execute_internal;
124
2
  orig_compile_string = zend_compile_string;
125
2
  zend_compile_string = fuzzer_compile_string;
126
2
}
fuzzer-execute.c:fuzzer_init_php_for_execute
Line
Count
Source
110
2
static void fuzzer_init_php_for_execute(const char *extra_ini) {
111
  /* Compilation will often trigger fatal errors.
112
   * Use tracked allocation mode to avoid leaks in that case. */
113
2
  putenv("USE_TRACKED_ALLOC=1");
114
115
  /* Just like other SAPIs, ignore SIGPIPEs. */
116
2
  signal(SIGPIPE, SIG_IGN);
117
118
2
  fuzzer_init_php(extra_ini);
119
120
2
  orig_execute_ex = zend_execute_ex;
121
2
  zend_execute_ex = fuzzer_execute_ex;
122
2
  orig_execute_internal = zend_execute_internal ? zend_execute_internal : execute_internal;
123
2
  zend_execute_internal = fuzzer_execute_internal;
124
2
  orig_compile_string = zend_compile_string;
125
2
  zend_compile_string = fuzzer_compile_string;
126
2
}
127
128
4
ZEND_ATTRIBUTE_UNUSED static void create_file(void) {
129
  /* For opcache_invalidate() to work, the dummy file name used for fuzzing needs to
130
   * actually exist. */
131
4
  FILE *f = fopen(FILE_NAME, "w");
132
4
  fclose(f);
133
4
}
fuzzer-tracing-jit.c:create_file
Line
Count
Source
128
2
ZEND_ATTRIBUTE_UNUSED static void create_file(void) {
129
  /* For opcache_invalidate() to work, the dummy file name used for fuzzing needs to
130
   * actually exist. */
131
2
  FILE *f = fopen(FILE_NAME, "w");
132
2
  fclose(f);
133
2
}
fuzzer-function-jit.c:create_file
Line
Count
Source
128
2
ZEND_ATTRIBUTE_UNUSED static void create_file(void) {
129
  /* For opcache_invalidate() to work, the dummy file name used for fuzzing needs to
130
   * actually exist. */
131
2
  FILE *f = fopen(FILE_NAME, "w");
132
2
  fclose(f);
133
2
}
Unexecuted instantiation: fuzzer-execute.c:create_file
134
135
50.8k
ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) {
136
50.8k
  steps_left = MAX_STEPS;
137
50.8k
  zend_object *exception = EG(exception);
138
50.8k
  EG(exception) = NULL;
139
50.8k
  zval retval, args[2];
140
50.8k
  zend_function *fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("opcache_invalidate"));
141
50.8k
  ZEND_ASSERT(fn != NULL);
142
143
50.8k
  ZVAL_STRING(&args[0], FILE_NAME);
144
50.8k
  ZVAL_TRUE(&args[1]);
145
50.8k
  zend_call_known_function(fn, NULL, NULL, &retval, 2, args, NULL);
146
50.8k
  ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE);
147
50.8k
  zval_ptr_dtor(&args[0]);
148
50.8k
  zval_ptr_dtor(&retval);
149
50.8k
  EG(exception) = exception;
150
50.8k
}
fuzzer-tracing-jit.c:opcache_invalidate
Line
Count
Source
135
29.0k
ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) {
136
29.0k
  steps_left = MAX_STEPS;
137
29.0k
  zend_object *exception = EG(exception);
138
29.0k
  EG(exception) = NULL;
139
29.0k
  zval retval, args[2];
140
29.0k
  zend_function *fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("opcache_invalidate"));
141
29.0k
  ZEND_ASSERT(fn != NULL);
142
143
29.0k
  ZVAL_STRING(&args[0], FILE_NAME);
144
29.0k
  ZVAL_TRUE(&args[1]);
145
29.0k
  zend_call_known_function(fn, NULL, NULL, &retval, 2, args, NULL);
146
29.0k
  ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE);
147
29.0k
  zval_ptr_dtor(&args[0]);
148
29.0k
  zval_ptr_dtor(&retval);
149
29.0k
  EG(exception) = exception;
150
29.0k
}
fuzzer-function-jit.c:opcache_invalidate
Line
Count
Source
135
21.8k
ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) {
136
21.8k
  steps_left = MAX_STEPS;
137
21.8k
  zend_object *exception = EG(exception);
138
21.8k
  EG(exception) = NULL;
139
21.8k
  zval retval, args[2];
140
21.8k
  zend_function *fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("opcache_invalidate"));
141
21.8k
  ZEND_ASSERT(fn != NULL);
142
143
21.8k
  ZVAL_STRING(&args[0], FILE_NAME);
144
21.8k
  ZVAL_TRUE(&args[1]);
145
21.8k
  zend_call_known_function(fn, NULL, NULL, &retval, 2, args, NULL);
146
21.8k
  ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE);
147
21.8k
  zval_ptr_dtor(&args[0]);
148
21.8k
  zval_ptr_dtor(&retval);
149
21.8k
  EG(exception) = exception;
150
21.8k
}
Unexecuted instantiation: fuzzer-execute.c:opcache_invalidate