Skip to content

Commit 5d94ad7

Browse files
committed
Merge branch 'PHP-7.4'
* PHP-7.4: Fix #78438: Corruption when __unserializing deeply nested structures
2 parents f51421c + ca265eb commit 5d94ad7

File tree

2 files changed

+138
-9
lines changed

2 files changed

+138
-9
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
--TEST--
2+
Bug #78438 (Corruption when __unserializing deeply nested structures)
3+
--FILE--
4+
<?php
5+
6+
class Node
7+
{
8+
public $childs = [];
9+
10+
public function __serialize()
11+
{
12+
return [$this->childs];
13+
}
14+
15+
public function __unserialize(array $data)
16+
{
17+
list($this->childs) = $data;
18+
}
19+
}
20+
21+
function createTree ($width, $depth) {
22+
$root = new Node();
23+
24+
$nextLevel = [$root];
25+
26+
for ($level=1; $level<$depth; $level++) {
27+
$levelRoots = $nextLevel;
28+
$nextLevel = [];
29+
30+
while (count($levelRoots) > 0) {
31+
$levelRoot = array_shift($levelRoots);
32+
33+
for ($w = 0; $w < $width; $w++) {
34+
$tester = new Node();
35+
36+
$levelRoot->childs[] = $tester;
37+
38+
$nextLevel[] = $tester;
39+
}
40+
}
41+
}
42+
43+
return $root;
44+
}
45+
46+
47+
$width = 3;
48+
ob_implicit_flush();
49+
50+
foreach (range(1, 8) as $depth) {
51+
$tree = createTree($width, $depth);
52+
53+
echo "Testcase tree $width x $depth".PHP_EOL;
54+
55+
echo "> Serializing now".PHP_EOL;
56+
$serialized = serialize($tree);
57+
58+
echo "> Unserializing now".PHP_EOL;
59+
$tree = unserialize($serialized);
60+
61+
// Lets test whether all is ok!
62+
$expectedSize = ($width**$depth - 1)/($width-1);
63+
64+
$nodes = [$tree];
65+
$count = 0;
66+
67+
while (count($nodes) > 0) {
68+
$count++;
69+
70+
$node = array_shift($nodes);
71+
foreach ($node->childs as $node) {
72+
$nodes[] = $node;
73+
}
74+
}
75+
76+
echo "> Unserialized total node count was $count, expected $expectedSize: ".($expectedSize === $count ? 'CORRECT!' : 'INCORRECT');
77+
78+
echo PHP_EOL;
79+
echo PHP_EOL;
80+
}
81+
?>
82+
--EXPECT--
83+
Testcase tree 3 x 1
84+
> Serializing now
85+
> Unserializing now
86+
> Unserialized total node count was 1, expected 1: CORRECT!
87+
88+
Testcase tree 3 x 2
89+
> Serializing now
90+
> Unserializing now
91+
> Unserialized total node count was 4, expected 4: CORRECT!
92+
93+
Testcase tree 3 x 3
94+
> Serializing now
95+
> Unserializing now
96+
> Unserialized total node count was 13, expected 13: CORRECT!
97+
98+
Testcase tree 3 x 4
99+
> Serializing now
100+
> Unserializing now
101+
> Unserialized total node count was 40, expected 40: CORRECT!
102+
103+
Testcase tree 3 x 5
104+
> Serializing now
105+
> Unserializing now
106+
> Unserialized total node count was 121, expected 121: CORRECT!
107+
108+
Testcase tree 3 x 6
109+
> Serializing now
110+
> Unserializing now
111+
> Unserialized total node count was 364, expected 364: CORRECT!
112+
113+
Testcase tree 3 x 7
114+
> Serializing now
115+
> Unserializing now
116+
> Unserialized total node count was 1093, expected 1093: CORRECT!
117+
118+
Testcase tree 3 x 8
119+
> Serializing now
120+
> Unserializing now
121+
> Unserialized total node count was 3280, expected 3280: CORRECT!

ext/standard/var_unserializer.re

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ typedef struct {
4040
typedef struct {
4141
zend_long used_slots;
4242
void *next;
43-
zval data[VAR_ENTRIES_MAX];
43+
zval data[VAR_DTOR_ENTRIES_MAX];
4444
} var_dtor_entries;
4545

4646
struct php_unserialize_data {
@@ -122,16 +122,17 @@ PHPAPI void var_push_dtor(php_unserialize_data_t *var_hashx, zval *rval)
122122
}
123123
}
124124

125-
PHPAPI zval *var_tmp_var(php_unserialize_data_t *var_hashx)
125+
static zend_always_inline zval *tmp_var(php_unserialize_data_t *var_hashx, zend_long num)
126126
{
127127
var_dtor_entries *var_hash;
128+
zend_long used_slots;
128129

129-
if (!var_hashx || !*var_hashx) {
130+
if (!var_hashx || !*var_hashx || num < 1) {
130131
return NULL;
131132
}
132133

133134
var_hash = (*var_hashx)->last_dtor;
134-
if (!var_hash || var_hash->used_slots == VAR_DTOR_ENTRIES_MAX) {
135+
if (!var_hash || var_hash->used_slots + num > VAR_DTOR_ENTRIES_MAX) {
135136
var_hash = emalloc(sizeof(var_dtor_entries));
136137
var_hash->used_slots = 0;
137138
var_hash->next = 0;
@@ -144,9 +145,16 @@ PHPAPI zval *var_tmp_var(php_unserialize_data_t *var_hashx)
144145

145146
(*var_hashx)->last_dtor = var_hash;
146147
}
147-
ZVAL_UNDEF(&var_hash->data[var_hash->used_slots]);
148-
Z_EXTRA(var_hash->data[var_hash->used_slots]) = 0;
149-
return &var_hash->data[var_hash->used_slots++];
148+
for (used_slots = var_hash->used_slots; var_hash->used_slots < used_slots + num; var_hash->used_slots++) {
149+
ZVAL_UNDEF(&var_hash->data[var_hash->used_slots]);
150+
Z_EXTRA(var_hash->data[var_hash->used_slots]) = 0;
151+
}
152+
return &var_hash->data[used_slots];
153+
}
154+
155+
PHPAPI zval *var_tmp_var(php_unserialize_data_t *var_hashx)
156+
{
157+
return tmp_var(var_hashx, 1);
150158
}
151159

152160
PHPAPI void var_replace(php_unserialize_data_t *var_hashx, zval *ozval, zval *nzval)
@@ -653,10 +661,10 @@ static inline int object_common(UNSERIALIZE_PARAMETER, zend_long elements, zend_
653661
/* Delay __unserialize() call until end of serialization. We use two slots here to
654662
* store both the object and the unserialized data array. */
655663
ZVAL_DEREF(rval);
656-
tmp = var_tmp_var(var_hash);
664+
tmp = tmp_var(var_hash, 2);
657665
ZVAL_COPY(tmp, rval);
658666
Z_EXTRA_P(tmp) = VAR_UNSERIALIZE_FLAG;
659-
tmp = var_tmp_var(var_hash);
667+
tmp++;
660668
ZVAL_COPY_VALUE(tmp, &ary);
661669

662670
return finish_nested_data(UNSERIALIZE_PASSTHRU);

0 commit comments

Comments
 (0)