Skip to content

Commit 86afbe1

Browse files
committed
Merge branch 'PHP-8.2'
2 parents e8edd35 + deddf46 commit 86afbe1

16 files changed

+273
-13
lines changed

ext/dom/document.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,6 +1285,7 @@ static xmlDocPtr dom_document_parser(zval *id, int mode, char *source, size_t so
12851285
options |= XML_PARSE_NOBLANKS;
12861286
}
12871287

1288+
php_libxml_sanitize_parse_ctxt_options(ctxt);
12881289
xmlCtxtUseOptions(ctxt, options);
12891290

12901291
ctxt->recovery = recover;
@@ -1594,7 +1595,9 @@ PHP_METHOD(DOMDocument, xinclude)
15941595

15951596
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
15961597

1598+
PHP_LIBXML_SANITIZE_GLOBALS(xinclude);
15971599
err = xmlXIncludeProcessFlags(docp, (int)flags);
1600+
PHP_LIBXML_RESTORE_GLOBALS(xinclude);
15981601

15991602
/* XML_XINCLUDE_START and XML_XINCLUDE_END nodes need to be removed as these
16001603
are added via xmlXIncludeProcess to mark beginning and ending of xincluded document
@@ -1634,6 +1637,7 @@ PHP_METHOD(DOMDocument, validate)
16341637

16351638
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
16361639

1640+
PHP_LIBXML_SANITIZE_GLOBALS(validate);
16371641
cvp = xmlNewValidCtxt();
16381642

16391643
cvp->userData = NULL;
@@ -1645,6 +1649,7 @@ PHP_METHOD(DOMDocument, validate)
16451649
} else {
16461650
RETVAL_FALSE;
16471651
}
1652+
PHP_LIBXML_RESTORE_GLOBALS(validate);
16481653

16491654
xmlFreeValidCtxt(cvp);
16501655

@@ -1679,14 +1684,18 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
16791684

16801685
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
16811686

1687+
PHP_LIBXML_SANITIZE_GLOBALS(new_parser_ctxt);
1688+
16821689
switch (type) {
16831690
case DOM_LOAD_FILE:
16841691
if (CHECK_NULL_PATH(source, source_len)) {
1692+
PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
16851693
zend_argument_value_error(1, "must not contain any null bytes");
16861694
RETURN_THROWS();
16871695
}
16881696
valid_file = _dom_get_valid_file_path(source, resolved_path, MAXPATHLEN);
16891697
if (!valid_file) {
1698+
PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
16901699
php_error_docref(NULL, E_WARNING, "Invalid Schema file source");
16911700
RETURN_FALSE;
16921701
}
@@ -1707,6 +1716,7 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
17071716
parser);
17081717
sptr = xmlSchemaParse(parser);
17091718
xmlSchemaFreeParserCtxt(parser);
1719+
PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
17101720
if (!sptr) {
17111721
if (!EG(exception)) {
17121722
php_error_docref(NULL, E_WARNING, "Invalid Schema");
@@ -1727,11 +1737,13 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
17271737
valid_opts |= XML_SCHEMA_VAL_VC_I_CREATE;
17281738
}
17291739

1740+
PHP_LIBXML_SANITIZE_GLOBALS(validate);
17301741
xmlSchemaSetValidOptions(vptr, valid_opts);
17311742
xmlSchemaSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
17321743
is_valid = xmlSchemaValidateDoc(vptr, docp);
17331744
xmlSchemaFree(sptr);
17341745
xmlSchemaFreeValidCtxt(vptr);
1746+
PHP_LIBXML_RESTORE_GLOBALS(validate);
17351747

17361748
if (is_valid == 0) {
17371749
RETURN_TRUE;
@@ -1802,12 +1814,14 @@ static void _dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAMETERS, int typ
18021814
return;
18031815
}
18041816

1817+
PHP_LIBXML_SANITIZE_GLOBALS(parse);
18051818
xmlRelaxNGSetParserErrors(parser,
18061819
(xmlRelaxNGValidityErrorFunc) php_libxml_error_handler,
18071820
(xmlRelaxNGValidityWarningFunc) php_libxml_error_handler,
18081821
parser);
18091822
sptr = xmlRelaxNGParse(parser);
18101823
xmlRelaxNGFreeParserCtxt(parser);
1824+
PHP_LIBXML_RESTORE_GLOBALS(parse);
18111825
if (!sptr) {
18121826
php_error_docref(NULL, E_WARNING, "Invalid RelaxNG");
18131827
RETURN_FALSE;
@@ -1903,6 +1917,7 @@ static void dom_load_html(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */
19031917
ctxt->sax->error = php_libxml_ctx_error;
19041918
ctxt->sax->warning = php_libxml_ctx_warning;
19051919
}
1920+
php_libxml_sanitize_parse_ctxt_options(ctxt);
19061921
if (options) {
19071922
htmlCtxtUseOptions(ctxt, (int)options);
19081923
}

ext/dom/documentfragment.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ PHP_METHOD(DOMDocumentFragment, appendXML) {
8080
}
8181

