From f143edda87ad1d624b91e5e03f31c8316c71ec7c Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Sun, 2 Nov 2025 13:37:13 +0100 Subject: [PATCH 1/2] Fix crashes when trying to instantiate uninstantiable classes via date static constructors --- ext/date/php_date.c | 28 ++++++--- .../instantiate_uninstantiable_classes.phpt | 61 +++++++++++++++++++ 2 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 ext/date/tests/instantiate_uninstantiable_classes.phpt diff --git a/ext/date/php_date.c b/ext/date/php_date.c index e54b1945e1e39..8ad6856406244 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -1476,7 +1476,8 @@ static void create_date_period_datetime(timelib_time *datetime, zend_class_entry if (datetime) { php_date_obj *date_obj; - object_init_ex(zv, ce); + zend_result result = object_init_ex(zv, ce); + ZEND_ASSERT(result == SUCCESS && "should succeed as it reuses an existing object's ce"); date_obj = Z_PHPDATE_P(zv); date_obj->time = timelib_time_clone(datetime); } else { @@ -2345,6 +2346,7 @@ static void add_common_properties(HashTable *myht, zend_object *zobj) } /* Advanced Interface */ +/* TODO: remove this API because it is unsafe to use as-is, as it does not propagate the failure/success status. */ PHPAPI zval *php_date_instantiate(zend_class_entry *pce, zval *object) /* {{{ */ { object_init_ex(object, pce); @@ -2564,7 +2566,9 @@ PHP_FUNCTION(date_create_from_format) Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone) ZEND_PARSE_PARAMETERS_END(); - php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, return_value); + if (object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date) != SUCCESS) { + RETURN_THROWS(); + } if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, format_str, timezone_object, PHP_DATE_INIT_FORMAT)) { zval_ptr_dtor(return_value); RETURN_FALSE; @@ -2642,7 +2646,9 @@ PHP_METHOD(DateTime, createFromImmutable) old_obj = Z_PHPDATE_P(datetimeimmutable_object); DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetimeimmutable_object)); - php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, return_value); + if (object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date) != SUCCESS) { + RETURN_THROWS(); + } new_obj = Z_PHPDATE_P(return_value); new_obj->time = timelib_time_clone(old_obj->time); @@ -2663,7 +2669,9 @@ PHP_METHOD(DateTime, createFromInterface) old_obj = Z_PHPDATE_P(datetimeinterface_object); DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetimeinterface_object)); - php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, return_value); + if (object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date) != SUCCESS) { + RETURN_THROWS(); + } new_obj = Z_PHPDATE_P(return_value); new_obj->time = timelib_time_clone(old_obj->time); @@ -2684,7 +2692,9 @@ PHP_METHOD(DateTimeImmutable, createFromMutable) old_obj = Z_PHPDATE_P(datetime_object); DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetime_object)); - php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, return_value); + if (object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable) != SUCCESS) { + RETURN_THROWS(); + } new_obj = Z_PHPDATE_P(return_value); new_obj->time = timelib_time_clone(old_obj->time); @@ -2705,7 +2715,9 @@ PHP_METHOD(DateTimeImmutable, createFromInterface) old_obj = Z_PHPDATE_P(datetimeinterface_object); DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetimeinterface_object)); - php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, return_value); + if (object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable) != SUCCESS) { + RETURN_THROWS(); + } new_obj = Z_PHPDATE_P(return_value); new_obj->time = timelib_time_clone(old_obj->time); @@ -4950,7 +4962,9 @@ PHP_METHOD(DatePeriod, createFromISO8601String) RETURN_THROWS(); } - object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_period); + if (object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_period) != SUCCESS) { + RETURN_THROWS(); + } dpobj = Z_PHPPERIOD_P(return_value); dpobj->current = NULL; diff --git a/ext/date/tests/instantiate_uninstantiable_classes.phpt b/ext/date/tests/instantiate_uninstantiable_classes.phpt new file mode 100644 index 0000000000000..fe5942974f49d --- /dev/null +++ b/ext/date/tests/instantiate_uninstantiable_classes.phpt @@ -0,0 +1,61 @@ +--TEST-- +Instantiating uninstantiable classes via static constructors +--FILE-- +getMessage(), "\n"; +} + +try { + MyDateTime::createFromFormat('Y-m-d', '2025-01-01'); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +try { + MyDateTime::createFromImmutable(new DateTimeImmutable()); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +try { + MyDateTime::createFromInterface(new DateTimeImmutable()); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +try { + MyDateTimeImmutable::createFromMutable(new DateTime()); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +try { + MyDateTimeImmutable::createFromInterface(new DateTime()); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot instantiate abstract class MyDatePeriod +Cannot instantiate abstract class MyDateTime +Cannot instantiate abstract class MyDateTime +Cannot instantiate abstract class MyDateTime +Cannot instantiate abstract class MyDateTimeImmutable +Cannot instantiate abstract class MyDateTimeImmutable From 78e89036c570a7946f44b9204bfa91bd2550c760 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:54:34 +0100 Subject: [PATCH 2/2] also DateTimeImmutable::createFromFormat --- ext/date/php_date.c | 4 +++- ext/date/tests/instantiate_uninstantiable_classes.phpt | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 8ad6856406244..84fad72948fd4 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -2590,7 +2590,9 @@ PHP_FUNCTION(date_create_immutable_from_format) Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone) ZEND_PARSE_PARAMETERS_END(); - php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, return_value); + if (object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable) != SUCCESS) { + RETURN_THROWS(); + } if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, format_str, timezone_object, PHP_DATE_INIT_FORMAT)) { zval_ptr_dtor(return_value); RETURN_FALSE; diff --git a/ext/date/tests/instantiate_uninstantiable_classes.phpt b/ext/date/tests/instantiate_uninstantiable_classes.phpt index fe5942974f49d..282696b04c9c6 100644 --- a/ext/date/tests/instantiate_uninstantiable_classes.phpt +++ b/ext/date/tests/instantiate_uninstantiable_classes.phpt @@ -39,6 +39,12 @@ try { echo $e->getMessage(), "\n"; } +try { + MyDateTimeImmutable::createFromFormat('Y-m-d', '2025-01-01'); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + try { MyDateTimeImmutable::createFromMutable(new DateTime()); } catch (Error $e) { @@ -59,3 +65,4 @@ Cannot instantiate abstract class MyDateTime Cannot instantiate abstract class MyDateTime Cannot instantiate abstract class MyDateTimeImmutable Cannot instantiate abstract class MyDateTimeImmutable +Cannot instantiate abstract class MyDateTimeImmutable