diff --git a/NEWS b/NEWS
index 65b1f85ba378..239becd194f6 100644
--- a/NEWS
+++ b/NEWS
@@ -129,6 +129,10 @@ PHP NEWS
. Support reference values in Phar::mungServer(). (ndossche)
. Invalid values now throw in Phar::mungServer() instead of being silently
ignored. (ndossche)
+ . Fixed a bypass of the magic ".phar" directory protection in
+ Phar::addEmptyDir() for paths starting with "/.phar". (Weilin Du)
+ . Phar::addEmptyDir() now allows non-magic directory names that merely
+ share the ".phar" prefix. (Weilin Du)
. Support overridden methods in SplFileInfo for getMTime() and getPathname()
when building a phar. (ndossche)
. Mark Phar::buildFromIterator() base directory argument as a path.
@@ -150,6 +154,8 @@ PHP NEWS
- Session:
. Fixed bug 71162 (updateTimestamp never called when session data is empty).
(Girgias)
+ . Null bytes in session.cookie_path, session.cookie_domain, and
+ session.cache_limiter are now rejected with a warning. (jorgsowa)
- Soap:
. Soap::__setCookie() when cookie name is a digit is now not stored and
diff --git a/UPGRADING b/UPGRADING
index de086c600f56..1bba53274c16 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -55,6 +55,11 @@ PHP 8.6 UPGRADE NOTES
- Phar:
. Phar::mungServer() now raises a ValueError when an invalid
argument value is passed instead of being silently ignored.
+ . Phar::addEmptyDir() now rejects `/.phar` paths in addition to `.phar`
+ paths, and raises the same BadMethodCallException for attempts to create
+ the reserved magic ".phar" directory through that form.
+ . Phar::addEmptyDir() now treats non-magic names that merely share the
+ `.phar` prefix as ordinary directories.
- PGSQL:
. pg_fetch_object() now reports the ValueError for a non-empty
@@ -70,6 +75,11 @@ PHP 8.6 UPGRADE NOTES
argument value is passed.
- Session:
+ . Setting session.cookie_path, session.cookie_domain, or session.cache_limiter
+ to a value containing null bytes now emits a warning and leaves the setting
+ unchanged. Previously, null bytes were silently accepted: for cookie_path and
+ cookie_domain this caused the SAPI to drop the Set-Cookie header; for
+ cache_limiter the value was silently truncated at the null byte.
. A ValueError is not thrown if $name is a string containing null bytes in
session_module_name().
. session_encode() now returns an empty string instead of false for empty
@@ -380,6 +390,8 @@ PHP 8.6 UPGRADE NOTES
- Standard:
. Improved performance of array_fill_keys().
. Improved performance of array_map() with multiple arrays passed.
+ . Improved performance of array_sum() and array_product() for
+ integer-only arrays.
. Improved performance of array_unshift().
. Improved performance of array_walk().
. Improved performance of intval('+0b...', 2) and intval('0b...', 2).
diff --git a/ext/dom/tests/gh22077.phpt b/ext/dom/tests/gh22077.phpt
new file mode 100644
index 000000000000..fd4e42cc8aaf
--- /dev/null
+++ b/ext/dom/tests/gh22077.phpt
@@ -0,0 +1,20 @@
+--TEST--
+GH-22077 (UAF in custom XPath function)
+--FILE--
+registerNamespace("my", "my.ns");
+$xpath->registerPHPFunctionNS('my.ns', 'include', function(): DOMElement {
+ $includedDocument = new DOMDocument;
+ $includedDocument->loadXML('');
+ return $includedDocument->documentElement;
+});
+$nodeset = $xpath->query('my:include()/uaf');
+$node = $nodeset->item(0);
+var_dump($nodeset->length);
+var_dump($node->ownerDocument->saveXML($node));
+?>
+--EXPECT--
+int(2)
+string(6) ""
diff --git a/ext/dom/xpath.c b/ext/dom/xpath.c
index a06583d5d087..2b9f1f0258d0 100644
--- a/ext/dom/xpath.c
+++ b/ext/dom/xpath.c
@@ -33,6 +33,24 @@
#ifdef LIBXML_XPATH_ENABLED
+static dom_object *dom_xpath_intern_for_doc(dom_xpath_object *xpath_obj, xmlDocPtr doc)
+{
+ if (xpath_obj->dom.document && xpath_obj->dom.document->ptr == doc) {
+ return &xpath_obj->dom;
+ }
+ HashTable *node_list = xpath_obj->xpath_callbacks.node_list;
+ if (node_list) {
+ zval *entry;
+ ZEND_HASH_PACKED_FOREACH_VAL(node_list, entry) {
+ dom_object *obj = Z_DOMOBJ_P(entry);
+ if (obj->document && obj->document->ptr == doc) {
+ return obj;
+ }
+ } ZEND_HASH_FOREACH_END();
+ }
+ return &xpath_obj->dom;
+}
+
void dom_xpath_objects_free_storage(zend_object *object)
{
dom_xpath_object *intern = php_xpath_obj_from_obj(object);
@@ -352,7 +370,8 @@ static void php_xpath_eval(INTERNAL_FUNCTION_PARAMETERS, int type, bool modern)
node = php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern);
} else {
- php_dom_create_object(node, &child, &intern->dom);
+ dom_object *parent = dom_xpath_intern_for_doc(intern, node->doc);
+ php_dom_create_object(node, &child, parent);
}
add_next_index_zval(&retval, &child);
}
diff --git a/ext/intl/intl_convertcpp.cpp b/ext/intl/intl_convertcpp.cpp
index cd7614b3a6c6..70d28e5c23ca 100644
--- a/ext/intl/intl_convertcpp.cpp
+++ b/ext/intl/intl_convertcpp.cpp
@@ -72,7 +72,7 @@ zend_string* intl_charFromString(const UnicodeString &from, UErrorCode *status)
const UChar *utf16buf = from.getBuffer();
int32_t actual_len;
- u_strToUTF8WithSub(ZSTR_VAL(u8res), capacity, &actual_len, utf16buf, from.length(),
+ u_strToUTF8WithSub(ZSTR_VAL(u8res), capacity + 1, &actual_len, utf16buf, from.length(),
U_SENTINEL, NULL, status);
if (U_FAILURE(*status)) {
diff --git a/ext/intl/tests/gh21998.phpt b/ext/intl/tests/gh21998.phpt
new file mode 100644
index 000000000000..392336dab522
--- /dev/null
+++ b/ext/intl/tests/gh21998.phpt
@@ -0,0 +1,15 @@
+--TEST--
+GH-21998 (NumberFormatter::format(INF) leaves a non-NUL-terminated zend_string)
+--EXTENSIONS--
+intl
+--FILE--
+format(INF));
+var_dump($fmt->format(-INF));
+var_dump($fmt->format(NAN));
+?>
+--EXPECT--
+string(3) "∞"
+string(4) "-∞"
+string(3) "NaN"
diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c
index b9f8e1211ff5..6a62d9717e7b 100644
--- a/ext/pcre/php_pcre.c
+++ b/ext/pcre/php_pcre.c
@@ -1079,7 +1079,7 @@ static void populate_subpat_array(
/* Add MARK, if available */
if (mark) {
ZVAL_STRING(&val, (char *)mark);
- zend_hash_str_add_new(subpats_ht, ZEND_STRL("MARK"), &val);
+ zend_hash_str_update(subpats_ht, ZEND_STRL("MARK"), &val);
}
}
diff --git a/ext/pcre/tests/mark_named_collision.phpt b/ext/pcre/tests/mark_named_collision.phpt
new file mode 100644
index 000000000000..b644c4d44023
--- /dev/null
+++ b/ext/pcre/tests/mark_named_collision.phpt
@@ -0,0 +1,15 @@
+--TEST--
+preg_match: (*MARK:name) directive does not collide with named group called MARK
+--FILE--
+[abc])(*MARK:value)/', "abc", $m);
+echo "count: ", count($m), "\n";
+echo "MARK: ", $m['MARK'], "\n";
+echo "json: ", json_encode($m), "\n";
+echo "keys: ", implode(',', array_keys($m)), "\n";
+?>
+--EXPECT--
+count: 3
+MARK: value
+json: {"0":"a","MARK":"value","1":"a"}
+keys: 0,MARK,1
diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c
index 81c4fd14f7b3..46db925cfd50 100644
--- a/ext/phar/phar_object.c
+++ b/ext/phar/phar_object.c
@@ -3789,9 +3789,16 @@ PHP_METHOD(Phar, addEmptyDir)
PHAR_ARCHIVE_OBJECT();
- if (zend_string_starts_with_literal(dir_name, ".phar")) {
- zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory");
- RETURN_THROWS();
+ if (
+ zend_string_starts_with_literal(dir_name, ".phar")
+ || zend_string_starts_with_literal(dir_name, "/.phar")
+ ) {
+ size_t prefix_len = (ZSTR_VAL(dir_name)[0] == '/') + sizeof(".phar") - 1;
+ char next_char = ZSTR_VAL(dir_name)[prefix_len];
+ if (next_char == '/' || next_char == '\\' || next_char == '\0') {
+ zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory");
+ RETURN_THROWS();
+ }
}
phar_mkdir(&phar_obj->archive, dir_name);
diff --git a/ext/phar/tests/mkdir.phpt b/ext/phar/tests/mkdir.phpt
index 1ffdc7fe252d..2c1586b0de5c 100644
--- a/ext/phar/tests/mkdir.phpt
+++ b/ext/phar/tests/mkdir.phpt
@@ -24,6 +24,13 @@ $a->addEmptyDir('.phar');
} catch (Exception $e) {
echo $e->getMessage(),"\n";
}
+try {
+$a->addEmptyDir('/.phar');
+} catch (Exception $e) {
+echo $e->getMessage(),"\n";
+}
+$a->addEmptyDir('/.pharx');
+var_dump(is_dir($pname . '/.pharx'));
?>
--CLEAN--
name));
+ }
+ return FAILURE;
+ }
+
return OnUpdateStr(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
}
diff --git a/ext/session/tests/session_str_settings_null_byte.phpt b/ext/session/tests/session_str_settings_null_byte.phpt
new file mode 100644
index 000000000000..693ec6971601
--- /dev/null
+++ b/ext/session/tests/session_str_settings_null_byte.phpt
@@ -0,0 +1,37 @@
+--TEST--
+session.cookie_path, session.cookie_domain, and session.cache_limiter must not contain null bytes
+--EXTENSIONS--
+session
+--SKIPIF--
+
+--FILE--
+
+--EXPECTF--
+Warning: ini_set(): "session.cookie_path" must not contain null bytes in %s on line %d
+bool(false)
+
+Warning: ini_set(): "session.cookie_domain" must not contain null bytes in %s on line %d
+bool(false)
+
+Warning: ini_set(): "session.cache_limiter" must not contain null bytes in %s on line %d
+bool(false)
+
+Warning: session_set_cookie_params(): "session.cookie_path" must not contain null bytes in %s on line %d
+bool(false)
+
+Warning: session_set_cookie_params(): "session.cookie_domain" must not contain null bytes in %s on line %d
+bool(false)
+Done
diff --git a/ext/standard/array.c b/ext/standard/array.c
index 49a3bbd557ba..25259c47d61b 100644
--- a/ext/standard/array.c
+++ b/ext/standard/array.c
@@ -6314,11 +6314,50 @@ PHP_FUNCTION(array_rand)
}
/* }}} */
+/* Apply a single array_sum/array_product step to return_value. */
+static zend_always_inline void php_array_binop_apply(
+ zval *return_value, zval *entry, const char *op_name, binary_op_type op)
+{
+ /* For objects we try to cast them to a numeric type */
+ if (Z_TYPE_P(entry) == IS_OBJECT) {
+ zval dst;
+ zend_result status = Z_OBJ_HT_P(entry)->cast_object(Z_OBJ_P(entry), &dst, _IS_NUMBER);
+
+ /* Do not type error for BC */
+ if (status == FAILURE || (Z_TYPE(dst) != IS_LONG && Z_TYPE(dst) != IS_DOUBLE)) {
+ php_error_docref(NULL, E_WARNING, "%s is not supported on type %s",
+ op_name, zend_zval_type_name(entry));
+ return;
+ }
+ op(return_value, return_value, &dst);
+ return;
+ }
+
+ zend_result status = op(return_value, return_value, entry);
+ if (status == FAILURE) {
+ ZEND_ASSERT(EG(exception));
+ zend_clear_exception();
+ /* BC resources: previously resources were cast to int */
+ if (Z_TYPE_P(entry) == IS_RESOURCE) {
+ zval tmp;
+ ZVAL_LONG(&tmp, Z_RES_HANDLE_P(entry));
+ op(return_value, return_value, &tmp);
+ }
+ /* BC non numeric strings: previously were cast to 0 */
+ else if (Z_TYPE_P(entry) == IS_STRING) {
+ zval tmp;
+ ZVAL_LONG(&tmp, 0);
+ op(return_value, return_value, &tmp);
+ }
+ php_error_docref(NULL, E_WARNING, "%s is not supported on type %s",
+ op_name, zend_zval_type_name(entry));
+ }
+}
+
/* Wrapper for array_sum and array_product */
-static void php_array_binop(INTERNAL_FUNCTION_PARAMETERS, const char *op_name, binary_op_type op, zend_long initial)
+static zend_always_inline void php_array_binop(INTERNAL_FUNCTION_PARAMETERS, const char *op_name, binary_op_type op, zend_long initial)
{
HashTable *input;
- zval *entry;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_HT(input)
@@ -6329,42 +6368,39 @@ static void php_array_binop(INTERNAL_FUNCTION_PARAMETERS, const char *op_name, b
}
ZVAL_LONG(return_value, initial);
- ZEND_HASH_FOREACH_VAL(input, entry) {
- /* For objects we try to cast them to a numeric type */
- if (Z_TYPE_P(entry) == IS_OBJECT) {
- zval dst;
- zend_result status = Z_OBJ_HT_P(entry)->cast_object(Z_OBJ_P(entry), &dst, _IS_NUMBER);
-
- /* Do not type error for BC */
- if (status == FAILURE || (Z_TYPE(dst) != IS_LONG && Z_TYPE(dst) != IS_DOUBLE)) {
- php_error_docref(NULL, E_WARNING, "%s is not supported on type %s",
- op_name, zend_zval_type_name(entry));
- continue;
- }
- op(return_value, return_value, &dst);
- continue;
- }
- zend_result status = op(return_value, return_value, entry);
- if (status == FAILURE) {
- ZEND_ASSERT(EG(exception));
- zend_clear_exception();
- /* BC resources: previously resources were cast to int */
- if (Z_TYPE_P(entry) == IS_RESOURCE) {
- zval tmp;
- ZVAL_LONG(&tmp, Z_RES_HANDLE_P(entry));
- op(return_value, return_value, &tmp);
+ if (op == add_function) {
+ zval *entry;
+ ZEND_HASH_FOREACH_VAL(input, entry) {
+ if (EXPECTED(Z_TYPE_P(entry) == IS_LONG) && EXPECTED(Z_TYPE_P(return_value) == IS_LONG)) {
+ fast_long_add_function(return_value, return_value, entry);
+ continue;
}
- /* BC non numeric strings: previously were cast to 0 */
- else if (Z_TYPE_P(entry) == IS_STRING) {
- zval tmp;
- ZVAL_LONG(&tmp, 0);
- op(return_value, return_value, &tmp);
+ php_array_binop_apply(return_value, entry, op_name, op);
+ } ZEND_HASH_FOREACH_END();
+ } else if (op == mul_function) {
+ zval *entry;
+ ZEND_HASH_FOREACH_VAL(input, entry) {
+ if (EXPECTED(Z_TYPE_P(entry) == IS_LONG) && EXPECTED(Z_TYPE_P(return_value) == IS_LONG)) {
+ zend_long lval;
+ double dval;
+ int overflow;
+ ZEND_SIGNED_MULTIPLY_LONG(Z_LVAL_P(return_value), Z_LVAL_P(entry), lval, dval, overflow);
+ if (UNEXPECTED(overflow)) {
+ ZVAL_DOUBLE(return_value, dval);
+ } else {
+ Z_LVAL_P(return_value) = lval;
+ }
+ continue;
}
- php_error_docref(NULL, E_WARNING, "%s is not supported on type %s",
- op_name, zend_zval_type_name(entry));
- }
- } ZEND_HASH_FOREACH_END();
+ php_array_binop_apply(return_value, entry, op_name, op);
+ } ZEND_HASH_FOREACH_END();
+ } else {
+ zval *entry;
+ ZEND_HASH_FOREACH_VAL(input, entry) {
+ php_array_binop_apply(return_value, entry, op_name, op);
+ } ZEND_HASH_FOREACH_END();
+ }
}
/* {{{ Returns the sum of the array entries */
diff --git a/ext/standard/tests/array/array_product_packed_long_overflow.phpt b/ext/standard/tests/array/array_product_packed_long_overflow.phpt
new file mode 100644
index 000000000000..7c1d8a0adebd
--- /dev/null
+++ b/ext/standard/tests/array/array_product_packed_long_overflow.phpt
@@ -0,0 +1,22 @@
+--TEST--
+array_product() packed long overflow continues in double mode
+--FILE--
+
+--EXPECT--
+bool(true)
+bool(true)
+bool(true)
+bool(true)
diff --git a/ext/standard/tests/array/array_sum_packed_long_overflow.phpt b/ext/standard/tests/array/array_sum_packed_long_overflow.phpt
new file mode 100644
index 000000000000..22e4f3be52aa
--- /dev/null
+++ b/ext/standard/tests/array/array_sum_packed_long_overflow.phpt
@@ -0,0 +1,22 @@
+--TEST--
+array_sum() packed long overflow continues in double mode
+--FILE--
+
+--EXPECT--
+bool(true)
+bool(true)
+bool(true)
+bool(true)
diff --git a/ext/standard/tests/array/array_sum_product_integration.phpt b/ext/standard/tests/array/array_sum_product_integration.phpt
new file mode 100644
index 000000000000..4b83042a3b45
--- /dev/null
+++ b/ext/standard/tests/array/array_sum_product_integration.phpt
@@ -0,0 +1,32 @@
+--TEST--
+array_sum()/array_product(): PHP 8.3 nested-array warning and array_column() integration
+--FILE--
+ 'Pen', 'price' => 3],
+ ['name' => 'Paper', 'price' => 5],
+];
+$prices = array_column($products, 'price');
+var_dump($prices === [3, 5]);
+var_dump(array_sum($prices) === 8);
+var_dump(array_product($prices) === 15);
+
+echo "-- PHP 8.3: nested array emits warning and is skipped --\n";
+var_dump(array_sum([1, [2], 3]));
+var_dump(array_product([2, [3], 4]));
+
+?>
+--EXPECTF--
+-- array_column() + array_sum()/array_product() integration --
+bool(true)
+bool(true)
+bool(true)
+-- PHP 8.3: nested array emits warning and is skipped --
+
+Warning: array_sum(): Addition is not supported on type array in %s on line %d
+int(4)
+
+Warning: array_product(): Multiplication is not supported on type array in %s on line %d
+int(8)