From da6767d917a21ed407eaddcb79bb2a2a111b5855 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 5 Feb 2025 22:13:25 +0100 Subject: [PATCH 1/4] Fix infinite recursion on deprecated attribute evaluation Fixes GH-17711 Fixes GH-18022 --- .../deprecated/class_constants/gh17711.phpt | 18 ++++++++++++++ Zend/zend_constants.c | 4 +++- Zend/zend_constants.h | 15 ++++++++++++ Zend/zend_vm_def.h | 4 +++- Zend/zend_vm_execute.h | 24 ++++++++++++++----- 5 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 Zend/tests/attributes/deprecated/class_constants/gh17711.phpt diff --git a/Zend/tests/attributes/deprecated/class_constants/gh17711.phpt b/Zend/tests/attributes/deprecated/class_constants/gh17711.phpt new file mode 100644 index 0000000000000..f37e8c90b92f8 --- /dev/null +++ b/Zend/tests/attributes/deprecated/class_constants/gh17711.phpt @@ -0,0 +1,18 @@ +--TEST-- +GH-17711: Infinite recursion through deprecated class constants self-referencing through deprecation message +--FILE-- + +--EXPECTF-- +Deprecated: Constant C::C is deprecated, Message in %s on line %d +string(7) "Message" diff --git a/Zend/zend_constants.c b/Zend/zend_constants.c index d453b8bb73717..2145cb1915354 100644 --- a/Zend/zend_constants.c +++ b/Zend/zend_constants.c @@ -353,8 +353,10 @@ ZEND_API zval *zend_get_class_constant_ex(zend_string *class_name, zend_string * } if (UNEXPECTED(ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED)) { - if ((flags & ZEND_FETCH_CLASS_SILENT) == 0) { + if ((flags & ZEND_FETCH_CLASS_SILENT) == 0 && !CONST_IS_RECURSIVE(c)) { + CONST_PROTECT_RECURSION(c); zend_deprecated_class_constant(c, constant_name); + CONST_UNPROTECT_RECURSION(c); if (EG(exception)) { goto failure; } diff --git a/Zend/zend_constants.h b/Zend/zend_constants.h index 6f0710c0ce63e..cef17b3a5089e 100644 --- a/Zend/zend_constants.h +++ b/Zend/zend_constants.h @@ -27,6 +27,21 @@ #define CONST_NO_FILE_CACHE (1<<1) /* Can't be saved in file cache */ #define CONST_DEPRECATED (1<<2) /* Deprecated */ #define CONST_OWNED (1<<3) /* constant should be destroyed together with class */ +#define CONST_RECURSIVE (1<<4) /* Recursion protection for constant evaluation */ + +#define CONST_IS_RECURSIVE(c) (Z_CONSTANT_FLAGS((c)->value) & CONST_RECURSIVE) +#define CONST_PROTECT_RECURSION(c) \ + do { \ + if (Z_TYPE((c)->value) == IS_CONSTANT_AST) { \ + Z_CONSTANT_FLAGS((c)->value) |= CONST_RECURSIVE; \ + } \ + } while (0) +#define CONST_UNPROTECT_RECURSION(c) \ + do { \ + if (Z_TYPE((c)->value) == IS_CONSTANT_AST) { \ + Z_CONSTANT_FLAGS((c)->value) &= ~CONST_RECURSIVE; \ + } \ + } while (0) #define PHP_USER_CONSTANT 0x7fffff /* a constant defined in user space */ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index a4fa8063a5bd4..37e0e0fb43d95 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6094,8 +6094,10 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO } bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED; - if (UNEXPECTED(is_constant_deprecated)) { + if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) { + CONST_PROTECT_RECURSION(c); zend_deprecated_class_constant(c, constant_name); + CONST_UNPROTECT_RECURSION(c); if (EG(exception)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 2f6f0af2d872c..5270fc841cd8d 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7615,8 +7615,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_CONS } bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED; - if (UNEXPECTED(is_constant_deprecated)) { + if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) { + CONST_PROTECT_RECURSION(c); zend_deprecated_class_constant(c, constant_name); + CONST_UNPROTECT_RECURSION(c); if (EG(exception)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); @@ -8775,8 +8777,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_CONS } bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED; - if (UNEXPECTED(is_constant_deprecated)) { + if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) { + CONST_PROTECT_RECURSION(c); zend_deprecated_class_constant(c, constant_name); + CONST_UNPROTECT_RECURSION(c); if (EG(exception)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); @@ -25874,8 +25878,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_ } bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED; - if (UNEXPECTED(is_constant_deprecated)) { + if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) { + CONST_PROTECT_RECURSION(c); zend_deprecated_class_constant(c, constant_name); + CONST_UNPROTECT_RECURSION(c); if (EG(exception)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); @@ -26443,8 +26449,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_ } bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED; - if (UNEXPECTED(is_constant_deprecated)) { + if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) { + CONST_PROTECT_RECURSION(c); zend_deprecated_class_constant(c, constant_name); + CONST_UNPROTECT_RECURSION(c); if (EG(exception)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); @@ -35282,8 +35290,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUS } bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED; - if (UNEXPECTED(is_constant_deprecated)) { + if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) { + CONST_PROTECT_RECURSION(c); zend_deprecated_class_constant(c, constant_name); + CONST_UNPROTECT_RECURSION(c); if (EG(exception)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); @@ -35641,8 +35651,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUS } bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED; - if (UNEXPECTED(is_constant_deprecated)) { + if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) { + CONST_PROTECT_RECURSION(c); zend_deprecated_class_constant(c, constant_name); + CONST_UNPROTECT_RECURSION(c); if (EG(exception)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); From 2d188032d89f51de1837d3bafa53d970377d3f88 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 25 Mar 2025 18:18:57 +0100 Subject: [PATCH 2/4] Fix another indirect recursive case --- .../deprecated/class_constants/gh17711.phpt | 14 ++++++++++++-- Zend/zend_API.c | 2 +- Zend/zend_compile.c | 4 ++++ Zend/zend_constants.h | 8 ++------ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Zend/tests/attributes/deprecated/class_constants/gh17711.phpt b/Zend/tests/attributes/deprecated/class_constants/gh17711.phpt index f37e8c90b92f8..abec209343ab3 100644 --- a/Zend/tests/attributes/deprecated/class_constants/gh17711.phpt +++ b/Zend/tests/attributes/deprecated/class_constants/gh17711.phpt @@ -3,16 +3,26 @@ GH-17711: Infinite recursion through deprecated class constants self-referencing --FILE-- --EXPECTF-- Deprecated: Constant C::C is deprecated, Message in %s on line %d string(7) "Message" + +Deprecated: Constant D::C is deprecated, test in %s on line %d +string(4) "test" diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 5aac3c1f7d77c..6e254561d3745 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1439,7 +1439,7 @@ ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_ ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&class_type->constants_table, key, c) { if (c->ce == class_type) { - if (Z_TYPE(c->value) == IS_CONSTANT_AST) { + if (Z_TYPE(c->value) == IS_CONSTANT_AST || (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED)) { new_c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant)); memcpy(new_c, c, sizeof(zend_class_constant)); c = new_c; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 832dedc421042..195c65d504374 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8822,6 +8822,10 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as if (deprecated) { ZEND_CLASS_CONST_FLAGS(c) |= ZEND_ACC_DEPRECATED; + /* For deprecated constants, we need to flag the zval for recursion + * detection. Make sure the zval is separated out of shm. */ + ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS; + ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; } } } diff --git a/Zend/zend_constants.h b/Zend/zend_constants.h index cef17b3a5089e..bd759c2891500 100644 --- a/Zend/zend_constants.h +++ b/Zend/zend_constants.h @@ -32,15 +32,11 @@ #define CONST_IS_RECURSIVE(c) (Z_CONSTANT_FLAGS((c)->value) & CONST_RECURSIVE) #define CONST_PROTECT_RECURSION(c) \ do { \ - if (Z_TYPE((c)->value) == IS_CONSTANT_AST) { \ - Z_CONSTANT_FLAGS((c)->value) |= CONST_RECURSIVE; \ - } \ + Z_CONSTANT_FLAGS((c)->value) |= CONST_RECURSIVE; \ } while (0) #define CONST_UNPROTECT_RECURSION(c) \ do { \ - if (Z_TYPE((c)->value) == IS_CONSTANT_AST) { \ - Z_CONSTANT_FLAGS((c)->value) &= ~CONST_RECURSIVE; \ - } \ + Z_CONSTANT_FLAGS((c)->value) &= ~CONST_RECURSIVE; \ } while (0) #define PHP_USER_CONSTANT 0x7fffff /* a constant defined in user space */ From c07640d9d47c102cbca6658af68e5fe5bb23c582 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 25 Mar 2025 18:27:33 +0100 Subject: [PATCH 3/4] Also ensure shm separation with preloading --- ext/opcache/ZendAccelerator.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 459449d85f23c..8c2b5fb170b9d 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3800,6 +3800,11 @@ static bool preload_try_resolve_constants(zend_class_entry *ce) ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->constants_table, key, c) { val = &c->value; if (Z_TYPE_P(val) == IS_CONSTANT_AST) { + /* For deprecated constants, we need to flag the zval for recursion + * detection. Make sure the zval is separated out of shm. */ + if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED) { + ok = true; + } if (EXPECTED(zend_update_class_constant(c, key, c->ce) == SUCCESS)) { was_changed = changed = true; } else { From a90bdc4c2b0dffdf9c50e7a9919169b8ea8169dd Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 26 Mar 2025 17:17:20 +0100 Subject: [PATCH 4/4] Fix typo --- ext/opcache/ZendAccelerator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 8c2b5fb170b9d..f71f43b330ffa 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3803,7 +3803,7 @@ static bool preload_try_resolve_constants(zend_class_entry *ce) /* For deprecated constants, we need to flag the zval for recursion * detection. Make sure the zval is separated out of shm. */ if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED) { - ok = true; + ok = false; } if (EXPECTED(zend_update_class_constant(c, key, c->ce) == SUCCESS)) { was_changed = changed = true;