Skip to content

Commit 575ee23

Browse files
committed
Merge branch 'PHP-8.3' into PHP-8.4
* PHP-8.3: Fix GH-17187: unreachable program point in zend_hash
2 parents ef03609 + b621b3a commit 575ee23

File tree

4 files changed

+234
-26
lines changed

4 files changed

+234
-26
lines changed

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ PHP NEWS
120120
- Windows:
121121
. Hardened proc_open() against cmd.exe hijacking. (cmb)
122122

123+
- XML:
124+
. Fixed bug GH-1718 (unreachable program point in zend_hash). (nielsdos)
125+
123126
05 Dec 2024, PHP 8.4.2
124127

125128
- BcMath:

ext/xml/tests/gh17187_1.phpt

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
--TEST--
2+
GH-17187 (unreachable program point in zend_hash)
3+
--EXTENSIONS--
4+
xml
5+
--CREDITS--
6+
chongwick
7+
--FILE--
8+
<?php
9+
class ImmutableParser {
10+
private $parser;
11+
private $immutableData;
12+
private $arrayCopy;
13+
14+
public function __construct() {
15+
$this->parser = xml_parser_create();
16+
xml_set_element_handler($this->parser, function ($parser, $name, $attrs) {
17+
echo "open\n";
18+
var_dump($name, $attrs);
19+
$this->arrayCopy = [$this]; // Create cycle intentionally
20+
$this->immutableData = $this->arrayCopy;
21+
}, function ($parser, $name) {
22+
echo "close\n";
23+
var_dump($name);
24+
});
25+
}
26+
27+
public function parseXml($xml) {
28+
$this->immutableData = array();
29+
xml_parse_into_struct($this->parser, $xml, $this->immutableData, $this->immutableData);
30+
return $this->immutableData;
31+
}
32+
}
33+
$immutableParser = new ImmutableParser();
34+
$xml = "<container><child/></container>";
35+
$immutableData = $immutableParser->parseXml($xml);
36+
var_dump($immutableData);
37+
?>
38+
--EXPECT--
39+
open
40+
string(9) "CONTAINER"
41+
array(0) {
42+
}
43+
open
44+
string(5) "CHILD"
45+
array(0) {
46+
}
47+
close
48+
string(5) "CHILD"
49+
close
50+
string(9) "CONTAINER"
51+
array(5) {
52+
[0]=>
53+
object(ImmutableParser)#1 (3) {
54+
["parser":"ImmutableParser":private]=>
55+
object(XMLParser)#2 (0) {
56+
}
57+
["immutableData":"ImmutableParser":private]=>
58+
*RECURSION*
59+
["arrayCopy":"ImmutableParser":private]=>
60+
array(1) {
61+
[0]=>
62+
*RECURSION*
63+
}
64+
}
65+
["CHILD"]=>
66+
array(1) {
67+
[0]=>
68+
int(1)
69+
}
70+
[1]=>
71+
array(3) {
72+
["tag"]=>
73+
string(5) "CHILD"
74+
["type"]=>
75+
string(8) "complete"
76+
["level"]=>
77+
int(2)
78+
}
79+
["CONTAINER"]=>
80+
array(1) {
81+
[0]=>
82+
int(2)
83+
}
84+
[2]=>
85+
array(3) {
86+
["tag"]=>
87+
string(9) "CONTAINER"
88+
["type"]=>
89+
string(5) "close"
90+
["level"]=>
91+
int(1)
92+
}
93+
}