8282
if (data) {
83+
PHP_LIBXML_SANITIZE_GLOBALS(parse);
8384
err = xmlParseBalancedChunkMemory(nodep->doc, NULL, NULL, 0, (xmlChar *) data, &lst);
85+
PHP_LIBXML_RESTORE_GLOBALS(parse);
8486
if (err != 0) {
8587
RETURN_FALSE;
8688
}
Lines changed: 36 additions & 0 deletions
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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,42 @@ PHP_LIBXML_API void php_libxml_shutdown(void);
145145
ZEND_TSRMLS_CACHE_EXTERN()
146146
#endif
147147

148+
/* Other extension may override the global state options, these global options
149+
* are copied initially to ctxt->options. Set the options to a known good value.
150+
* See libxml2 globals.c and parserInternals.c.
151+
* The unique_name argument allows multiple sanitizes and restores within the
152+
* same function, even nested is necessary. */
153+
#define PHP_LIBXML_SANITIZE_GLOBALS(unique_name) \
154+
int xml_old_loadsubset_##unique_name = xmlLoadExtDtdDefaultValue; \
155+
xmlLoadExtDtdDefaultValue = 0; \
156+
int xml_old_validate_##unique_name = xmlDoValidityCheckingDefaultValue; \
157+
xmlDoValidityCheckingDefaultValue = 0; \
158+
int xml_old_pedantic_##unique_name = xmlPedanticParserDefault(0); \
159+
int xml_old_substitute_##unique_name = xmlSubstituteEntitiesDefault(0); \
160+
int xml_old_linenrs_##unique_name = xmlLineNumbersDefault(0); \
161+
int xml_old_blanks_##unique_name = xmlKeepBlanksDefault(1);
162+
163+
#define PHP_LIBXML_RESTORE_GLOBALS(unique_name) \
164+
xmlLoadExtDtdDefaultValue = xml_old_loadsubset_##unique_name; \
165+
xmlDoValidityCheckingDefaultValue = xml_old_validate_##unique_name; \
166+
(void) xmlPedanticParserDefault(xml_old_pedantic_##unique_name); \
167+
(void) xmlSubstituteEntitiesDefault(xml_old_substitute_##unique_name); \
168+
(void) xmlLineNumbersDefault(xml_old_linenrs_##unique_name); \
169+
(void) xmlKeepBlanksDefault(xml_old_blanks_##unique_name);
170+
171+
/* Alternative for above, working directly on the context and not setting globals.
172+
* Generally faster because no locking is involved, and this has the advantage that it sets the options to a known good value. */
173+
static zend_always_inline void php_libxml_sanitize_parse_ctxt_options(xmlParserCtxtPtr ctxt)
174+
{
175+
ctxt->loadsubset = 0;
176+
ctxt->validate = 0;
177+
ctxt->pedantic = 0;
178+
ctxt->replaceEntities = 0;
179+
ctxt->linenumbers = 0;
180+
ctxt->keepBlanks = 1;
181+
ctxt->options = 0;
182+
}
183+
148184
#else /* HAVE_LIBXML */
149185
#define libxml_module_ptr NULL
150186
#endif

ext/phar/dirstream.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,26 +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';
111-
((php_stream_dirent *) buf)->d_type = DT_UNKNOWN;
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));
112114

113115
return sizeof(php_stream_dirent);
114116
}
Lines changed: 27 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2256,7 +2256,9 @@ PHP_FUNCTION(simplexml_load_file)
22562256
RETURN_THROWS();
22572257
}
22582258

2259+
PHP_LIBXML_SANITIZE_GLOBALS(read_file);
22592260
docp = xmlReadFile(filename, NULL, (int)options);
2261+
PHP_LIBXML_RESTORE_GLOBALS(read_file);
22602262

22612263
if (!docp) {
22622264
RETURN_FALSE;
@@ -2309,7 +2311,9 @@ PHP_FUNCTION(simplexml_load_string)
23092311
RETURN_THROWS();
23102312
}
23112313

2314+
PHP_LIBXML_SANITIZE_GLOBALS(read_memory);
23122315
docp = xmlReadMemory(data, (int)data_len, NULL, NULL, (int)options);
2316+
PHP_LIBXML_RESTORE_GLOBALS(read_memory);
23132317

23142318
if (!docp) {
23152319
RETURN_FALSE;
@@ -2358,7 +2362,9 @@ PHP_METHOD(SimpleXMLElement, __construct)
23582362
RETURN_THROWS();
23592363
}
23602364

2365+
PHP_LIBXML_SANITIZE_GLOBALS(read_file_or_memory);
23612366
docp = is_url ? xmlReadFile(data, NULL, (int)options) : xmlReadMemory(data, (int)data_len, NULL, NULL, (int)options);
2367+
PHP_LIBXML_RESTORE_GLOBALS(read_file_or_memory);
23622368

23632369
if (!docp) {
23642370
((php_libxml_node_object *)sxe)->document = NULL;
Lines changed: 36 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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)