Skip to content

Commit 16b3003

Browse files
committed
Fix bug #73825 - Heap out of bounds read on unserialize in finish_nested_data()
1 parent b28b8b2 commit 16b3003

File tree

3 files changed

+76
-36
lines changed

3 files changed

+76
-36
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Bug #73825 Heap out of bounds read on unserialize in finish_nested_data()
3+
--FILE--
4+
<?php
5+
$obj = unserialize('O:8:"00000000":');
6+
var_dump($obj);
7+
?>
8+
--EXPECTF--
9+
Warning: Bad unserialize data in %sbug73825.php on line %d
10+
11+
Notice: unserialize(): Error at offset 13 of 15 bytes in %sbug73825.php on line %d
12+
bool(false)

ext/standard/var_unserializer.c

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,11 @@ static inline long object_common1(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
405405
{
406406
long elements;
407407

408+
if( *p >= max - 2) {
409+
zend_error(E_WARNING, "Bad unserialize data");
410+
return -1;
411+
}
412+
408413
elements = parse_iv2((*p) + 2, p);
409414

410415
(*p) += 2;
@@ -415,7 +420,7 @@ static inline long object_common1(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
415420
/* If this class implements Serializable, it should not land here but in object_custom(). The passed string
416421
obviously doesn't descend from the regular serializer. */
417422
zend_error(E_WARNING, "Erroneous data format for unserializing '%s'", ce->name);
418-
return 0;
423+
return -1;
419424
}
420425

421426
return elements;
@@ -492,7 +497,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
492497

493498

494499

495-
#line 496 "ext/standard/var_unserializer.c"
500+
#line 501 "ext/standard/var_unserializer.c"
496501
{
497502
YYCTYPE yych;
498503
static const unsigned char yybm[] = {
@@ -552,9 +557,9 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
552557
yych = *(YYMARKER = ++YYCURSOR);
553558
if (yych == ':') goto yy95;
554559
yy3:
555-
#line 861 "ext/standard/var_unserializer.re"
560+
#line 875 "ext/standard/var_unserializer.re"
556561
{ return 0; }
557-
#line 558 "ext/standard/var_unserializer.c"
562+
#line 563 "ext/standard/var_unserializer.c"
558563
yy4:
559564
yych = *(YYMARKER = ++YYCURSOR);
560565
if (yych == ':') goto yy89;
@@ -597,13 +602,13 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
597602
goto yy3;
598603
yy14:
599604
++YYCURSOR;
600-
#line 855 "ext/standard/var_unserializer.re"
605+
#line 869 "ext/standard/var_unserializer.re"
601606
{
602607
/* this is the case where we have less data than planned */
603608
php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Unexpected end of serialized data");
604609
return 0; /* not sure if it should be 0 or 1 here? */
605610
}
606-
#line 607 "ext/standard/var_unserializer.c"
611+
#line 612 "ext/standard/var_unserializer.c"
607612
yy16:
608613
yych = *++YYCURSOR;
609614
goto yy3;
@@ -634,7 +639,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
634639
yych = *++YYCURSOR;
635640
if (yych != '"') goto yy18;
636641
++YYCURSOR;
637-
#line 708 "ext/standard/var_unserializer.re"
642+
#line 717 "ext/standard/var_unserializer.re"
638643
{
639644
size_t len, len2, len3, maxlen;
640645
long elements;
@@ -774,14 +779,19 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
774779

775780
elements = object_common1(UNSERIALIZE_PASSTHRU, ce);
776781

782+
if (elements < 0) {
783+
efree(class_name);
784+
return 0;
785+
}
786+
777787
if (incomplete_class) {
778788
php_store_class_name(*rval, class_name, len2);
779789
}
780790
efree(class_name);
781791

782792
return object_common2(UNSERIALIZE_PASSTHRU, elements);
783793
}
784-
#line 785 "ext/standard/var_unserializer.c"
794+
#line 795 "ext/standard/var_unserializer.c"
785795
yy25:
786796
yych = *++YYCURSOR;
787797
if (yych <= ',') {
@@ -806,16 +816,20 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
806816
yych = *++YYCURSOR;
807817
if (yych != '"') goto yy18;
808818
++YYCURSOR;
809-
#line 699 "ext/standard/var_unserializer.re"
819+
#line 704 "ext/standard/var_unserializer.re"
810820
{
821+
long elements;
811822
if (!var_hash) return 0;
812823

813824
INIT_PZVAL(*rval);
814825

815-
return object_common2(UNSERIALIZE_PASSTHRU,
816-
object_common1(UNSERIALIZE_PASSTHRU, ZEND_STANDARD_CLASS_DEF_PTR));
826+
elements = object_common1(UNSERIALIZE_PASSTHRU, ZEND_STANDARD_CLASS_DEF_PTR);
827+
if (elements < 0) {
828+
return 0;
829+
}
830+
return object_common2(UNSERIALIZE_PASSTHRU, elements);
817831
}
818-
#line 819 "ext/standard/var_unserializer.c"
832+
#line 833 "ext/standard/var_unserializer.c"
819833
yy32:
820834
yych = *++YYCURSOR;
821835
if (yych == '+') goto yy33;
@@ -836,7 +850,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
836850
yych = *++YYCURSOR;
837851
if (yych != '{') goto yy18;
838852
++YYCURSOR;
839-
#line 678 "ext/standard/var_unserializer.re"
853+
#line 683 "ext/standard/var_unserializer.re"
840854
{
841855
long elements = parse_iv(start + 2);
842856
/* use iv() not uiv() in order to check data range */
@@ -857,7 +871,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
857871

858872
return finish_nested_data(UNSERIALIZE_PASSTHRU);
859873
}
860-
#line 861 "ext/standard/var_unserializer.c"
874+
#line 875 "ext/standard/var_unserializer.c"
861875
yy39:
862876
yych = *++YYCURSOR;
863877
if (yych == '+') goto yy40;
@@ -878,7 +892,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
878892
yych = *++YYCURSOR;
879893
if (yych != '"') goto yy18;
880894
++YYCURSOR;
881-
#line 643 "ext/standard/var_unserializer.re"
895+
#line 648 "ext/standard/var_unserializer.re"
882896
{
883897
size_t len, maxlen;
884898
char *str;
@@ -913,7 +927,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
913927
ZVAL_STRINGL(*rval, str, len, 0);
914928
return 1;
915929
}
916-
#line 917 "ext/standard/var_unserializer.c"
930+
#line 931 "ext/standard/var_unserializer.c"
917931
yy46:
918932
yych = *++YYCURSOR;
919933
if (yych == '+') goto yy47;
@@ -934,7 +948,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
934948
yych = *++YYCURSOR;
935949
if (yych != '"') goto yy18;
936950
++YYCURSOR;
937-
#line 610 "ext/standard/var_unserializer.re"
951+
#line 615 "ext/standard/var_unserializer.re"
938952
{
939953
size_t len, maxlen;
940954
char *str;
@@ -967,7 +981,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
967981
ZVAL_STRINGL(*rval, str, len, 1);
968982
return 1;
969983
}
970-
#line 971 "ext/standard/var_unserializer.c"
984+
#line 985 "ext/standard/var_unserializer.c"
971985
yy53:
972986
yych = *++YYCURSOR;
973987
if (yych <= '/') {
@@ -1055,7 +1069,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
10551069
}
10561070
yy63:
10571071
++YYCURSOR;
1058-
#line 600 "ext/standard/var_unserializer.re"
1072+
#line 605 "ext/standard/var_unserializer.re"
10591073
{
10601074
#if SIZEOF_LONG == 4
10611075
use_double:
@@ -1065,7 +1079,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
10651079
ZVAL_DOUBLE(*rval, zend_strtod((const char *)start + 2, NULL));
10661080
return 1;
10671081
}
1068-
#line 1069 "ext/standard/var_unserializer.c"
1082+
#line 1083 "ext/standard/var_unserializer.c"
10691083
yy65:
10701084
yych = *++YYCURSOR;
10711085
if (yych <= ',') {
@@ -1124,7 +1138,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
11241138
yych = *++YYCURSOR;
11251139
if (yych != ';') goto yy18;
11261140
++YYCURSOR;
1127-
#line 585 "ext/standard/var_unserializer.re"
1141+
#line 590 "ext/standard/var_unserializer.re"
11281142
{
11291143
*p = YYCURSOR;
11301144
INIT_PZVAL(*rval);
@@ -1139,7 +1153,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
11391153

11401154
return 1;
11411155
}
1142-
#line 1143 "ext/standard/var_unserializer.c"
1156+
#line 1157 "ext/standard/var_unserializer.c"
11431157
yy76:
11441158
yych = *++YYCURSOR;
11451159
if (yych == 'N') goto yy73;
@@ -1166,7 +1180,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
11661180
if (yych <= '9') goto yy79;
11671181
if (yych != ';') goto yy18;
11681182
++YYCURSOR;
1169-
#line 558 "ext/standard/var_unserializer.re"
1183+
#line 563 "ext/standard/var_unserializer.re"
11701184
{
11711185
#if SIZEOF_LONG == 4
11721186
int digits = YYCURSOR - start - 3;
@@ -1193,32 +1207,32 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
11931207
ZVAL_LONG(*rval, parse_iv(start + 2));
11941208
return 1;
11951209
}
1196-
#line 1197 "ext/standard/var_unserializer.c"
1210+
#line 1211 "ext/standard/var_unserializer.c"
11971211
yy83:
11981212
yych = *++YYCURSOR;
11991213
if (yych <= '/') goto yy18;
12001214
if (yych >= '2') goto yy18;
12011215
yych = *++YYCURSOR;
12021216
if (yych != ';') goto yy18;
12031217
++YYCURSOR;
1204-
#line 551 "ext/standard/var_unserializer.re"
1218+
#line 556 "ext/standard/var_unserializer.re"
12051219
{
12061220
*p = YYCURSOR;
12071221
INIT_PZVAL(*rval);
12081222
ZVAL_BOOL(*rval, parse_iv(start + 2));
12091223
return 1;
12101224
}
1211-
#line 1212 "ext/standard/var_unserializer.c"
1225+
#line 1226 "ext/standard/var_unserializer.c"
12121226
yy87:
12131227
++YYCURSOR;
1214-
#line 544 "ext/standard/var_unserializer.re"
1228+
#line 549 "ext/standard/var_unserializer.re"
12151229
{
12161230
*p = YYCURSOR;
12171231
INIT_PZVAL(*rval);
12181232
ZVAL_NULL(*rval);
12191233
return 1;
12201234
}
1221-
#line 1222 "ext/standard/var_unserializer.c"
1235+
#line 1236 "ext/standard/var_unserializer.c"
12221236
yy89:
12231237
yych = *++YYCURSOR;
12241238
if (yych <= ',') {
@@ -1241,7 +1255,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
12411255
if (yych <= '9') goto yy91;
12421256
if (yych != ';') goto yy18;
12431257
++YYCURSOR;
1244-
#line 521 "ext/standard/var_unserializer.re"
1258+
#line 526 "ext/standard/var_unserializer.re"
12451259
{
12461260
long id;
12471261

@@ -1264,7 +1278,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
12641278

12651279
return 1;
12661280
}
1267-
#line 1268 "ext/standard/var_unserializer.c"
1281+
#line 1282 "ext/standard/var_unserializer.c"
12681282
yy95:
12691283
yych = *++YYCURSOR;
12701284
if (yych <= ',') {
@@ -1287,7 +1301,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
12871301
if (yych <= '9') goto yy97;
12881302
if (yych != ';') goto yy18;
12891303
++YYCURSOR;
1290-
#line 500 "ext/standard/var_unserializer.re"
1304+
#line 505 "ext/standard/var_unserializer.re"
12911305
{
12921306
long id;
12931307

@@ -1308,9 +1322,9 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
13081322

13091323
return 1;
13101324
}
1311-
#line 1312 "ext/standard/var_unserializer.c"
1325+
#line 1326 "ext/standard/var_unserializer.c"
13121326
}
1313-
#line 863 "ext/standard/var_unserializer.re"
1327+
#line 877 "ext/standard/var_unserializer.re"
13141328

13151329

13161330
return 0;

ext/standard/var_unserializer.re

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,11 @@ static inline long object_common1(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
409409
{
410410
long elements;
411411

412+
if( *p >= max - 2) {
413+
zend_error(E_WARNING, "Bad unserialize data");
414+
return -1;
415+
}
416+
412417
elements = parse_iv2((*p) + 2, p);
413418

414419
(*p) += 2;
@@ -419,7 +424,7 @@ static inline long object_common1(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
419424
/* If this class implements Serializable, it should not land here but in object_custom(). The passed string
420425
obviously doesn't descend from the regular serializer. */
421426
zend_error(E_WARNING, "Erroneous data format for unserializing '%s'", ce->name);
422-
return 0;
427+
return -1;
423428
}
424429

425430
return elements;
@@ -697,12 +702,16 @@ use_double:
697702
}
698703
699704
"o:" iv ":" ["] {
705+
long elements;
700706
if (!var_hash) return 0;
701707

702708
INIT_PZVAL(*rval);
703709

704-
return object_common2(UNSERIALIZE_PASSTHRU,
705-
object_common1(UNSERIALIZE_PASSTHRU, ZEND_STANDARD_CLASS_DEF_PTR));
710+
elements = object_common1(UNSERIALIZE_PASSTHRU, ZEND_STANDARD_CLASS_DEF_PTR);
711+
if (elements < 0) {
712+
return 0;
713+
}
714+
return object_common2(UNSERIALIZE_PASSTHRU, elements);
706715
}
707716

708717
object ":" uiv ":" ["] {
@@ -844,6 +853,11 @@ object ":" uiv ":" ["] {
844853

845854
elements = object_common1(UNSERIALIZE_PASSTHRU, ce);
846855

856+
if (elements < 0) {
857+
efree(class_name);
858+
return 0;
859+
}
860+
847861
if (incomplete_class) {
848862
php_store_class_name(*rval, class_name, len2);
849863
}

0 commit comments

Comments
 (0)