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 */