From 1946f6bae5162578d9155cdc0f1d281179d06326 Mon Sep 17 00:00:00 2001 From: lazerg Date: Mon, 26 Oct 2026 09:11:30 +0500 Subject: [PATCH 1/2] Fix GH-24712: Use-after-free in foreach by reference with destructor --- NEWS | 2 ++ Zend/tests/gh24712.phpt | 15 +++++++++++++++ Zend/zend_vm_def.h | 4 +++- Zend/zend_vm_execute.h | 4 +++- 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/gh24712.phpt diff --git a/NEWS b/NEWS index 5ea9ecbafb0b..72d7d5158452 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,8 @@ PHP NEWS for grouping. (Klaas Skelte van der Werf) . Fixed bug GH-24265 (Unwind/GracefulExit discarded in zend_fiber_execute()). (David Carlier) + . Fixed bug GH-24712 (Use-after-free in foreach by reference when destructor + modifies the loop variable). (Lazizbek Ergashev) - Date: . Fixed bug GH-20503 (Assertion failure with ext/date DateInterval property diff --git a/Zend/tests/gh24712.phpt b/Zend/tests/gh24712.phpt new file mode 100644 index 000000000000..9206b9bb975c --- /dev/null +++ b/Zend/tests/gh24712.phpt @@ -0,0 +1,15 @@ +--TEST-- +GH-24712 (Use-after-free in foreach by reference when destructor modifies the loop variable) +--FILE-- + +--EXPECT-- +NULL diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index df255c5a868f..0e0ccd5493ad 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -7402,11 +7402,13 @@ ZEND_VM_C_LABEL(fe_fetch_w_exit): zval *variable_ptr = EX_VAR(opline->op2.var); if (EXPECTED(variable_ptr != value)) { zend_reference *ref; + zval garbage; ref = Z_REF_P(value); GC_ADDREF(ref); - i_zval_ptr_dtor(variable_ptr); + ZVAL_COPY_VALUE(&garbage, variable_ptr); ZVAL_REF(variable_ptr, ref); + i_zval_ptr_dtor(&garbage); } } else { Z_ADDREF_P(value); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 4b313f20cd39..134d18ffcca8 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -23317,11 +23317,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_FETCH_RW_SPEC_VAR_HANDLER(Z zval *variable_ptr = EX_VAR(opline->op2.var); if (EXPECTED(variable_ptr != value)) { zend_reference *ref; + zval garbage; ref = Z_REF_P(value); GC_ADDREF(ref); - i_zval_ptr_dtor(variable_ptr); + ZVAL_COPY_VALUE(&garbage, variable_ptr); ZVAL_REF(variable_ptr, ref); + i_zval_ptr_dtor(&garbage); } } else { Z_ADDREF_P(value); From 0561ddf46c39f79a7901c772ec9a0cedf10ae4aa Mon Sep 17 00:00:00 2001 From: lazerg Date: Mon, 26 Oct 2026 21:09:15 +0500 Subject: [PATCH 2/2] Fix GH-24712: Bind static variables before releasing the old value --- Zend/tests/gh24712_2.phpt | 27 +++++++++++++++++++++++++++ Zend/zend_vm_def.h | 15 +++++++++++---- Zend/zend_vm_execute.h | 15 +++++++++++---- 3 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 Zend/tests/gh24712_2.phpt diff --git a/Zend/tests/gh24712_2.phpt b/Zend/tests/gh24712_2.phpt new file mode 100644 index 000000000000..bc76f17c7711 --- /dev/null +++ b/Zend/tests/gh24712_2.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-24712 (Use-after-free in static variable binding when destructor modifies the variable) +--FILE-- +var] = null; + } +} +function init() { + return 1; +} +$a = new C('a'); +static $a; +var_dump($a); + +for ($i = 0; $i < 2; $i++) { + unset($b); + $b = $i ? new C('b') : 0; + static $b = init(); +} +var_dump($b); +?> +--EXPECT-- +NULL +NULL diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0e0ccd5493ad..56dbd802c8ae 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9058,6 +9058,7 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, ANY, REF) HashTable *ht; zval *value; zval *variable_ptr; + zval garbage; variable_ptr = GET_OP1_ZVAL_PTR_PTR_UNDEF(BP_VAR_W); @@ -9087,19 +9088,22 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, ANY, REF) ref->sources.ptr = NULL; Z_REF_P(value) = ref; Z_TYPE_INFO_P(value) = IS_REFERENCE_EX; - i_zval_ptr_dtor(variable_ptr); + ZVAL_COPY_VALUE(&garbage, variable_ptr); ZVAL_REF(variable_ptr, ref); + i_zval_ptr_dtor(&garbage); } else { Z_ADDREF_P(value); - i_zval_ptr_dtor(variable_ptr); + ZVAL_COPY_VALUE(&garbage, variable_ptr); ZVAL_REF(variable_ptr, Z_REF_P(value)); + i_zval_ptr_dtor(&garbage); if (OP2_TYPE != IS_UNUSED) { FREE_OP2(); } } } else { - i_zval_ptr_dtor(variable_ptr); + ZVAL_COPY_VALUE(&garbage, variable_ptr); ZVAL_COPY(variable_ptr, value); + i_zval_ptr_dtor(&garbage); } ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -9124,11 +9128,14 @@ ZEND_VM_HANDLER(203, ZEND_BIND_INIT_STATIC_OR_JMP, CV, JMP_ADDR) if (Z_TYPE_P(value) == IS_NULL) { ZEND_VM_NEXT_OPCODE(); } else { + zval garbage; + SAVE_OPLINE(); - zval_ptr_dtor(variable_ptr); ZEND_ASSERT(Z_TYPE_P(value) == IS_REFERENCE); Z_ADDREF_P(value); + ZVAL_COPY_VALUE(&garbage, variable_ptr); ZVAL_REF(variable_ptr, Z_REF_P(value)); + zval_ptr_dtor(&garbage); ZEND_VM_JMP_EX(OP_JMP_ADDR(opline, opline->op2), 1); } } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 134d18ffcca8..00f94f0ce8a2 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -41858,6 +41858,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_HANDLER(ZE HashTable *ht; zval *value; zval *variable_ptr; + zval garbage; variable_ptr = EX_VAR(opline->op1.var); @@ -41887,19 +41888,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_HANDLER(ZE ref->sources.ptr = NULL; Z_REF_P(value) = ref; Z_TYPE_INFO_P(value) = IS_REFERENCE_EX; - i_zval_ptr_dtor(variable_ptr); + ZVAL_COPY_VALUE(&garbage, variable_ptr); ZVAL_REF(variable_ptr, ref); + i_zval_ptr_dtor(&garbage); } else { Z_ADDREF_P(value); - i_zval_ptr_dtor(variable_ptr); + ZVAL_COPY_VALUE(&garbage, variable_ptr); ZVAL_REF(variable_ptr, Z_REF_P(value)); + i_zval_ptr_dtor(&garbage); if (opline->op2_type != IS_UNUSED) { FREE_OP(opline->op2_type, opline->op2.var); } } } else { - i_zval_ptr_dtor(variable_ptr); + ZVAL_COPY_VALUE(&garbage, variable_ptr); ZVAL_COPY(variable_ptr, value); + i_zval_ptr_dtor(&garbage); } ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -41924,11 +41928,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_INIT_STATIC_OR_JMP_SPEC_C if (Z_TYPE_P(value) == IS_NULL) { ZEND_VM_NEXT_OPCODE(); } else { + zval garbage; + SAVE_OPLINE(); - zval_ptr_dtor(variable_ptr); ZEND_ASSERT(Z_TYPE_P(value) == IS_REFERENCE); Z_ADDREF_P(value); + ZVAL_COPY_VALUE(&garbage, variable_ptr); ZVAL_REF(variable_ptr, Z_REF_P(value)); + zval_ptr_dtor(&garbage); ZEND_VM_JMP_EX(OP_JMP_ADDR(opline, opline->op2), 1); } }