Skip to content

Commit 0870ebb

Browse files
committed
Merge branch 'PHP-8.0' into PHP-8.1
2 parents 3e9792f + c283c3a commit 0870ebb

16 files changed

+268
-12
lines changed

ext/dom/document.c

+15
Original file line numberDiff line numberDiff line change
@@ -1281,6 +1281,7 @@ static xmlDocPtr dom_document_parser(zval *id, int mode, char *source, size_t so
12811281
options |= XML_PARSE_NOBLANKS;
12821282
}
12831283

1284+
php_libxml_sanitize_parse_ctxt_options(ctxt);
12841285
xmlCtxtUseOptions(ctxt, options);
12851286

12861287
ctxt->recovery = recover;
@@ -1575,7 +1576,9 @@ PHP_METHOD(DOMDocument, xinclude)
15751576

15761577
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
15771578

1579+
PHP_LIBXML_SANITIZE_GLOBALS(xinclude);
15781580
err = xmlXIncludeProcessFlags(docp, (int)flags);
1581+
PHP_LIBXML_RESTORE_GLOBALS(xinclude);
15791582

15801583
/* XML_XINCLUDE_START and XML_XINCLUDE_END nodes need to be removed as these
15811584
are added via xmlXIncludeProcess to mark beginning and ending of xincluded document
@@ -1613,6 +1616,7 @@ PHP_METHOD(DOMDocument, validate)
16131616

16141617
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
16151618

1619+
PHP_LIBXML_SANITIZE_GLOBALS(validate);
16161620
cvp = xmlNewValidCtxt();
16171621

16181622
cvp->userData = NULL;
@@ -1624,6 +1628,7 @@ PHP_METHOD(DOMDocument, validate)
16241628
} else {
16251629
RETVAL_FALSE;
16261630
}
1631+
PHP_LIBXML_RESTORE_GLOBALS(validate);
16271632

16281633
xmlFreeValidCtxt(cvp);
16291634

@@ -1658,14 +1663,18 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
16581663

16591664
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
16601665

1666+
PHP_LIBXML_SANITIZE_GLOBALS(new_parser_ctxt);
1667+
16611668
switch (type) {
16621669
case DOM_LOAD_FILE:
16631670
if (CHECK_NULL_PATH(source, source_len)) {
1671+
PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
16641672
zend_argument_value_error(1, "must not contain any null bytes");
16651673
RETURN_THROWS();
16661674
}
16671675
valid_file = _dom_get_valid_file_path(source, resolved_path, MAXPATHLEN);
16681676
if (!valid_file) {
1677+
PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
16691678
php_error_docref(NULL, E_WARNING, "Invalid Schema file source");
16701679
RETURN_FALSE;
16711680
}
@@ -1686,6 +1695,7 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
16861695
parser);
16871696
sptr = xmlSchemaParse(parser);
16881697
xmlSchemaFreeParserCtxt(parser);
1698+
PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
16891699
if (!sptr) {
16901700
if (!EG(exception)) {
16911701
php_error_docref(NULL, E_WARNING, "Invalid Schema");
@@ -1706,11 +1716,13 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
17061716
valid_opts |= XML_SCHEMA_VAL_VC_I_CREATE;
17071717
}
17081718

1719+
PHP_LIBXML_SANITIZE_GLOBALS(validate);
17091720
xmlSchemaSetValidOptions(vptr, valid_opts);
17101721
xmlSchemaSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
17111722
is_valid = xmlSchemaValidateDoc(vptr, docp);
17121723
xmlSchemaFree(sptr);
17131724
xmlSchemaFreeValidCtxt(vptr);
1725+
PHP_LIBXML_RESTORE_GLOBALS(validate);
17141726

17151727
if (is_valid == 0) {
17161728
RETURN_TRUE;
@@ -1781,12 +1793,14 @@ static void _dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAMETERS, int typ
17811793
return;
17821794
}
17831795

1796+
PHP_LIBXML_SANITIZE_GLOBALS(parse);
17841797
xmlRelaxNGSetParserErrors(parser,
17851798
(xmlRelaxNGValidityErrorFunc) php_libxml_error_handler,
17861799
(xmlRelaxNGValidityWarningFunc) php_libxml_error_handler,
17871800
parser);
17881801
sptr = xmlRelaxNGParse(parser);
17891802
xmlRelaxNGFreeParserCtxt(parser);
1803+
PHP_LIBXML_RESTORE_GLOBALS(parse);
17901804
if (!sptr) {
17911805
php_error_docref(NULL, E_WARNING, "Invalid RelaxNG");
17921806
RETURN_FALSE;
@@ -1885,6 +1899,7 @@ static void dom_load_html(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */
18851899
ctxt->sax->error = php_libxml_ctx_error;
18861900
ctxt->sax->warning = php_libxml_ctx_warning;
18871901
}
1902+
php_libxml_sanitize_parse_ctxt_options(ctxt);
18881903
if (options) {
18891904
htmlCtxtUseOptions(ctxt, (int)options);
18901905
}

