Skip to content
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

In some specific cases SWITCH with one default statement will cause segfault #11245

Closed
slawomir-pryczek opened this issue May 15, 2023 · 4 comments · Fixed by ThePHPF/thephp.foundation#90

Comments

@slawomir-pryczek
Copy link

slawomir-pryczek commented May 15, 2023

Description

The following code:

crash.php

<?php	
	$a = "0";	
	//$b = "1";
	//$c = "2";

	function xx() {return "somegarbage";}
	switch (xx()) {	
		default:	
			if (!empty($xx)) {return;}	
	}
?>

Resulted in this output:

[Mon May 15 19:04:11 2023] php-fpm[2966681]: segfault at 7ff038602ce800 ip 0000562ae61c48c9 sp 00007ffff9523b08 error 4 in php-fpm[562ae6000000+2f0000] likely on CPU 2 (core 2, socket 0)
[Mon May 15 19:04:11 2023] Code: 0f 85 31 8c e5 ff 48 8b 47 10 48 8b 57 18 48 83 c0 38 48 39 d0 48 89 47 10 48 0f 43 d0 48 8b 47 50 48 89 57 18 48 85 c0 74 0f <48> 8b 10 48 89 57 50 c3 0f 1f 80 00 00 00 00 be 06 00 00 00 e9 4e

Hi Guys, i found something totally weird. Basically when you put this code on the server it'll cause php to segfault each time it is executed. The parameter of switch needs to be function call, you need one default branch and you need the if empty and you also need return. Now the weird part is that for the variables $a, $b, $c it DEPENDS on the URL it seems. When i put the file on /a/b/crash.php i need to keep $a, $b, $c... if i put it in the root dir of the website i need to keep only $a.

Segfaults each time after you get the correct number of variables prior to switch.

  1. If i add case "x": it'll start working correctly and i'm no longer able to make this code segfault. So it may be that single case switch is corrupting memory?
  2. If i replace "default:" -> "case 'somegarbage':" which should be exactly the same code i'm no longer able to make this segfault

Also:
[xx@xxxxx]$ php -f ping_pp.php
Segmentation fault (core dumped)
[5356978.846558] traps: php[2973761] general protection fault ip:55c832a10f17 sp:7ffc3e0f4058 error:0 in php[55c832800000+2e7000]

Attaching core dump:
Stack trace of thread 2973761:
#0 0x000055c832a10f17 ZEND_FREE_SPEC_TMPVAR_HANDLER (php + 0x410f17)
#1 0x000055c832a5b1b9 execute_ex (php + 0x45b1b9)
#2 0x000055c832a64899 zend_execute (php + 0x464899)
#3 0x000055c8329f3c80 zend_execute_scripts (php + 0x3f3c80)
#4 0x000055c83298ea8a php_execute_script (php + 0x38ea8a)
#5 0x000055c832addfdd do_cli (php + 0x4ddfdd)
#6 0x000055c83283dcd3 main (php + 0x23dcd3)
#7 0x00007ff89bff2510 __libc_start_call_main (libc.so.6 + 0x27510)
#8 0x00007ff89bff25c9 __libc_start_main@@GLIBC_2.34 (libc.so.6 + 0x275c9)
#9 0x000055c83283de25 _start (php + 0x23de25)
ELF object binary architecture: AMD x86-64

PHP Version

PHP Version 8.1.18

Operating System

6.1.9-200.fc37.x86_64 (Fedora Core 37)

@iluuu1994
Copy link
Member

It's an optimizer issue.

In function ::{main} (before dfa):
var op1 of 4 (ZEND_FREE) does not use/def an ssa var

@slawomir-pryczek
Copy link
Author

Thanks for the comment. Any workaround for now, or to fix that - opcache needs to be disabled (or maybe I can update php to recent version)?

@iluuu1994
Copy link
Member

@slawomir-pryczek PHP 8.2 suffers from the same issue. You can disable SSA based optimization with opcache.optimization_level=0x7FFEBFDF, which will be significantly better than completely disabling opcache.

@nielsdos
Copy link
Member

nielsdos commented May 16, 2023

Its already broken before SSA is constructed:

L0002 0000 ASSIGN CV0($a) string("0")
L0009 0001 T5 = ISSET_ISEMPTY_CV (empty) CV1($xx)
L0009 0002 JMPNZ T5 0004
L0009 0003 RETURN null
L0009 0004 FREE V3
L0012 0005 RETURN int(1)

Free's V3, but V3 is nowhere to be found. Dumping the opcodes before the optimisation pipeline runs shows that V3 used to be the call result of xx().
Worth noting: the call is removed in zend_optimize_func_calls() which replaces V3 by a QM_ASSIGN. The breakage seems to be in zend_optimize_cfg(). Before zend_optimize_cfg() runs the opcodes look fine, but after it ran there's a FREE V3 without an assignment to V3.
When ZEND_FREE is eliminated (by the /* V = OP, FREE(V) => OP. NOP */ case), the source opline is removed; but note that the removal only happens if the source still exists; so when multiple FREEs exist for a single source, this breaks. You can see this is what happens by modifying the code to also NOP the FREE opline if the source is already gone.

nielsdos added a commit to nielsdos/php-src that referenced this issue May 16, 2023
…ment will cause segfault)

The block optimizer pass allows the use of sources of the preceding
block if the block is a follower and not a target. This causes issues
when trying to remove FREE instructions: if the source is not in the
block of the FREE, then the FREE and source are still removed. Therefore
the other successor blocks, which must consume or FREE the temporary,
will still contain the FREE opline. This opline will now refer to a
temporary that doesn't exist anymore, which most of the time results in
a crash. For these kind of non-local scenarios, we'll let the SSA
based optimizations handle those cases.
nielsdos added a commit that referenced this issue May 22, 2023
* PHP-8.1:
  Fix GH-11245 (In some specific cases SWITCH with one default statement will cause segfault)
nielsdos added a commit that referenced this issue May 22, 2023
* PHP-8.2:
  Fix GH-11245 (In some specific cases SWITCH with one default statement will cause segfault)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
@iluuu1994 @slawomir-pryczek @nielsdos and others