Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.4.27

- CLI
. Fix GH-22567 (Windows ZTS CLI SAPI should refresh its TSRMLS cache during
request activation). (matyhtf)

- DOM:
. Fixed use-after-free when re-constructing a DOMXPath whose php:function
registrations are freed while still reachable from the cycle collector.
Expand All @@ -13,11 +17,15 @@ PHP NEWS
. Fixed Dom\HTMLDocument giving attributes the namespace of their element
when a fragment is parsed with an xlink, xml or xmlns context element.
(Ilia Alshanetsky)
. Fixed bug GH-23729 (DOMXPath::__construct() use-after-free during an
evaluation). (David Carlier)

- Intl:
. Fixed cloning IntlDateFormatter and MessageFormatter losing PHP-side state
such as dateType, timeType, calendar and the message pattern.
(Ilia Alshanetsky)
. Fixed Collator attribute and strength methods not rejecting an
unconstructed Collator. (Ilia Alshanetsky)

- Lexbor:
. Merge patches lexbor/lexbor@8a14bc0 and lexbor/lexbor@f67ce4b, fixing a
Expand All @@ -28,10 +36,20 @@ PHP NEWS
. Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in
a truncated UTF-8 sequence). (Lazizbek Ergashev)

- Opcache:
. Fixed OSS-Fuzz #546798343 (Heap-buffer-overflow in optimizer with
FCCs and inlining). (ndossche)
. Fixed bug GH-23693 (Tracing JIT produces wrong results for a guard on a
loop-invariant addition). (Ilia Alshanetsky)

- PDO:
. Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid
column index. (Ilia Alshanetsky)

- Readline:
. Fixed a heap over-read in the interactive shell prompt when cli.prompt is
set to an empty string. (Ilia Alshanetsky)

- Sockets:
. Fixed socket_select() silently truncating sets larger than FD_SETSIZE on
Windows. (David Carlier)
Expand All @@ -40,9 +58,20 @@ PHP NEWS
. Fixed a crash when SQLite3::close() is called from a userland callback.
(Ilia Alshanetsky)

- Standard:
. Fixed three Windows-only proc_open() defects: an uninitialized
PROCESS_INFORMATION, an indeterminate comspec pointer after a failed
lookup, and an unchecked CreateFileA() failure. (Ilia Alshanetsky)

- XSL:
. Fixed bug GH-23730 (use-after-free when XSLTProcessor::importStylesheet()
is called during a transformation). (David Carlier)

- Zip:
. Fixed ZipArchive::extractTo() ignoring files given in a non-list array.
(David Carlier)
. Fixed bug GH-23747 (ZipArchive::close() use-after-free from a progress or
cancel callback). (David Carlier)


24 Sep 2026, PHP 8.4.26
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/optimize_func_calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ static void zend_delete_call_instructions(zend_op_array *op_array, zend_op *opli
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_CALLABLE_CONVERT:
call++;
break;
case ZEND_SEND_VAL:
Expand Down
1 change: 1 addition & 0 deletions ext/dom/php_dom.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ extern zend_module_entry dom_module_entry;

typedef struct dom_xpath_object {
php_dom_xpath_callbacks xpath_callbacks;
uint32_t evaluation_depth;
bool register_node_ns;
dom_object dom;
} dom_xpath_object;
Expand Down
73 changes: 73 additions & 0 deletions ext/dom/tests/gh23729.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
--TEST--
GH-23729 (Use-after-free when DOMXPath is reconstructed during an evaluation)
--CREDITS--
djarfluka
--EXTENSIONS--
dom
--FILE--
<?php

function reconstruct() {
try {
$GLOBALS['xpath']->__construct($GLOBALS['other']);
} catch (Error $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
return 'r';
}

function nested() {
echo 'nested: ', $GLOBALS['xpath']->evaluate('string(/root/b)'), PHP_EOL;
return 'n';
}

function test(string $class, object $doc, object $other) {
$xpath = new $class($doc);
$xpath->registerNamespace('php', 'http://php.net/xpath');
$xpath->registerPhpFunctions();

$GLOBALS['xpath'] = $xpath;
$GLOBALS['other'] = $other;

var_dump($xpath->evaluate('string(php:function("reconstruct"))'));
/* The evaluation the callback tried to destroy must still be usable. */
var_dump($xpath->evaluate('string(/root/a)'));
/* A nested evaluation must not lift the guard of the outer one. */
var_dump($xpath->evaluate('concat(php:function("nested"), php:function("reconstruct"))'));
var_dump($xpath->query('//b[php:function("reconstruct")]')->length);

/* Reconstructing outside of an evaluation is still allowed. */
$xpath->__construct($other);
var_dump($xpath->document->documentElement->nodeName);
}

$doc = new DOMDocument();
$doc->loadXML('<root><a>1</a><b>2</b></root>');
$other = new DOMDocument();
$other->loadXML('<other/>');
test(DOMXPath::class, $doc, $other);

$doc = Dom\XMLDocument::createFromString('<root><a>1</a><b>2</b></root>');
$other = Dom\XMLDocument::createFromString('<other/>');
test(Dom\XPath::class, $doc, $other);

?>
--EXPECT--
Error: Cannot call DOMXPath::__construct() while an XPath evaluation is in progress
string(1) "r"
string(1) "1"
nested: 2
Error: Cannot call DOMXPath::__construct() while an XPath evaluation is in progress
string(2) "nr"
Error: Cannot call DOMXPath::__construct() while an XPath evaluation is in progress
int(1)
string(5) "other"
Error: Cannot call Dom\XPath::__construct() while an XPath evaluation is in progress
string(1) "r"
string(1) "1"
nested: 2
Error: Cannot call Dom\XPath::__construct() while an XPath evaluation is in progress
string(2) "nr"
Error: Cannot call Dom\XPath::__construct() while an XPath evaluation is in progress
int(1)
string(5) "other"
10 changes: 9 additions & 1 deletion ext/dom/xpath.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ static void dom_xpath_construct(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *
RETURN_THROWS();
}

dom_xpath_object *intern = Z_XPATHOBJ_P(ZEND_THIS);
if (UNEXPECTED(intern->evaluation_depth > 0)) {
zend_throw_error(NULL, "Cannot call %s::__construct() while an XPath evaluation is in progress",
ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
RETURN_THROWS();
}

DOM_GET_OBJ(docp, doc, xmlDocPtr, docobj);

xmlXPathContextPtr ctx = xmlXPathNewContext(docp);
Expand All @@ -134,7 +141,6 @@ static void dom_xpath_construct(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *
RETURN_THROWS();
}

dom_xpath_object *intern = Z_XPATHOBJ_P(ZEND_THIS);
xmlXPathContextPtr oldctx = intern->dom.ptr;
if (oldctx != NULL) {
php_libxml_decrement_doc_ref((php_libxml_node_object *) &intern->dom);
Expand Down Expand Up @@ -301,7 +307,9 @@ static void php_xpath_eval(INTERNAL_FUNCTION_PARAMETERS, int type, bool modern)
ctxp->nsNr = in_scope_ns.count;
}

intern->evaluation_depth++;
xmlXPathObjectPtr xpathobjp = xmlXPathEvalExpression(BAD_CAST expr, ctxp);
intern->evaluation_depth--;
ctxp->node = NULL;

if (register_node_ns && nodep != NULL) {
Expand Down
36 changes: 36 additions & 0 deletions ext/intl/collator/collator_attr.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ PHP_FUNCTION( collator_get_attribute )
/* Fetch the object. */
COLLATOR_METHOD_FETCH_OBJECT;

if (!co || !co->ucoll) {
intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) );
intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
"Object not initialized", 0 );
zend_throw_error(NULL, "Object not initialized");

RETURN_THROWS();
}

value = ucol_getAttribute( co->ucoll, attribute, COLLATOR_ERROR_CODE_P( co ) );
COLLATOR_CHECK_STATUS( co, "Error getting attribute value" );

Expand All @@ -64,6 +73,15 @@ PHP_FUNCTION( collator_set_attribute )
/* Fetch the object. */
COLLATOR_METHOD_FETCH_OBJECT;

if (!co || !co->ucoll) {
intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) );
intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
"Object not initialized", 0 );
zend_throw_error(NULL, "Object not initialized");

RETURN_THROWS();
}

/* Set new value for the given attribute. */
ucol_setAttribute( co->ucoll, attribute, value, COLLATOR_ERROR_CODE_P( co ) );
COLLATOR_CHECK_STATUS( co, "Error setting attribute value" );
Expand All @@ -87,6 +105,15 @@ PHP_FUNCTION( collator_get_strength )
/* Fetch the object. */
COLLATOR_METHOD_FETCH_OBJECT;

if (!co || !co->ucoll) {
intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) );
intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
"Object not initialized", 0 );
zend_throw_error(NULL, "Object not initialized");

RETURN_THROWS();
}

/* Get current strength and return it. */
RETURN_LONG( ucol_getStrength( co->ucoll ) );
}
Expand All @@ -109,6 +136,15 @@ PHP_FUNCTION( collator_set_strength )
/* Fetch the object. */
COLLATOR_METHOD_FETCH_OBJECT;

if (!co || !co->ucoll) {
intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) );
intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
"Object not initialized", 0 );
zend_throw_error(NULL, "Object not initialized");

RETURN_THROWS();
}

/* Set given strength. */
ucol_setStrength( co->ucoll, strength );

Expand Down
42 changes: 42 additions & 0 deletions ext/intl/tests/collator_attribute_unconstructed.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
Collator attribute and strength methods on unconstructed object
--EXTENSIONS--
intl
--FILE--
<?php
class C extends Collator {
public function __construct() {
// omitting parent::__construct();
}
}
$c = new C();

try {
var_dump($c->getAttribute(Collator::NUMERIC_COLLATION));
} catch (Error $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}

try {
var_dump($c->setAttribute(Collator::NUMERIC_COLLATION, Collator::ON));
} catch (Error $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}

try {
var_dump($c->getStrength());
} catch (Error $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}

try {
var_dump($c->setStrength(Collator::SECONDARY));
} catch (Error $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
?>
--EXPECT--
Error: Object not initialized
Error: Object not initialized
Error: Object not initialized
Error: Object not initialized
19 changes: 18 additions & 1 deletion ext/opcache/jit/ir/ir_x86.dasc
Original file line number Diff line number Diff line change
Expand Up @@ -1923,6 +1923,21 @@ static bool ir_match_has_mem_deps(ir_ctx *ctx, ir_ref ref, ir_ref root)
return 0;
}

/* A naive check if anything that emits code, and so clobbers the flags, is
* scheduled between the flags setting instruction and the fusion root */
static bool ir_match_has_flags_deps(ir_ctx *ctx, ir_ref ref, ir_ref root)
{
ir_ref pos = ctx->prev_ref[root];

while (pos > ref) {
if (ctx->ir_base[pos].op != IR_SNAPSHOT) {
return 1;
}
pos = ctx->prev_ref[pos];
}
return pos != ref;
}

static void ir_match_fuse_load(ir_ctx *ctx, ir_ref ref, ir_ref root)
{
if (ir_in_same_block(ctx, ref) &&
Expand Down Expand Up @@ -3089,7 +3104,9 @@ store_int:
if (IR_IS_CONST_REF(op2_insn->op2)
&& !IR_IS_SYM_CONST(ctx->ir_base[op2_insn->op2].op)
&& ctx->ir_base[op2_insn->op2].val.i64 == 0) {
if (op2_insn->op1 == insn->op2 - 1) { /* previous instruction */
if (op2_insn->op1 == insn->op2 - 1 /* previous instruction */
&& ir_in_same_block(ctx, op2_insn->op1)
&& !ir_match_has_flags_deps(ctx, insn->op2, ref)) {
ir_insn *op1_insn = &ctx->ir_base[op2_insn->op1];

if ((op1_insn->op == IR_OR || op1_insn->op == IR_AND || op1_insn->op == IR_XOR) ||
Expand Down
41 changes: 41 additions & 0 deletions ext/opcache/tests/jit/gh23693.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
GH-23693: Tracing JIT reads stale flags for a guard on a hoisted addition
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit_buffer_size=64M
opcache.jit=tracing
opcache.jit_hot_func=1
--EXTENSIONS--
opcache
--FILE--
<?php
function f(int $pos, int $n): int {
if ($pos < 0) {
return -1;
}
$y = $pos >> 2;
$s = 0;
for ($dy = -1; $dy <= 1; ++$dy) {
for ($i = 0; $i < $n; ++$i) {
$ny = $y + $dy;
if ($ny < 0) {
continue;
}
if ($ny >= 4) {
continue;
}
$s += ($ny << 2) | ($i & 3);
}
}
return $s;
}
for ($k = 0; $k < 30; ++$k) {
f(16, 200);
}
var_dump(f(16, 200));
var_dump(f(0, 200));
?>
--EXPECT--
int(2700)
int(1400)
19 changes: 19 additions & 0 deletions ext/opcache/tests/opt/oss_fuzz_546798343.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
OSS-Fuzz #546798343 (Heap-buffer-overflow in zend_delete_call_instructions with callable conversion)
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
--FILE--
<?php

$x = function() {};
gonnaBeInlined($x(...));
function gonnaBeInlined($foo) {
}

echo "Done";
?>
--EXPECT--
Done
Loading
Loading