Skip to content

Commit f2e8c5d

Browse files
authored
unserialize: Strictly check for :{ at object start (#10214)
* unserialize: Strictly check for `:{` at object start * unserialize: Update CVE tests It's unlikely that the object syntax error contributed to the actual CVE. The CVE is rather caused by the incorrect object serialization data of the `C` format. Add a second string without such a syntax error to ensure that path is still executed as well to ensure the CVE is absent. * Fix test expectation in gmp/tests/bug74670.phpt No changes to the input required, because the test actually is intended to verify the behavior for a missing `}`, it's just that the report position changed. * NEWS * UPGRADING
1 parent 31fd34a commit f2e8c5d

File tree

9 files changed

+103
-3
lines changed

9 files changed

+103
-3
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ PHP NEWS
3838

3939
- Standard:
4040
. Fix GH-10187 (Segfault in stripslashes() with arm64). (nielsdos)
41+
. Fixed bug GH-10214 (Incomplete validation of object syntax during
42+
unserialize()). (timwolla)
4143

4244
05 Jan 2023, PHP 8.2.1
4345

UPGRADING

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ PHP 8.2 UPGRADE NOTES
223223
widened to iterable from Iterator, allowing arrays to be passed.
224224
RFC: https://wiki.php.net/rfc/iterator_xyz_accept_array
225225

226+
- Standard
227+
. unserialize() now performs a stricter validation of the structure of serialized
228+
objects.
229+
226230
- XML
227231
. xml_parser_set_option() now actually returns false when attempting to set a
228232
negative tag start. Previously it returned true while emitting an E_WARNING.

ext/gmp/tests/bug74670.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ $str = 'C:3:"GMP":4:{s:6666666666:""}';
88
var_dump(unserialize($str));
99
?>
1010
--EXPECTF--
11-
Notice: unserialize(): Error at offset 13 of 29 bytes in %s on line %d
11+
Notice: unserialize(): Error at offset 17 of 29 bytes in %s on line %d
1212
bool(false)

ext/spl/tests/bug73029.phpt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ Bug #73029: Missing type check when unserializing SplArray
33
--FILE--
44
<?php
55
try {
6+
$a = 'C:11:"ArrayObject":19:{x:i:0;r:2;;m:a:0:{}}';
7+
$m = unserialize($a);
8+
$x = $m[2];
9+
} catch(UnexpectedValueException $e) {
10+
print $e->getMessage() . "\n";
11+
}
12+
try {
613
$a = 'C:11:"ArrayObject":19:0x:i:0;r:2;;m:a:0:{}}';
714
$m = unserialize($a);
815
$x = $m[2];
@@ -11,6 +18,10 @@ $x = $m[2];
1118
}
1219
?>
1320
DONE
14-
--EXPECT--
21+
--EXPECTF--
1522
Error at offset 10 of 19 bytes
23+
24+
Notice: unserialize(): Error at offset 22 of 43 bytes in %s on line %d
25+
26+
Warning: Trying to access array offset on value of type bool in %s on line %d
1627
DONE

ext/standard/tests/serialize/bug73341.phpt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
Bug #73144 (Use-afte-free in ArrayObject Deserialization)
33
--FILE--
44
<?php
5+
try {
6+
$token = 'a:2:{i:0;O:1:"0":2:{s:1:"0";i:0;s:1:"0";a:1:{i:0;C:11:"ArrayObject":7:{x:i:0;r}';
7+
$obj = unserialize($token);
8+
} catch(Exception $e) {
9+
echo $e->getMessage()."\n";
10+
}
11+
512
try {
613
$token = 'a:2:{i:0;O:1:"0":2:0s:1:"0";i:0;s:1:"0";a:1:{i:0;C:11:"ArrayObject":7:{x:i:0;r}';
714
$obj = unserialize($token);
@@ -20,5 +27,7 @@ unserialize($exploit);
2027
--EXPECTF--
2128
Error at offset 6 of 7 bytes
2229

30+
Notice: unserialize(): Error at offset 19 of 79 bytes in %s on line %d
31+
2332
Notice: ArrayObject::unserialize(): Unexpected end of serialized data in %sbug73341.php on line %d
2433
Error at offset 24 of 34 bytes

ext/standard/tests/serialize/bug74111.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ $s = 'O:8:"stdClass":00000000';
66
var_dump(unserialize($s));
77
?>
88
--EXPECTF--
9-
Notice: unserialize(): Error at offset 25 of 23 bytes in %s on line %d
9+
Notice: unserialize(): Error at offset 23 of 23 bytes in %s on line %d
1010
bool(false)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Object serialization / unserialization: Strict format
3+
--FILE--
4+
<?php
5+
class A {public $a;}
6+
7+
var_dump(unserialize('O:1:"A":1x{s:1:"a";N;}'));
8+
//0123456789012345678901
9+
var_dump(unserialize('O:1:"A":1:xs:1:"a";N;}'));
10+
//0123456789012345678901
11+
?>
12+
--EXPECTF--
13+
Notice: unserialize(): Error at offset 9 of 22 bytes in %s on line %d
14+
bool(false)
15+
16+
Notice: unserialize(): Error at offset 10 of 22 bytes in %s on line %d
17+
bool(false)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Object serialization / unserialization: Strict format (2)
3+
--FILE--
4+
<?php
5+
class A implements Serializable {
6+
public function serialize() {}
7+
public function unserialize($data) {}
8+
public function __serialize() {}
9+
public function __unserialize($data) {}
10+
}
11+
12+
var_dump(unserialize('C:1:"A":3x{foo}'));
13+
//012345678901234
14+
var_dump(unserialize('C:1:"A":3:xfoo}'));
15+
//012345678901234
16+
var_dump(unserialize('C:1:"A":3:{foox'));
17+
//012345678901234
18+
var_dump(unserialize('C:1:"A":'));
19+
//01234567
20+
21+
?>
22+
--EXPECTF--
23+
Notice: unserialize(): Error at offset 9 of 15 bytes in %s on line %d
24+
bool(false)
25+
26+
Notice: unserialize(): Error at offset 10 of 15 bytes in %s on line %d
27+
bool(false)
28+
29+
Notice: unserialize(): Error at offset 14 of 15 bytes in %s on line %d
30+
bool(false)
31+
32+
Notice: unserialize(): Error at offset 8 of 8 bytes in %s on line %d
33+
bool(false)

ext/standard/var_unserializer.re

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,19 @@ static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
743743

744744
datalen = parse_iv2((*p) + 2, p);
745745

746+
if (max - (*p) < 2) {
747+
return 0;
748+
}
749+
750+
if ((*p)[0] != ':') {
751+
return 0;
752+
}
753+
754+
if ((*p)[1] != '{') {
755+
(*p) += 1;
756+
return 0;
757+
}
758+
746759
(*p) += 2;
747760

748761
if (datalen < 0 || (max - (*p)) <= datalen) {
@@ -754,6 +767,7 @@ static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
754767
* with unserialize reading past the end of the passed buffer if the string is not
755768
* appropriately terminated (usually NUL terminated, but '}' is also sufficient.) */
756769
if ((*p)[datalen] != '}') {
770+
(*p) += datalen;
757771
return 0;
758772
}
759773

@@ -1293,6 +1307,16 @@ object ":" uiv ":" ["] {
12931307
return 0;
12941308
}
12951309

1310+
YYCURSOR = *p;
1311+
1312+
if (*(YYCURSOR) != ':') {
1313+
return 0;
1314+
}
1315+
if (*(YYCURSOR+1) != '{') {
1316+
*p = YYCURSOR+1;
1317+
return 0;
1318+
}
1319+
12961320
*p += 2;
12971321

12981322
has_unserialize = !incomplete_class && ce->__unserialize;

0 commit comments

Comments
 (0)