Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Resolve Inconsistency in zend_jit_return Between JIT and Non-JIT Engines #14841

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

Open
segrax wants to merge 4 commits into php:PHP-8.2
base: PHP-8.2
Choose a base branch
Loading
from segrax:PHP-8.1

Conversation

Copy link

@segrax segrax commented Jul 6, 2024

This resolves an inconsistency in both the x86 and ARM64 implementation of zend_jit_return between the JIT and non-JIT engines (op: ZEND_RETURN_SPEC_OBSERVER).

Issue

  • Non-JIT Engine: zend_observer_fcall_end is invoked after the returned zval is copied into execute_data->return_value.
  • JIT Engine: zend_observer_fcall_end is invoked before the returned zval is copied.

Behaviour

Extensions using the value in execute_data->return_value behave differently in JIT mode, which can lead to incorrect readings, and/or segfaults if values are being modified.

Affects

PHP 8.1 to 8.4 (issue is present in the new JIT)

Tests

I've extended the zend_test extension to allow hooks to be executed from calls to zend_observer_fcall_end, and added a test which modifies the return value

Copy link
Member

devnexen commented Jul 6, 2024

I asked to the rest of the team if your PR would qualify as security fix (8.1 is EOL), if not then it would need to target PHP-8.2 instead. We shall see.

Copy link
Author

segrax commented Jul 6, 2024

I asked to the rest of the team if your PR would qualify as security fix (8.1 is EOL), if not then it would need to target PHP-8.2 instead. We shall see.

Ah, I misread the contributing guide and version table. I'm happy to rebase on 8.2

@segrax segrax changed the base branch from PHP-8.1 to PHP-8.2 July 6, 2024 19:46
Copy link
Author

segrax commented Jul 6, 2024

Re-based to PHP-8.2

Copy link
Author

segrax commented Jul 7, 2024
edited
Loading

There was another small fix regarding parameters,

In non-JIT mode, IS_CONST types pass a pointer to a temporary _zval_struct into zend_observer_fcall_end for the retval parameter, resulting in two different _zval_struct objects with a matching zend_value. The new hook function in zend_test copies the zval from the executed hook return value to execute_data->return_value. Consequently, the retval parameter in observer_end still referred to the original zval stored in the temporary _zval_struct.

This caused the test to display different return values for JIT and non-JIT modes, while still producing the expected final result outside the observer.

Data comparison inside observer_end:

BEFORE 2nd Fix:
JIT:
execute_data->return_value: ptr:d0216080 zval.value.lval:41b3cf90
retval:			 ptr:421e0830 zval.value.lval:41b3cf90
NON:
execute_data->return_value: ptr:fc016080 zval.value.lval:4065bf90
retval:			 ptr:fc016080 zval.value.lval:4065bf90
AFTER 2nd Fix:
JIT:
execute_data->return_value: ptr:b5016080 zval.value.lval:6aa32c78
retval:			 ptr:b5016080 zval.value.lval:6aa32c78
NON:
execute_data->return_value: ptr:c1616080 zval.value.lval:20232c78
retval:			 ptr:c1616080 zval.value.lval:20232c78

Copy link
Member

iluuu1994 commented Jul 7, 2024
edited
Loading

@segrax Thank you for your PR! Note that Dmitry, who is responsible for the JIT, is on vacation for the next two weeks. So it might take a minute for you to get a proper review.

carlosjfernandes and segrax reacted with thumbs up emoji

