Skip to content

Commit 2e6002a

Browse files
committed
Test for unserializing twice
1 parent 972f3bc commit 2e6002a

File tree

2 files changed

+58
-11
lines changed

2 files changed

+58
-11
lines changed

ext/spl/spl_cachediterable.c

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ typedef struct _zval_pair {
4545
zval value;
4646
} zval_pair;
4747

48+
static const zval_pair empty_element_list[0];
49+
4850
typedef struct _spl_cached_entries {
4951
size_t size;
5052
zval_pair *entries;
@@ -75,18 +77,22 @@ static spl_cachediterable_object *cached_iterable_from_obj(zend_object *obj)
7577
*/
7678
static bool spl_cached_entries_empty(spl_cached_entries *array)
7779
{
78-
if (array->entries) {
79-
ZEND_ASSERT(array->size > 0);
80+
if (array->size > 0) {
81+
ZEND_ASSERT(array->entries != empty_element_list);
8082
return false;
8183
}
82-
ZEND_ASSERT(array->size == 0);
84+
ZEND_ASSERT(array->entries == empty_element_list || array->entries == NULL);
8385
return true;
8486
}
8587

86-
static void spl_cached_entries_default_ctor(spl_cached_entries *array)
88+
static bool spl_cached_entries_uninitialized(spl_cached_entries *array)
8789
{
88-
array->size = 0;
89-
array->entries = NULL;
90+
if (array->entries == NULL) {
91+
ZEND_ASSERT(array->size == 0);
92+
return true;
93+
}
94+
ZEND_ASSERT((array->entries == empty_element_list && array->size == 0) || array->size > 0);
95+
return false;
9096
}
9197

9298
/* Initializes the range [from, to) to null. Does not dtor existing entries. */
@@ -127,7 +133,8 @@ static void spl_cached_entries_init_from_array(spl_cached_entries *array, zend_a
127133
i++;
128134
} ZEND_HASH_FOREACH_END();
129135
} else {
130-
spl_cached_entries_default_ctor(array);
136+
array->size = 0;
137+
array->entries = (zval_pair *)empty_element_list;
131138
}
132139
}
133140

@@ -227,8 +234,13 @@ static void spl_cachediterable_copy_range(spl_cached_entries *array, size_t offs
227234
static void spl_cached_entries_copy_ctor(spl_cached_entries *to, spl_cached_entries *from)
228235
{
229236
zend_long size = from->size;
237+
if (!size) {
238+
to->size = 0;
239+
to->entries = (zval_pair *)empty_element_list;
240+
return;
241+
}
230242

231-
to->size = 0; /* reset size in case ecalloc() fails */
243+
to->size = 0; /* reset size in case emalloc() fails */
232244
to->entries = safe_emalloc(size, sizeof(zval_pair), 0);
233245
to->size = size;
234246

@@ -268,6 +280,8 @@ static HashTable* spl_cachediterable_object_get_gc(zend_object *obj, zval **tabl
268280
*table = &intern->array.entries[0].key;
269281
*n = (int)intern->array.size * 2;
270282

283+
// TODO: This is probably redundant if dynamic properties are not allowed
284+
// and the properties can't be mutated.
271285
return ht;
272286
}
273287

@@ -313,6 +327,8 @@ static zend_object *spl_cachediterable_object_new_ex(zend_class_entry *class_typ
313327
if (orig && clone_orig) {
314328
spl_cachediterable_object *other = cached_iterable_from_obj(orig);
315329
spl_cached_entries_copy_ctor(&intern->array, &other->array);
330+
} else {
331+
intern->array.entries = NULL;
316332
}
317333

318334
return &intern->std;
@@ -365,9 +381,10 @@ PHP_METHOD(CachedIterable, __construct)
365381

366382
spl_cachediterable_object *intern = Z_CACHEDITERABLE_P(object);
367383

368-
if (!spl_cached_entries_empty(&intern->array)) {
384+
if (UNEXPECTED(!spl_cached_entries_uninitialized(&intern->array))) {
385+
zend_throw_exception(spl_ce_RuntimeException, "Called CachedIterable::__construct twice", 0);
369386
/* called __construct() twice, bail out */
370-
return;
387+
RETURN_THROWS();
371388
}
372389

373390
switch (Z_TYPE_P(iterable)) {
@@ -503,10 +520,11 @@ PHP_METHOD(CachedIterable, __unserialize)
503520
RETURN_THROWS();
504521
}
505522
spl_cachediterable_object *intern = Z_CACHEDITERABLE_P(ZEND_THIS);
506-
if (intern->array.size) {
523+
if (UNEXPECTED(!spl_cached_entries_uninitialized(&intern->array))) {
507524
zend_throw_exception(spl_ce_RuntimeException, "Already unserialized", 0);
508525
RETURN_THROWS();
509526
}
527+
510528
ZEND_ASSERT(intern->array.entries == NULL);
511529

512530
size_t num_entries = raw_size / 2;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
CachedIterable cannot be re-initialized
3+
--FILE--
4+
<?php
5+
6+
$it = new CachedIterable([]);
7+
8+
try {
9+
$it->__construct(['first']);
10+
echo "Unexpectedly called constructor\n";
11+
} catch (Throwable $t) {
12+
printf("Caught %s: %s\n", $t::class, $t->getMessage());
13+
}
14+
var_dump($it);
15+
try {
16+
$it->__unserialize([new ArrayObject(), new stdClass()]);
17+
echo "Unexpectedly called __unserialize\n";
18+
} catch (Throwable $t) {
19+
printf("Caught %s: %s\n", $t::class, $t->getMessage());
20+
}
21+
var_dump($it);
22+
?>
23+
--EXPECT--
24+
Caught RuntimeException: Called CachedIterable::__construct twice
25+
object(CachedIterable)#1 (0) {
26+
}
27+
Caught RuntimeException: Already unserialized
28+
object(CachedIterable)#1 (0) {
29+
}

0 commit comments

Comments
 (0)