ext/xml/tests/gh17187_2.phpt

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
--TEST--
2+
GH-17187 (unreachable program point in zend_hash)
3+
--EXTENSIONS--
4+
xml
5+
--CREDITS--
6+
chongwick
7+
--FILE--
8+
<?php
9+
class ImmutableParser {
10+
public $parser;
11+
public $immutableData1;
12+
public $immutableData2;
13+
14+
public function __construct() {
15+
$this->parser = xml_parser_create();
16+
xml_set_element_handler($this->parser, function ($parser, $name, $attrs) {
17+
echo "open\n";
18+
var_dump($name, $attrs);
19+
$this->immutableData1 = 0xdead;
20+
$this->immutableData2 = 0xbeef;
21+
}, function ($parser, $name) {
22+
echo "close\n";
23+
var_dump($name);
24+
});
25+
}
26+
27+
public function parseXml($xml) {
28+
$this->immutableData1 = array();
29+
$this->immutableData2 = array();
30+
xml_parse_into_struct($this->parser, $xml, $this->immutableData1, $this->immutableData2);
31+
}
32+
}
33+
$immutableParser = new ImmutableParser();
34+
$xml = "<container><child/></container>";
35+
$immutableParser->parseXml($xml);
36+
var_dump($immutableParser->immutableData1);
37+
var_dump($immutableParser->immutableData2);
38+
?>
39+
--EXPECT--
40+
open
41+
string(9) "CONTAINER"
42+
array(0) {
43+
}
44+
open
45+
string(5) "CHILD"
46+
array(0) {
47+
}
48+
close
49+
string(5) "CHILD"
50+
close
51+
string(9) "CONTAINER"
52+
int(57005)
53+
int(48879)

ext/xml/xml.c

