From 233ffccc354f2da7ec7f1df162208462b12bb1e2 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 12 Dec 2022 14:22:41 +0000 Subject: [PATCH 1/3] Fix GH-10072: PHP crashes when execute_ex is overridden and a __call trampoline is used from internal code --- Zend/tests/gh10072.phpt | 105 ++++++++++++++++++++++++++++++++++++++++ Zend/zend_vm_def.h | 6 ++- Zend/zend_vm_execute.h | 12 +++-- 3 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 Zend/tests/gh10072.phpt diff --git a/Zend/tests/gh10072.phpt b/Zend/tests/gh10072.phpt new file mode 100644 index 00000000000..2499ab0ca02 --- /dev/null +++ b/Zend/tests/gh10072.phpt @@ -0,0 +1,105 @@ +--TEST-- +GH-10072 (PHP crashes when execute_ex is overridden and a trampoline is used from internal code) +--EXTENSIONS-- +zend_test +--INI-- +zend_test.replace_zend_execute_ex=1 +--FILE-- +handle; + } + + + public function stream_close(): void + { + } + + public function stream_open(string $path, string $mode, int $options = 0, ?string &$openedPath = null): bool + { + return true; + } + + + public function stream_read(int $count) + { + return 0; + } + + + public function stream_seek(int $offset, int $whence = SEEK_SET): bool + { + return true; + } + + + public function stream_set_option(int $option, int $arg1, ?int $arg2): bool + { + return false; + } + + + public function stream_stat() + { + return []; + } + + + public function stream_tell() + { + return []; + } + + + public function stream_truncate(int $newSize): bool + { + return true; + } + + + public function stream_write(string $data) + { + } + + + public function unlink(string $path): bool + { + return false; + } +} + +class TrampolineTest { + /** @var resource|null */ + public $context; + + /** @var object|null */ + private $wrapper; + + public function __call(string $name, array $arguments) { + if (!$this->wrapper) { + $this->wrapper = new DummyStreamWrapper(); + } + echo 'Trampoline for ', $name, PHP_EOL; + return $this->wrapper->$name(...$arguments); + } + +} + +stream_wrapper_register('custom', TrampolineTest::class); + + +$fp = fopen("custom://myvar", "r+"); +?> +--EXPECT-- +Trampoline for stream_open +Trampoline for stream_close diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index eb40d302306..d869116c33e 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8663,7 +8663,9 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY, SPEC(OBSERVER)) SAVE_OPLINE_EX(); ZEND_OBSERVER_FCALL_BEGIN(execute_data); execute_data = EX(prev_execute_data); - LOAD_OPLINE(); + if (execute_data) { + LOAD_OPLINE(); + } ZEND_ADD_CALL_FLAG(call, ZEND_CALL_TOP); zend_execute_ex(call); } @@ -8713,7 +8715,7 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY, SPEC(OBSERVER)) execute_data = EG(current_execute_data); - if (!EX(func) || !ZEND_USER_CODE(EX(func)->type) || (call_info & ZEND_CALL_TOP)) { + if (!execute_data || !EX(func) || !ZEND_USER_CODE(EX(func)->type) || (call_info & ZEND_CALL_TOP)) { ZEND_VM_RETURN(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a0b4efd7372..c22a1a104e9 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3319,7 +3319,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CALL_TRAMPOLINE_SPEC_HANDLER(Z SAVE_OPLINE_EX(); execute_data = EX(prev_execute_data); - LOAD_OPLINE(); + if (execute_data) { + LOAD_OPLINE(); + } ZEND_ADD_CALL_FLAG(call, ZEND_CALL_TOP); zend_execute_ex(call); } @@ -3369,7 +3371,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CALL_TRAMPOLINE_SPEC_HANDLER(Z execute_data = EG(current_execute_data); - if (!EX(func) || !ZEND_USER_CODE(EX(func)->type) || (call_info & ZEND_CALL_TOP)) { + if (!execute_data || !EX(func) || !ZEND_USER_CODE(EX(func)->type) || (call_info & ZEND_CALL_TOP)) { ZEND_VM_RETURN(); } @@ -3456,7 +3458,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CALL_TRAMPOLINE_SPEC_OBSERVER_ SAVE_OPLINE_EX(); zend_observer_fcall_begin(execute_data); execute_data = EX(prev_execute_data); - LOAD_OPLINE(); + if (execute_data) { + LOAD_OPLINE(); + } ZEND_ADD_CALL_FLAG(call, ZEND_CALL_TOP); zend_execute_ex(call); } @@ -3506,7 +3510,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CALL_TRAMPOLINE_SPEC_OBSERVER_ execute_data = EG(current_execute_data); - if (!EX(func) || !ZEND_USER_CODE(EX(func)->type) || (call_info & ZEND_CALL_TOP)) { + if (!execute_data || !EX(func) || !ZEND_USER_CODE(EX(func)->type) || (call_info & ZEND_CALL_TOP)) { ZEND_VM_RETURN(); } From b489e0f2b847f21cdd8decbd3c1c9e83d0c61c1e Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 12 Dec 2022 15:29:48 +0000 Subject: [PATCH 2/3] Make sure to disable JIT when overriding execute_ex --- Zend/tests/gh10072.phpt | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/tests/gh10072.phpt b/Zend/tests/gh10072.phpt index 2499ab0ca02..95a0d434505 100644 --- a/Zend/tests/gh10072.phpt +++ b/Zend/tests/gh10072.phpt @@ -4,6 +4,7 @@ GH-10072 (PHP crashes when execute_ex is overridden and a trampoline is used fro zend_test --INI-- zend_test.replace_zend_execute_ex=1 +opcache.jit=disable --FILE-- Date: Tue, 13 Dec 2022 07:30:21 +0000 Subject: [PATCH 3/3] Add secondary test that registers a trampoline as a shutdown function --- Zend/tests/gh10072-2.phpt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Zend/tests/gh10072-2.phpt diff --git a/Zend/tests/gh10072-2.phpt b/Zend/tests/gh10072-2.phpt new file mode 100644 index 00000000000..3de75a2b6c0 --- /dev/null +++ b/Zend/tests/gh10072-2.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-10072 (PHP crashes when execute_ex is overridden and a trampoline is used from internal code during shutdown) +--EXTENSIONS-- +zend_test +--INI-- +zend_test.replace_zend_execute_ex=1 +opcache.jit=disable +--FILE-- + +--EXPECT-- +Trampoline for shutdown