Skip to content

Commit 20c9c4a

Browse files
committed
Fix validation logic of php:function() callbacks in dom and xsl
Two issues: - Assumed that at least 1 argument (function name) was provided. - Incorrect error path for the non-callable case. Closes GH-12593.
1 parent 77a497d commit 20c9c4a

File tree

5 files changed

+90
-4
lines changed

5 files changed

+90
-4
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ PHP NEWS
1111
- DOM:
1212
. Fix registerNodeClass with abstract class crashing. (nielsdos)
1313
. Add missing NULL pointer error check. (icy17)
14+
. Fix validation logic of php:function() callbacks. (nielsdos)
1415

1516
- Fiber:
1617
. Fixed bug GH-11121 (ReflectionFiber segfault). (danog, trowski, bwoebi)
@@ -60,6 +61,7 @@ PHP NEWS
6061

6162
- XSL:
6263
. Add missing module dependency. (nielsdos)
64+
. Fix validation logic of php:function() callbacks. (nielsdos)
6365

6466
26 Oct 2023, PHP 8.1.25
6567

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
php:function() edge cases
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$doc = new DOMDocument();
9+
$doc->loadHTML('<a href="https://php.net">hello</a>');
10+
$xpath = new DOMXpath($doc);
11+
$xpath->registerNamespace("php", "http://php.net/xpath");
12+
$xpath->registerPHPFunctions();
13+
try {
14+
$xpath->query("//a[php:function(3)]");
15+
} catch (TypeError $e) {
16+
echo $e->getMessage(), "\n";
17+
}
18+
try {
19+
$xpath->query("//a[php:function()]");
20+
} catch (Throwable $e) {
21+
echo $e->getMessage(), "\n";
22+
}
23+
24+
?>
25+
--EXPECT--
26+
Handler name must be a string
27+
Function name must be passed as the first argument

ext/dom/xpath.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,17 @@ static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs,
7070
return;
7171
}
7272

73+
if (UNEXPECTED(nargs == 0)) {
74+
zend_throw_error(NULL, "Function name must be passed as the first argument");
75+
return;
76+
}
77+
7378
fci.param_count = nargs - 1;
7479
if (fci.param_count > 0) {
7580
fci.params = safe_emalloc(fci.param_count, sizeof(zval), 0);
7681
}
7782
/* Reverse order to pop values off ctxt stack */
78-
for (i = nargs - 2; i >= 0; i--) {
83+
for (i = fci.param_count - 1; i >= 0; i--) {
7984
obj = valuePop(ctxt);
8085
switch (obj->type) {
8186
case XPATH_STRING:
@@ -128,11 +133,12 @@ static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs,
128133

129134
fci.size = sizeof(fci);
130135

136+
/* Last element of the stack is the function name */
131137
obj = valuePop(ctxt);
132138
if (obj->stringval == NULL) {
133139
zend_type_error("Handler name must be a string");
134140
xmlXPathFreeObject(obj);
135-
goto cleanup;
141+
goto cleanup_no_callable;
136142
}
137143
ZVAL_STRING(&fci.function_name, (char *) obj->stringval);
138144
xmlXPathFreeObject(obj);
@@ -177,6 +183,7 @@ static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs,
177183
cleanup:
178184
zend_string_release_ex(callable, 0);
179185
zval_ptr_dtor_nogc(&fci.function_name);
186+
cleanup_no_callable:
180187
if (fci.param_count > 0) {
181188
for (i = 0; i < nargs - 1; i++) {
182189
zval_ptr_dtor(&fci.params[i]);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
php:function() edge cases
3+
--EXTENSIONS--
4+
xsl
5+
--FILE--
6+
<?php
7+
8+
function test($input) {
9+
$xsl = new DomDocument();
10+
$xsl->loadXML('<?xml version="1.0" encoding="iso-8859-1" ?>
11+
<xsl:stylesheet version="1.0"
12+
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
13+
xmlns:php="http://php.net/xsl">
14+
<xsl:template match="/">
15+
<xsl:value-of select="php:function(' . $input . ')" />
16+
</xsl:template>
17+
</xsl:stylesheet>');
18+
19+
$inputdom = new DomDocument();
20+
$inputdom->loadXML('<?xml version="1.0" encoding="iso-8859-1" ?>
21+
<today></today>');
22+
23+
$proc = new XsltProcessor();
24+
$proc->registerPhpFunctions();
25+
$xsl = $proc->importStylesheet($xsl);
26+
try {
27+
$proc->transformToDoc($inputdom);
28+
} catch (Exception $e) {
29+
echo $e->getMessage(), "\n";
30+
}
31+
}
32+
33+
try {
34+
test("");
35+
} catch (Throwable $e) {
36+
echo $e->getMessage(), "\n";
37+
}
38+
39+
test("3");
40+
41+
?>
42+
--EXPECTF--
43+
Function name must be passed as the first argument
44+
45+
Warning: XSLTProcessor::transformToDoc(): Handler name must be a string in %s on line %d

ext/xsl/xsltprocessor.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,17 @@ static void xsl_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, int t
141141
return;
142142
}
143143

144+
if (UNEXPECTED(nargs == 0)) {
145+
zend_throw_error(NULL, "Function name must be passed as the first argument");
146+
return;
147+
}
148+
144149
fci.param_count = nargs - 1;
145150
if (fci.param_count > 0) {
146151
args = safe_emalloc(fci.param_count, sizeof(zval), 0);
147152
}
148153
/* Reverse order to pop values off ctxt stack */
149-
for (i = nargs - 2; i >= 0; i--) {
154+
for (i = fci.param_count - 1; i >= 0; i--) {
150155
obj = valuePop(ctxt);
151156
if (obj == NULL) {
152157
ZVAL_NULL(&args[i]);
@@ -221,7 +226,7 @@ static void xsl_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, int t
221226
fci.params = NULL;
222227
}
223228

224-
229+
/* Last element of the stack is the function name */
225230
obj = valuePop(ctxt);
226231
if (obj == NULL || obj->stringval == NULL) {
227232
php_error_docref(NULL, E_WARNING, "Handler name must be a string");

0 commit comments

Comments
 (0)