+85-26
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,13 @@ typedef struct {
7979
zend_fcall_info_cache externalEntityRefHandler;
8080
zend_fcall_info_cache startNamespaceDeclHandler;
8181
zend_fcall_info_cache endNamespaceDeclHandler;
82-
8382
zval data;
8483
zval info;
84+
8585
int level;
8686
int toffset;
8787
int curtag;
88-
zval *ctag;
88+
zend_long ctag_index;
8989
char **ltags;
9090
bool lastwasopen;
9191
bool skipwhite;
@@ -333,6 +333,8 @@ static void xml_parser_free_obj(zend_object *object)
333333
{
334334
xml_parser *parser = xml_parser_from_obj(object);
335335

336+
zval_ptr_dtor(&parser->info);
337+
zval_ptr_dtor(&parser->data);
336338
if (parser->parser) {
337339
XML_ParserFree(parser->parser);
338340
}
@@ -425,6 +427,8 @@ static HashTable *xml_parser_get_gc(zend_object *object, zval **table, int *n)
425427
if (ZEND_FCC_INITIALIZED(parser->endNamespaceDeclHandler)) {
426428
zend_get_gc_buffer_add_fcc(gc_buffer, &parser->endNamespaceDeclHandler);
427429
}
430+
zend_get_gc_buffer_add_zval(gc_buffer, &parser->data);
431+
zend_get_gc_buffer_add_zval(gc_buffer, &parser->info);
428432

429433
zend_get_gc_buffer_use(gc_buffer, table, n);
430434

@@ -553,15 +557,18 @@ static void xml_add_to_info(xml_parser *parser, const char *name)
553557
{
554558
zval *element;
555559

556-
if (Z_ISUNDEF(parser->info)) {
560+
if (Z_ISUNDEF(parser->info) || UNEXPECTED(Z_TYPE_P(Z_REFVAL(parser->info)) != IS_ARRAY)) {
557561
return;
558562
}
559563

564+
SEPARATE_ARRAY(Z_REFVAL(parser->info));
565+
zend_array *arr = Z_ARRVAL_P(Z_REFVAL(parser->info));
566+
560567
size_t name_len = strlen(name);
561-
if ((element = zend_hash_str_find(Z_ARRVAL(parser->info), name, name_len)) == NULL) {
568+
if ((element = zend_hash_str_find(arr, name, name_len)) == NULL) {
562569
zval values;
563570
array_init(&values);
564-
element = zend_hash_str_update(Z_ARRVAL(parser->info), name, name_len, &values);
571+
element = zend_hash_str_update(arr, name, name_len, &values);
565572
}
566573

567574
add_next_index_long(element, parser->curtag);
@@ -585,6 +592,28 @@ static zend_string *xml_decode_tag(xml_parser *parser, const XML_Char *tag)
585592
}
586593
/* }}} */
587594

595+
static zval *xml_get_separated_data(xml_parser *parser)
596+
{
597+
if (EXPECTED(Z_TYPE_P(Z_REFVAL(parser->data)) == IS_ARRAY)) {
598+
SEPARATE_ARRAY(Z_REFVAL(parser->data));
599+
return Z_REFVAL(parser->data);
600+
}
601+
return NULL;
602+
}
603+
604+
static zval *xml_get_ctag(xml_parser *parser)
605+
{
606+
zval *data = xml_get_separated_data(parser);
607+
if (EXPECTED(data)) {
608+
zval *zv = zend_hash_index_find_deref(Z_ARRVAL_P(data), parser->ctag_index);
609+
if (EXPECTED(zv && Z_TYPE_P(zv) == IS_ARRAY)) {
610+
SEPARATE_ARRAY(zv);
611+
return zv;
612+
}
613+
}
614+
return NULL;
615+
}
616+
588617
/* {{{ xml_startElementHandler() */
589618
void xml_startElementHandler(void *userData, const XML_Char *name, const XML_Char **attributes)
590619
{
@@ -666,7 +695,19 @@ void xml_startElementHandler(void *userData, const XML_Char *name, const XML_Cha
666695
zval_ptr_dtor(&atr);
667696
}
668697

669-
parser->ctag = zend_hash_next_index_insert(Z_ARRVAL(parser->data), &tag);
698+
zval *data = xml_get_separated_data(parser);
699+
if (EXPECTED(data)) {
700+
/* Note: due to array resizes or user interference,
701+
* we have to store an index instead of a zval into the array's memory. */
702+
zend_array *arr = Z_ARRVAL_P(data);
703+
if (EXPECTED(zend_hash_next_index_insert(arr, &tag))) {
704+
parser->ctag_index = arr->nNextFreeElement - 1;
705+
} else {
706+
zval_ptr_dtor(&tag);
707+
}
708+
} else {
709+
zval_ptr_dtor(&tag);
710+
}
670711
} else if (parser->level == (XML_MAXLEVEL + 1)) {
671712
php_error_docref(NULL, E_WARNING, "Maximum depth exceeded - Results truncated");
672713
}
@@ -701,17 +742,21 @@ void xml_endElementHandler(void *userData, const XML_Char *name)
701742
zval tag;
702743

703744
if (parser->lastwasopen) {
704-
add_assoc_string(parser->ctag, "type", "complete");
745+
zval *zv = xml_get_ctag(parser);
746+
if (EXPECTED(zv)) {
747+
add_assoc_string(zv, "type", "complete");
748+
}
705749
} else {
706-
array_init(&tag);
707-
708750
xml_add_to_info(parser, ZSTR_VAL(tag_name) + parser->toffset);
709751

710-
add_assoc_string(&tag, "tag", SKIP_TAGSTART(ZSTR_VAL(tag_name))); /* cast to avoid gcc-warning */
711-
add_assoc_string(&tag, "type", "close");
712-
add_assoc_long(&tag, "level", parser->level);
713-
714-
zend_hash_next_index_insert(Z_ARRVAL(parser->data), &tag);
752+
zval *data = xml_get_separated_data(parser);
753+
if (EXPECTED(data)) {
754+
array_init(&tag);
755+
add_assoc_string(&tag, "tag", SKIP_TAGSTART(ZSTR_VAL(tag_name))); /* cast to avoid gcc-warning */
756+
add_assoc_string(&tag, "type", "close");
757+
add_assoc_long(&tag, "level", parser->level);
758+
zend_hash_next_index_insert(Z_ARRVAL_P(data), &tag);
759+
}
715760
}
716761

717762
parser->lastwasopen = 0;
@@ -770,27 +815,41 @@ void xml_characterDataHandler(void *userData, const XML_Char *s, int len)
770815
}
771816
}
772817
if (parser->lastwasopen) {
818+
zval *ctag = xml_get_ctag(parser);
819+
if (UNEXPECTED(!ctag)) {
820+
zend_string_release_ex(decoded_value, false);
821+
return;
822+
}
823+
773824
zval *myval;
774825
/* check if the current tag already has a value - if yes append to that! */
775-
if ((myval = zend_hash_find(Z_ARRVAL_P(parser->ctag), ZSTR_KNOWN(ZEND_STR_VALUE)))) {
826+
if ((myval = zend_hash_find(Z_ARRVAL_P(ctag), ZSTR_KNOWN(ZEND_STR_VALUE))) && Z_TYPE_P(myval) == IS_STRING) {
776827
size_t newlen = Z_STRLEN_P(myval) + ZSTR_LEN(decoded_value);
777828
Z_STR_P(myval) = zend_string_extend(Z_STR_P(myval), newlen, 0);
778829
strncpy(Z_STRVAL_P(myval) + Z_STRLEN_P(myval) - ZSTR_LEN(decoded_value),
779830
ZSTR_VAL(decoded_value), ZSTR_LEN(decoded_value) + 1);
780831
zend_string_release_ex(decoded_value, 0);
781832
} else {
782833
if (doprint || (! parser->skipwhite)) {
783-
add_assoc_str(parser->ctag, "value", decoded_value);
834+
add_assoc_str(ctag, "value", decoded_value);
784835
} else {
785836
zend_string_release_ex(decoded_value, 0);
786837
}
787838
}
788839
} else {
789840
zval tag;
790841
zval *curtag, *mytype, *myval;
791-
ZEND_HASH_REVERSE_FOREACH_VAL(Z_ARRVAL(parser->data), curtag) {
792-
if ((mytype = zend_hash_str_find(Z_ARRVAL_P(curtag),"type", sizeof("type") - 1))) {
793-
if (zend_string_equals_literal(Z_STR_P(mytype), "cdata")) {
842+
843+
zval *data = xml_get_separated_data(parser);
844+
if (UNEXPECTED(!data)) {
845+
zend_string_release_ex(decoded_value, false);
846+
return;
847+
}
848+
849+
ZEND_HASH_REVERSE_FOREACH_VAL(Z_ARRVAL_P(data), curtag) {
850+
if (EXPECTED(Z_TYPE_P(curtag) == IS_ARRAY) && (mytype = zend_hash_str_find(Z_ARRVAL_P(curtag),"type", sizeof("type") - 1))) {
851+
if (EXPECTED(Z_TYPE_P(mytype) == IS_STRING) && zend_string_equals_literal(Z_STR_P(mytype), "cdata")) {
852+
SEPARATE_ARRAY(curtag);
794853
if ((myval = zend_hash_find(Z_ARRVAL_P(curtag), ZSTR_KNOWN(ZEND_STR_VALUE)))) {
795854
size_t newlen = Z_STRLEN_P(myval) + ZSTR_LEN(decoded_value);
796855
Z_STR_P(myval) = zend_string_extend(Z_STR_P(myval), newlen, 0);
@@ -810,7 +869,7 @@ void xml_characterDataHandler(void *userData, const XML_Char *s, int len)
810869
add_assoc_str(&tag, "value", decoded_value);
811870
add_assoc_string(&tag, "type", "cdata");
812871
add_assoc_long(&tag, "level", parser->level);
813-
zend_hash_next_index_insert(Z_ARRVAL(parser->data), &tag);
872+
zend_hash_next_index_insert(Z_ARRVAL_P(data), &tag);
814873
} else if (parser->level == (XML_MAXLEVEL + 1)) {
815874
php_error_docref(NULL, E_WARNING, "Maximum depth exceeded - Results truncated");
816875
} else {
@@ -1366,21 +1425,21 @@ PHP_FUNCTION(xml_parse_into_struct)
13661425
}
13671426

13681427
if (info) {
1369-
info = zend_try_array_init(info);
1370-
if (!info) {
1428+
if (!zend_try_array_init(info)) {
13711429
RETURN_THROWS();
13721430
}
13731431
}
13741432

1375-
xdata = zend_try_array_init(xdata);
1376-
if (!xdata) {
1433+
if (!zend_try_array_init(xdata)) {
13771434
RETURN_THROWS();
13781435
}
13791436

1380-
ZVAL_COPY_VALUE(&parser->data, xdata);
1437+
zval_ptr_dtor(&parser->data);
1438+
ZVAL_COPY(&parser->data, xdata);
13811439

13821440
if (info) {
1383-
ZVAL_COPY_VALUE(&parser->info, info);
1441+
zval_ptr_dtor(&parser->info);
1442+
ZVAL_COPY(&parser->info, info);
13841443
}
13851444

13861445
parser->level = 0;

0 commit comments

Comments
 (0)