-
Notifications
You must be signed in to change notification settings - Fork 7.9k
[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
Conversation
Zend/zend_vm_def.h
Outdated
@@ -4304,7 +4304,7 @@ ZEND_VM_INLINE_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY, SPEC(OBSERVER)) | |||
} | |||
} | |||
ZEND_OBSERVER_SAVE_OPLINE(); | |||
ZEND_OBSERVER_FCALL_END(execute_data, return_value); | |||
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this may use after free for the IS_VAR REFERENCE case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah no, retval_ptr is derefed in that case. However the IS_CV case looks problematic, because the value gets moved into return_value there and retval_ptr is changed to a NULL zval.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, nice catch. I was scanning for dtor's and totally missed that ZVAL_NULL(retval_ptr)
for IS_CV
. I'll fix that and add some more comprehensive tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just added a fix for the IS_CV
+ refcounted return case and added more tests for various VM var types. Thanks again for catching that one!
Also add loads more tests for various VM var types
Zend/zend_vm_def.h
Outdated
@@ -4248,13 +4248,17 @@ 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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
@nikic Do you have any other issues with this PR? I'm planning on merging this first thing tomorrow morning in time for the RC5 tag. |
Zend/zend_vm_def.h
Outdated
@@ -4365,7 +4375,7 @@ ZEND_VM_COLD_CONST_HANDLER(111, ZEND_RETURN_BY_REF, CONST|TMP|VAR|CV, ANY, SRC, | |||
FREE_OP1_VAR_PTR(); | |||
} while (0); | |||
|
|||
ZEND_OBSERVER_FCALL_END(execute_data, EX(return_value)); | |||
ZEND_OBSERVER_FCALL_END(execute_data, retval_ptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a use after free for all the !EX(return_value) cases above, that already free the value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good catch. I didn't realize that freeing OP1 was freeing the retval from the opline. I fixed this in 0fa78a5.
--FILE-- | ||
<?php | ||
function &getMessage() { | ||
$retval = 'I should be observable'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be something refcounted to check for use after free.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A refcounted retval is being tested in observer_retval_04.phpt
. In this test I was targeting this branch. Do you think we have sufficient coverage with all the retval tests?
Zend/zend_vm_def.h
Outdated
zend_generator_close(generator, 1); | ||
ZEND_VM_RETURN(); | ||
} else { | ||
if (zend_observer_fcall_op_array_extension != -1) { | ||
zend_observer_fcall_end(execute_data, EX(return_value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks pretty fishy to me, especially in conjunction with the EX(return_value) initialization below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Zend/zend_vm_def.h
Outdated
@@ -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); |
There was a problem hiding this comment.
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.
Just to offer a possible alternative that might be less fragile, you could do something like this:
I.e. pretend we always want a return_value if observer if used. Less efficient, but maybe more straightforward? |
This PR ensures that if a retval is not used, it is still available to observer end handlers.