25
25
#include <php_ini.h>
26
26
#include <pthread.h>
27
27
#include <stdatomic.h>
28
+ #include <sys/mman.h>
28
29
29
30
#include <ext/standard/info.h>
30
31
#include <ext/standard/php_string.h>
@@ -146,6 +147,104 @@ static void ddtrace_sort_modules(void *base, size_t count, size_t siz, compare_f
146
147
}
147
148
#endif
148
149
150
+ #if PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80200
151
+ // On PHP 8.0.0-8.0.16 and 8.1.0-8.1.2 call_attribute_constructor would stack allocate a dummy frame, which could have become inaccessible upon access.
152
+ // Thus, we implement the fix which was applied to PHP itself as well: we move the stack allocated data to the VM stack.
153
+ // See also https://github.com/php/php-src/commit/f7c3f6e7e25471da9cfb2ba082a77cc3c85bc6ed
154
+ static void dd_patched_zend_call_known_function (
155
+ zend_function * fn , zend_object * object , zend_class_entry * called_scope , zval * retval_ptr ,
156
+ uint32_t param_count , zval * params , HashTable * named_params )
157
+ {
158
+ zval retval ;
159
+ zend_fcall_info fci ;
160
+ zend_fcall_info_cache fcic ;
161
+
162
+ // If current_execute_data is on the stack, move it to the VM stack
163
+ zend_execute_data * execute_data = EG (current_execute_data );
164
+ if ((uintptr_t )& retval > (uintptr_t )EX (func ) && (uintptr_t )& retval - 0xfffff < (uintptr_t )EX (func )) {
165
+ zend_execute_data * call = zend_vm_stack_push_call_frame_ex (
166
+ ZEND_MM_ALIGNED_SIZE_EX (sizeof (zend_execute_data ), sizeof (zval )) +
167
+ ZEND_MM_ALIGNED_SIZE_EX (sizeof (zend_op ), sizeof (zval )) +
168
+ ZEND_MM_ALIGNED_SIZE_EX (sizeof (zend_function ), sizeof (zval )),
169
+ 0 , EX (func ), 0 , NULL );
170
+
171
+ memcpy (call , execute_data , sizeof (zend_execute_data ));
172
+ zend_op * opline = (zend_op * )(call + 1 );
173
+ memcpy (opline , EX (opline ), sizeof (zend_op ));
174
+ zend_function * func = (zend_function * )(opline + 1 );
175
+ func -> common .fn_flags |= ZEND_ACC_CALL_VIA_TRAMPOLINE ; // See https://github.com/php/php-src/commit/2f6a06ccb0ef78e6122bb9e67f9b8b1ad07776e1
176
+ memcpy ((zend_op * )(call + 1 ) + 1 , EX (func ), sizeof (zend_function ));
177
+
178
+ call -> opline = opline ;
179
+ call -> func = func ;
180
+
181
+ EG (current_execute_data ) = call ;
182
+ }
183
+
184
+ // here follows the original implementation of zend_call_known_function
185
+
186
+ fci .size = sizeof (fci );
187
+ fci .object = object ;
188
+ fci .retval = retval_ptr ? retval_ptr : & retval ;
189
+ fci .param_count = param_count ;
190
+ fci .params = params ;
191
+ fci .named_params = named_params ;
192
+ ZVAL_UNDEF (& fci .function_name ); /* Unused */
193
+
194
+ fcic .function_handler = fn ;
195
+ fcic .object = object ;
196
+ fcic .called_scope = called_scope ;
197
+
198
+ zend_result result = zend_call_function (& fci , & fcic );
199
+ if (UNEXPECTED (result == FAILURE )) {
200
+ if (!EG (exception )) {
201
+ zend_error_noreturn (E_CORE_ERROR , "Couldn't execute method %s%s%s" ,
202
+ fn -> common .scope ? ZSTR_VAL (fn -> common .scope -> name ) : "" ,
203
+ fn -> common .scope ? "::" : "" , ZSTR_VAL (fn -> common .function_name ));
204
+ }
205
+ }
206
+
207
+ if (!retval_ptr ) {
208
+ zval_ptr_dtor (& retval );
209
+ }
210
+ }
211
+
212
+ // We need to hijack zend_call_known_function as that's what's being called by call_attribute_constructor, and call_attribute_constructor itself is not exported.
213
+ static void dd_patch_zend_call_known_function (void ) {
214
+ size_t page_size = sysconf (_SC_PAGESIZE );
215
+ void * page = (void * )(~(page_size - 1 ) & (uintptr_t )zend_call_known_function );
216
+ // 20 is the largest size of a trampoline we have to inject
217
+ if ((((uintptr_t )zend_call_known_function + 20 ) & page_size ) < 20 ) {
218
+ page_size <<= 1 ; // if overlapping pages, use two
219
+ }
220
+ if (mprotect (page , page_size , PROT_READ | PROT_WRITE ) != 0 ) { // Some architectures enforce W^X (either write _or_ execute, but not both).
221
+ return ; // Make absolutely sure we can write
222
+ }
223
+
224
+ #ifdef __aarch64__
225
+ // x13 is a scratch register
226
+ uint32_t absolute_jump_instrs [] = {
227
+ 0x1000006D , // adr x13, 12 (load address from memory after this)
228
+ 0xF94001AD , // ldr x13, [x13]
229
+ 0xD61F01A0 , // br x13
230
+ };
231
+ // The magical 12 is sizeof(absolute_jump_instrs) and hardcoded in the assembly above.
232
+ memcpy (zend_call_known_function , absolute_jump_instrs , 12 );
233
+ * (void * * )(12 + (uintptr_t )zend_call_known_function ) = dd_patched_zend_call_known_function ;
234
+ #else
235
+ // $r10 doesn't really have special meaning
236
+ uint8_t absolute_jump_instrs [] = {
237
+ 0x49 , 0xBA , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , // mov $r10, imm_addr
238
+ 0x41 , 0xFF , 0xE2 // jmp $r10
239
+ };
240
+ * (void * * )& absolute_jump_instrs [2 ] = dd_patched_zend_call_known_function ;
241
+ memcpy (zend_call_known_function , absolute_jump_instrs , sizeof (absolute_jump_instrs ));
242
+ #endif
243
+
244
+ mprotect (page , page_size , PROT_READ | PROT_EXEC );
245
+ }
246
+ #endif
247
+
149
248
// put this into startup so that other extensions running code as part of rinit do not crash
150
249
static int ddtrace_startup (zend_extension * extension ) {
151
250
UNUSED (extension );
@@ -170,6 +269,18 @@ static int ddtrace_startup(zend_extension *extension) {
170
269
zai_interceptor_startup ();
171
270
#endif
172
271
272
+ #if PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80200
273
+ #if PHP_VERSION_ID < 80100
274
+ #define BUG_STACK_ALLOCATED_CALL_PATCH_VERSION 16
275
+ #else
276
+ #define BUG_STACK_ALLOCATED_CALL_PATCH_VERSION 3
277
+ #endif
278
+ zend_long patch_version = Z_LVAL_P (zend_get_constant_str (ZEND_STRL ("PHP_RELEASE_VERSION" )));
279
+ if (patch_version < BUG_STACK_ALLOCATED_CALL_PATCH_VERSION ) {
280
+ dd_patch_zend_call_known_function ();
281
+ }
282
+ #endif
283
+
173
284
ddtrace_excluded_modules_startup ();
174
285
// We deliberately leave handler replacement during startup, even though this uses some config
175
286
// This touches global state, which, while unlikely, may play badly when interacting with other extensions, if done post-startup
0 commit comments