Commit 48d2b45e074 for php.net

commit 48d2b45e0748a0a83eddc841cb493a0ae817159a
Author: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com>
Date:   Fri Sep 4 15:42:29 2026 +0200

    PFA: Fix UAF of inherited attribute argument value (#23258)

diff --git a/Zend/tests/partial_application/attributes_004.phpt b/Zend/tests/partial_application/attributes_004.phpt
new file mode 100644
index 00000000000..15e62b96df6
--- /dev/null
+++ b/Zend/tests/partial_application/attributes_004.phpt
@@ -0,0 +1,60 @@
+--TEST--
+PFA attribute UAF
+--CREDITS--
+Ryan @ Calif.io
+--FILE--
+<?php
+
+/* Reproduces with opcache.enable_cli=0. Not adding this to the INI section so
+ * that it's tested in all configurations. */
+
+#[NoDiscard(ND_MESSAGE)]
+function userTarget(mixed $value): mixed
+{
+    return $value;
+}
+
+define('ND_MESSAGE', str_repeat('message-', 32).time());
+
+function arguments(ReflectionFunction $function): array
+{
+    return $function->getAttributes(NoDiscard::class)[0]->getArguments();
+}
+
+$original = new ReflectionFunction('userTarget');
+var_dump(arguments($original));
+
+$partial = userTarget(?);
+echo "partial-created\n";
+var_dump(arguments($original));
+var_dump(arguments(new ReflectionFunction($partial)));
+
+unset($partial);
+gc_collect_cycles();
+echo "partial-destroyed\n";
+var_dump(arguments($original));
+
+userTarget('discarded');
+
+?>
+--EXPECTF--
+array(1) {
+  [0]=>
+  string(266) "message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-%d"
+}
+partial-created
+array(1) {
+  [0]=>
+  string(266) "message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-%d"
+}
+array(1) {
+  [0]=>
+  string(266) "message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-%d"
+}
+partial-destroyed
+array(1) {
+  [0]=>
+  string(266) "message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-%d"
+}
+
+Warning: The return value of function userTarget() should either be used or intentionally ignored by casting it as (void), message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-message-%d in %s on line %d
diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c
index de243bd7e65..a4cc4a4ce57 100644
--- a/Zend/zend_partial.c
+++ b/Zend/zend_partial.c
@@ -472,7 +472,9 @@ static zend_ast *zp_attribute_to_ast(zend_attribute *attribute)
 	if (attribute->argc) {
 		args_ast = zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST);
 		for (uint32_t i = 0; i < attribute->argc; i++) {
-			zend_ast *arg_ast = zend_ast_create_zval(&attribute->args[i].value);
+			zval *zv = &attribute->args[i].value;
+			Z_TRY_ADDREF_P(zv);
+			zend_ast *arg_ast = zend_ast_create_zval(zv);
 			if (attribute->args[i].name) {
 				arg_ast = zend_ast_create(ZEND_AST_NAMED_ARG,
 						zend_ast_create_zval_from_str(
@@ -561,6 +563,7 @@ static zend_ast *zp_compile_forwarding_call(
 			args_ast = zend_ast_list_add(args_ast, default_value_ast);
 		} else if (zp_is_const_arg(const_args, offset)) {
 			ZEND_ASSERT(Z_TYPE(argv[offset]) < IS_OBJECT);
+			ZEND_ASSERT(!Z_REFCOUNTED(argv[offset]));

 			/* This argument never changes, so we can burn it into the op_array
 			 * and check its type ahead of time. */