ext/dom/documentfragment.c

+2
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ PHP_METHOD(DOMDocumentFragment, appendXML) {
114114
}
115115

116116
if (data) {
117+
PHP_LIBXML_SANITIZE_GLOBALS(parse);
117118
err = xmlParseBalancedChunkMemory(nodep->doc, NULL, NULL, 0, (xmlChar *) data, &lst);
119+
PHP_LIBXML_RESTORE_GLOBALS(parse);
118120
if (err != 0) {
119121
RETURN_FALSE;
120122
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('libxml')) die('skip libxml extension not available');
6+
if (!extension_loaded('dom')) die('skip dom extension not available');
7+
if (!extension_loaded('zend-test')) die('skip zend-test extension not available');
8+
?>
9+
--FILE--
10+
<?php
11+
12+
$xml = "<?xml version='1.0'?><!DOCTYPE root [<!ENTITY % bork SYSTEM \"php://nope\"> %bork;]><nothing/>";
13+
14+
libxml_use_internal_errors(true);
15+
16+
function parseXML($xml) {
17+
$doc = new DOMDocument();
18+
@$doc->loadXML($xml);
19+
$doc->createDocumentFragment()->appendXML("&bork;");
20+
foreach (libxml_get_errors() as $error) {
21+
var_dump(trim($error->message));
22+
}
23+
}
24+
25+
parseXML($xml);
26+
zend_test_override_libxml_global_state();
27+
parseXML($xml);
28+
29+
echo "Done\n";
30+
31+
?>
32+
--EXPECT--
33+
string(25) "Entity 'bork' not defined"
34+
string(25) "Entity 'bork' not defined"
35+
string(25) "Entity 'bork' not defined"
36+
Done

ext/libxml/php_libxml.h

+36
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,42 @@ PHP_LIBXML_API void php_libxml_shutdown(void);
118118
ZEND_TSRMLS_CACHE_EXTERN()
119119
#endif
120120

121+
/* Other extension may override the global state options, these global options
122+
* are copied initially to ctxt->options. Set the options to a known good value.
123+
* See libxml2 globals.c and parserInternals.c.
124+
* The unique_name argument allows multiple sanitizes and restores within the
125+
* same function, even nested is necessary. */
126+
#define PHP_LIBXML_SANITIZE_GLOBALS(unique_name) \
127+
int xml_old_loadsubset_##unique_name = xmlLoadExtDtdDefaultValue; \
128+
xmlLoadExtDtdDefaultValue = 0; \
129+
int xml_old_validate_##unique_name = xmlDoValidityCheckingDefaultValue; \
130+
xmlDoValidityCheckingDefaultValue = 0; \
131+
int xml_old_pedantic_##unique_name = xmlPedanticParserDefault(0); \
132+
int xml_old_substitute_##unique_name = xmlSubstituteEntitiesDefault(0); \
133+
int xml_old_linenrs_##unique_name = xmlLineNumbersDefault(0); \
134+
int xml_old_blanks_##unique_name = xmlKeepBlanksDefault(1);
135+
136+
#define PHP_LIBXML_RESTORE_GLOBALS(unique_name) \
137+
xmlLoadExtDtdDefaultValue = xml_old_loadsubset_##unique_name; \
138+
xmlDoValidityCheckingDefaultValue = xml_old_validate_##unique_name; \
139+
(void) xmlPedanticParserDefault(xml_old_pedantic_##unique_name); \
140+
(void) xmlSubstituteEntitiesDefault(xml_old_substitute_##unique_name); \
141+
(void) xmlLineNumbersDefault(xml_old_linenrs_##unique_name); \
142+
(void) xmlKeepBlanksDefault(xml_old_blanks_##unique_name);
143+
144+
/* Alternative for above, working directly on the context and not setting globals.
145+
* Generally faster because no locking is involved, and this has the advantage that it sets the options to a known good value. */
146+
static zend_always_inline void php_libxml_sanitize_parse_ctxt_options(xmlParserCtxtPtr ctxt)
147+
{
148+
ctxt->loadsubset = 0;
149+
ctxt->validate = 0;
150+
ctxt->pedantic = 0;
151+
ctxt->replaceEntities = 0;
152+
ctxt->linenumbers = 0;
153+
ctxt->keepBlanks = 1;
154+
ctxt->options = 0;
155+
}
156+
121157
#else /* HAVE_LIBXML */
122158
#define libxml_module_ptr NULL
123159
#endif

ext/phar/dirstream.c

+9-6
Original file line numberDiff line numberDiff line change
@@ -89,25 +89,28 @@ static int phar_dir_seek(php_stream *stream, zend_off_t offset, int whence, zend
8989
*/
9090
static ssize_t phar_dir_read(php_stream *stream, char *buf, size_t count) /* {{{ */
9191
{
92-
size_t to_read;
9392
HashTable *data = (HashTable *)stream->abstract;
9493
zend_string *str_key;
9594
zend_ulong unused;
9695

96+
if (count != sizeof(php_stream_dirent)) {
97+
return -1;
98+
}
99+
97100
if (HASH_KEY_NON_EXISTENT == zend_hash_get_current_key(data, &str_key, &unused)) {
98101
return 0;
99102
}
100103

101104
zend_hash_move_forward(data);
102-
to_read = MIN(ZSTR_LEN(str_key), count);
103105

104-
if (to_read == 0 || count < ZSTR_LEN(str_key)) {
106+
php_stream_dirent *dirent = (php_stream_dirent *) buf;
107+
108+
if (sizeof(dirent->d_name) <= ZSTR_LEN(str_key)) {
105109
return 0;
106110
}
107111

108-
memset(buf, 0, sizeof(php_stream_dirent));
109-
memcpy(((php_stream_dirent *) buf)->d_name, ZSTR_VAL(str_key), to_read);
110-
((php_stream_dirent *) buf)->d_name[to_read + 1] = '\0';
112+
memset(dirent, 0, sizeof(php_stream_dirent));
113+
PHP_STRLCPY(dirent->d_name, ZSTR_VAL(str_key), sizeof(dirent->d_name), ZSTR_LEN(str_key));
111114

112115
return sizeof(php_stream_dirent);
113116
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
GHSA-jqcx-ccgc-xwhv (Buffer overflow and overread in phar_dir_read())
3+
--SKIPIF--
4+
<?php if (!extension_loaded("phar")) die("skip"); ?>
5+
--INI--
6+
phar.readonly=0
7+
--FILE--
8+
<?php
9+
$phar = new Phar(__DIR__. '/GHSA-jqcx-ccgc-xwhv.phar');
10+
$phar->startBuffering();
11+
$phar->addFromString(str_repeat('A', PHP_MAXPATHLEN - 1), 'This is the content of file 1.');
12+
$phar->addFromString(str_repeat('B', PHP_MAXPATHLEN - 1).'C', 'This is the content of file 2.');
13+
$phar->stopBuffering();
14+
15+
$handle = opendir('phar://' . __DIR__ . '/GHSA-jqcx-ccgc-xwhv.phar');
16+
var_dump(strlen(readdir($handle)));
17+
// Must not be a string of length PHP_MAXPATHLEN+1
18+
var_dump(readdir($handle));
19+
closedir($handle);
20+
?>
21+
--CLEAN--
22+
<?php
23+
unlink(__DIR__. '/GHSA-jqcx-ccgc-xwhv.phar');
24+
?>
25+
--EXPECTF--
26+
int(%d)
27+
bool(false)

ext/simplexml/simplexml.c

+6
Original file line numberDiff line numberDiff line change
@@ -2262,7 +2262,9 @@ PHP_FUNCTION(simplexml_load_file)
22622262
RETURN_THROWS();
22632263
}
22642264

2265+
PHP_LIBXML_SANITIZE_GLOBALS(read_file);
22652266
docp = xmlReadFile(filename, NULL, (int)options);
2267+
PHP_LIBXML_RESTORE_GLOBALS(read_file);
22662268

22672269
if (!docp) {
22682270
RETURN_FALSE;
@@ -2315,7 +2317,9 @@ PHP_FUNCTION(simplexml_load_string)
23152317
RETURN_THROWS();
23162318
}
23172319

2320+
PHP_LIBXML_SANITIZE_GLOBALS(read_memory);
23182321
docp = xmlReadMemory(data, (int)data_len, NULL, NULL, (int)options);
2322+
PHP_LIBXML_RESTORE_GLOBALS(read_memory);
23192323

23202324
if (!docp) {
23212325
RETURN_FALSE;
@@ -2364,7 +2368,9 @@ PHP_METHOD(SimpleXMLElement, __construct)
23642368
RETURN_THROWS();
23652369
}
23662370

2371+
PHP_LIBXML_SANITIZE_GLOBALS(read_file_or_memory);
23672372
docp = is_url ? xmlReadFile(data, NULL, (int)options) : xmlReadMemory(data, (int)data_len, NULL, NULL, (int)options);
2373+
PHP_LIBXML_RESTORE_GLOBALS(read_file_or_memory);
23682374

23692375
if (!docp) {
23702376
((php_libxml_node_object *)sxe)->document = NULL;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('libxml')) die('skip libxml extension not available');
6+
if (!extension_loaded('simplexml')) die('skip simplexml extension not available');
7+
if (!extension_loaded('zend-test')) die('skip zend-test extension not available');
8+
?>
9+
--FILE--
10+
<?php
11+
12+
$xml = "<?xml version='1.0'?><!DOCTYPE root [<!ENTITY % bork SYSTEM \"php://nope\"> %bork;]><nothing/>";
13+
14+
libxml_use_internal_errors(true);
15+
zend_test_override_libxml_global_state();
16+
17+
echo "--- String test ---\n";
18+
simplexml_load_string($xml);
19+
echo "--- Constructor test ---\n";
20+
new SimpleXMLElement($xml);
21+
echo "--- File test ---\n";
22+
file_put_contents("libxml_global_state_entity_loader_bypass.tmp", $xml);
23+
simplexml_load_file("libxml_global_state_entity_loader_bypass.tmp");
24+
25+
echo "Done\n";
26+
27+
?>
28+
--CLEAN--
29+
<?php
30+
@unlink("libxml_global_state_entity_loader_bypass.tmp");
31+
?>
32+
--EXPECT--
33+
--- String test ---
34+
--- Constructor test ---
35+
--- File test ---
36+
Done

ext/soap/php_xml.c

+2
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ xmlDocPtr soap_xmlParseFile(const char *filename)
9191
if (ctxt) {
9292
bool old;
9393

94+
php_libxml_sanitize_parse_ctxt_options(ctxt);
9495
ctxt->keepBlanks = 0;
9596
ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace;
9697
ctxt->sax->comment = soap_Comment;
@@ -139,6 +140,7 @@ xmlDocPtr soap_xmlParseMemory(const void *buf, size_t buf_size)
139140
if (ctxt) {
140141
bool old;
141142

143+
php_libxml_sanitize_parse_ctxt_options(ctxt);
142144
ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace;
143145
ctxt->sax->comment = soap_Comment;
144146
ctxt->sax->warning = NULL;

ext/xml/compat.c

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "php.h"
1818
#if defined(HAVE_LIBXML) && (defined(HAVE_XML) || defined(HAVE_XMLRPC)) && !defined(HAVE_LIBEXPAT)
1919
#include "expat_compat.h"
20+
#include "ext/libxml/php_libxml.h"
2021

2122
typedef struct _php_xml_ns {
2223
xmlNsPtr nsptr;
@@ -469,6 +470,7 @@ XML_ParserCreate_MM(const XML_Char *encoding, const XML_Memory_Handling_Suite *m
469470
return NULL;
470471
}
471472

473+
php_libxml_sanitize_parse_ctxt_options(parser->parser);
472474
xmlCtxtUseOptions(parser->parser, XML_PARSE_OLDSAX);
473475

474476
parser->parser->replaceEntities = 1;

0 commit comments

Comments
 (0)