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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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).
Expand Down
20 changes: 20 additions & 0 deletions ext/dom/tests/gh22077.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
GH-22077 (UAF in custom XPath function)
--FILE--
<?php
$document = new DOMDocument;
$xpath = new DOMXPath($document);
$xpath->registerNamespace("my", "my.ns");
$xpath->registerPHPFunctionNS('my.ns', 'include', function(): DOMElement {
$includedDocument = new DOMDocument;
$includedDocument->loadXML('<root><uaf/><node/><uaf/></root>');
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) "<uaf/>"
21 changes: 20 additions & 1 deletion ext/dom/xpath.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion ext/intl/intl_convertcpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
15 changes: 15 additions & 0 deletions ext/intl/tests/gh21998.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
GH-21998 (NumberFormatter::format(INF) leaves a non-NUL-terminated zend_string)
--EXTENSIONS--
intl
--FILE--
<?php
$fmt = new NumberFormatter("en", NumberFormatter::DECIMAL);
var_dump($fmt->format(INF));
var_dump($fmt->format(-INF));
var_dump($fmt->format(NAN));
?>
--EXPECT--
string(3) "∞"
string(4) "-∞"
string(3) "NaN"
2 changes: 1 addition & 1 deletion ext/pcre/php_pcre.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
15 changes: 15 additions & 0 deletions ext/pcre/tests/mark_named_collision.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
preg_match: (*MARK:name) directive does not collide with named group called MARK
--FILE--
<?php
preg_match('/(?P<MARK>[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
13 changes: 10 additions & 3 deletions ext/phar/phar_object.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions ext/phar/tests/mkdir.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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--
<?php
Expand All @@ -43,3 +50,5 @@ Warning: rmdir(): phar error: cannot remove directory "" in phar "foo.phar", dir

Warning: rmdir(): phar error: cannot remove directory "a" in phar "%smkdir.phar.php", phar error: path "a" exists and is a not a directory in %smkdir.php on line %d
Cannot create a directory in magic ".phar" directory
Cannot create a directory in magic ".phar" directory
bool(true)
7 changes: 7 additions & 0 deletions ext/session/session.c
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,13 @@ static PHP_INI_MH(OnUpdateSessionStr)
SESSION_CHECK_ACTIVE_STATE;
SESSION_CHECK_OUTPUT_STATE;

if (new_value && zend_str_has_nul_byte(new_value)) {
if (stage != ZEND_INI_STAGE_DEACTIVATE) {
php_error_docref(NULL, E_WARNING, "\"%s\" must not contain null bytes", ZSTR_VAL(entry->name));
}
return FAILURE;
}

return OnUpdateStr(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
}

Expand Down
37 changes: 37 additions & 0 deletions ext/session/tests/session_str_settings_null_byte.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
session.cookie_path, session.cookie_domain, and session.cache_limiter must not contain null bytes
--EXTENSIONS--
session
--SKIPIF--
<?php include('skipif.inc'); ?>
--FILE--
<?php

ob_start();

var_dump(ini_set('session.cookie_path', "/path\0evil"));
var_dump(ini_set('session.cookie_domain', "example\0evil.com"));
var_dump(ini_set('session.cache_limiter', "nocache\0evil"));

var_dump(session_set_cookie_params(0, "/path\0evil"));
var_dump(session_set_cookie_params(0, null, "example\0evil.com"));

ob_end_flush();
echo "Done";
?>
--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
106 changes: 71 additions & 35 deletions ext/standard/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 */
Expand Down
22 changes: 22 additions & 0 deletions ext/standard/tests/array/array_product_packed_long_overflow.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
array_product() packed long overflow continues in double mode
--FILE--
<?php

$tests = [
[[PHP_INT_MAX, 2, 3], ((float) PHP_INT_MAX * 2) * 3],
[[PHP_INT_MIN, -1, 2], ((float) PHP_INT_MIN * -1) * 2],
];

foreach ($tests as [$input, $expected]) {
$result = array_product($input);
var_dump(is_float($result));
var_dump($result === $expected);
}

?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
Loading