Skip to content

Commit bf727cf

Browse files
authored
RFC: Make unserialize() emit a warning for trailing bytes (#9630)
1 parent 626331f commit bf727cf

7 files changed

+130
-7
lines changed

NEWS

+2
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ PHP NEWS
164164

165165
- Standard:
166166
. E_NOTICEs emitted by unserialize() have been promoted to E_WARNING. (timwolla)
167+
. unserialize() now emits a new E_WARNING if the input contains unconsumed
168+
bytes. (timwolla)
167169
. Make array_pad's $length warning less confusing. (nielsdos)
168170
. E_WARNING emitted by strtok in the caase both arguments are not provided when
169171
starting tokenisation. (David Carlier)

UPGRADING

+4-1
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,11 @@ PHP 8.3 UPGRADE NOTES
141141
. pg_insert now raises a ValueError instead of a WARNING when the table specified is invalid.
142142

143143
- Standard:
144-
. E_NOTICEs emitted by unserialized() have been promoted to E_WARNING.
144+
. E_NOTICEs emitted by unserialize() have been promoted to E_WARNING.
145145
RFC: https://wiki.php.net/rfc/improve_unserialize_error_handling
146+
. unserialize() now emits a new E_WARNING if the input contains unconsumed
147+
bytes.
148+
RFC: https://wiki.php.net/rfc/unserialize_warn_on_trailing_data
146149
. array_pad() is now only limited by the maximum number of elements an array
147150
can have. Before, it was only possible to add at most 1048576 elements at a
148151
time.

ext/standard/tests/serialize/typed_property_ref_overwrite.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Test {
77
public ?object $prop;
88
}
99
$s = <<<'STR'
10-
O:4:"Test":2:{s:4:"prop";R:1;s:4:"prop";N;}}
10+
O:4:"Test":2:{s:4:"prop";R:1;s:4:"prop";N;}
1111
STR;
1212
var_dump(unserialize($s));
1313

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Test unserialize() with extra data at the end of a valid value
3+
--FILE--
4+
<?php
5+
6+
var_dump(unserialize('i:5;i:6;'));
7+
var_dump(unserialize('N;i:6;'));
8+
var_dump(unserialize('b:1;i:6;'));
9+
var_dump(unserialize('a:1:{s:3:"foo";b:1;}i:6;'));
10+
11+
?>
12+
--EXPECTF--
13+
Warning: unserialize(): Extra data starting at offset 4 of 8 bytes in %s on line %d
14+
int(5)
15+
16+
Warning: unserialize(): Extra data starting at offset 2 of 6 bytes in %s on line %d
17+
NULL
18+
19+
Warning: unserialize(): Extra data starting at offset 4 of 8 bytes in %s on line %d
20+
bool(true)
21+
22+
Warning: unserialize(): Extra data starting at offset 20 of 24 bytes in %s on line %d
23+
array(1) {
24+
["foo"]=>
25+
bool(true)
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
Test unserialize() with extra data at the end of a valid value with nested unserialize
3+
--FILE--
4+
<?php
5+
6+
final class Foo {
7+
public $foo;
8+
9+
public function __unserialize(array $foo)
10+
{
11+
$this->foo = unserialize($foo['bar']);
12+
}
13+
14+
public function __serialize(): array
15+
{
16+
return [
17+
'bar' => serialize($this->foo) . 'garbage',
18+
];
19+
}
20+
}
21+
22+
$f = new Foo;
23+
$f->foo = ['a', 'b', 'c'];
24+
25+
var_dump(unserialize(serialize($f) . 'garbage'));
26+
27+
?>
28+
--EXPECTF--
29+
Warning: unserialize(): Extra data starting at offset 81 of 88 bytes in %s on line %d
30+
31+
Warning: unserialize(): Extra data starting at offset 42 of 49 bytes in %s on line %d
32+
object(Foo)#2 (1) {
33+
["foo"]=>
34+
array(3) {
35+
[0]=>
36+
string(1) "a"
37+
[1]=>
38+
string(1) "b"
39+
[2]=>
40+
string(1) "c"
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
Test unserialize() with extra data at the end of a valid value with Serializable
3+
--FILE--
4+
<?php
5+
6+
final class Foo implements Serializable {
7+
public $foo;
8+
9+
public function unserialize(string $foo)
10+
{
11+
$this->foo = unserialize($foo);
12+
}
13+
14+
public function serialize(): string
15+
{
16+
return serialize($this->foo) . 'garbage';
17+
}
18+
}
19+
20+
$f = new Foo;
21+
$f->foo = ['a', 'b', 'c'];
22+
23+
var_dump(unserialize(serialize($f) . 'garbage'));
24+
25+
?>
26+
--EXPECTF--
27+
Deprecated: Foo implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary) in %s on line %d
28+
29+
Warning: unserialize(): Extra data starting at offset 42 of 49 bytes in %s on line %d
30+
31+
Warning: unserialize(): Extra data starting at offset 64 of 71 bytes in %s on line %d
32+
object(Foo)#2 (1) {
33+
["foo"]=>
34+
array(3) {
35+
[0]=>
36+
string(1) "a"
37+
[1]=>
38+
string(1) "b"
39+
[2]=>
40+
string(1) "c"
41+
}
42+
}

ext/standard/var.c

+13-5
Original file line numberDiff line numberDiff line change
@@ -1391,11 +1391,19 @@ PHPAPI void php_unserialize_with_options(zval *return_value, const char *buf, co
13911391
zval_ptr_dtor(return_value);
13921392
}
13931393
RETVAL_FALSE;
1394-
} else if (BG(unserialize).level > 1) {
1395-
ZVAL_COPY(return_value, retval);
1396-
} else if (Z_REFCOUNTED_P(return_value)) {
1397-
zend_refcounted *ref = Z_COUNTED_P(return_value);
1398-
gc_check_possible_root(ref);
1394+
} else {
1395+
if ((char*)p < buf + buf_len) {
1396+
if (!EG(exception)) {
1397+
php_error_docref(NULL, E_WARNING, "Extra data starting at offset " ZEND_LONG_FMT " of %zd bytes",
1398+
(zend_long)((char*)p - buf), buf_len);
1399+
}
1400+
}
1401+
if (BG(unserialize).level > 1) {
1402+
ZVAL_COPY(return_value, retval);
1403+
} else if (Z_REFCOUNTED_P(return_value)) {
1404+
zend_refcounted *ref = Z_COUNTED_P(return_value);
1405+
gc_check_possible_root(ref);
1406+
}
13991407
}
14001408

14011409
cleanup:

0 commit comments

Comments
 (0)