Skip to content

[Observer] Provide unused retvals to observers #6422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
23 changes: 19 additions & 4 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -4248,7 +4248,11 @@ ZEND_VM_INLINE_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY, SPEC(OBSERVER))
if (return_value) {
ZVAL_NULL(return_value);
}
ZEND_OBSERVER_SAVE_OPLINE();
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might it make sense to just move this to the very start of the function, rather than duplication it in the branches?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would make sense and would be simpler. But the main issue is that any errors that are raised in the return handler would trigger after the end handler had fired.

For example, the output from observer_retval_05.phpt would change to this:

  <foo>
+  </foo:NULL>

Warning: Undefined variable $i_do_not_exist in %s on line %d
-  </foo:NULL>

For the PHP tracer extension I'm working on, that would mean that we couldn't attach the E_WARNING to the open foo span that raised the error.

} else if (!return_value) {
ZEND_OBSERVER_SAVE_OPLINE();
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr);
if (OP1_TYPE & (IS_VAR|IS_TMP_VAR)) {
if (Z_REFCOUNTED_P(retval_ptr) && !Z_DELREF_P(retval_ptr)) {
SAVE_OPLINE();
Expand All @@ -4263,6 +4267,8 @@ ZEND_VM_INLINE_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY, SPEC(OBSERVER))
Z_ADDREF_P(return_value);
}
}
ZEND_OBSERVER_SAVE_OPLINE();
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr);
} else if (OP1_TYPE == IS_CV) {
do {
if (Z_OPT_REFCOUNTED_P(retval_ptr)) {
Expand All @@ -4274,6 +4280,8 @@ ZEND_VM_INLINE_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY, SPEC(OBSERVER))
gc_possible_root(ref);
}
ZVAL_NULL(retval_ptr);
ZEND_OBSERVER_SAVE_OPLINE();
ZEND_OBSERVER_FCALL_END(execute_data, return_value);
break;
} else {
Z_ADDREF_P(retval_ptr);
Expand All @@ -4286,6 +4294,8 @@ ZEND_VM_INLINE_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY, SPEC(OBSERVER))
}
}
ZVAL_COPY_VALUE(return_value, retval_ptr);
ZEND_OBSERVER_SAVE_OPLINE();
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr);
} while (0);
} else /* if (OP1_TYPE == IS_VAR) */ {
if (UNEXPECTED(Z_ISREF_P(retval_ptr))) {
Expand All @@ -4301,10 +4311,10 @@ ZEND_VM_INLINE_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY, SPEC(OBSERVER))
} else {
ZVAL_COPY_VALUE(return_value, retval_ptr);
}
ZEND_OBSERVER_SAVE_OPLINE();
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's still a potential use-after-free here. If retval_ptr is a reference with refcount one, then it will be freed above, but (the new) retval_ptr will still point into it.

Generally I think that you should just do

ZEND_OBSERVER_SAVE_OPLINE();
ZEND_OBSERVER_FCALL_END(execute_data, return_value);

one at the end up the whole if (return_value) branch. There's no need to handle these differently in each case.

}
}
ZEND_OBSERVER_SAVE_OPLINE();
ZEND_OBSERVER_FCALL_END(execute_data, return_value);
ZEND_VM_DISPATCH_TO_HELPER(zend_leave_helper);
}

Expand All @@ -4323,17 +4333,20 @@ ZEND_VM_COLD_CONST_HANDLER(111, ZEND_RETURN_BY_REF, CONST|TMP|VAR|CV, ANY, SRC,

retval_ptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
if (!EX(return_value)) {
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr);
FREE_OP1();
} else {
if (OP1_TYPE == IS_VAR && UNEXPECTED(Z_ISREF_P(retval_ptr))) {
ZVAL_COPY_VALUE(EX(return_value), retval_ptr);
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr);
break;
}

ZVAL_NEW_REF(EX(return_value), retval_ptr);
if (OP1_TYPE == IS_CONST) {
Z_TRY_ADDREF_P(retval_ptr);
}
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr);
}
break;
}
Expand All @@ -4346,7 +4359,9 @@ ZEND_VM_COLD_CONST_HANDLER(111, ZEND_RETURN_BY_REF, CONST|TMP|VAR|CV, ANY, SRC,
zend_error(E_NOTICE, "Only variable references should be returned by reference");
if (EX(return_value)) {
ZVAL_NEW_REF(EX(return_value), retval_ptr);
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr);
} else {
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr);
FREE_OP1_VAR_PTR();
}
break;
Expand All @@ -4362,10 +4377,10 @@ ZEND_VM_COLD_CONST_HANDLER(111, ZEND_RETURN_BY_REF, CONST|TMP|VAR|CV, ANY, SRC,
ZVAL_REF(EX(return_value), Z_REF_P(retval_ptr));
}

ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr);
FREE_OP1_VAR_PTR();
} while (0);

ZEND_OBSERVER_FCALL_END(execute_data, EX(return_value));
ZEND_VM_DISPATCH_TO_HELPER(zend_leave_helper);
}

Expand Down Expand Up @@ -7710,7 +7725,7 @@ ZEND_VM_HELPER(zend_dispatch_try_catch_finally_helper, ANY, ANY, uint32_t try_ca

/* Uncaught exception */
if (zend_observer_fcall_op_array_extension != -1) {
zend_observer_fcall_end(execute_data, EX(return_value));
zend_observer_fcall_end(execute_data, NULL);
}
cleanup_live_vars(execute_data, op_num, 0);
if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) {
Expand Down
Loading