Comment on lines 10938 to 10939
if (return_value_used != 0) {
if (opline->op1_type == IS_CONST) {
Copy link
Member

Choose a reason for hiding this comment

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

Can you please try to minimize the patch, avoiding indentation changes. e.g.

	if (return_value_used == 0) {
		/* pass */
	} else if (opline->op1_type == IS_CONST) {

Copy link
Author

Choose a reason for hiding this comment

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

Yep, done

Copy link
Member

dstogov commented Jul 23, 2024

cc: @bwoebi

Copy link
Member

dstogov commented Jul 24, 2024

Thanks!
Now it's clear that the patch doesn't affect JIT without observer.
Porting to master also should be simpler.

@bwoebi please check the observer related behavior and merge this.

Copy link
Member

bwoebi commented Jul 24, 2024
edited
Loading

I'm confused, like the patch seems to be only concerned with the pass case and IS_CONST and IS_TMP_VAR?

Reading the IS_CV case, isn't it sending the pointer to the CV rather than the ret_addr to zend_observer_fcall_end?
And same for the IS_TMP_VAR and the IS_VAR case (the else part)?

I must admit I don't really understand what the

		if (Z_MODE(op1_addr) == IS_REG) {
			zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
			if (!zend_jit_spill_store(Dst, op1_addr, dst, op1_info, 1)) {
				return 0;
			}
			op1_addr = dst;
		}

part is doing.

At least - IF the value is returned - it's always supposed to be the return address, not sometimes op1_addr. Like, wouldn't this be correct:

	if (return_value_used == 0) {
		if (ZEND_OBSERVER_ENABLED && Z_MODE(op1_addr) == IS_REG) {
			zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
			if (!zend_jit_spill_store(Dst, op1_addr, dst, op1_info, 1)) {
				return 0;
			}
			op1_addr = dst;
		}
		ret_addr = op1_addr;
	} else if (..) {
		// everything else unchanged here for IS_CV/VAR/TMP_VAR/CONST handling
	}
	if (ZEND_OBSERVER_ENABLED) {
		|	LOAD_ZVAL_ADDR FCARG2a, ret_addr
		|	mov FCARG1a, FP
		|	SET_EX_OPLINE opline, r0
		|	EXT_CALL zend_observer_fcall_end, r0
	}

Maybe @dstogov can help here?

Copy link
Member

dstogov commented Jul 24, 2024

The last @bwoebi suggestion seems right!

Copy link
Author

segrax commented Aug 12, 2024

I'm confused, like the patch seems to be only concerned with the pass case and IS_CONST and IS_TMP_VAR?

@bwoebi yeah, currently it only fixes the issue i found while working with the OpenTelemetry extension, there is likely the same issue with the other types, but too be honest, i wasn't entirely sure what everything here was doing.

Figured I would submit what I had, and work from there

At least - IF the value is returned - it's always supposed to be the return address, not sometimes op1_addr. Like, wouldn't this be correct:

	if (return_value_used == 0) {
		if (ZEND_OBSERVER_ENABLED && Z_MODE(op1_addr) == IS_REG) {
			zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
			if (!zend_jit_spill_store(Dst, op1_addr, dst, op1_info, 1)) {
				return 0;
			}
			op1_addr = dst;
		}
		ret_addr = op1_addr;
	} else if (..) {
		// everything else unchanged here for IS_CV/VAR/TMP_VAR/CONST handling
	}
	if (ZEND_OBSERVER_ENABLED) {
		|	LOAD_ZVAL_ADDR FCARG2a, ret_addr
		|	mov FCARG1a, FP
		|	SET_EX_OPLINE opline, r0
		|	EXT_CALL zend_observer_fcall_end, r0
	}

It seems this change causes a test failure, but only in the debug builds (my local build is fine, but I also haven't checked what flags are being used in the debug pipeline builds).

EX(opline) is correctly set for nested JIT user code calls [ext/opcache/tests/jit/gh13772.phpt]

Copy link
Member

dstogov commented Aug 26, 2024

@segrax did you see ext/opcache/tests/jit/gh13772.phpt test failure?

Copy link
Author

segrax commented Aug 26, 2024

@segrax did you see ext/opcache/tests/jit/gh13772.phpt test failure?

Hey @dstogov,

Yeah, I did see the failure, it came from the change in the last recommendation, but it seems to have caused the test failure, So was waiting to hear back from @bwoebi before reverting it or making any further changes

It seems this change causes a test failure, but only in the debug builds (my local build is fine, but I also haven't checked what flags are being used in the debug pipeline builds).

EX(opline) is correctly set for nested JIT user code calls [ext/opcache/tests/jit/gh13772.phpt]

@devnexen devnexen removed their request for review August 26, 2024 20:20
Copy link
Member

dstogov commented Aug 27, 2024

OK. ping we when you'll need the next round of my review.

Copy link
Author

segrax commented Sep 15, 2024

Hey @dstogov, I've reverted that last change

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 shouldn't change anything for JIT without observer.

I don't care about observer.
@bwoebi if this is right for you please take care about merging and porting to master.

Copy link
Author

segrax commented Nov 8, 2024

Hey @dstogov and @bwoebi,

Just checking to see if there's been any progress on this,
Let me know if there’s anything I can help with to move things along

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Reviewers

@dstogov dstogov dstogov approved these changes

@iluuu1994 iluuu1994 Awaiting requested review from iluuu1994

@arnaud-lb arnaud-lb Awaiting requested review from arnaud-lb

@bukka bukka Awaiting requested review from bukka

Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /