Skip to content

Commit 9b65615

Browse files
committed
Fix GH-20503: Assertion failure with ext/date DateInterval property hash
When a DateInterval object has a circular reference (e.g., $obj->prop = $obj), calling json_encode() triggered an assertion failure because the get_properties handler modified a HashTable with refcount > 1. Fixed by duplicating the properties HashTable when its refcount is greater than 1 before modifying it.
1 parent fb1ec9a commit 9b65615

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

ext/date/php_date.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ static HashTable *date_object_get_gc(zend_object *object, zval **table, int *n);
352352
static HashTable *date_object_get_properties_for(zend_object *object, zend_prop_purpose purpose);
353353
static HashTable *date_object_get_gc_interval(zend_object *object, zval **table, int *n);
354354
static HashTable *date_object_get_properties_interval(zend_object *object);
355+
static HashTable *date_object_get_properties_for_interval(zend_object *object, zend_prop_purpose purpose);
355356
static HashTable *date_object_get_gc_period(zend_object *object, zval **table, int *n);
356357
static HashTable *date_object_get_properties_for_timezone(zend_object *object, zend_prop_purpose purpose);
357358
static HashTable *date_object_get_gc_timezone(zend_object *object, zval **table, int *n);
@@ -1816,6 +1817,7 @@ static void date_register_classes(void) /* {{{ */
18161817
date_object_handlers_interval.read_property = date_interval_read_property;
18171818
date_object_handlers_interval.write_property = date_interval_write_property;
18181819
date_object_handlers_interval.get_properties = date_object_get_properties_interval;
1820+
date_object_handlers_interval.get_properties_for = date_object_get_properties_for_interval;
18191821
date_object_handlers_interval.get_property_ptr_ptr = date_interval_get_property_ptr_ptr;
18201822
date_object_handlers_interval.get_gc = date_object_get_gc_interval;
18211823
date_object_handlers_interval.compare = date_interval_compare_objects;
@@ -2235,6 +2237,40 @@ static HashTable *date_object_get_properties_interval(zend_object *object) /* {{
22352237
return props;
22362238
}
22372239

2240+
/* Duplicate the table if it has refcount > 1 to avoid modifying shared data.
2241+
* This can happen with circular references (e.g., $obj->prop = $obj). */
2242+
if (GC_REFCOUNT(props) > 1) {
2243+
props = zend_array_dup(props);
2244+
}
2245+
2246+
date_interval_object_to_hash(intervalobj, props);
2247+
2248+
return props;
2249+
} /* }}} */
2250+
2251+
static HashTable *date_object_get_properties_for_interval(zend_object *object, zend_prop_purpose purpose) /* {{{ */
2252+
{
2253+
HashTable *props;
2254+
php_interval_obj *intervalobj;
2255+
2256+
switch (purpose) {
2257+
case ZEND_PROP_PURPOSE_DEBUG:
2258+
case ZEND_PROP_PURPOSE_SERIALIZE:
2259+
case ZEND_PROP_PURPOSE_VAR_EXPORT:
2260+
case ZEND_PROP_PURPOSE_JSON:
2261+
case ZEND_PROP_PURPOSE_ARRAY_CAST:
2262+
break;
2263+
default:
2264+
return zend_std_get_properties_for(object, purpose);
2265+
}
2266+
2267+
intervalobj = php_interval_obj_from_obj(object);
2268+
props = zend_array_dup(zend_std_get_properties(object));
2269+
2270+
if (!intervalobj->initialized) {
2271+
return props;
2272+
}
2273+
22382274
date_interval_object_to_hash(intervalobj, props);
22392275

22402276
return props;

ext/date/tests/bug-gh20503.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
GH-20503 (Assertion failure with DateInterval and json_encode on circular reference)
3+
--FILE--
4+
<?php
5+
$obj = new DateInterval('P1W');
6+
$obj->circular = $obj;
7+
8+
// json_encode with circular reference previously caused an assertion failure
9+
// in debug builds when modifying a HashTable with refcount > 1
10+
$result = json_encode($obj);
11+
var_dump($result === false);
12+
// Error can be either JSON_ERROR_RECURSION or JSON_ERROR_DEPTH depending on detection method
13+
var_dump(json_last_error() === JSON_ERROR_RECURSION || json_last_error() === JSON_ERROR_DEPTH);
14+
15+
// Also verify array cast works
16+
$props = (array) $obj;
17+
var_dump(count($props) > 0);
18+
var_dump(isset($props['circular']));
19+
?>
20+
--EXPECTF--
21+
Deprecated: Creation of dynamic property DateInterval::$circular is deprecated in %s on line %d
22+
bool(true)
23+
bool(true)
24+
bool(true)
25+
bool(true)

0 commit comments

Comments
 (0)