diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index fa8c33f863731..eda6ed5eb7a26 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -14,6 +14,12 @@ PHP 8.5 INTERNALS UPGRADE NOTES 1. Internal API changes ======================== +- Zend + . Added zend_safe_assign_to_variable_noref() function to safely assign + a value to a non-reference zval. + . Added zval_ptr_safe_dtor() to safely destroy a zval when a destructor + could interfere. + ======================== 2. Build system changes ======================== diff --git a/Zend/tests/weakrefs/gh17442_1.phpt b/Zend/tests/weakrefs/gh17442_1.phpt new file mode 100644 index 0000000000000..fc7f60174ed9e --- /dev/null +++ b/Zend/tests/weakrefs/gh17442_1.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-17442 (Engine UAF with reference assign and dtor) - untyped +--CREDITS-- +YuanchengJiang +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Exception: Test in %s:%d +Stack trace: +#0 [internal function]: class@anonymous->__destruct() +#1 %s(%d): headers_sent(NULL, 0) +#2 {main} + thrown in %s on line %d diff --git a/Zend/tests/weakrefs/gh17442_2.phpt b/Zend/tests/weakrefs/gh17442_2.phpt new file mode 100644 index 0000000000000..296a23a651383 --- /dev/null +++ b/Zend/tests/weakrefs/gh17442_2.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-17442 (Engine UAF with reference assign and dtor) - typed +--CREDITS-- +YuanchengJiang +nielsdos +--FILE-- +obj = new stdClass; + +$map[$test->obj] = new class { + function __destruct() { + global $test; + var_dump($test->obj); + throw new Exception("Test"); + } +}; + +headers_sent($test->obj); +?> +--EXPECTF-- +string(0) "" + +Fatal error: Uncaught Exception: Test in %s:%d +Stack trace: +#0 [internal function]: class@anonymous->__destruct() +#1 %s(%d): headers_sent('') +#2 {main} + thrown in %s on line %d diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 8c239a952f32b..90bb9370e1fdd 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -4666,8 +4666,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_ex(zend_reference *ref, zval *val zval_ptr_dtor(val); return FAILURE; } else { - zval_ptr_dtor(&ref->val); - ZVAL_COPY_VALUE(&ref->val, val); + zend_safe_assign_to_variable_noref(&ref->val, val); return SUCCESS; } } diff --git a/Zend/zend_API.h b/Zend/zend_API.h index d91da91bf299e..6aeffce25d8e5 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -1107,7 +1107,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_NULL(_zv); \ } while (0) @@ -1129,7 +1129,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_FALSE(_zv); \ } while (0) @@ -1151,7 +1151,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_TRUE(_zv); \ } while (0) @@ -1173,7 +1173,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_BOOL(_zv, bval); \ } while (0) @@ -1195,7 +1195,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_LONG(_zv, lval); \ } while (0) @@ -1217,7 +1217,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_DOUBLE(_zv, dval); \ } while (0) @@ -1239,7 +1239,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_EMPTY_STRING(_zv); \ } while (0) @@ -1261,7 +1261,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_STR(_zv, str); \ } while (0) @@ -1283,7 +1283,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_NEW_STR(_zv, str); \ } while (0) @@ -1305,7 +1305,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_STRING(_zv, string); \ } while (0) @@ -1327,7 +1327,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_STRINGL(_zv, string, len); \ } while (0) @@ -1349,7 +1349,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_ARR(_zv, arr); \ } while (0) @@ -1371,7 +1371,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_RES(_zv, res); \ } while (0) @@ -1393,7 +1393,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_COPY_VALUE(_zv, other_zv); \ } while (0) @@ -1415,7 +1415,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_COPY_VALUE(_zv, other_zv); \ } while (0) @@ -1447,7 +1447,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval } \ _zv = &ref->val; \ } \ - zval_ptr_dtor(_zv); \ + zval_ptr_safe_dtor(_zv); \ ZVAL_COPY_VALUE(_zv, other_zv); \ } while (0) @@ -1485,10 +1485,7 @@ static zend_always_inline zval *zend_try_array_init_size(zval *zv, uint32_t size } zv = &ref->val; } - zval garbage; - ZVAL_COPY_VALUE(&garbage, zv); - ZVAL_NULL(zv); - zval_ptr_dtor(&garbage); + zval_ptr_safe_dtor(zv); ZVAL_ARR(zv, arr); return zv; } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index a1fbe049f3f99..1734269186116 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -207,6 +207,17 @@ static zend_always_inline zval* zend_assign_to_variable_ex(zval *variable_ptr, z return variable_ptr; } +static zend_always_inline void zend_safe_assign_to_variable_noref(zval *variable_ptr, zval *value) { + if (Z_REFCOUNTED_P(variable_ptr)) { + ZEND_ASSERT(Z_TYPE_P(variable_ptr) != IS_REFERENCE); + zend_refcounted *ref = Z_COUNTED_P(variable_ptr); + ZVAL_COPY_VALUE(variable_ptr, value); + GC_DTOR_NO_REF(ref); + } else { + ZVAL_COPY_VALUE(variable_ptr, value); + } +} + ZEND_API zend_result ZEND_FASTCALL zval_update_constant(zval *pp); ZEND_API zend_result ZEND_FASTCALL zval_update_constant_ex(zval *pp, zend_class_entry *scope); ZEND_API zend_result ZEND_FASTCALL zval_update_constant_with_ctx(zval *pp, zend_class_entry *scope, zend_ast_evaluate_ctx *ctx); diff --git a/Zend/zend_variables.c b/Zend/zend_variables.c index 27e09d7db22b1..00f10b08f80ab 100644 --- a/Zend/zend_variables.c +++ b/Zend/zend_variables.c @@ -85,6 +85,20 @@ ZEND_API void zval_ptr_dtor(zval *zval_ptr) /* {{{ */ } /* }}} */ +ZEND_API void zval_ptr_safe_dtor(zval *zval_ptr) +{ + if (Z_REFCOUNTED_P(zval_ptr)) { + zend_refcounted *ref = Z_COUNTED_P(zval_ptr); + + if (GC_DELREF(ref) == 0) { + ZVAL_NULL(zval_ptr); + rc_dtor_func(ref); + } else { + gc_check_possible_root(ref); + } + } +} + ZEND_API void zval_internal_ptr_dtor(zval *zval_ptr) /* {{{ */ { if (Z_REFCOUNTED_P(zval_ptr)) { diff --git a/Zend/zend_variables.h b/Zend/zend_variables.h index d504b0f0f5795..1cb745ca1b1dc 100644 --- a/Zend/zend_variables.h +++ b/Zend/zend_variables.h @@ -78,6 +78,7 @@ static zend_always_inline void zval_ptr_dtor_str(zval *zval_ptr) } ZEND_API void zval_ptr_dtor(zval *zval_ptr); +ZEND_API void zval_ptr_safe_dtor(zval *zval_ptr); ZEND_API void zval_internal_ptr_dtor(zval *zvalue); /* Kept for compatibility */