From a480965c90b4f4948f211c8b139788371e031838 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Wed, 17 Jun 2026 04:48:17 +0800 Subject: [PATCH 1/2] ext/opcache: unwrap reference wrappers in typed by-value returns (#21973) --- NEWS | 2 ++ Zend/zend_vm_def.h | 5 +++- Zend/zend_vm_execute.h | 25 +++++++++++++++---- ext/opcache/tests/opt/gh21972.phpt | 39 ++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 ext/opcache/tests/opt/gh21972.phpt diff --git a/NEWS b/NEWS index 05bdd1b35ff7..afb45fca45ca 100644 --- a/NEWS +++ b/NEWS @@ -93,6 +93,8 @@ PHP NEWS . Fix persistent free of non-persistent connect_attr key (David Carlier). - Opcache: + . Fixed bug GH-21972 (Corrupted variable type when a typed by-value return + contains a reference wrapper). (Weilin Du) . Fixed tracing JIT crash when a VM interrupt is handled during an observed user function call. (Levi Morrison) . Fixed bug GH-22004 (Assertion failure at ext/opcache/jit/zend_jit_trace.c). diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 34906e1bfcca..c46b17101546 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4387,7 +4387,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV ZVAL_DEREF(retval_ptr); } - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ref)))) { ZEND_VM_NEXT_OPCODE(); } @@ -4417,6 +4417,9 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV } retval_ptr = retval_ref; } + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } } SAVE_OPLINE(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index d4b2aff1907b..f4f35ce2a8a9 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -10772,7 +10772,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP ZVAL_DEREF(retval_ptr); } - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ref)))) { ZEND_VM_NEXT_OPCODE(); } @@ -10802,6 +10802,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP } retval_ptr = retval_ref; } + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } } SAVE_OPLINE(); @@ -21517,7 +21520,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN ZVAL_DEREF(retval_ptr); } - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ref)))) { ZEND_VM_NEXT_OPCODE(); } @@ -21547,6 +21550,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN } retval_ptr = retval_ref; } + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } } SAVE_OPLINE(); @@ -30016,7 +30022,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN ZVAL_DEREF(retval_ptr); } - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ref)))) { ZEND_VM_NEXT_OPCODE(); } @@ -30046,6 +30052,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN } retval_ptr = retval_ref; } + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } } SAVE_OPLINE(); @@ -37830,7 +37839,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED ZVAL_DEREF(retval_ptr); } - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ref)))) { ZEND_VM_NEXT_OPCODE(); } @@ -37860,6 +37869,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED } retval_ptr = retval_ref; } + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } } SAVE_OPLINE(); @@ -50642,7 +50654,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU ZVAL_DEREF(retval_ptr); } - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ref)))) { ZEND_VM_NEXT_OPCODE(); } @@ -50672,6 +50684,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU } retval_ptr = retval_ref; } + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } } SAVE_OPLINE(); diff --git a/ext/opcache/tests/opt/gh21972.phpt b/ext/opcache/tests/opt/gh21972.phpt new file mode 100644 index 000000000000..f34f9b45ccf2 --- /dev/null +++ b/ext/opcache/tests/opt/gh21972.phpt @@ -0,0 +1,39 @@ +--TEST-- +GH-21972: Typed by-value return must not leak reference wrapper +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECT-- +string(6) "string" +string(5) "false" +string(5) "false" From f0450fa46be9e2ca8ff82ff276d2d64fdc555a31 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Tue, 16 Jun 2026 15:01:55 -0400 Subject: [PATCH 2/2] Fix freeing uninitialized memory in LDAP sort control parsing _php_ldap_control_from_array() allocated the sort_keys array with safe_emalloc() and only wrote its NULL terminator after the per-key loop finished. A sort key missing the "attr" entry makes the loop bail out early, leaving the array partially uninitialized; the failure cleanup then walks it as a NULL-terminated list and calls efree() on the uninitialized slots. Allocate the array zeroed with ecalloc() so the unwritten slots are NULL. Reachable from userland via the $controls argument of ldap_search() and the other control-taking LDAP functions. Closes GH-22342 --- ext/ldap/ldap.c | 2 +- .../tests/ldap_sort_control_missing_attr.phpt | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 ext/ldap/tests/ldap_sort_control_missing_attr.phpt diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index e6ea4d908af5..eec471995e4b 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -563,7 +563,7 @@ static int _php_ldap_control_from_array(LDAP *ld, LDAPControl** ctrl, zval* arra zval *sortkey, *tmp; num_keys = zend_hash_num_elements(Z_ARRVAL_P(val)); - sort_keys = safe_emalloc((num_keys+1), sizeof(LDAPSortKey*), 0); + sort_keys = ecalloc((num_keys+1), sizeof(LDAPSortKey*)); tmpstrings1 = safe_emalloc(num_keys, sizeof(zend_string*), 0); tmpstrings2 = safe_emalloc(num_keys, sizeof(zend_string*), 0); num_tmpstrings1 = 0; diff --git a/ext/ldap/tests/ldap_sort_control_missing_attr.phpt b/ext/ldap/tests/ldap_sort_control_missing_attr.phpt new file mode 100644 index 000000000000..0d6c3921f008 --- /dev/null +++ b/ext/ldap/tests/ldap_sort_control_missing_attr.phpt @@ -0,0 +1,30 @@ +--TEST-- +ldap_search(): malformed sort control (sort key missing "attr") must not free uninitialized memory +--EXTENSIONS-- +ldap +--FILE-- + LDAP_CONTROL_SORTREQUEST, + 'value' => [ + ['attr' => 'cn'], + ['reverse' => true], + ], + ], + ]); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "ok\n"; +?> +--EXPECT-- +ldap_search(): Sort key list must have an "attr" key +ok