Skip to content

Commit dca53bd

Browse files
ZNeumannlavarou
andauthored
fix: add guards to oapi cufa opcode check (#708)
When instrumenting wordpress/drupal hooks, we want to instrument the hook function calls, which are dispatched by php's call_user_function_array. This php function (cufa for short) is flattened/inlined, so to determine if we are in a function called by a cufa call, we need to check the opcodes of the caller to the current function. If the caller is a php internal function, checking these opcodes was not safe and lead to invalid reads. As such, before reading the opcodes, we first check if the caller is a user function. We only want to instrument cufa calls from user functions anyway. --------- Co-authored-by: Michal Nowacki <[email protected]>
1 parent 6846ba4 commit dca53bd

File tree

2 files changed

+41
-7
lines changed

2 files changed

+41
-7
lines changed

agent/php_execute.c

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,6 +1934,7 @@ static void nr_php_observer_attempt_call_cufa_handler(NR_EXECUTE_PROTO) {
19341934
nrl_verbosedebug(NRL_AGENT, "%s: cannot get previous execute data", __func__);
19351935
return;
19361936
}
1937+
19371938
if (NULL == execute_data->prev_execute_data->opline) {
19381939
nrl_verbosedebug(NRL_AGENT, "%s: cannot get previous opline", __func__);
19391940
return;
@@ -1966,20 +1967,43 @@ static void nr_php_observer_attempt_call_cufa_handler(NR_EXECUTE_PROTO) {
19661967
* cause additional performance overhead, this should be considered a last
19671968
* resort.
19681969
*/
1969-
const zend_op* prev_opline = execute_data->prev_execute_data->opline - 1;
1970+
1971+
/*
1972+
* When Observer API is used, this code executes in the context of
1973+
* zend_execute and not in the context of VM (as was the case pre-OAPI),
1974+
* therefore we need to ensure we're dealing with a user function. We cannot
1975+
* safely access the opline of internal functions, and we only want to
1976+
* instrument cufa calls from user functions anyway.
1977+
*/
1978+
if (UNEXPECTED(NULL == execute_data->prev_execute_data->func)) {
1979+
nrl_verbosedebug(NRL_AGENT, "%s: cannot get previous function", __func__);
1980+
return;
1981+
}
1982+
if (!ZEND_USER_CODE(execute_data->prev_execute_data->func->type)) {
1983+
nrl_verbosedebug(NRL_AGENT, "%s: caller is php internal function", __func__);
1984+
return;
1985+
}
1986+
1987+
const zend_op* prev_opline = execute_data->prev_execute_data->opline;
1988+
1989+
/*
1990+
* Extra safety check. Previously, we instrumented by overwritting ZEND_DO_FCALL.
1991+
* Within OAPI, for consistency's sake, we will ensure the same
1992+
*/
1993+
if (ZEND_DO_FCALL != prev_opline->opcode) {
1994+
nrl_verbosedebug(NRL_AGENT, "%s: cannot get previous function name", __func__);
1995+
return;
1996+
}
1997+
prev_opline -= 1; // Checks previous opcode
19701998
if (ZEND_CHECK_UNDEF_ARGS == prev_opline->opcode) {
1971-
prev_opline = execute_data->prev_execute_data->opline - 2;
1999+
prev_opline -= 1; // Checks previous opcode
19722000
}
19732001
if (ZEND_SEND_ARRAY == prev_opline->opcode) {
2002+
19742003
if (UNEXPECTED((NULL == execute_data->func))) {
19752004
nrl_verbosedebug(NRL_AGENT, "%s: cannot get current function", __func__);
19762005
return;
19772006
}
1978-
if (UNEXPECTED(NULL == execute_data->prev_execute_data->func)) {
1979-
nrl_verbosedebug(NRL_AGENT, "%s: cannot get previous function", __func__);
1980-
return;
1981-
}
1982-
19832007
if (UNEXPECTED(NULL == execute_data->prev_execute_data->func->common.function_name)) {
19842008
nrl_verbosedebug(NRL_AGENT, "%s: cannot get previous function name", __func__);
19852009
return;

tests/integration/frameworks/wordpress/test_wordpress_do_action.php8.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
*/
2222

2323
/*EXPECT
24+
g
2425
f
2526
h
2627
g
@@ -70,4 +71,13 @@ function f() {
7071
}
7172
}
7273

74+
/*
75+
* Initiates a non-flattened call stack of internal->user_code
76+
* to ensure that cufa instrumentation properly handles skipping
77+
* opline lookups of internal functions
78+
*/
79+
$function = new ReflectionFunction('g');
80+
$function->invoke();
81+
82+
7383
do_action("f");

0 commit comments

Comments
 (0)