Skip to content

Fix GH-10168: heap-buffer-overflow at zval_undefined_cv #10500

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 3 commits into from

Conversation

nielsdos
Copy link
Member

@nielsdos nielsdos commented Feb 3, 2023

Fixes #10168

cc'ing @iluuu1994 since he also was involved in the issue report

The problem is that we're using the variable_ptr in the opcode handler
after it has already been destroyed. The solution is to delay
executing the destructors until after the variable_ptr is used.
To accomplish this we introduce 2 new API functions:

  • zend_assign_to_variable_delay_garbage_handling(); and
  • zend_assign_to_variable_handle_garbage() that allows users to delay
    the garbage handling. zend_assign_to_variable() is now a wrapper for
    those such that there is no BC break.

We only have to apply this to ASSIGN_DIM and ASSIGN. That's because the
others rely on properties, in which case the variable_ptr can't be
destroyed.

The first commit fixes the bug, the second one adds a regression test.
Comparing the performance before and after this fix using Valgrind on
Zend/bench.php.
Instruction counts yields 3,339,632,671 before and 3,340,132,746 after.
The difference is only a performance decrease of ~0.015%, which seems
negligible.

Additional notes
I do have a local, experimental, patchset that improves performance of ZEND_ASSIGN and even results in a net win, but that's kinda unrelated to this PR and probably master-only material. Just thought I'd mention it since I did measurements of VM performance's tiny decrease.

nielsdos and others added 2 commits February 4, 2023 00:37
Fixes phpGH-10168

The problem is that we're using the variable_ptr in the opcode handler
*after* it has already been destroyed. The solution is to delay
executing the destructors until after the variable_ptr is used.
To accomplish this we introduce 2 new API functions:
* zend_assign_to_variable_delay_garbage_handling(); and
* zend_assign_to_variable_handle_garbage() that allows users to delay
the garbage handling. zend_assign_to_variable() is now a wrapper for
those such that there is no BC break.

We only have to apply this to ASSIGN_DIM and ASSIGN. That's because the
others rely on properties, in which case the variable_ptr can't be
destroyed.

The first commit fixes the bug, the second one adds a regression test.
Comparing the performance before and after this fix using Valgrind on
Zend/bench.php.
Instruction counts yields 3,339,632,671 before and 3,340,132,746 after.
The difference is only a performance decrease of ~0.015%, which seems
negligible.
Copy link
Member

@iluuu1994 iluuu1994 left a comment

Choose a reason for hiding this comment

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

This makes sense to me. @dstogov Are you ok with this change?

@nielsdos
Copy link
Member Author

nielsdos commented Feb 4, 2023

I pushed the name change. Thanks.

Copy link
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

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

In general the fix is right and performance degradation is not critical, however, I would prefer to try a different approach to fix this.

Instead of delaying destructor, I suggest to create a clone of zend_assign_to_variable() with two destinations (e.g. . zend_assign_to_two_variables) This wouldn't affect assignments without return value and would reduce number of reference-counting for assignments with return values. (Something similar is done in JIT). I can not be sure which approach is better without implementation.

@nielsdos, @iluuu1994 can you try implementing this or I may try to do this myself.
Please respond to let me start or wait for your result. Any other arguments are welcome.

@iluuu1994
Copy link
Member

@dstogov Thanks for your feedback! Niels or I can make the changes. @nielsdos Let me know if you'd still like to work on this. If not I'll create a patch.

@nielsdos
Copy link
Member Author

nielsdos commented Feb 6, 2023

@dstogov @iluuu1994 Thanks for checking this. I'll try dstogov's suggestion tonight and create an alternative PR for that. I'll also do the same performance measurement. We can then see which PR gets closed and which PR gets merged :)

@nielsdos
Copy link
Member Author

nielsdos commented Feb 6, 2023

Alternative implementation available at GH-10524.

Copy link
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

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

This looks almost fine. I proposed just two optimization tweaks to reduce slight performance degradation.

Comment on lines +184 to +191
static zend_always_inline zval* zend_assign_to_variable(zval *variable_ptr, zval *value, zend_uchar value_type, bool strict)
{
zend_refcounted *garbage;
variable_ptr = zend_assign_to_variable_delay_garbage_handling(variable_ptr, value, value_type, strict, &garbage);
zend_handle_garbage_from_variable_assignment(garbage);
return variable_ptr;
}

Copy link
Member

Choose a reason for hiding this comment

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

Please keep the original version of zend_assign_to_variable(). This will eliminate effect on everything except ASSIGN and ASSIGN_DIM.

Comment on lines -2688 to +2699
value = zend_assign_to_variable(variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES());
zend_refcounted *garbage;
value = zend_assign_to_variable_delay_garbage_handling(variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES(), &garbage);
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_COPY(EX_VAR(opline->result.var), value);
}
zend_handle_garbage_from_variable_assignment(garbage);

Copy link
Member

Choose a reason for hiding this comment

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

Rewrite this to allow specializer selecting the more efficient version.

	if (RETURN_VALUE_USED(opline)) {
		zend_refcounted *garbage;
		value = zend_assign_to_variable_delay_garbage_handling(variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES(), &garbage);
		ZVAL_COPY(EX_VAR(opline->result.var), value);
		zend_handle_garbage_from_variable_assignment(garbage);
	} else {
		zend_assign_to_variable(variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES());
	}

@nielsdos
Copy link
Member Author

nielsdos commented Feb 7, 2023

Closing this one as preference was given to the alternative approach.

@nielsdos nielsdos closed this Feb 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants