diff --git a/NEWS b/NEWS
index 47ac440813aa..8ae9fe6b2d59 100644
--- a/NEWS
+++ b/NEWS
@@ -5,11 +5,22 @@ PHP NEWS
- Core:
. Fixed bug GH-23644 (Optimizer leaves a constant-vs-constant comparison
unfolded, crashing the VM in zval_undefined_cv). (ndossche)
+ . Fixed OSS-Fuzz 532353396 (assertion failure with static type). (Girgias)
+ . Fix GH-23662 (Avoid NAN warning in print_r()). (CodedByManish)
+
+- 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.
(Ilia Alshanetsky)
+ . Fixed Dom\HTMLDocument::getElementById() not finding ids of SVG and
+ MathML elements. (Ilia Alshanetsky)
+ . 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)
- Intl:
. Fixed cloning IntlDateFormatter and MessageFormatter losing PHP-side state
@@ -27,13 +38,38 @@ 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)
+ . Fixed the argument reported in error messages when setting an invalid
+ attribute via the PDO constructor options array. (Ilia Alshanetsky)
- PGSQL:
. Fixed pg_lo_write() rejecting data containing null bytes. (Ilia Alshanetsky)
+- Readline:
+ . Fixed a heap over-read in the interactive shell prompt when cli.prompt is
+ set to an empty string. (Ilia Alshanetsky)
+
+- SQLite:
+ . 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)
+
+- Zlib:
+ . Fixed inflate_init() dropping the preset dictionary for raw streams with
+ a non-default window. (Ilia Alshanetsky)
+
24 Sep 2026, PHP 8.5.11
diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c
index 8b29f47c9497..ff530c02de75 100644
--- a/Zend/Optimizer/optimize_func_calls.c
+++ b/Zend/Optimizer/optimize_func_calls.c
@@ -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:
diff --git a/Zend/tests/gh23662.phpt b/Zend/tests/gh23662.phpt
new file mode 100644
index 000000000000..426b6e2aa5d7
--- /dev/null
+++ b/Zend/tests/gh23662.phpt
@@ -0,0 +1,12 @@
+--TEST--
+GH-23662 (print_r() results in warning for NAN)
+--FILE--
+
+--EXPECT--
+NANArray
+(
+ [0] => NAN
+)
diff --git a/Zend/tests/typehints/bug76198.phpt b/Zend/tests/type_declarations/iterable/bug76198.phpt
similarity index 100%
rename from Zend/tests/typehints/bug76198.phpt
rename to Zend/tests/type_declarations/iterable/bug76198.phpt
diff --git a/Zend/tests/typehints/bug43332_1.phpt b/Zend/tests/type_declarations/relative_types/bug43332_1.phpt
similarity index 86%
rename from Zend/tests/typehints/bug43332_1.phpt
rename to Zend/tests/type_declarations/relative_types/bug43332_1.phpt
index 35d817c985ee..7639633c3e95 100644
--- a/Zend/tests/typehints/bug43332_1.phpt
+++ b/Zend/tests/type_declarations/relative_types/bug43332_1.phpt
@@ -1,5 +1,5 @@
--TEST--
-Bug #43332.1 (self and parent as type hint in namespace)
+Bug #43332.1 (self and parent as type declaration in namespace)
--FILE--
+--EXPECTF--
+Fatal error: Type declaration 'static' must be unqualified in %s on line %d
diff --git a/Zend/tests/type_declarations/relative_types/static/namespace_relative_static.phpt b/Zend/tests/type_declarations/relative_types/static/namespace_relative_static.phpt
new file mode 100644
index 000000000000..0c1cde76f147
--- /dev/null
+++ b/Zend/tests/type_declarations/relative_types/static/namespace_relative_static.phpt
@@ -0,0 +1,12 @@
+--TEST--
+namespace\static is not a valid type declaration
+--DESCRIPTION--
+OSS-Fuzz: https://issues.oss-fuzz.com/issues/532353396
+--FILE--
+
+--EXPECTF--
+Fatal error: Type declaration 'static' must be unqualified in %s on line %d
diff --git a/Zend/tests/type_declarations/static_type_in_final_class.phpt b/Zend/tests/type_declarations/relative_types/static/static_type_in_final_class.phpt
similarity index 100%
rename from Zend/tests/type_declarations/static_type_in_final_class.phpt
rename to Zend/tests/type_declarations/relative_types/static/static_type_in_final_class.phpt
diff --git a/Zend/tests/type_declarations/static_type_outside_class.phpt b/Zend/tests/type_declarations/relative_types/static/static_type_outside_class.phpt
similarity index 100%
rename from Zend/tests/type_declarations/static_type_outside_class.phpt
rename to Zend/tests/type_declarations/relative_types/static/static_type_outside_class.phpt
diff --git a/Zend/tests/type_declarations/static_type_param.phpt b/Zend/tests/type_declarations/relative_types/static/static_type_param.phpt
similarity index 100%
rename from Zend/tests/type_declarations/static_type_param.phpt
rename to Zend/tests/type_declarations/relative_types/static/static_type_param.phpt
diff --git a/Zend/tests/type_declarations/static_type_property.phpt b/Zend/tests/type_declarations/relative_types/static/static_type_property.phpt
similarity index 100%
rename from Zend/tests/type_declarations/static_type_property.phpt
rename to Zend/tests/type_declarations/relative_types/static/static_type_property.phpt
diff --git a/Zend/tests/type_declarations/static_type_return.phpt b/Zend/tests/type_declarations/relative_types/static/static_type_return.phpt
similarity index 100%
rename from Zend/tests/type_declarations/static_type_return.phpt
rename to Zend/tests/type_declarations/relative_types/static/static_type_return.phpt
diff --git a/Zend/tests/type_declarations/static_type_trait.phpt b/Zend/tests/type_declarations/relative_types/static/static_type_trait.phpt
similarity index 100%
rename from Zend/tests/type_declarations/static_type_trait.phpt
rename to Zend/tests/type_declarations/relative_types/static/static_type_trait.phpt
diff --git a/Zend/zend.c b/Zend/zend.c
index aa621c4daa8e..43cf8f5ccef2 100644
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@ -597,6 +597,9 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
case IS_LONG:
smart_str_append_long(buf, Z_LVAL_P(expr));
break;
+ case IS_DOUBLE:
+ smart_str_append_double(buf, Z_DVAL_P(expr), EG(precision), false);
+ break;
case IS_REFERENCE:
zend_print_zval_r_to_buf(buf, Z_REFVAL_P(expr), indent);
break;
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index d8d61ea979e4..941873663848 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -272,6 +272,7 @@ static const builtin_type_info builtin_types[] = {
{ZEND_STRL("iterable"), IS_ITERABLE},
{ZEND_STRL("object"), IS_OBJECT},
{ZEND_STRL("mixed"), IS_MIXED},
+ {ZEND_STRL("static"), IS_STATIC},
{NULL, 0, IS_UNDEF}
};
@@ -7100,6 +7101,8 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
ZSTR_VAL(zend_string_tolower(type_name)));
}
+ ZEND_ASSERT(type_code != IS_STATIC && "unqualified static type should have been handled by ZEND_AST_TYPE branch");
+
/* Transform iterable into a type union alias */
if (type_code == IS_ITERABLE) {
/* Set iterable bit for BC compat during Reflection and string representation of type */
diff --git a/ext/dom/html5_parser.c b/ext/dom/html5_parser.c
index 34320a122f53..24f89cb0fafb 100644
--- a/ext/dom/html5_parser.c
+++ b/ext/dom/html5_parser.c
@@ -241,7 +241,7 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert(
lxml_attr->children = lxml_attr->last = lxml_text;
lxml_text->parent = (xmlNodePtr) lxml_attr;
- if (attr->node.ns == LXB_NS_XMLNS) {
+ if (attr->node.ns == LXB_NS_XMLNS && (attr->node.prefix || strcmp((const char *) local_name, "xmlns") == 0)) {
if (strcmp((const char *) local_name, "xmlns") != 0) {
if (prefixed_xmlns_ns == NULL) {
prefixed_xmlns_ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, "xmlns", DOM_XMLNS_NS_URI);
@@ -251,13 +251,13 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert(
lxml_attr->ns = php_dom_libxml_ns_mapper_ensure_prefixless_xmlns_ns(ns_mapper);
}
lxml_attr->ns->_private = (void *) php_dom_ns_is_xmlns_magic_token;
- } else if (attr->node.ns == LXB_NS_XLINK) {
+ } else if (attr->node.prefix && attr->node.ns == LXB_NS_XLINK) {
if (xlink_ns == NULL) {
xlink_ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, "xlink", DOM_XLINK_NS_URI);
xlink_ns->_private = (void *) php_dom_ns_is_xlink_magic_token;
}
lxml_attr->ns = xlink_ns;
- } else if (attr->node.ns == LXB_NS_XML) {
+ } else if (attr->node.prefix && attr->node.ns == LXB_NS_XML) {
if (xml_ns == NULL) {
xml_ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, "xml", DOM_XML_NS_URI);
xml_ns->_private = (void *) php_dom_ns_is_xml_magic_token;
@@ -274,7 +274,7 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert(
last_added_attr = lxml_attr;
/* xmlIsID does some other stuff too that is irrelevant here. */
- if (local_name_length == 2 && local_name[0] == 'i' && local_name[1] == 'd' && attr->node.ns == LXB_NS_HTML) {
+ if (local_name_length == 2 && local_name[0] == 'i' && local_name[1] == 'd' && lxml_attr->ns == NULL) {
if (xmlAddID(NULL, lxml_doc, value, lxml_attr) == 0) {
/* If the ID already exists, the ID attribute still needs to be marked as an ID. */
lxml_attr->atype = XML_ATTRIBUTE_ID;
diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h
index aea45caf2b25..fae232e54f72 100644
--- a/ext/dom/php_dom.h
+++ b/ext/dom/php_dom.h
@@ -65,6 +65,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;
diff --git a/ext/dom/tests/gh23729.phpt b/ext/dom/tests/gh23729.phpt
new file mode 100644
index 000000000000..93ad67fde005
--- /dev/null
+++ b/ext/dom/tests/gh23729.phpt
@@ -0,0 +1,73 @@
+--TEST--
+GH-23729 (Use-after-free when DOMXPath is reconstructed during an evaluation)
+--CREDITS--
+djarfluka
+--EXTENSIONS--
+dom
+--FILE--
+__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('12');
+$other = new DOMDocument();
+$other->loadXML('');
+test(DOMXPath::class, $doc, $other);
+
+$doc = Dom\XMLDocument::createFromString('12');
+$other = Dom\XMLDocument::createFromString('');
+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"
diff --git a/ext/dom/tests/modern/html/parser/HTMLDocument_unprefixed_attributes_in_foreign_content.phpt b/ext/dom/tests/modern/html/parser/HTMLDocument_unprefixed_attributes_in_foreign_content.phpt
new file mode 100644
index 000000000000..ba6d33fdbbc5
--- /dev/null
+++ b/ext/dom/tests/modern/html/parser/HTMLDocument_unprefixed_attributes_in_foreign_content.phpt
@@ -0,0 +1,59 @@
+--TEST--
+Unprefixed attributes in foreign content are in no namespace
+--EXTENSIONS--
+dom
+--FILE--
+
';
+$doc = Dom\HTMLDocument::createFromString($html, LIBXML_NOERROR);
+
+var_dump([
+ 'svg #s' => $doc->getElementById('s')?->tagName,
+ 'math #m' => $doc->getElementById('m')?->tagName,
+ 'html #p' => $doc->getElementById('p')?->tagName,
+ 'xml:id #r' => $doc->getElementById('r')?->tagName,
+]);
+
+foreach ($doc->getElementById('s')->attributes as $attr) {
+ echo $attr->name, ' => ', var_export($attr->namespaceURI, true), "\n";
+}
+
+$contexts = [
+ 'http://www.w3.org/1999/xlink' => 'z',
+ 'http://www.w3.org/XML/1998/namespace' => 'z',
+ 'http://www.w3.org/2000/xmlns/' => 'xmlns',
+];
+foreach ($contexts as $uri => $name) {
+ $fragment_doc = Dom\HTMLDocument::createEmpty();
+ $context = $fragment_doc->createElementNS($uri, $name);
+ $fragment_doc->appendChild($context);
+ $context->innerHTML = '';
+
+ echo $uri, "\n ", $context->innerHTML, "\n ";
+ var_dump($fragment_doc->getElementById('q')?->tagName);
+}
+?>
+--EXPECT--
+array(4) {
+ ["svg #s"]=>
+ string(3) "svg"
+ ["math #m"]=>
+ string(4) "math"
+ ["html #p"]=>
+ string(1) "P"
+ ["xml:id #r"]=>
+ NULL
+}
+id => NULL
+xlink:href => 'http://www.w3.org/1999/xlink'
+xml:lang => 'http://www.w3.org/XML/1998/namespace'
+xmlns:xlink => 'http://www.w3.org/2000/xmlns/'
+http://www.w3.org/1999/xlink
+
+ string(1) "Z"
+http://www.w3.org/XML/1998/namespace
+
+ string(1) "Z"
+http://www.w3.org/2000/xmlns/
+
+ string(1) "Z"
diff --git a/ext/dom/xpath.c b/ext/dom/xpath.c
index 4688ed7743eb..8a2a347b046a 100644
--- a/ext/dom/xpath.c
+++ b/ext/dom/xpath.c
@@ -164,6 +164,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);
@@ -172,7 +179,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);
@@ -330,7 +336,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) {
diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc
index ca42001a8816..f5efb66698da 100644
--- a/ext/opcache/jit/ir/ir_x86.dasc
+++ b/ext/opcache/jit/ir/ir_x86.dasc
@@ -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) &&
@@ -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) ||
diff --git a/ext/opcache/tests/jit/gh23693.phpt b/ext/opcache/tests/jit/gh23693.phpt
new file mode 100644
index 000000000000..1540f0c1368f
--- /dev/null
+++ b/ext/opcache/tests/jit/gh23693.phpt
@@ -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--
+> 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)
diff --git a/ext/opcache/tests/opt/oss_fuzz_546798343.phpt b/ext/opcache/tests/opt/oss_fuzz_546798343.phpt
new file mode 100644
index 000000000000..f7f058a11652
--- /dev/null
+++ b/ext/opcache/tests/opt/oss_fuzz_546798343.phpt
@@ -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--
+
+--EXPECT--
+Done
diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
index 43c2cf8c0945..ea22fb388a7a 100644
--- a/ext/pdo/pdo_dbh.c
+++ b/ext/pdo/pdo_dbh.c
@@ -516,7 +516,7 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen
ZVAL_DEREF(attr_value);
/* TODO: Should the constructor fail when the attribute cannot be set? */
- pdo_dbh_attribute_set(dbh, long_key, attr_value, 3);
+ pdo_dbh_attribute_set(dbh, long_key, attr_value, 4);
} ZEND_HASH_FOREACH_END();
}
diff --git a/ext/pdo/tests/pdo_constructor_option_argnum.phpt b/ext/pdo/tests/pdo_constructor_option_argnum.phpt
new file mode 100644
index 000000000000..642e9bf182aa
--- /dev/null
+++ b/ext/pdo/tests/pdo_constructor_option_argnum.phpt
@@ -0,0 +1,15 @@
+--TEST--
+PDO constructor option attribute errors report $options as argument #4
+--EXTENSIONS--
+pdo
+pdo_sqlite
+--FILE--
+ 999]);
+} catch (Throwable $e) {
+ echo get_class($e), ": ", str_replace(PHP_VERSION, '', $e->getMessage()), "\n";
+}
+?>
+--EXPECT--
+ValueError: PDO::__construct(): Argument #4 ($options) Error mode must be one of the PDO::ERRMODE_* constants
diff --git a/ext/readline/readline_cli.c b/ext/readline/readline_cli.c
index 985a797754d9..bf508a79a708 100644
--- a/ext/readline/readline_cli.c
+++ b/ext/readline/readline_cli.c
@@ -130,7 +130,7 @@ static zend_string *cli_get_prompt(char *block, char prompt) /* {{{ */
char *prompt_spec = CLIR_G(prompt) ? CLIR_G(prompt) : DEFAULT_PROMPT;
bool unicode_warned = false;
- do {
+ while (*prompt_spec) {
if (*prompt_spec == '\\') {
switch (prompt_spec[1]) {
case '\\':
@@ -198,9 +198,9 @@ static zend_string *cli_get_prompt(char *block, char prompt) /* {{{ */
smart_str_appendc(&retval, '?');
}
}
- } while (++prompt_spec && *prompt_spec);
- smart_str_0(&retval);
- return retval.s;
+ ++prompt_spec;
+ }
+ return smart_str_extract(&retval);
}
/* }}} */
diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c
index a61f1a5d9a4d..e104126dbfe2 100644
--- a/ext/sockets/sockets.c
+++ b/ext/sockets/sockets.c
@@ -579,7 +579,7 @@ static int php_sock_array_to_fd_set(uint32_t arg_num, zval *sock_array, fd_set *
num++;
} ZEND_HASH_FOREACH_END();
- return num ? 1 : 0;
+ return num;
}
/* }}} */
@@ -630,7 +630,7 @@ PHP_FUNCTION(socket_select)
struct timeval *tv_p = NULL;
fd_set rfds, wfds, efds;
PHP_SOCKET max_fd = 0;
- int retval, sets = 0;
+ int retval, max_set_count = 0;
zend_long sec, usec = 0;
bool sec_is_null = 0;
@@ -648,30 +648,39 @@ PHP_FUNCTION(socket_select)
FD_ZERO(&efds);
if (r_array != NULL) {
- sets += retval = php_sock_array_to_fd_set(1, r_array, &rfds, &max_fd);
+ retval = php_sock_array_to_fd_set(1, r_array, &rfds, &max_fd);
if (retval == -1) {
RETURN_THROWS();
}
+ if (retval > max_set_count) {
+ max_set_count = retval;
+ }
}
if (w_array != NULL) {
- sets += retval = php_sock_array_to_fd_set(2, w_array, &wfds, &max_fd);
+ retval = php_sock_array_to_fd_set(2, w_array, &wfds, &max_fd);
if (retval == -1) {
RETURN_THROWS();
}
+ if (retval > max_set_count) {
+ max_set_count = retval;
+ }
}
if (e_array != NULL) {
- sets += retval = php_sock_array_to_fd_set(3, e_array, &efds, &max_fd);
+ retval = php_sock_array_to_fd_set(3, e_array, &efds, &max_fd);
if (retval == -1) {
RETURN_THROWS();
}
+ if (retval > max_set_count) {
+ max_set_count = retval;
+ }
}
- if (!sets) {
+ if (!max_set_count) {
zend_value_error("socket_select(): At least one array argument must be passed");
RETURN_THROWS();
}
- if (!PHP_SAFE_MAX_FD(max_fd, 0)) {
+ if (!PHP_SAFE_MAX_FD(max_fd, max_set_count)) {
RETURN_FALSE;
}
diff --git a/ext/sockets/tests/socket_select_fd_setsize_overflow.phpt b/ext/sockets/tests/socket_select_fd_setsize_overflow.phpt
new file mode 100644
index 000000000000..bb20d0df919a
--- /dev/null
+++ b/ext/sockets/tests/socket_select_fd_setsize_overflow.phpt
@@ -0,0 +1,50 @@
+--TEST--
+socket_select() reports an overflowing set instead of silently truncating it
+--EXTENSIONS--
+sockets
+--SKIPIF--
+
+--FILE--
+ 256);
+
+$read = $sockets;
+$write = $except = null;
+var_dump(socket_select($read, $write, $except, 0));
+
+$write = $sockets;
+$read = $except = null;
+var_dump(socket_select($read, $write, $except, 0));
+
+$except = $sockets;
+$read = $write = null;
+var_dump(socket_select($read, $write, $except, 0));
+
+foreach ($sockets as $socket) {
+ socket_close($socket);
+}
+?>
+--EXPECTF--
+bool(true)
+
+Warning: socket_select(): PHP needs to be recompiled with a larger value of FD_SETSIZE.%A
+bool(false)
+
+Warning: socket_select(): PHP needs to be recompiled with a larger value of FD_SETSIZE.%A
+bool(false)
+
+Warning: socket_select(): PHP needs to be recompiled with a larger value of FD_SETSIZE.%A
+bool(false)
diff --git a/ext/sqlite3/php_sqlite3_structs.h b/ext/sqlite3/php_sqlite3_structs.h
index 5d9f69cc5770..e5002751ceab 100644
--- a/ext/sqlite3/php_sqlite3_structs.h
+++ b/ext/sqlite3/php_sqlite3_structs.h
@@ -50,6 +50,7 @@ typedef struct _php_sqlite3_func {
zend_fcall_info_cache func;
zend_fcall_info_cache step;
zend_fcall_info_cache fini;
+ unsigned int *in_callback_ptr;
} php_sqlite3_func;
/* Structure for SQLite collation function */
@@ -58,6 +59,7 @@ typedef struct _php_sqlite3_collation {
zend_string *collation_name;
zend_fcall_info_cache cmp_func;
+ unsigned int *in_callback_ptr;
} php_sqlite3_collation;
/* Structure for SQLite Database object. */
@@ -70,6 +72,8 @@ typedef struct _php_sqlite3_db_object {
php_sqlite3_collation *collations;
zend_fcall_info_cache authorizer_fcc;
+ unsigned int in_callback;
+
zend_llist free_list;
zend_object zo;
} php_sqlite3_db_object;
diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c
index c9a30cb52e0b..c6898aedc1b9 100644
--- a/ext/sqlite3/sqlite3.c
+++ b/ext/sqlite3/sqlite3.c
@@ -195,6 +195,10 @@ PHP_METHOD(SQLite3, close)
}
if (db_obj->initialised) {
+ if (db_obj->in_callback) {
+ zend_throw_error(NULL, "Cannot close SQLite3 database while inside a callback");
+ RETURN_THROWS();
+ }
zend_llist_clean(&(db_obj->free_list));
if(db_obj->db) {
errcode = sqlite3_close(db_obj->db);
@@ -769,6 +773,9 @@ static int sqlite3_do_callback(zend_fcall_info_cache *fcc, uint32_t argc, sqlite
uint32_t fake_argc;
zend_result ret = SUCCESS;
php_sqlite3_agg_context *agg_context = NULL;
+ bool bailout = false;
+ php_sqlite3_func *cb_func = (php_sqlite3_func *)sqlite3_user_data(context);
+ unsigned int *in_callback = cb_func ? cb_func->in_callback_ptr : NULL;
if (is_agg) {
is_agg = 2;
@@ -776,6 +783,10 @@ static int sqlite3_do_callback(zend_fcall_info_cache *fcc, uint32_t argc, sqlite
fake_argc = argc + is_agg;
+ if (in_callback) {
+ (*in_callback)++;
+ }
+
/* build up the params */
if (fake_argc) {
zargs = (zval *)safe_emalloc(fake_argc, sizeof(zval), 0);
@@ -818,7 +829,15 @@ static int sqlite3_do_callback(zend_fcall_info_cache *fcc, uint32_t argc, sqlite
}
}
+ zend_try {
zend_call_known_fcc(fcc, &retval, fake_argc, zargs, /* named_params */ NULL);
+ } zend_catch {
+ bailout = true;
+ } zend_end_try();
+
+ if (in_callback) {
+ (*in_callback)--;
+ }
/* clean up the params */
if (is_agg) {
@@ -885,6 +904,9 @@ static int sqlite3_do_callback(zend_fcall_info_cache *fcc, uint32_t argc, sqlite
if (!Z_ISUNDEF(retval)) {
zval_ptr_dtor(&retval);
}
+ if (bailout) {
+ zend_bailout();
+ }
return ret;
}
/* }}}*/
@@ -925,6 +947,7 @@ static int php_sqlite3_callback_compare(void *coll, int a_len, const void *a, in
zval zargs[2];
zval retval;
int ret = 0;
+ bool bailout = false;
// Exception occurred on previous callback. Don't attempt to call function.
if (EG(exception)) {
@@ -934,10 +957,26 @@ static int php_sqlite3_callback_compare(void *coll, int a_len, const void *a, in
ZVAL_STRINGL(&zargs[0], a, a_len);
ZVAL_STRINGL(&zargs[1], b, b_len);
+ if (collation->in_callback_ptr) {
+ (*collation->in_callback_ptr)++;
+ }
+
+ zend_try {
zend_call_known_fcc(&collation->cmp_func, &retval, /* argc */ 2, zargs, /* named_params */ NULL);
+ } zend_catch {
+ bailout = true;
+ } zend_end_try();
+
+ if (collation->in_callback_ptr) {
+ (*collation->in_callback_ptr)--;
+ }
zval_ptr_dtor(&zargs[0]);
zval_ptr_dtor(&zargs[1]);
+ if (bailout) {
+ zval_ptr_dtor(&retval);
+ zend_bailout();
+ }
if (EG(exception)) {
ret = 0;
@@ -983,6 +1022,7 @@ PHP_METHOD(SQLite3, createFunction)
}
func = (php_sqlite3_func *)ecalloc(1, sizeof(*func));
+ func->in_callback_ptr = &db_obj->in_callback;
if (sqlite3_create_function(db_obj->db, ZSTR_VAL(sql_func), sql_func_num_args, flags | SQLITE_UTF8, func, php_sqlite3_callback_func, NULL, NULL) == SQLITE_OK) {
func->func_name = zend_string_copy(sql_func);
@@ -1031,6 +1071,7 @@ PHP_METHOD(SQLite3, createAggregate)
}
func = (php_sqlite3_func *)ecalloc(1, sizeof(*func));
+ func->in_callback_ptr = &db_obj->in_callback;
if (sqlite3_create_function(db_obj->db, ZSTR_VAL(sql_func), sql_func_num_args, SQLITE_UTF8, func, NULL, php_sqlite3_callback_step, php_sqlite3_callback_final) == SQLITE_OK) {
func->func_name = zend_string_copy(sql_func);
@@ -1078,6 +1119,7 @@ PHP_METHOD(SQLite3, createCollation)
}
collation = (php_sqlite3_collation *)ecalloc(1, sizeof(*collation));
+ collation->in_callback_ptr = &db_obj->in_callback;
if (sqlite3_create_collation(db_obj->db, ZSTR_VAL(collation_name), SQLITE_UTF8, collation, php_sqlite3_callback_compare) == SQLITE_OK) {
collation->collation_name = zend_string_copy(collation_name);
@@ -2246,8 +2288,15 @@ static int php_sqlite3_authorizer(void *autharg, int action, const char *arg1, c
}
int authreturn = SQLITE_DENY;
+ bool bailout = false;
+ db_obj->in_callback++;
+ zend_try {
zend_call_known_fcc(&db_obj->authorizer_fcc, &retval, /* argc */ 5, argv, /* named_params */ NULL);
+ } zend_catch {
+ bailout = true;
+ } zend_end_try();
+ db_obj->in_callback--;
if (Z_ISUNDEF(retval)) {
php_sqlite3_error(db_obj, 0, "An error occurred while invoking the authorizer callback");
} else {
@@ -2270,6 +2319,9 @@ static int php_sqlite3_authorizer(void *autharg, int action, const char *arg1, c
zval_ptr_dtor(&argv[3]);
zval_ptr_dtor(&argv[4]);
+ if (bailout) {
+ zend_bailout();
+ }
return authreturn;
}
/* }}} */
diff --git a/ext/sqlite3/tests/sqlite3_close_from_callback.phpt b/ext/sqlite3/tests/sqlite3_close_from_callback.phpt
new file mode 100644
index 000000000000..0b5523717f76
--- /dev/null
+++ b/ext/sqlite3/tests/sqlite3_close_from_callback.phpt
@@ -0,0 +1,35 @@
+--TEST--
+SQLite3::close() from within a UDF callback must not corrupt active statement
+--EXTENSIONS--
+sqlite3
+--FILE--
+createFunction('boom', function () use ($db) {
+ try {
+ var_dump($db->close());
+ } catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+ }
+ return 1;
+});
+$stmt = $db->prepare('SELECT boom()');
+var_dump($stmt !== false);
+$res = $stmt->execute();
+var_dump($res !== false);
+var_dump($res->fetchArray(SQLITE3_NUM));
+$res->finalize();
+var_dump($db->close());
+echo "done\n";
+?>
+--EXPECT--
+bool(true)
+Error: Cannot close SQLite3 database while inside a callback
+bool(true)
+Error: Cannot close SQLite3 database while inside a callback
+array(1) {
+ [0]=>
+ int(1)
+}
+bool(true)
+done
diff --git a/ext/sqlite3/tests/sqlite3_close_from_callback_aggregate.phpt b/ext/sqlite3/tests/sqlite3_close_from_callback_aggregate.phpt
new file mode 100644
index 000000000000..b5bdb9615a78
--- /dev/null
+++ b/ext/sqlite3/tests/sqlite3_close_from_callback_aggregate.phpt
@@ -0,0 +1,41 @@
+--TEST--
+SQLite3::close() from within a createAggregate() callback must not corrupt active statement
+--EXTENSIONS--
+sqlite3
+--FILE--
+exec('CREATE TABLE t (a INTEGER)');
+$db->exec('INSERT INTO t VALUES (1)');
+$db->exec('INSERT INTO t VALUES (2)');
+
+$reported = false;
+$db->createAggregate('agg', function ($context, $rows, $a) use (&$db, &$reported) {
+ if (!$reported) {
+ $reported = true;
+ try {
+ var_dump($db->close());
+ } catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+ }
+ }
+ return (int) $context + $a;
+}, function ($context, $rows) {
+ return $context;
+}, 1);
+
+$stmt = $db->prepare('SELECT agg(a) FROM t');
+$res = $stmt->execute();
+var_dump($res->fetchArray(SQLITE3_NUM));
+$res->finalize();
+var_dump($db->close());
+echo "done\n";
+?>
+--EXPECT--
+Error: Cannot close SQLite3 database while inside a callback
+array(1) {
+ [0]=>
+ int(3)
+}
+bool(true)
+done
diff --git a/ext/sqlite3/tests/sqlite3_close_from_callback_authorizer.phpt b/ext/sqlite3/tests/sqlite3_close_from_callback_authorizer.phpt
new file mode 100644
index 000000000000..1d5cf9e49227
--- /dev/null
+++ b/ext/sqlite3/tests/sqlite3_close_from_callback_authorizer.phpt
@@ -0,0 +1,38 @@
+--TEST--
+SQLite3::close() from within a setAuthorizer() callback must not finalize live statements
+--EXTENSIONS--
+sqlite3
+--FILE--
+exec('CREATE TABLE t (a INTEGER)');
+$db->exec('INSERT INTO t VALUES (7)');
+$live = $db->prepare('SELECT a FROM t');
+
+$reported = false;
+$db->setAuthorizer(function ($action, $arg1, $arg2, $arg3, $arg4) use (&$db, &$reported) {
+ if (!$reported) {
+ $reported = true;
+ try {
+ var_dump($db->close());
+ } catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+ }
+ }
+ return SQLite3::OK;
+});
+
+$db->prepare('SELECT 1');
+
+$res = $live->execute();
+var_dump($res->fetchArray(SQLITE3_NUM));
+$res->finalize();
+echo "done\n";
+?>
+--EXPECT--
+Error: Cannot close SQLite3 database while inside a callback
+array(1) {
+ [0]=>
+ int(7)
+}
+done
diff --git a/ext/sqlite3/tests/sqlite3_close_from_callback_collation.phpt b/ext/sqlite3/tests/sqlite3_close_from_callback_collation.phpt
new file mode 100644
index 000000000000..16906894161c
--- /dev/null
+++ b/ext/sqlite3/tests/sqlite3_close_from_callback_collation.phpt
@@ -0,0 +1,39 @@
+--TEST--
+SQLite3::close() from within a createCollation() callback must not corrupt active statement
+--EXTENSIONS--
+sqlite3
+--FILE--
+exec('CREATE TABLE t (s TEXT)');
+$db->exec("INSERT INTO t VALUES ('b')");
+$db->exec("INSERT INTO t VALUES ('a')");
+
+$reported = false;
+$db->createCollation('CB', function ($x, $y) use (&$db, &$reported) {
+ if (!$reported) {
+ $reported = true;
+ try {
+ var_dump($db->close());
+ } catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+ }
+ }
+ return strcmp($x, $y);
+});
+
+$stmt = $db->prepare('SELECT s FROM t ORDER BY s COLLATE CB');
+$res = $stmt->execute();
+while ($row = $res->fetchArray(SQLITE3_NUM)) {
+ echo $row[0], "\n";
+}
+$res->finalize();
+var_dump($db->close());
+echo "done\n";
+?>
+--EXPECT--
+Error: Cannot close SQLite3 database while inside a callback
+a
+b
+bool(true)
+done
diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c
index 01661351be8e..da03160c2771 100644
--- a/ext/standard/proc_open.c
+++ b/ext/standard/proc_open.c
@@ -691,7 +691,7 @@ static void init_startup_info(STARTUPINFOW *si, descriptorspec_item *descriptors
static void init_process_info(PROCESS_INFORMATION *pi)
{
- memset(&pi, 0, sizeof(pi));
+ memset(pi, 0, sizeof(*pi));
}
/* on success, returns length of *comspec, which then needs to be efree'd by caller */
@@ -742,7 +742,7 @@ static size_t find_comspec_nt(wchar_t **comspec)
static zend_result convert_command_to_use_shell(wchar_t **cmdw, size_t cmdw_len)
{
- wchar_t *comspec;
+ wchar_t *comspec = NULL;
size_t len = find_comspec_nt(&comspec);
if (len == 0) {
php_error_docref(NULL, E_WARNING, "Command conversion failed");
@@ -827,7 +827,7 @@ static zend_result set_proc_descriptor_to_blackhole(descriptorspec_item *desc)
#ifdef PHP_WIN32
desc->childend = CreateFileA("nul", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
- if (desc->childend == NULL) {
+ if (desc->childend == INVALID_HANDLE_VALUE) {
php_error_docref(NULL, E_WARNING, "Failed to open nul");
return FAILURE;
}
diff --git a/ext/xsl/php_xsl.h b/ext/xsl/php_xsl.h
index 36bd9cc72844..ac4ac5fc2fba 100644
--- a/ext/xsl/php_xsl.h
+++ b/ext/xsl/php_xsl.h
@@ -55,6 +55,7 @@ extern zend_module_entry xsl_module_entry;
typedef struct xsl_object {
void *ptr;
HashTable *parameter;
+ uint32_t transform_depth;
bool hasKeys;
php_libxml_ref_obj *sheet_ref_obj;
zend_long securityPrefs;
diff --git a/ext/xsl/tests/gh23730.phpt b/ext/xsl/tests/gh23730.phpt
new file mode 100644
index 000000000000..893f6277734b
--- /dev/null
+++ b/ext/xsl/tests/gh23730.phpt
@@ -0,0 +1,81 @@
+--TEST--
+GH-23730 (Use-after-free when a stylesheet is imported during a transformation)
+--EXTENSIONS--
+dom
+xsl
+--CREDITS--
+djarfluka
+--FILE--
+importStylesheet($GLOBALS['other']);
+ echo $from, ': no error', PHP_EOL;
+ } catch (Error $e) {
+ echo $from, ': ', $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+}
+
+function callback($nodes) {
+ import_other('callback');
+ return $nodes[0];
+}
+
+$xml = new DOMDocument();
+$xml->registerNodeClass(DOMElement::class, MyElement::class);
+$xml->loadXML('- a
');
+
+$xsl = new DOMDocument();
+$xsl->loadXML(<<
+
+
+
+XML);
+
+$other = new DOMDocument();
+$other->loadXML('REPLACED');
+
+$proc = new XSLTProcessor();
+$proc->registerPHPFunctions();
+$proc->importStylesheet($xsl);
+
+$GLOBALS['proc'] = $proc;
+$GLOBALS['other'] = $other;
+
+var_dump($proc->transformToXml($xml));
+var_dump($proc->transformToDoc($xml)->textContent);
+
+$uri = tempnam(sys_get_temp_dir(), 'gh23730');
+var_dump($proc->transformToUri($xml, $uri) > 0);
+@unlink($uri);
+
+/* Importing outside of a transformation is still allowed. */
+var_dump($proc->importStylesheet($other));
+var_dump($proc->transformToXml($xml));
+
+?>
+--EXPECT--
+callback: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+destructor: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+string(24) "
+a
+"
+callback: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+destructor: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+string(1) "a"
+callback: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+destructor: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+bool(true)
+bool(true)
+string(31) "
+REPLACED
+"
diff --git a/ext/xsl/xsltprocessor.c b/ext/xsl/xsltprocessor.c
index 68ff9eb76ab6..aafb49775eef 100644
--- a/ext/xsl/xsltprocessor.c
+++ b/ext/xsl/xsltprocessor.c
@@ -175,6 +175,12 @@ PHP_METHOD(XSLTProcessor, importStylesheet)
RETURN_THROWS();
}
+ xsl_object *intern = Z_XSL_P(id);
+ if (UNEXPECTED(intern->transform_depth > 0)) {
+ zend_throw_error(NULL, "Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress");
+ RETURN_THROWS();
+ }
+
nodep = php_libxml_import_node(docp);
if (nodep == NULL) {
zend_argument_type_error(1, "must be a valid XML node");
@@ -251,8 +257,6 @@ PHP_METHOD(XSLTProcessor, importStylesheet)
RETURN_FALSE;
}
- xsl_object *intern = Z_XSL_P(id);
-
/* Detach object */
clone_lxml_obj->document->ptr = NULL;
/* The namespace mappings need to be kept alive.
@@ -333,6 +337,8 @@ static xmlDocPtr php_xsl_apply_stylesheet(zval *id, xsl_object *intern, xsltStyl
return NULL;
}
+ intern->transform_depth++;
+
if (intern->profiling) {
if (php_check_open_basedir(ZSTR_VAL(intern->profiling))) {
f = NULL;
@@ -438,6 +444,8 @@ static xmlDocPtr php_xsl_apply_stylesheet(zval *id, xsl_object *intern, xsltStyl
efree(intern->doc);
intern->doc = NULL;
+ intern->transform_depth--;
+
return newdocp;
}
diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c
index 40ba748129c0..12d004cc27d8 100644
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@ -1537,8 +1537,16 @@ PHP_METHOD(ZipArchive, open)
if (ze_obj->archive) {
/* we already have an opened zip, free it */
+ if (ze_obj->archive->close) {
+ efree(resolved_path);
+ zend_throw_error(NULL, "Already being closed");
+ RETURN_THROWS();
+ }
intern = ze_obj->archive->za;
- if (zip_close(intern) != 0) {
+ ze_obj->archive->close = true;
+ err = zip_close(intern);
+ ze_obj->archive->close = false;
+ if (err != 0) {
php_error_docref(NULL, E_WARNING, "Empty string as source");
efree(resolved_path);
RETURN_FALSE;
@@ -1623,7 +1631,14 @@ PHP_METHOD(ZipArchive, close)
ze_obj = Z_ZIP_P(self);
+ if (ze_obj->archive->close) {
+ zend_throw_error(NULL, "Already being closed");
+ RETURN_THROWS();
+ }
+
+ ze_obj->archive->close = true;
err = zip_close(intern);
+ ze_obj->archive->close = false;
if (err) {
php_error_docref(NULL, E_WARNING, "%s", zip_strerror(intern));
/* Save error for property reader */
diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h
index e761364d0cc5..38773659012d 100644
--- a/ext/zip/php_zip.h
+++ b/ext/zip/php_zip.h
@@ -73,6 +73,7 @@ typedef struct _php_zip_archive {
/* libzip reads buffers until the archive is closed, can outlive the object. */
char **buffers;
int buffers_cnt;
+ bool close;
#ifdef HAVE_PROGRESS_CALLBACK
zend_fcall_info_cache progress_callback;
#endif
diff --git a/ext/zip/tests/gh23747.phpt b/ext/zip/tests/gh23747.phpt
new file mode 100644
index 000000000000..24e977410e17
--- /dev/null
+++ b/ext/zip/tests/gh23747.phpt
@@ -0,0 +1,102 @@
+--TEST--
+GH-23747 (ZipArchive::close() from inside a progress or cancel callback causes segv)
+--CREDITS--
+djarfluka
+--EXTENSIONS--
+zip
+--SKIPIF--
+
+--FILE--
+open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
+ for ($i = 0; $i < 64; $i++) {
+ $zip->addFromString("f$i.txt", str_repeat('x', 2000));
+ }
+}
+
+$filename = __DIR__ . '/gh23747.zip';
+
+$zip = new ZipArchive();
+populate($zip, $filename);
+$zip->registerProgressCallback(0.0, function ($rate) use ($zip) {
+ static $done = false;
+ if (!$done) {
+ $done = true;
+ try {
+ $zip->close();
+ } catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ }
+});
+var_dump($zip->close());
+
+$zip = new ZipArchive();
+populate($zip, $filename);
+$zip->registerCancelCallback(function () use ($zip) {
+ static $done = false;
+ if (!$done) {
+ $done = true;
+ try {
+ $zip->close();
+ } catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ }
+ return 0;
+});
+var_dump($zip->close());
+
+$zip = new ZipArchive();
+populate($zip, $filename);
+$zip->registerProgressCallback(0.0, function ($rate) use ($zip) {
+ static $done = false;
+ if (!$done) {
+ $done = true;
+ try {
+ $zip->close();
+ } catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ }
+});
+var_dump($zip->open($filename));
+var_dump($zip->count());
+
+$zip = new ZipArchive();
+populate($zip, $filename);
+$zip->registerProgressCallback(0.0, function ($rate) use ($zip, $filename) {
+ static $done = false;
+ if (!$done) {
+ $done = true;
+ try {
+ $zip->open($filename);
+ } catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ }
+});
+var_dump($zip->close());
+?>
+--CLEAN--
+
+--EXPECT--
+Error: Already being closed
+bool(true)
+Error: Already being closed
+bool(true)
+Error: Already being closed
+bool(true)
+int(64)
+Error: Already being closed
+bool(true)
diff --git a/ext/zlib/tests/inflate_raw_dictionary_window.phpt b/ext/zlib/tests/inflate_raw_dictionary_window.phpt
new file mode 100644
index 000000000000..0269127c1368
--- /dev/null
+++ b/ext/zlib/tests/inflate_raw_dictionary_window.phpt
@@ -0,0 +1,20 @@
+--TEST--
+inflate_init(): preset dictionary is honored for raw encoding with a non-default window
+--EXTENSIONS--
+zlib
+--FILE--
+ 10, 'dictionary' => $dict];
+
+$def = deflate_init(ZLIB_ENCODING_RAW, $opts);
+$comp = deflate_add($def, $data, ZLIB_FINISH);
+
+$inf = inflate_init(ZLIB_ENCODING_RAW, $opts);
+$out = inflate_add($inf, $comp, ZLIB_FINISH);
+
+var_dump($out === $data);
+?>
+--EXPECT--
+bool(true)
diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c
index b221b1f4b55c..003ffa801115 100644
--- a/ext/zlib/zlib.c
+++ b/ext/zlib/zlib.c
@@ -904,6 +904,7 @@ PHP_FUNCTION(inflate_init)
ctx->inflateDictlen = dictlen;
ctx->status = Z_OK;
+ zend_long orig_encoding = encoding;
if (encoding < 0) {
encoding += 15 - window;
} else {
@@ -917,7 +918,7 @@ PHP_FUNCTION(inflate_init)
RETURN_FALSE;
}
- if (encoding == PHP_ZLIB_ENCODING_RAW && dictlen > 0) {
+ if (orig_encoding == PHP_ZLIB_ENCODING_RAW && dictlen > 0) {
switch (inflateSetDictionary(&ctx->Z, (Bytef *) ctx->inflateDict, ctx->inflateDictlen)) {
case Z_OK:
efree(ctx->inflateDict);
diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c
index 923686969a3c..a6dc09b085cf 100644
--- a/sapi/cli/php_cli.c
+++ b/sapi/cli/php_cli.c
@@ -356,6 +356,15 @@ static void sapi_cli_log_message(const char *message, int syslog_type_int) /* {{
}
/* }}} */
+static int sapi_cli_activate(void) /* {{{ */
+{
+#if defined(PHP_WIN32) && defined(ZTS)
+ ZEND_TSRMLS_CACHE_UPDATE();
+#endif
+ return SUCCESS;
+}
+/* }}} */
+
static int sapi_cli_deactivate(void) /* {{{ */
{
fflush(stdout);
@@ -420,7 +429,7 @@ static sapi_module_struct cli_sapi_module = {
php_cli_startup, /* startup */
php_module_shutdown_wrapper, /* shutdown */
- NULL, /* activate */
+ sapi_cli_activate, /* activate */
sapi_cli_deactivate, /* deactivate */
sapi_cli_ub_write, /* unbuffered write */