/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 | } |