diff --git a/.circleci/config.yml b/.circleci/config.yml
index 26834a9392e0c..bff38447b8b90 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -5,7 +5,7 @@ jobs:
resource_class: arm.medium
docker:
- image: cimg/base:current-22.04
- - image: mysql:8
+ - image: mysql:8.3
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: true
MYSQL_ROOT_PASSWORD: ''
diff --git a/.github/actions/setup-oracle/action.yml b/.github/actions/setup-oracle/action.yml
index 1208e93a24893..a7afda2e4bcab 100644
--- a/.github/actions/setup-oracle/action.yml
+++ b/.github/actions/setup-oracle/action.yml
@@ -13,10 +13,10 @@ runs:
-d gvenzl/oracle-xe:slim
mkdir /opt/oracle
- wget -nv https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip
- unzip instantclient-basiclite-linuxx64.zip && rm instantclient-basiclite-linuxx64.zip
- wget -nv https://download.oracle.com/otn_software/linux/instantclient/instantclient-sdk-linuxx64.zip
- unzip instantclient-sdk-linuxx64.zip && rm instantclient-sdk-linuxx64.zip
+ wget -nv https://download.oracle.com/otn_software/linux/instantclient/2114000/instantclient-basiclite-linux.x64-21.14.0.0.0dbru.zip
+ unzip instantclient-basiclite-linux.x64-21.14.0.0.0dbru.zip && rm instantclient-basiclite-linux.x64-21.14.0.0.0dbru.zip
+ wget -nv https://download.oracle.com/otn_software/linux/instantclient/2114000/instantclient-sdk-linux.x64-21.14.0.0.0dbru.zip
+ unzip instantclient-sdk-linux.x64-21.14.0.0.0dbru.zip && rm instantclient-sdk-linux.x64-21.14.0.0.0dbru.zip
mv instantclient_*_* /opt/oracle/instantclient
# interferes with libldap2 headers
rm /opt/oracle/instantclient/sdk/include/ldap.h
diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index e72cd8f77bfd8..3aeeed92d15f9 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -72,6 +72,7 @@ jobs:
# job id, not the job name)
key: "LINUX_X64_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}-${{hashFiles('main/php_version.h')}}"
append-timestamp: false
+ save: ${{ github.event_name != 'pull_request' }}
- name: ./configure
uses: ./.github/actions/configure-x64
with:
@@ -111,6 +112,7 @@ jobs:
with:
key: "${{github.job}}-${{hashFiles('main/php_version.h')}}"
append-timestamp: false
+ save: ${{ github.event_name != 'pull_request' }}
- name: ./configure
uses: ./.github/actions/configure-macos
with:
diff --git a/NEWS b/NEWS
index 087409db243ee..ba10381234f9a 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,86 @@
PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
-?? ??? ????, PHP 8.2.19
+06 Jun 2024, PHP 8.2.20
+
+- CGI:
+ . Fixed buffer limit on Windows, replacing read call usage by _read.
+ (David Carlier)
+ . Fixed bug GHSA-3qgc-jrrr-25jv (Bypass of CVE-2012-1823, Argument Injection
+ in PHP-CGI). (CVE-2024-4577) (nielsdos)
+
+- CLI:
+ . Fixed bug GH-14189 (PHP Interactive shell input state incorrectly handles
+ quoted heredoc literals.). (nielsdos)
+
+- Core:
+ . Fixed bug GH-13970 (Incorrect validation of #[Attribute] flags type for
+ non-compile-time expressions). (ilutov)
+ . Fixed bug GH-14140 (Floating point bug in range operation on Apple Silicon
+ hardware). (Derick, Saki)
+
+- DOM:
+ . Fix crashes when entity declaration is removed while still having entity
+ references. (nielsdos)
+ . Fix references not handled correctly in C14N. (nielsdos)
+ . Fix crash when calling childNodes next() when iterator is exhausted.
+ (nielsdos)
+ . Fix crash in ParentNode::append() when dealing with a fragment
+ containing text nodes. (nielsdos)
+
+- FFI:
+ . Fixed bug GH-14215 (Cannot use FFI::load on CRLF header file with
+ apache2handler). (nielsdos)
+
+- Filter:
+ . Fixed bug GHSA-w8qr-v226-r27w (Filter bypass in filter_var FILTER_VALIDATE_URL).
+ (CVE-2024-5458) (nielsdos)
+
+- FPM:
+ . Fix bug GH-14175 (Show decimal number instead of scientific notation in
+ systemd status). (Benjamin Cremer)
+
+- Hash:
+ . ext/hash: Swap the checking order of `__has_builtin` and `__GNUC__`
+ (Saki Takamachi)
+
+- Intl:
+ . Fixed build regression on systems without C++17 compilers. (Calvin Buckley,
+ Peter Kokot)
+
+- Ini:
+ . Fixed bug GH-14100 (Corrected spelling mistake in php.ini files).
+ (Marcus Xavier)
+
+- MySQLnd:
+ . Fix bug GH-14255 (mysqli_fetch_assoc reports error from
+ nested query). (Kamil Tekiela)
+
+- Opcache:
+ . Fixed bug GH-14109 (Fix accidental persisting of internal class constant in
+ shm). (ilutov)
+
+- OpenSSL:
+ . The openssl_private_decrypt function in PHP, when using PKCS1 padding
+ (OPENSSL_PKCS1_PADDING, which is the default), is vulnerable to the Marvin Attack
+ unless it is used with an OpenSSL version that includes the changes from this pull
+ request: https://github.com/openssl/openssl/pull/13817 (rsa_pkcs1_implicit_rejection).
+ These changes are part of OpenSSL 3.2 and have also been backported to stable
+ versions of various Linux distributions, as well as to the PHP builds provided for
+ Windows since the previous release. All distributors and builders should ensure that
+ this version is used to prevent PHP from being vulnerable. (CVE-2024-2408)
+
+- Standard:
+ . Fixed bug GHSA-9fcc-425m-g385 (Bypass of CVE-2024-1874).
+ (CVE-2024-5585) (nielsdos)
+
+- XML:
+ . Fixed bug GH-14124 (Segmentation fault with XML extension under certain
+ memory limit). (nielsdos)
+
+- XMLReader:
+ . Fixed bug GH-14183 (XMLReader::open() can't be overridden). (nielsdos)
+
+09 May 2024, PHP 8.2.19
- Core:
. Fixed bug GH-13772 (Invalid execute_data->opline pointers in observer fcall
diff --git a/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt b/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt
index 01ab29c5efab9..70e4fdb7f338a 100644
--- a/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt
+++ b/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt
@@ -6,6 +6,15 @@ Attribute flags type is validated.
#[Attribute("foo")]
class A1 { }
+#[A1]
+class Foo {}
+
+try {
+ (new ReflectionClass(Foo::class))->getAttributes()[0]->newInstance();
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
?>
---EXPECTF--
-Fatal error: Attribute::__construct(): Argument #1 ($flags) must be of type int, string given in %s
+--EXPECT--
+Attribute::__construct(): Argument #1 ($flags) must be of type int, string given
diff --git a/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt b/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt
index 72433a9f13930..efaa969af827e 100644
--- a/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt
+++ b/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt
@@ -6,6 +6,15 @@ Attribute flags value is validated.
#[Attribute(-1)]
class A1 { }
+#[A1]
+class Foo { }
+
+try {
+ var_dump((new ReflectionClass(Foo::class))->getAttributes()[0]->newInstance());
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
?>
---EXPECTF--
-Fatal error: Invalid attribute flags specified in %s
+--EXPECT--
+Invalid attribute flags specified
diff --git a/Zend/tests/attributes/023_ast_node_in_validation.phpt b/Zend/tests/attributes/023_ast_node_in_validation.phpt
index 332d83fe86f61..063a6b7e815d2 100644
--- a/Zend/tests/attributes/023_ast_node_in_validation.phpt
+++ b/Zend/tests/attributes/023_ast_node_in_validation.phpt
@@ -6,6 +6,15 @@ Attribute flags value is validated.
#[Attribute(Foo::BAR)]
class A1 { }
+#[A1]
+class Bar { }
+
+try {
+ var_dump((new ReflectionClass(Bar::class))->getAttributes()[0]->newInstance());
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
?>
---EXPECTF--
-Fatal error: Class "Foo" not found in %s on line %d
+--EXPECT--
+Class "Foo" not found
diff --git a/Zend/tests/attributes/032_attribute_validation_scope.phpt b/Zend/tests/attributes/032_attribute_validation_scope.phpt
index 039a427254f4d..d157c35929bf2 100644
--- a/Zend/tests/attributes/032_attribute_validation_scope.phpt
+++ b/Zend/tests/attributes/032_attribute_validation_scope.phpt
@@ -1,9 +1,19 @@
--TEST--
-Validation for "Attribute" does not use a scope when evaluating constant ASTs
+Validation for "Attribute" uses the class scope when evaluating constant ASTs
--FILE--
getAttributes()[0]->newInstance());
?>
---EXPECTF--
-Fatal error: Cannot access "parent" when no class scope is active in %s on line %d
+--EXPECT--
+object(x)#1 (0) {
+}
diff --git a/Zend/tests/attributes/033_attribute_flags_type_is_not_validated_at_comp_time.phpt b/Zend/tests/attributes/033_attribute_flags_type_is_not_validated_at_comp_time.phpt
new file mode 100644
index 0000000000000..76b29c65ba87b
--- /dev/null
+++ b/Zend/tests/attributes/033_attribute_flags_type_is_not_validated_at_comp_time.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Attribute flags type is not validated at compile time.
+--FILE--
+
+===DONE===
+--EXPECT--
+===DONE===
diff --git a/Zend/tests/gh14009_001.phpt b/Zend/tests/gh14009_001.phpt
new file mode 100644
index 0000000000000..81325e814c271
--- /dev/null
+++ b/Zend/tests/gh14009_001.phpt
@@ -0,0 +1,41 @@
+--TEST--
+GH-14009: Traits inherit prototype
+--FILE--
+common();
+ }
+}
+
+class B extends P {
+ protected function common() {
+ echo __METHOD__, "\n";
+ }
+}
+
+trait T {
+ protected function common() {
+ echo __METHOD__, "\n";
+ }
+}
+
+class C extends P {
+ use T;
+}
+
+$a = new A();
+$a->test(new B());
+$a->test(new C());
+
+?>
+--EXPECT--
+B::common
+T::common
diff --git a/Zend/tests/gh14009_002.phpt b/Zend/tests/gh14009_002.phpt
new file mode 100644
index 0000000000000..86047e020205e
--- /dev/null
+++ b/Zend/tests/gh14009_002.phpt
@@ -0,0 +1,22 @@
+--TEST--
+GH-14009: Traits inherit prototype
+--FILE--
+
+--EXPECTF--
+Fatal error: Declaration of P::common() must be compatible with T::common(int $param) in %s on line %d
diff --git a/Zend/tests/gh14009_003.phpt b/Zend/tests/gh14009_003.phpt
new file mode 100644
index 0000000000000..71ee5baa360a6
--- /dev/null
+++ b/Zend/tests/gh14009_003.phpt
@@ -0,0 +1,19 @@
+--TEST--
+GH-14009: Traits inherit prototype
+--FILE--
+
+--EXPECT--
diff --git a/Zend/tests/gh14009_004.phpt b/Zend/tests/gh14009_004.phpt
new file mode 100644
index 0000000000000..01bad46fedcb7
--- /dev/null
+++ b/Zend/tests/gh14009_004.phpt
@@ -0,0 +1,37 @@
+--TEST--
+GH-14009: Traits inherit prototype
+--FILE--
+test();
+ }
+}
+
+D::callTest(new C());
+
+?>
+--EXPECT--
+B::test
diff --git a/Zend/tests/gh14009_005.phpt b/Zend/tests/gh14009_005.phpt
new file mode 100644
index 0000000000000..c079bfb832aa8
--- /dev/null
+++ b/Zend/tests/gh14009_005.phpt
@@ -0,0 +1,38 @@
+--TEST--
+GH-14009: Traits inherit prototype
+--FILE--
+ ". __CLASS__ . "::" . __METHOD__ . "\n";
+ }
+}
+
+class A {
+ use T;
+ public function foo() {
+ $this->test(__METHOD__);
+ }
+ public function bar() {
+ $this->test(__METHOD__);
+ }
+}
+
+class B extends A {
+ use T;
+ public function foo() {
+ $this->test(__METHOD__);
+ }
+}
+
+(new A)->foo();
+(new A)->bar();
+(new B)->foo();
+(new B)->bar();
+?>
+--EXPECT--
+A::foo -> A::T::test
+A::bar -> A::T::test
+B::foo -> B::T::test
+A::bar -> A::T::test
diff --git a/Zend/tests/interface_constructor_prototype_001.phpt b/Zend/tests/interface_constructor_prototype_001.phpt
new file mode 100644
index 0000000000000..67341367ac58f
--- /dev/null
+++ b/Zend/tests/interface_constructor_prototype_001.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Interfaces don't set prototypes to their parent method
+--FILE--
+
+--EXPECTF--
+Fatal error: Declaration of Test::__construct(int $param) must be compatible with B::__construct(int|float $param) in %s on line %d
diff --git a/Zend/tests/interface_constructor_prototype_002.phpt b/Zend/tests/interface_constructor_prototype_002.phpt
new file mode 100644
index 0000000000000..76398d2d215b9
--- /dev/null
+++ b/Zend/tests/interface_constructor_prototype_002.phpt
@@ -0,0 +1,26 @@
+--TEST--
+Interfaces don't set prototypes to their parent method
+--XFAIL--
+X::__constructor()'s prototype is set to B::__construct(). Y::__construct() then
+uses prototype to verify LSP, but misses A::__construct() which has a stricter
+signature.
+--FILE--
+
+--EXPECTF--
+Fatal error: Declaration of Y::__construct(int $param) must be compatible with A::__construct(int|float $param) in %s on line %d
diff --git a/Zend/zend.h b/Zend/zend.h
index ad8fde7a8ad1c..67b8a5291e375 100644
--- a/Zend/zend.h
+++ b/Zend/zend.h
@@ -20,7 +20,7 @@
#ifndef ZEND_H
#define ZEND_H
-#define ZEND_VERSION "4.2.19-dev"
+#define ZEND_VERSION "4.2.20"
#define ZEND_ENGINE_3
diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c
index 5a446c8c2859e..2471a6739e3c6 100644
--- a/Zend/zend_attributes.c
+++ b/Zend/zend_attributes.c
@@ -35,31 +35,39 @@ static zend_object_handlers attributes_object_handlers_sensitive_parameter_value
static HashTable internal_attributes;
void validate_attribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
+{
+}
+
+uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_entry *scope)
{
// TODO: More proper signature validation: Too many args, incorrect arg names.
if (attr->argc > 0) {
zval flags;
- /* As this is run in the middle of compilation, fetch the attribute value without
- * specifying a scope. The class is not fully linked yet, and we may seen an
- * inconsistent state. */
- if (FAILURE == zend_get_attribute_value(&flags, attr, 0, NULL)) {
- return;
+ if (FAILURE == zend_get_attribute_value(&flags, attr, 0, scope)) {
+ ZEND_ASSERT(EG(exception));
+ return 0;
}
if (Z_TYPE(flags) != IS_LONG) {
- zend_error_noreturn(E_ERROR,
+ zend_throw_error(NULL,
"Attribute::__construct(): Argument #1 ($flags) must be of type int, %s given",
zend_zval_type_name(&flags)
);
+ zval_ptr_dtor(&flags);
+ return 0;
}
- if (Z_LVAL(flags) & ~ZEND_ATTRIBUTE_FLAGS) {
- zend_error_noreturn(E_ERROR, "Invalid attribute flags specified");
+ uint32_t flags_l = Z_LVAL(flags);
+ if (flags_l & ~ZEND_ATTRIBUTE_FLAGS) {
+ zend_throw_error(NULL, "Invalid attribute flags specified");
+ return 0;
}
- zval_ptr_dtor(&flags);
+ return flags_l;
}
+
+ return ZEND_ATTRIBUTE_TARGET_ALL;
}
static void validate_allow_dynamic_properties(
diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h
index fc02a7d656e25..746155f514735 100644
--- a/Zend/zend_attributes.h
+++ b/Zend/zend_attributes.h
@@ -85,6 +85,8 @@ ZEND_API zend_attribute *zend_add_attribute(
HashTable **attributes, zend_string *name, uint32_t argc,
uint32_t flags, uint32_t offset, uint32_t lineno);
+uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_entry *scope);
+
END_EXTERN_C()
static zend_always_inline zend_attribute *zend_add_class_attribute(zend_class_entry *ce, zend_string *name, uint32_t argc)
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c
index cec0ffdb1853b..8c7ea963891bf 100644
--- a/Zend/zend_inheritance.c
+++ b/Zend/zend_inheritance.c
@@ -1075,26 +1075,45 @@ static void perform_delayable_implementation_check(
}
}
-static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
+
+#define ZEND_INHERITANCE_LAZY_CHILD_CLONE (1<<0)
+#define ZEND_INHERITANCE_CHECK_SILENT (1<<1) /* don't throw errors */
+#define ZEND_INHERITANCE_CHECK_PROTO (1<<2) /* check method prototype (it might be already checked before) */
+#define ZEND_INHERITANCE_CHECK_VISIBILITY (1<<3)
+#define ZEND_INHERITANCE_SET_CHILD_CHANGED (1<<4)
+#define ZEND_INHERITANCE_SET_CHILD_PROTO (1<<5)
+
+static inheritance_status do_inheritance_check_on_method(
zend_function *child, zend_class_entry *child_scope,
zend_function *parent, zend_class_entry *parent_scope,
- zend_class_entry *ce, zval *child_zv,
- bool check_visibility, bool check_only, bool checked) /* {{{ */
+ zend_class_entry *ce, zval *child_zv, uint32_t flags) /* {{{ */
{
uint32_t child_flags;
uint32_t parent_flags = parent->common.fn_flags;
zend_function *proto;
- if (UNEXPECTED((parent_flags & ZEND_ACC_PRIVATE) && !(parent_flags & ZEND_ACC_ABSTRACT) && !(parent_flags & ZEND_ACC_CTOR))) {
- if (!check_only) {
+#define SEPARATE_METHOD() do { \
+ if ((flags & ZEND_INHERITANCE_LAZY_CHILD_CLONE) \
+ && child_scope != ce && child->type == ZEND_USER_FUNCTION) { \
+ /* op_array wasn't duplicated yet */ \
+ zend_function *new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); \
+ memcpy(new_function, child, sizeof(zend_op_array)); \
+ Z_PTR_P(child_zv) = child = new_function; \
+ flags &= ~ZEND_INHERITANCE_LAZY_CHILD_CLONE; \
+ } \
+ } while(0)
+
+ if (UNEXPECTED((parent_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_ABSTRACT|ZEND_ACC_CTOR)) == ZEND_ACC_PRIVATE)) {
+ if (flags & ZEND_INHERITANCE_SET_CHILD_CHANGED) {
+ SEPARATE_METHOD();
child->common.fn_flags |= ZEND_ACC_CHANGED;
}
/* The parent method is private and not an abstract so we don't need to check any inheritance rules */
return INHERITANCE_SUCCESS;
}
- if (!checked && UNEXPECTED(parent_flags & ZEND_ACC_FINAL)) {
- if (check_only) {
+ if ((flags & ZEND_INHERITANCE_CHECK_PROTO) && UNEXPECTED(parent_flags & ZEND_ACC_FINAL)) {
+ if (flags & ZEND_INHERITANCE_CHECK_SILENT) {
return INHERITANCE_ERROR;
}
zend_error_at_noreturn(E_COMPILE_ERROR, func_filename(child), func_lineno(child),
@@ -1105,8 +1124,9 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
child_flags = child->common.fn_flags;
/* You cannot change from static to non static and vice versa.
*/
- if (!checked && UNEXPECTED((child_flags & ZEND_ACC_STATIC) != (parent_flags & ZEND_ACC_STATIC))) {
- if (check_only) {
+ if ((flags & ZEND_INHERITANCE_CHECK_PROTO)
+ && UNEXPECTED((child_flags & ZEND_ACC_STATIC) != (parent_flags & ZEND_ACC_STATIC))) {
+ if (flags & ZEND_INHERITANCE_CHECK_SILENT) {
return INHERITANCE_ERROR;
}
if (child_flags & ZEND_ACC_STATIC) {
@@ -1121,8 +1141,9 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
}
/* Disallow making an inherited method abstract. */
- if (!checked && UNEXPECTED((child_flags & ZEND_ACC_ABSTRACT) > (parent_flags & ZEND_ACC_ABSTRACT))) {
- if (check_only) {
+ if ((flags & ZEND_INHERITANCE_CHECK_PROTO)
+ && UNEXPECTED((child_flags & ZEND_ACC_ABSTRACT) > (parent_flags & ZEND_ACC_ABSTRACT))) {
+ if (flags & ZEND_INHERITANCE_CHECK_SILENT) {
return INHERITANCE_ERROR;
}
zend_error_at_noreturn(E_COMPILE_ERROR, func_filename(child), func_lineno(child),
@@ -1130,7 +1151,9 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
ZEND_FN_SCOPE_NAME(parent), ZSTR_VAL(child->common.function_name), ZEND_FN_SCOPE_NAME(child));
}
- if (!check_only && (parent_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_CHANGED))) {
+ if ((flags & ZEND_INHERITANCE_SET_CHILD_CHANGED)
+ && (parent_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_CHANGED))) {
+ SEPARATE_METHOD();
child->common.fn_flags |= ZEND_ACC_CHANGED;
}
@@ -1146,27 +1169,16 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
parent = proto;
}
- if (!check_only && child->common.prototype != proto && child_zv) {
- do {
- if (child->common.scope != ce && child->type == ZEND_USER_FUNCTION) {
- if (ce->ce_flags & ZEND_ACC_INTERFACE) {
- /* Few parent interfaces contain the same method */
- break;
- } else {
- /* op_array wasn't duplicated yet */
- zend_function *new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array));
- memcpy(new_function, child, sizeof(zend_op_array));
- Z_PTR_P(child_zv) = child = new_function;
- }
- }
- child->common.prototype = proto;
- } while (0);
+ if ((flags & ZEND_INHERITANCE_SET_CHILD_PROTO)
+ && child->common.prototype != proto) {
+ SEPARATE_METHOD();
+ child->common.prototype = proto;
}
/* Prevent derived classes from restricting access that was available in parent classes (except deriving from non-abstract ctors) */
- if (!checked && check_visibility
+ if ((flags & ZEND_INHERITANCE_CHECK_VISIBILITY)
&& (child_flags & ZEND_ACC_PPP_MASK) > (parent_flags & ZEND_ACC_PPP_MASK)) {
- if (check_only) {
+ if (flags & ZEND_INHERITANCE_CHECK_SILENT) {
return INHERITANCE_ERROR;
}
zend_error_at_noreturn(E_COMPILE_ERROR, func_filename(child), func_lineno(child),
@@ -1174,25 +1186,20 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
ZEND_FN_SCOPE_NAME(child), ZSTR_VAL(child->common.function_name), zend_visibility_string(parent_flags), ZEND_FN_SCOPE_NAME(parent), (parent_flags&ZEND_ACC_PUBLIC) ? "" : " or weaker");
}
- if (!checked) {
- if (check_only) {
+ if (flags & ZEND_INHERITANCE_CHECK_PROTO) {
+ if (flags & ZEND_INHERITANCE_CHECK_SILENT) {
return zend_do_perform_implementation_check(child, child_scope, parent, parent_scope);
}
perform_delayable_implementation_check(ce, child, child_scope, parent, parent_scope);
}
+
+#undef SEPARATE_METHOD
+
return INHERITANCE_SUCCESS;
}
/* }}} */
-static zend_never_inline void do_inheritance_check_on_method(
- zend_function *child, zend_class_entry *child_scope,
- zend_function *parent, zend_class_entry *parent_scope,
- zend_class_entry *ce, zval *child_zv, bool check_visibility)
-{
- do_inheritance_check_on_method_ex(child, child_scope, parent, parent_scope, ce, child_zv, check_visibility, 0, 0);
-}
-
-static zend_always_inline void do_inherit_method(zend_string *key, zend_function *parent, zend_class_entry *ce, bool is_interface, bool checked) /* {{{ */
+static void do_inherit_method(zend_string *key, zend_function *parent, zend_class_entry *ce, bool is_interface, uint32_t flags) /* {{{ */
{
zval *child = zend_hash_find_known_hash(&ce->function_table, key);
@@ -1204,15 +1211,8 @@ static zend_always_inline void do_inherit_method(zend_string *key, zend_function
return;
}
- if (checked) {
- do_inheritance_check_on_method_ex(
- func, func->common.scope, parent, parent->common.scope, ce, child,
- /* check_visibility */ 1, 0, checked);
- } else {
- do_inheritance_check_on_method(
- func, func->common.scope, parent, parent->common.scope, ce, child,
- /* check_visibility */ 1);
- }
+ do_inheritance_check_on_method(
+ func, func->common.scope, parent, parent->common.scope, ce, child, flags);
} else {
if (is_interface || (parent->common.fn_flags & (ZEND_ACC_ABSTRACT))) {
@@ -1618,16 +1618,15 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
zend_hash_extend(&ce->function_table,
zend_hash_num_elements(&ce->function_table) +
zend_hash_num_elements(&parent_ce->function_table), 0);
+ uint32_t flags =
+ ZEND_INHERITANCE_LAZY_CHILD_CLONE | ZEND_INHERITANCE_SET_CHILD_CHANGED | ZEND_INHERITANCE_SET_CHILD_PROTO;
- if (checked) {
- ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, func) {
- do_inherit_method(key, func, ce, 0, 1);
- } ZEND_HASH_FOREACH_END();
- } else {
- ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, func) {
- do_inherit_method(key, func, ce, 0, 0);
- } ZEND_HASH_FOREACH_END();
+ if (!checked) {
+ flags |= ZEND_INHERITANCE_CHECK_PROTO | ZEND_INHERITANCE_CHECK_VISIBILITY;
}
+ ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, func) {
+ do_inherit_method(key, func, ce, 0, flags);
+ } ZEND_HASH_FOREACH_END();
}
do_inherit_parent_constructor(ce);
@@ -1733,13 +1732,20 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry *
zend_function *func;
zend_string *key;
zend_class_constant *c;
+ uint32_t flags = ZEND_INHERITANCE_CHECK_PROTO | ZEND_INHERITANCE_CHECK_VISIBILITY;
+
+ if (!(ce->ce_flags & ZEND_ACC_INTERFACE)) {
+ /* We are not setting the prototype of overridden interface methods because of abstract
+ * constructors. See Zend/tests/interface_constructor_prototype_001.phpt. */
+ flags |= ZEND_INHERITANCE_LAZY_CHILD_CLONE | ZEND_INHERITANCE_SET_CHILD_PROTO;
+ }
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) {
do_inherit_iface_constant(key, c, ce, iface);
} ZEND_HASH_FOREACH_END();
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->function_table, key, func) {
- do_inherit_method(key, func, ce, 1, 0);
+ do_inherit_method(key, func, ce, 1, flags);
} ZEND_HASH_FOREACH_END();
do_implement_interface(ce, iface);
@@ -1868,6 +1874,7 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
{
zend_function *existing_fn = NULL;
zend_function *new_fn;
+ bool check_inheritance = false;
if ((existing_fn = zend_hash_find_ptr(&ce->function_table, key)) != NULL) {
/* if it is the same function with the same visibility and has not been assigned a class scope yet, regardless
@@ -1887,7 +1894,7 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
*/
do_inheritance_check_on_method(
existing_fn, fixup_trait_scope(existing_fn, ce), fn, fixup_trait_scope(fn, ce),
- ce, NULL, /* check_visibility */ 0);
+ ce, NULL, ZEND_INHERITANCE_CHECK_PROTO);
return;
}
@@ -1902,11 +1909,7 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
ZSTR_VAL(ce->name), ZSTR_VAL(name),
ZSTR_VAL(existing_fn->common.scope->name), ZSTR_VAL(existing_fn->common.function_name));
} else {
- /* Inherited members are overridden by members inserted by traits.
- * Check whether the trait method fulfills the inheritance requirements. */
- do_inheritance_check_on_method(
- fn, fixup_trait_scope(fn, ce), existing_fn, fixup_trait_scope(existing_fn, ce),
- ce, NULL, /* check_visibility */ 1);
+ check_inheritance = true;
}
}
@@ -1926,6 +1929,16 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
function_add_ref(new_fn);
fn = zend_hash_update_ptr(&ce->function_table, key, new_fn);
zend_add_magic_method(ce, fn, key);
+
+ if (check_inheritance) {
+ /* Inherited members are overridden by members inserted by traits.
+ * Check whether the trait method fulfills the inheritance requirements. */
+ do_inheritance_check_on_method(
+ fn, fixup_trait_scope(fn, ce), existing_fn, fixup_trait_scope(existing_fn, ce),
+ ce, NULL,
+ ZEND_INHERITANCE_CHECK_PROTO | ZEND_INHERITANCE_CHECK_VISIBILITY |
+ ZEND_INHERITANCE_SET_CHILD_CHANGED| ZEND_INHERITANCE_SET_CHILD_PROTO);
+ }
}
/* }}} */
@@ -3103,10 +3116,11 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
if (zv) {
zend_function *child_func = Z_FUNC_P(zv);
inheritance_status status =
- do_inheritance_check_on_method_ex(
+ do_inheritance_check_on_method(
child_func, child_func->common.scope,
parent_func, parent_func->common.scope,
- ce, NULL, /* check_visibility */ 1, 1, 0);
+ ce, NULL,
+ ZEND_INHERITANCE_CHECK_SILENT | ZEND_INHERITANCE_CHECK_PROTO | ZEND_INHERITANCE_CHECK_VISIBILITY);
if (UNEXPECTED(status == INHERITANCE_WARNING)) {
overall_status = INHERITANCE_WARNING;
} else if (UNEXPECTED(status != INHERITANCE_SUCCESS)) {
diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h
index d59fc6d07c1db..6ca9b7266b6e4 100644
--- a/Zend/zend_portability.h
+++ b/Zend/zend_portability.h
@@ -132,7 +132,7 @@
#if defined(HAVE_LIBDL) && !defined(ZEND_WIN32)
-# if __has_feature(address_sanitizer)
+# if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
# define __SANITIZE_ADDRESS__
# endif
diff --git a/build/php.m4 b/build/php.m4
index 2bc0733a978a6..8ce9656ad1d89 100644
--- a/build/php.m4
+++ b/build/php.m4
@@ -1929,9 +1929,8 @@ AC_DEFUN([PHP_SETUP_ICU],[
ICU_CFLAGS="$ICU_CFLAGS -DU_NO_DEFAULT_INCLUDE_UTF_HEADERS=1"
ICU_CXXFLAGS="$ICU_CXXFLAGS -DUNISTR_FROM_CHAR_EXPLICIT=explicit -DUNISTR_FROM_STRING_EXPLICIT=explicit"
- if test "$PKG_CONFIG icu-io --atleast-version=60"; then
- ICU_CFLAGS="$ICU_CFLAGS -DU_HIDE_OBSOLETE_UTF_OLD_H=1"
- fi
+ AS_IF([$PKG_CONFIG icu-io --atleast-version=60],
+ [ICU_CFLAGS="$ICU_CFLAGS -DU_HIDE_OBSOLETE_UTF_OLD_H=1"])
])
dnl
diff --git a/configure.ac b/configure.ac
index 683029f476a94..effbdb6ec2c9a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice.
dnl ----------------------------------------------------------------------------
AC_PREREQ([2.68])
-AC_INIT([PHP],[8.2.19-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net])
+AC_INIT([PHP],[8.2.20],[https://github.com/php/php-src/issues],[php],[https://www.php.net])
AC_CONFIG_SRCDIR([main/php_version.h])
AC_CONFIG_AUX_DIR([build])
AC_PRESERVE_HELP_ORDER
@@ -218,6 +218,9 @@ case $host_cpu in
;;
esac
+dnl See https://github.com/php/php-src/issues/14140
+AX_CHECK_COMPILE_FLAG([-ffp-contract=off], [CFLAGS="$CFLAGS -ffp-contract=off"])
+
dnl Mark symbols hidden by default if the compiler (for example, gcc >= 4)
dnl supports it. This can help reduce the binary size and startup time.
AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],
diff --git a/ext/bcmath/libbcmath/src/bcmath.h b/ext/bcmath/libbcmath/src/bcmath.h
index e6273c18a1101..2d247bd860b2e 100644
--- a/ext/bcmath/libbcmath/src/bcmath.h
+++ b/ext/bcmath/libbcmath/src/bcmath.h
@@ -128,7 +128,7 @@ int bc_modulo(bc_num num1, bc_num num2, bc_num *resul, int scale);
int bc_divmod(bc_num num1, bc_num num2, bc_num *quo, bc_num *rem, int scale);
-int bc_raisemod(bc_num base, bc_num expo, bc_num mo, bc_num *result, int scale);
+zend_result bc_raisemod(bc_num base, bc_num expo, bc_num mo, bc_num *result, int scale);
void bc_raise(bc_num num1, bc_num num2, bc_num *resul, int scale);
diff --git a/ext/com_dotnet/tests/bug73679.phpt b/ext/com_dotnet/tests/bug73679.phpt
index 9815cdcaacd82..56f02dd8f7234 100644
--- a/ext/com_dotnet/tests/bug73679.phpt
+++ b/ext/com_dotnet/tests/bug73679.phpt
@@ -5,6 +5,7 @@ com_dotnet
--SKIPIF--
--FILE--
--FILE--
curobj;
+ return Z_ISUNDEF(iterator->curobj) ? NULL : &iterator->curobj;
}
/* }}} */
diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h
index 32f2419323833..9c4e2f5ee5785 100644
--- a/ext/dom/dom_properties.h
+++ b/ext/dom/dom_properties.h
@@ -81,6 +81,11 @@ int dom_entity_actual_encoding_read(dom_object *obj, zval *retval);
int dom_entity_encoding_read(dom_object *obj, zval *retval);
int dom_entity_version_read(dom_object *obj, zval *retval);
+/* entity reference properties */
+int dom_entity_reference_child_read(dom_object *obj, zval *retval);
+int dom_entity_reference_text_content_read(dom_object *obj, zval *retval);
+int dom_entity_reference_child_nodes_read(dom_object *obj, zval *retval);
+
/* namednodemap properties */
int dom_namednodemap_length_read(dom_object *obj, zval *retval);
diff --git a/ext/dom/entityreference.c b/ext/dom/entityreference.c
index 61f0b92eedc22..30928b1c6d1bf 100644
--- a/ext/dom/entityreference.c
+++ b/ext/dom/entityreference.c
@@ -22,6 +22,7 @@
#include "php.h"
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
#include "php_dom.h"
+#include "dom_properties.h"
/*
* class DOMEntityReference extends DOMNode
@@ -65,4 +66,63 @@ PHP_METHOD(DOMEntityReference, __construct)
}
/* }}} end DOMEntityReference::__construct */
+/* The following property handlers are necessary because of special lifetime management with entities and entity
+ * references. The issue is that entity references hold a reference to an entity declaration, but don't
+ * register that reference anywhere. When the entity declaration disappears we have no way of notifying the
+ * entity references. Override the property handlers for the declaration-accessing properties to fix this problem. */
+
+xmlEntityPtr dom_entity_reference_fetch_and_sync_declaration(xmlNodePtr reference)
+{
+ xmlEntityPtr entity = xmlGetDocEntity(reference->doc, reference->name);
+ reference->children = (xmlNodePtr) entity;
+ reference->last = (xmlNodePtr) entity;
+ reference->content = entity ? entity->content : NULL;
+ return entity;
+}
+
+int dom_entity_reference_child_read(dom_object *obj, zval *retval)
+{
+ xmlNodePtr nodep = dom_object_get_node(obj);
+
+ if (nodep == NULL) {
+ php_dom_throw_error(INVALID_STATE_ERR, true);
+ return FAILURE;
+ }
+
+ xmlEntityPtr entity = dom_entity_reference_fetch_and_sync_declaration(nodep);
+ if (entity == NULL) {
+ ZVAL_NULL(retval);
+ return SUCCESS;
+ }
+
+ php_dom_create_object((xmlNodePtr) entity, retval, obj);
+ return SUCCESS;
+}
+
+int dom_entity_reference_text_content_read(dom_object *obj, zval *retval)
+{
+ xmlNodePtr nodep = dom_object_get_node(obj);
+
+ if (nodep == NULL) {
+ php_dom_throw_error(INVALID_STATE_ERR, true);
+ return FAILURE;
+ }
+
+ dom_entity_reference_fetch_and_sync_declaration(nodep);
+ return dom_node_text_content_read(obj, retval);
+}
+
+int dom_entity_reference_child_nodes_read(dom_object *obj, zval *retval)
+{
+ xmlNodePtr nodep = dom_object_get_node(obj);
+
+ if (nodep == NULL) {
+ php_dom_throw_error(INVALID_STATE_ERR, true);
+ return FAILURE;
+ }
+
+ dom_entity_reference_fetch_and_sync_declaration(nodep);
+ return dom_node_child_nodes_read(obj, retval);
+}
+
#endif
diff --git a/ext/dom/node.c b/ext/dom/node.c
index 973505c5b01a9..c80f9c3333c6c 100644
--- a/ext/dom/node.c
+++ b/ext/dom/node.c
@@ -1614,7 +1614,7 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
zval *tmp;
char *xquery;
- tmp = zend_hash_str_find(ht, "query", sizeof("query")-1);
+ tmp = zend_hash_str_find_deref(ht, "query", sizeof("query")-1);
if (!tmp) {
/* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
zend_argument_value_error(3 + mode, "must have a \"query\" key");
@@ -1630,12 +1630,13 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
ctxp = xmlXPathNewContext(docp);
ctxp->node = nodep;
- tmp = zend_hash_str_find(ht, "namespaces", sizeof("namespaces")-1);
+ tmp = zend_hash_str_find_deref(ht, "namespaces", sizeof("namespaces")-1);
if (tmp && Z_TYPE_P(tmp) == IS_ARRAY && !HT_IS_PACKED(Z_ARRVAL_P(tmp))) {
zval *tmpns;
zend_string *prefix;
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), prefix, tmpns) {
+ ZVAL_DEREF(tmpns);
if (Z_TYPE_P(tmpns) == IS_STRING) {
if (prefix) {
xmlXPathRegisterNs(ctxp, (xmlChar *) ZSTR_VAL(prefix), (xmlChar *) Z_STRVAL_P(tmpns));
@@ -1666,6 +1667,7 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
inclusive_ns_prefixes = safe_emalloc(zend_hash_num_elements(Z_ARRVAL_P(ns_prefixes)) + 1,
sizeof(xmlChar *), 0);
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(ns_prefixes), tmpns) {
+ ZVAL_DEREF(tmpns);
if (Z_TYPE_P(tmpns) == IS_STRING) {
inclusive_ns_prefixes[nscount++] = (xmlChar *) Z_STRVAL_P(tmpns);
}
diff --git a/ext/dom/nodelist.c b/ext/dom/nodelist.c
index 20f6320a54607..a8e713f67d03e 100644
--- a/ext/dom/nodelist.c
+++ b/ext/dom/nodelist.c
@@ -31,6 +31,16 @@
* Since:
*/
+static xmlNodePtr dom_nodelist_iter_start_first_child(xmlNodePtr nodep)
+{
+ if (nodep->type == XML_ENTITY_REF_NODE) {
+ /* See entityreference.c */
+ dom_entity_reference_fetch_and_sync_declaration(nodep);
+ }
+
+ return nodep->children;
+}
+
int php_dom_get_nodelist_length(dom_object *obj)
{
dom_nnodemap_object *objmap = (dom_nnodemap_object *) obj->ptr;
@@ -54,7 +64,7 @@ int php_dom_get_nodelist_length(dom_object *obj)
int count = 0;
if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) {
- xmlNodePtr curnode = nodep->children;
+ xmlNodePtr curnode = dom_nodelist_iter_start_first_child(nodep);
if (curnode) {
count++;
while (curnode->next != NULL) {
@@ -128,7 +138,7 @@ void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long
if (nodep) {
int count = 0;
if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) {
- xmlNodePtr curnode = nodep->children;
+ xmlNodePtr curnode = dom_nodelist_iter_start_first_child(nodep);
while (count < index && curnode != NULL) {
count++;
curnode = curnode->next;
diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c
index d6b0705545a17..c30db6fcd745f 100644
--- a/ext/dom/parentnode.c
+++ b/ext/dom/parentnode.c
@@ -150,6 +150,22 @@ static xmlDocPtr dom_doc_from_context_node(xmlNodePtr contextNode)
}
}
+/* Citing from the docs (https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-tree.html#xmlAddChild):
+ * "Add a new node to @parent, at the end of the child (or property) list merging adjacent TEXT nodes (in which case @cur is freed)".
+ * So we must use a custom way of adding that does not merge. */
+static void dom_add_child_without_merging(xmlNodePtr parent, xmlNodePtr child)
+{
+ if (parent->children == NULL) {
+ parent->children = child;
+ } else {
+ xmlNodePtr last = parent->last;
+ last->next = child;
+ child->prev = last;
+ }
+ parent->last = child;
+ child->parent = parent;
+}
+
xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNode, zval *nodes, int nodesc)
{
int i;
@@ -183,7 +199,7 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod
* So we must take a copy if this situation arises to prevent a use-after-free. */
bool will_free = newNode->type == XML_TEXT_NODE && fragment->last && fragment->last->type == XML_TEXT_NODE;
if (will_free) {
- newNode = xmlCopyNode(newNode, 1);
+ newNode = xmlCopyNode(newNode, 0);
}
if (newNode->type == XML_DOCUMENT_FRAG_NODE) {
@@ -192,9 +208,7 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod
while (newNode) {
xmlNodePtr next = newNode->next;
xmlUnlinkNode(newNode);
- if (!xmlAddChild(fragment, newNode)) {
- goto err;
- }
+ dom_add_child_without_merging(fragment, newNode);
newNode = next;
}
} else if (!xmlAddChild(fragment, newNode)) {
diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index c86cab99c6c79..c9123ab14dfcf 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -72,6 +72,7 @@ static HashTable classes;
static HashTable dom_document_prop_handlers;
static HashTable dom_documentfragment_prop_handlers;
static HashTable dom_node_prop_handlers;
+static HashTable dom_entity_reference_prop_handlers;
static HashTable dom_nodelist_prop_handlers;
static HashTable dom_namednodemap_prop_handlers;
static HashTable dom_characterdata_prop_handlers;
@@ -284,6 +285,14 @@ static void dom_register_prop_handler(HashTable *prop_handler, char *name, size_
zend_string_release_ex(str, 1);
}
+static void dom_override_prop_handler(HashTable *prop_handler, char *name, size_t name_len, dom_read_t read_func, dom_write_t write_func)
+{
+ dom_prop_handler hnd;
+ hnd.read_func = read_func;
+ hnd.write_func = write_func;
+ zend_hash_str_update_mem(prop_handler, name, name_len, &hnd, sizeof(dom_prop_handler));
+}
+
static zval *dom_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot)
{
dom_object *obj = php_dom_obj_from_obj(object);
@@ -807,7 +816,14 @@ PHP_MINIT_FUNCTION(dom)
dom_entityreference_class_entry = register_class_DOMEntityReference(dom_node_class_entry);
dom_entityreference_class_entry->create_object = dom_objects_new;
- zend_hash_add_ptr(&classes, dom_entityreference_class_entry->name, &dom_node_prop_handlers);
+
+ zend_hash_init(&dom_entity_reference_prop_handlers, 0, NULL, dom_dtor_prop_handler, true);
+ zend_hash_merge(&dom_entity_reference_prop_handlers, &dom_node_prop_handlers, dom_copy_prop_handler, false);
+ dom_override_prop_handler(&dom_entity_reference_prop_handlers, "firstChild", sizeof("firstChild")-1, dom_entity_reference_child_read, NULL);
+ dom_override_prop_handler(&dom_entity_reference_prop_handlers, "lastChild", sizeof("lastChild")-1, dom_entity_reference_child_read, NULL);
+ dom_override_prop_handler(&dom_entity_reference_prop_handlers, "textContent", sizeof("textContent")-1, dom_entity_reference_text_content_read, NULL);
+ dom_override_prop_handler(&dom_entity_reference_prop_handlers, "childNodes", sizeof("childNodes")-1, dom_entity_reference_child_nodes_read, NULL);
+ zend_hash_add_ptr(&classes, dom_entityreference_class_entry->name, &dom_entity_reference_prop_handlers);
dom_processinginstruction_class_entry = register_class_DOMProcessingInstruction(dom_node_class_entry);
dom_processinginstruction_class_entry->create_object = dom_objects_new;
@@ -869,6 +885,7 @@ PHP_MSHUTDOWN_FUNCTION(dom) /* {{{ */
zend_hash_destroy(&dom_document_prop_handlers);
zend_hash_destroy(&dom_documentfragment_prop_handlers);
zend_hash_destroy(&dom_node_prop_handlers);
+ zend_hash_destroy(&dom_entity_reference_prop_handlers);
zend_hash_destroy(&dom_namespace_node_prop_handlers);
zend_hash_destroy(&dom_nodelist_prop_handlers);
zend_hash_destroy(&dom_namednodemap_prop_handlers);
diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h
index 9a57996729dbf..ccf665c417cd8 100644
--- a/ext/dom/php_dom.h
+++ b/ext/dom/php_dom.h
@@ -139,6 +139,7 @@ xmlNode *php_dom_libxml_notation_iter(xmlHashTable *ht, int index);
zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, int by_ref);
void dom_set_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece, zend_class_entry *ce);
xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern);
+xmlEntityPtr dom_entity_reference_fetch_and_sync_declaration(xmlNodePtr reference);
void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc);
void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc);
diff --git a/ext/dom/tests/DOMNode_C14N_references.phpt b/ext/dom/tests/DOMNode_C14N_references.phpt
new file mode 100644
index 0000000000000..5612874f5f30f
--- /dev/null
+++ b/ext/dom/tests/DOMNode_C14N_references.phpt
@@ -0,0 +1,41 @@
+--TEST--
+Test: Canonicalization - C14N() with references
+--EXTENSIONS--
+dom
+--FILE--
+
+
+
+
+
+
+
+
+EOXML;
+
+$dom = new DOMDocument();
+$dom->loadXML($xml);
+$doc = $dom->documentElement->firstChild;
+
+$xpath = [
+ 'query' => '(//a:contain | //a:bar | .//namespace::*)',
+ 'namespaces' => ['a' => 'http://www.example.com/ns/foo'],
+];
+$prefixes = ['test'];
+
+foreach ($xpath['namespaces'] as $k => &$v);
+unset($v);
+foreach ($xpath as $k => &$v);
+unset($v);
+foreach ($prefixes as $k => &$v);
+unset($v);
+
+echo $doc->C14N(true, false, $xpath, $prefixes);
+?>
+--EXPECT--
+
diff --git a/ext/dom/tests/ParentNode_append_fragment_text_coalesce.phpt b/ext/dom/tests/ParentNode_append_fragment_text_coalesce.phpt
new file mode 100644
index 0000000000000..601819d611775
--- /dev/null
+++ b/ext/dom/tests/ParentNode_append_fragment_text_coalesce.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Text coalesce bug when appending fragment with text nodes
+--EXTENSIONS--
+dom
+--FILE--
+loadXML('');
+
+$sut = $document->createDocumentFragment();
+for($i = 0; $i < 10; $i++) {
+ $textNode = $document->createTextNode("Node$i");
+ $sut->append($textNode);
+}
+
+$document->documentElement->append($sut);
+echo $document->saveXML();
+?>
+--EXPECT--
+
+Node0Node1Node2Node3Node4Node5Node6Node7Node8Node9
diff --git a/ext/dom/tests/childNodes_current_crash.phpt b/ext/dom/tests/childNodes_current_crash.phpt
new file mode 100644
index 0000000000000..aa93cf33a6481
--- /dev/null
+++ b/ext/dom/tests/childNodes_current_crash.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Crash in childNodes iterator current()
+--EXTENSIONS--
+dom
+--FILE--
+loadXML('foo1');
+
+$nodes = $dom->documentElement->childNodes;
+$iter = $nodes->getIterator();
+
+var_dump($iter->valid());
+var_dump($iter->current()?->wholeText);
+$iter->next();
+var_dump($iter->valid());
+var_dump($iter->current()?->wholeText);
+
+?>
+--EXPECT--
+bool(true)
+string(4) "foo1"
+bool(false)
+NULL
diff --git a/ext/dom/tests/entity_reference_stale_01.phpt b/ext/dom/tests/entity_reference_stale_01.phpt
new file mode 100644
index 0000000000000..dc1828c3cd999
--- /dev/null
+++ b/ext/dom/tests/entity_reference_stale_01.phpt
@@ -0,0 +1,41 @@
+--TEST--
+Entity references with stale entity declaration 01
+--EXTENSIONS--
+dom
+--FILE--
+loadXML(<<
+]>
+&foo;
+XML);
+
+$ref = $dom->documentElement->firstChild;
+$decl = $ref->firstChild;
+
+$nodes = $ref->childNodes;
+$dom->removeChild($dom->doctype);
+unset($decl);
+
+var_dump($nodes);
+var_dump($ref->firstChild);
+var_dump($ref->lastChild);
+var_dump($ref->textContent);
+var_dump($ref->childNodes);
+
+?>
+--EXPECT--
+object(DOMNodeList)#4 (1) {
+ ["length"]=>
+ int(0)
+}
+NULL
+NULL
+string(0) ""
+object(DOMNodeList)#2 (1) {
+ ["length"]=>
+ int(0)
+}
diff --git a/ext/dom/tests/entity_reference_stale_02.phpt b/ext/dom/tests/entity_reference_stale_02.phpt
new file mode 100644
index 0000000000000..ea93b3ca3ef90
--- /dev/null
+++ b/ext/dom/tests/entity_reference_stale_02.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Entity references with stale entity declaration 02
+--EXTENSIONS--
+dom
+--FILE--
+loadXML(<<
+
+
+]>
+&foo1;
+XML);
+
+$ref = $dom->documentElement->firstChild;
+$decl = $ref->firstChild;
+
+$nodes = $ref->childNodes;
+$iter = $nodes->getIterator();
+$iter->next();
+$dom->removeChild($dom->doctype);
+unset($decl);
+
+try {
+ $iter->current()->publicId;
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Couldn't fetch DOMEntity. Node no longer exists
diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c
index 5c6ca05f96108..560338e71f335 100644
--- a/ext/ffi/ffi.c
+++ b/ext/ffi/ffi.c
@@ -3255,7 +3255,11 @@ static zend_ffi *zend_ffi_load(const char *filename, bool preload) /* {{{ */
code_size = buf.st_size;
code = emalloc(code_size + 1);
- fd = open(filename, O_RDONLY, 0);
+ int open_flags = O_RDONLY;
+#ifdef PHP_WIN32
+ open_flags |= _O_BINARY;
+#endif
+ fd = open(filename, open_flags, 0);
if (fd < 0 || read(fd, code, code_size) != code_size) {
if (preload) {
zend_error(E_WARNING, "FFI: Failed pre-loading '%s', cannot read_file", filename);
diff --git a/ext/ffi/ffi_parser.c b/ext/ffi/ffi_parser.c
index eca10c27d195b..b956f885ee001 100644
--- a/ext/ffi/ffi_parser.c
+++ b/ext/ffi/ffi_parser.c
@@ -3552,7 +3552,7 @@ static void parse(void) {
}
}
-int zend_ffi_parse_decl(const char *str, size_t len) {
+zend_result zend_ffi_parse_decl(const char *str, size_t len) {
if (SETJMP(FFI_G(bailout))==0) {
FFI_G(allow_vla) = 0;
FFI_G(attribute_parsing) = 0;
@@ -3565,7 +3565,7 @@ int zend_ffi_parse_decl(const char *str, size_t len) {
}
}
-int zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl *dcl) {
+zend_result zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl *dcl) {
int sym;
if (SETJMP(FFI_G(bailout))==0) {
diff --git a/ext/ffi/tests/gh14215.h b/ext/ffi/tests/gh14215.h
new file mode 100644
index 0000000000000..9a1245b891297
--- /dev/null
+++ b/ext/ffi/tests/gh14215.h
@@ -0,0 +1,3 @@
+#define FFI_LIB "Kernel32.dll"
+typedef unsigned long DWORD;
+DWORD GetLastError(void);
diff --git a/ext/ffi/tests/gh14215.phpt b/ext/ffi/tests/gh14215.phpt
new file mode 100644
index 0000000000000..e259621e7a596
--- /dev/null
+++ b/ext/ffi/tests/gh14215.phpt
@@ -0,0 +1,23 @@
+--TEST--
+GH-14215 (Cannot use FFI::load on CRLF header file with apache2handler)
+--EXTENSIONS--
+ffi
+zend_test
+--SKIPIF--
+
+--INI--
+ffi.enable=1
+--FILE--
+GetLastError());
+zend_test_set_fmode(true);
+?>
+--EXPECT--
+int(0)
diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c
index 3f58b2a3c4fb1..ca8e65c1f75f6 100644
--- a/ext/filter/logical_filters.c
+++ b/ext/filter/logical_filters.c
@@ -89,7 +89,7 @@
#define FORMAT_IPV4 4
#define FORMAT_IPV6 6
-static int _php_filter_validate_ipv6(char *str, size_t str_len, int ip[8]);
+static int _php_filter_validate_ipv6(const char *str, size_t str_len, int ip[8]);
static int php_filter_parse_int(const char *str, size_t str_len, zend_long *ret) { /* {{{ */
zend_long ctx_value;
@@ -580,6 +580,14 @@ static int is_userinfo_valid(zend_string *str)
return 1;
}
+static bool php_filter_is_valid_ipv6_hostname(const char *s, size_t l)
+{
+ const char *e = s + l;
+ const char *t = e - 1;
+
+ return *s == '[' && *t == ']' && _php_filter_validate_ipv6(s + 1, l - 2, NULL);
+}
+
void php_filter_validate_url(https://github.com/PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
{
php_url *url;
@@ -600,7 +608,7 @@ void php_filter_validate_url(https://github.com/PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
if (url->scheme != NULL &&
(zend_string_equals_literal_ci(url->scheme, "http") || zend_string_equals_literal_ci(url->scheme, "https"))) {
- char *e, *s, *t;
+ const char *s;
size_t l;
if (url->host == NULL) {
@@ -609,17 +617,14 @@ void php_filter_validate_url(https://github.com/PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
s = ZSTR_VAL(url->host);
l = ZSTR_LEN(url->host);
- e = s + l;
- t = e - 1;
-
- /* An IPv6 enclosed by square brackets is a valid hostname */
- if (*s == '[' && *t == ']' && _php_filter_validate_ipv6((s + 1), l - 2, NULL)) {
- php_url_free(url);
- return;
- }
- // Validate domain
- if (!_php_filter_validate_domain(ZSTR_VAL(url->host), l, FILTER_FLAG_HOSTNAME)) {
+ if (
+ /* An IPv6 enclosed by square brackets is a valid hostname.*/
+ !php_filter_is_valid_ipv6_hostname(s, l) &&
+ /* Validate domain.
+ * This includes a loose check for an IPv4 address. */
+ !_php_filter_validate_domain(ZSTR_VAL(url->host), l, FILTER_FLAG_HOSTNAME)
+ ) {
php_url_free(url);
RETURN_VALIDATION_FAILED
}
@@ -753,15 +758,15 @@ static int _php_filter_validate_ipv4(char *str, size_t str_len, int *ip) /* {{{
}
/* }}} */
-static int _php_filter_validate_ipv6(char *str, size_t str_len, int ip[8]) /* {{{ */
+static int _php_filter_validate_ipv6(const char *str, size_t str_len, int ip[8]) /* {{{ */
{
int compressed_pos = -1;
int blocks = 0;
int num, n, i;
char *ipv4;
- char *end;
+ const char *end;
int ip4elm[4];
- char *s = str;
+ const char *s = str;
if (!memchr(str, ':', str_len)) {
return 0;
diff --git a/ext/filter/tests/ghsa-w8qr-v226-r27w.phpt b/ext/filter/tests/ghsa-w8qr-v226-r27w.phpt
new file mode 100644
index 0000000000000..0092408ee5ad6
--- /dev/null
+++ b/ext/filter/tests/ghsa-w8qr-v226-r27w.phpt
@@ -0,0 +1,41 @@
+--TEST--
+GHSA-w8qr-v226-r27w
+--EXTENSIONS--
+filter
+--FILE--
+
+--EXPECT--
+--- These ones should fail ---
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+--- These ones should work ---
+string(21) "http://test@127.0.0.1/"
+string(50) "http://test@[2001:db8:3333:4444:5555:6666:102:304]/"
+string(17) "http://test@[::1]/"
diff --git a/ext/gd/libgd/gd_topal.c b/ext/gd/libgd/gd_topal.c
index a92a7acb17a43..2a9fb3d608dc8 100644
--- a/ext/gd/libgd/gd_topal.c
+++ b/ext/gd/libgd/gd_topal.c
@@ -1498,7 +1498,7 @@ static int gdImageTrueColorToPaletteBody (gdImagePtr oim, int dither, int colors
colorsWanted = maxColors;
}
if (!cimP) {
- nim->pixels = gdCalloc (sizeof (unsigned char *), oim->sy);
+ nim->pixels = gdCalloc (oim->sy, sizeof (unsigned char *));
if (!nim->pixels)
{
/* No can do */
@@ -1506,7 +1506,7 @@ static int gdImageTrueColorToPaletteBody (gdImagePtr oim, int dither, int colors
}
for (i = 0; (i < nim->sy); i++)
{
- nim->pixels[i] = gdCalloc (sizeof (unsigned char *), oim->sx);
+ nim->pixels[i] = gdCalloc (oim->sx, sizeof (unsigned char *));
if (!nim->pixels[i])
{
goto outOfMemory;
@@ -1514,7 +1514,7 @@ static int gdImageTrueColorToPaletteBody (gdImagePtr oim, int dither, int colors
}
}
- cquantize = (my_cquantize_ptr) gdCalloc (sizeof (my_cquantizer), 1);
+ cquantize = (my_cquantize_ptr) gdCalloc (1, sizeof (my_cquantizer));
if (!cquantize)
{
/* No can do */
diff --git a/ext/hash/murmur/endianness.h b/ext/hash/murmur/endianness.h
index cbad6dc72fb10..11f35a402be3e 100644
--- a/ext/hash/murmur/endianness.h
+++ b/ext/hash/murmur/endianness.h
@@ -13,14 +13,7 @@ FORCE_INLINE int IsBigEndian(void)
# define BSWAP32(u) _byteswap_ulong(u)
# define BSWAP64(u) _byteswap_uint64(u)
#else
-# ifdef __has_builtin
-# if __has_builtin(__builtin_bswap32)
-# define BSWAP32(u) __builtin_bswap32(u)
-# endif // __has_builtin(__builtin_bswap32)
-# if __has_builtin(__builtin_bswap64)
-# define BSWAP64(u) __builtin_bswap64(u)
-# endif // __has_builtin(__builtin_bswap64)
-# elif defined(__GNUC__) && ( \
+# if defined(__GNUC__) && ( \
__GNUC__ > 4 || ( \
__GNUC__ == 4 && ( \
__GNUC_MINOR__ >= 3 \
@@ -29,6 +22,13 @@ FORCE_INLINE int IsBigEndian(void)
)
# define BSWAP32(u) __builtin_bswap32(u)
# define BSWAP64(u) __builtin_bswap64(u)
+# elif defined(__has_builtin)
+# if __has_builtin(__builtin_bswap32)
+# define BSWAP32(u) __builtin_bswap32(u)
+# endif // __has_builtin(__builtin_bswap32)
+# if __has_builtin(__builtin_bswap64)
+# define BSWAP64(u) __builtin_bswap64(u)
+# endif // __has_builtin(__builtin_bswap64)
# endif // __has_builtin
#endif // defined(_MSC_VER)
diff --git a/ext/intl/config.m4 b/ext/intl/config.m4
index 48f5147ca7bbf..78396dc416926 100644
--- a/ext/intl/config.m4
+++ b/ext/intl/config.m4
@@ -82,7 +82,7 @@ if test "$PHP_INTL" != "no"; then
PHP_REQUIRE_CXX()
AC_MSG_CHECKING([if intl requires -std=gnu++17])
- AS_IF([test "$PKG_CONFIG icu-uc --atleast-version=74"],[
+ AS_IF([$PKG_CONFIG icu-uc --atleast-version=74],[
AC_MSG_RESULT([yes])
PHP_CXX_COMPILE_STDCXX(17, mandatory, PHP_INTL_STDCXX)
],[
diff --git a/ext/mysqli/mysqli.c b/ext/mysqli/mysqli.c
index 9fa46acbb7d29..14c2af6a1a902 100644
--- a/ext/mysqli/mysqli.c
+++ b/ext/mysqli/mysqli.c
@@ -731,7 +731,7 @@ void php_mysqli_fetch_into_hash_aux(zval *return_value, MYSQL_RES * result, zend
/* TODO: We don't have access to the connection object at this point, so we use low-level
* mysqlnd APIs to access the error information. We should try to pass through the connection
* object instead. */
- if (MyG(report_mode) & MYSQLI_REPORT_ERROR) {
+ if (MyG(report_mode) & MYSQLI_REPORT_ERROR && result->conn) {
MYSQLND_CONN_DATA *conn = result->conn;
unsigned error_no = conn->m->get_error_no(conn);
if (error_no) {
diff --git a/ext/mysqli/tests/gh14255.phpt b/ext/mysqli/tests/gh14255.phpt
new file mode 100644
index 0000000000000..375eda0c5b52c
--- /dev/null
+++ b/ext/mysqli/tests/gh14255.phpt
@@ -0,0 +1,31 @@
+--TEST--
+Bug GH-14255 (mysqli_fetch_assoc reports error from nested query)
+--EXTENSIONS--
+mysqli
+--SKIPIF--
+
+--FILE--
+query('SELECT 1 ');
+$c = $ca->fetch_assoc();
+try {
+ $mysqli->query('SELECT non_existent_column');
+} catch (Exception $e) {
+ echo "Caught exception"."\n";
+}
+$c = $ca->fetch_assoc();
+
+print "done!";
+?>
+--EXPECTF--
+Caught exception
+done!
diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c
index 0331518d7d3d6..cf091a802bb66 100644
--- a/ext/mysqlnd/mysqlnd_result.c
+++ b/ext/mysqlnd/mysqlnd_result.c
@@ -972,6 +972,13 @@ MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, const unsigned int
bool fetched_anything;
zval *row_data;
+ // We clean the error here because in unbuffered mode we could receive a new error
+ // and therefore consumers of this method are checking for errors
+ MYSQLND_CONN_DATA *conn = result->conn;
+ if (conn) {
+ SET_EMPTY_ERROR(conn->error_info);
+ }
+
DBG_ENTER("mysqlnd_res::fetch_into");
if (FAIL == result->m.fetch_row(result, &row_data, flags, &fetched_anything)) {
RETVAL_FALSE;
diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c
index 87f1aee1480bc..990ad64ed99bf 100644
--- a/ext/opcache/zend_persist.c
+++ b/ext/opcache/zend_persist.c
@@ -802,12 +802,17 @@ static zend_property_info *zend_persist_property_info(zend_property_info *prop)
static void zend_persist_class_constant(zval *zv)
{
- zend_class_constant *c = zend_shared_alloc_get_xlat_entry(Z_PTR_P(zv));
+ zend_class_constant *orig_c = Z_PTR_P(zv);
+ zend_class_constant *c = zend_shared_alloc_get_xlat_entry(orig_c);
zend_class_entry *ce;
if (c) {
Z_PTR_P(zv) = c;
return;
+ } else if (((orig_c->ce->ce_flags & ZEND_ACC_IMMUTABLE) && !(Z_CONSTANT_FLAGS(orig_c->value) & CONST_OWNED))
+ || orig_c->ce->type == ZEND_INTERNAL_CLASS) {
+ /* Class constant comes from a different file in shm or internal class, keep existing pointer. */
+ return;
} else if (!ZCG(current_persistent_script)->corrupted
&& zend_accel_in_shm(Z_PTR_P(zv))) {
return;
diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c
index 4e3af0d68c653..abe0a280d9a23 100644
--- a/ext/opcache/zend_persist_calc.c
+++ b/ext/opcache/zend_persist_calc.c
@@ -26,6 +26,7 @@
#include "zend_shared_alloc.h"
#include "zend_operators.h"
#include "zend_attributes.h"
+#include "zend_constants.h"
#define ADD_DUP_SIZE(m,s) ZCG(current_persistent_script)->size += zend_shared_memdup_size((void*)m, s)
#define ADD_SIZE(m) ZCG(current_persistent_script)->size += ZEND_ALIGNED_SIZE(m)
@@ -386,6 +387,11 @@ static void zend_persist_class_constant_calc(zval *zv)
zend_class_constant *c = Z_PTR_P(zv);
if (!zend_shared_alloc_get_xlat_entry(c)) {
+ if (((c->ce->ce_flags & ZEND_ACC_IMMUTABLE) && !(Z_CONSTANT_FLAGS(c->value) & CONST_OWNED))
+ || c->ce->type == ZEND_INTERNAL_CLASS) {
+ /* Class constant comes from a different file in shm or internal class, keep existing pointer. */
+ return;
+ }
if (!ZCG(current_persistent_script)->corrupted
&& zend_accel_in_shm(Z_PTR_P(zv))) {
return;
diff --git a/ext/openssl/tests/gh13860.phpt b/ext/openssl/tests/gh13860.phpt
index c7c0c40a15760..0b52e0e0583ab 100644
--- a/ext/openssl/tests/gh13860.phpt
+++ b/ext/openssl/tests/gh13860.phpt
@@ -9,7 +9,7 @@ if (!function_exists("proc_open")) die("skip no proc_open");
--FILE--
current_row) {
- S->current_row = ecalloc(sizeof(zval), stmt->column_count);
+ S->current_row = ecalloc(stmt->column_count, sizeof(zval));
}
for (unsigned i = 0; i < stmt->column_count; i++) {
zval_ptr_dtor_nogc(&S->current_row[i]);
diff --git a/ext/readline/readline.c b/ext/readline/readline.c
index db2776fb27a9a..1bd5e2fd6059a 100644
--- a/ext/readline/readline.c
+++ b/ext/readline/readline.c
@@ -456,7 +456,7 @@ char **php_readline_completion_cb(const char *text, int start, int end)
matches = rl_completion_matches(text,_readline_command_generator);
} else {
/* libedit will read matches[2] */
- matches = calloc(sizeof(char *), 3);
+ matches = calloc(3, sizeof(char *));
if (!matches) {
return NULL;
}
diff --git a/ext/readline/readline_cli.c b/ext/readline/readline_cli.c
index 84b261db34283..5ca77bc3c6789 100644
--- a/ext/readline/readline_cli.c
+++ b/ext/readline/readline_cli.c
@@ -343,6 +343,7 @@ static int cli_is_valid_code(char *code, size_t len, zend_string **prompt) /* {{
case ' ':
case '\t':
case '\'':
+ case '"':
break;
case '\r':
case '\n':
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 136d69b2864f4..f24b00a2857f9 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -6739,16 +6739,9 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
}
if (ce->type == ZEND_USER_CLASS) {
- uint32_t flags = ZEND_ATTRIBUTE_TARGET_ALL;
-
- if (marker->argc > 0) {
- zval tmp;
-
- if (FAILURE == zend_get_attribute_value(&tmp, marker, 0, ce)) {
- RETURN_THROWS();
- }
-
- flags = (uint32_t) Z_LVAL(tmp);
+ uint32_t flags = zend_attribute_attribute_get_flags(marker, ce);
+ if (EG(exception)) {
+ RETURN_THROWS();
}
if (!(attr->target & flags)) {
diff --git a/ext/standard/image.c b/ext/standard/image.c
index d5455963f3120..d064d9d4fd3b4 100644
--- a/ext/standard/image.c
+++ b/ext/standard/image.c
@@ -749,10 +749,10 @@ static signed short php_ifd_get16s(void *Short, int motorola_intel)
static int php_ifd_get32s(void *Long, int motorola_intel)
{
if (motorola_intel) {
- return ((( char *)Long)[0] << 24) | (((unsigned char *)Long)[1] << 16)
- | (((unsigned char *)Long)[2] << 8 ) | (((unsigned char *)Long)[3] << 0 );
+ return ((unsigned)((( char *)Long)[0]) << 24) | (((unsigned char *)Long)[1] << 16)
+ | ((( char *)Long)[2] << 8 ) | (((unsigned char *)Long)[3] << 0 );
} else {
- return ((( char *)Long)[3] << 24) | (((unsigned char *)Long)[2] << 16)
+ return ((unsigned)((( char *)Long)[3]) << 24) | (((unsigned char *)Long)[2] << 16)
| (((unsigned char *)Long)[1] << 8 ) | (((unsigned char *)Long)[0] << 0 );
}
}
diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c
index 26bbe0afa3ee0..03c0063a466de 100644
--- a/ext/standard/proc_open.c
+++ b/ext/standard/proc_open.c
@@ -546,48 +546,39 @@ static void append_win_escaped_arg(smart_str *str, zend_string *arg, bool is_cmd
smart_str_appendc(str, '"');
}
-static inline int stricmp_end(const char* suffix, const char* str) {
- size_t suffix_len = strlen(suffix);
- size_t str_len = strlen(str);
+static bool is_executed_by_cmd(const char *prog_name, size_t prog_name_length)
+{
+ size_t out_len;
+ WCHAR long_name[MAX_PATH];
+ WCHAR full_name[MAX_PATH];
+ LPWSTR file_part = NULL;
- if (suffix_len > str_len) {
- return -1; /* Suffix is longer than string, cannot match. */
- }
+ wchar_t *prog_name_wide = php_win32_cp_conv_any_to_w(prog_name, prog_name_length, &out_len);
- /* Compare the end of the string with the suffix, ignoring case. */
- return _stricmp(str + (str_len - suffix_len), suffix);
-}
+ if (GetLongPathNameW(prog_name_wide, long_name, MAX_PATH) == 0) {
+ /* This can fail for example with ERROR_FILE_NOT_FOUND (short path resolution only works for existing files)
+ * in which case we'll pass the path verbatim to the FullPath transformation. */
+ lstrcpynW(long_name, prog_name_wide, MAX_PATH);
+ }
-static bool is_executed_by_cmd(const char *prog_name)
-{
- /* If program name is cmd.exe, then return true. */
- if (_stricmp("cmd.exe", prog_name) == 0 || _stricmp("cmd", prog_name) == 0
- || stricmp_end("\\cmd.exe", prog_name) == 0 || stricmp_end("\\cmd", prog_name) == 0) {
- return true;
- }
+ free(prog_name_wide);
+ prog_name_wide = NULL;
- /* Find the last occurrence of the directory separator (backslash or forward slash). */
- char *last_separator = strrchr(prog_name, '\\');
- char *last_separator_fwd = strrchr(prog_name, '/');
- if (last_separator_fwd && (!last_separator || last_separator < last_separator_fwd)) {
- last_separator = last_separator_fwd;
+ if (GetFullPathNameW(long_name, MAX_PATH, full_name, &file_part) == 0 || file_part == NULL) {
+ return false;
}
- /* Find the last dot in the filename after the last directory separator. */
- char *extension = NULL;
- if (last_separator != NULL) {
- extension = strrchr(last_separator, '.');
+ bool uses_cmd = false;
+ if (_wcsicmp(file_part, L"cmd.exe") == 0 || _wcsicmp(file_part, L"cmd") == 0) {
+ uses_cmd = true;
} else {
- extension = strrchr(prog_name, '.');
- }
-
- if (extension == NULL || extension == prog_name) {
- /* No file extension found, it is not batch file. */
- return false;
+ const WCHAR *extension_dot = wcsrchr(file_part, L'.');
+ if (extension_dot && (_wcsicmp(extension_dot, L".bat") == 0 || _wcsicmp(extension_dot, L".cmd") == 0)) {
+ uses_cmd = true;
+ }
}
- /* Check if the file extension is ".bat" or ".cmd" which is always executed by cmd.exe. */
- return _stricmp(extension, ".bat") == 0 || _stricmp(extension, ".cmd") == 0;
+ return uses_cmd;
}
static zend_string *create_win_command_from_args(HashTable *args)
@@ -606,7 +597,7 @@ static zend_string *create_win_command_from_args(HashTable *args)
}
if (is_prog_name) {
- is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str));
+ is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str), ZSTR_LEN(arg_str));
} else {
smart_str_appendc(&str, ' ');
}
diff --git a/ext/standard/tests/array/gh14140.phpt b/ext/standard/tests/array/gh14140.phpt
new file mode 100644
index 0000000000000..2bc17a236a7a3
--- /dev/null
+++ b/ext/standard/tests/array/gh14140.phpt
@@ -0,0 +1,21 @@
+--TEST--
+GH-14140: Floating point bug in range operation on Apple Silicon hardware
+--FILE--
+
+--EXPECT--
+Array
+(
+ [0] => -0.03
+ [1] => -0.02
+ [2] => -0.01
+ [3] => 0
+ [4] => 0.01
+ [5] => 0.02
+ [6] => 0.03
+)
diff --git a/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_001.phpt b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_001.phpt
new file mode 100644
index 0000000000000..2873210608497
--- /dev/null
+++ b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_001.phpt
@@ -0,0 +1,56 @@
+--TEST--
+GHSA-9fcc-425m-g385 - bypass CVE-2024-1874 - batch file variation
+--SKIPIF--
+
+--FILE--
+
+--EXPECTF--
+'"%sghsa-9fcc-425m-g385_001.bat."' is not recognized as an internal or external command,
+operable program or batch file.
+%sghsa-9fcc-425m-g385_001.bat
+"¬epad.exe
+%sghsa-9fcc-425m-g385_001.bat.
+"¬epad.exe
+%sghsa-9fcc-425m-g385_001.bat. ...
+"¬epad.exe
+%sghsa-9fcc-425m-g385_001.bat. ... .
+"¬epad.exe
+'"%sghsa-9fcc-425m-g385_001.bat. ... . ."' is not recognized as an internal or external command,
+operable program or batch file.
+
+Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d
+--CLEAN--
+
diff --git a/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_002.phpt b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_002.phpt
new file mode 100644
index 0000000000000..714836557af5c
--- /dev/null
+++ b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_002.phpt
@@ -0,0 +1,66 @@
+--TEST--
+GHSA-9fcc-425m-g385 - bypass CVE-2024-1874 - cmd.exe variation
+--SKIPIF--
+
+--FILE--
+
+--EXPECTF--
+%sghsa-9fcc-425m-g385_002.bat
+"¬epad.exe
+%sghsa-9fcc-425m-g385_002.bat
+"¬epad.exe
+%sghsa-9fcc-425m-g385_002.bat
+"¬epad.exe
+%sghsa-9fcc-425m-g385_002.bat
+"¬epad.exe
+
+Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d
+%sghsa-9fcc-425m-g385_002.bat
+"¬epad.exe
+%sghsa-9fcc-425m-g385_002.bat
+"¬epad.exe
+
+Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d
+
+Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d
+
+Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d
+--CLEAN--
+
diff --git a/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_003.phpt b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_003.phpt
new file mode 100644
index 0000000000000..a632965eb989a
--- /dev/null
+++ b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_003.phpt
@@ -0,0 +1,550 @@
+--TEST--
+GHSA-9fcc-425m-g385 - bypass CVE-2024-1874 - exhaustive suffix test
+--SKIPIF--
+
+--FILE--
+ true));
+ var_dump($proc);
+ proc_close($proc);
+ } catch (Error) {}
+}
+
+?>
+--EXPECTF--
+Testing 1
+bool(false)
+Testing 2
+bool(false)
+Testing 3
+bool(false)
+Testing 4
+bool(false)
+Testing 5
+bool(false)
+Testing 6
+bool(false)
+Testing 7
+bool(false)
+Testing 8
+bool(false)
+Testing 9
+bool(false)
+Testing 10
+bool(false)
+Testing 11
+bool(false)
+Testing 12
+bool(false)
+Testing 13
+bool(false)
+Testing 14
+bool(false)
+Testing 15
+bool(false)
+Testing 16
+bool(false)
+Testing 17
+bool(false)
+Testing 18
+bool(false)
+Testing 19
+bool(false)
+Testing 20
+bool(false)
+Testing 21
+bool(false)
+Testing 22
+bool(false)
+Testing 23
+bool(false)
+Testing 24
+bool(false)
+Testing 25
+bool(false)
+Testing 26
+bool(false)
+Testing 27
+bool(false)
+Testing 28
+bool(false)
+Testing 29
+bool(false)
+Testing 30
+bool(false)
+Testing 31
+bool(false)
+Testing 32
+resource(%d) of type (process)
+%s.bat
+"¬epad.exe
+Testing 33
+bool(false)
+Testing 34
+bool(false)
+Testing 35
+bool(false)
+Testing 36
+bool(false)
+Testing 37
+bool(false)
+Testing 38
+bool(false)
+Testing 39
+bool(false)
+Testing 40
+bool(false)
+Testing 41
+bool(false)
+Testing 42
+bool(false)
+Testing 43
+bool(false)
+Testing 44
+bool(false)
+Testing 45
+bool(false)
+Testing 46
+resource(%d) of type (process)
+'"%s.bat."' is not recognized as an internal or external command,
+operable program or batch file.
+Testing 47
+bool(false)
+Testing 48
+bool(false)
+Testing 49
+bool(false)
+Testing 50
+bool(false)
+Testing 51
+bool(false)
+Testing 52
+bool(false)
+Testing 53
+bool(false)
+Testing 54
+bool(false)
+Testing 55
+bool(false)
+Testing 56
+bool(false)
+Testing 57
+bool(false)
+Testing 58
+bool(false)
+Testing 59
+bool(false)
+Testing 60
+bool(false)
+Testing 61
+bool(false)
+Testing 62
+bool(false)
+Testing 63
+bool(false)
+Testing 64
+bool(false)
+Testing 65
+bool(false)
+Testing 66
+bool(false)
+Testing 67
+bool(false)
+Testing 68
+bool(false)
+Testing 69
+bool(false)
+Testing 70
+bool(false)
+Testing 71
+bool(false)
+Testing 72
+bool(false)
+Testing 73
+bool(false)
+Testing 74
+bool(false)
+Testing 75
+bool(false)
+Testing 76
+bool(false)
+Testing 77
+bool(false)
+Testing 78
+bool(false)
+Testing 79
+bool(false)
+Testing 80
+bool(false)
+Testing 81
+bool(false)
+Testing 82
+bool(false)
+Testing 83
+bool(false)
+Testing 84
+bool(false)
+Testing 85
+bool(false)
+Testing 86
+bool(false)
+Testing 87
+bool(false)
+Testing 88
+bool(false)
+Testing 89
+bool(false)
+Testing 90
+bool(false)
+Testing 91
+bool(false)
+Testing 92
+bool(false)
+Testing 93
+bool(false)
+Testing 94
+bool(false)
+Testing 95
+bool(false)
+Testing 96
+bool(false)
+Testing 97
+bool(false)
+Testing 98
+bool(false)
+Testing 99
+bool(false)
+Testing 100
+bool(false)
+Testing 101
+bool(false)
+Testing 102
+bool(false)
+Testing 103
+bool(false)
+Testing 104
+bool(false)
+Testing 105
+bool(false)
+Testing 106
+bool(false)
+Testing 107
+bool(false)
+Testing 108
+bool(false)
+Testing 109
+bool(false)
+Testing 110
+bool(false)
+Testing 111
+bool(false)
+Testing 112
+bool(false)
+Testing 113
+bool(false)
+Testing 114
+bool(false)
+Testing 115
+bool(false)
+Testing 116
+bool(false)
+Testing 117
+bool(false)
+Testing 118
+bool(false)
+Testing 119
+bool(false)
+Testing 120
+bool(false)
+Testing 121
+bool(false)
+Testing 122
+bool(false)
+Testing 123
+bool(false)
+Testing 124
+bool(false)
+Testing 125
+bool(false)
+Testing 126
+bool(false)
+Testing 127
+bool(false)
+Testing 128
+bool(false)
+Testing 129
+bool(false)
+Testing 130
+bool(false)
+Testing 131
+bool(false)
+Testing 132
+bool(false)
+Testing 133
+bool(false)
+Testing 134
+bool(false)
+Testing 135
+bool(false)
+Testing 136
+bool(false)
+Testing 137
+bool(false)
+Testing 138
+bool(false)
+Testing 139
+bool(false)
+Testing 140
+bool(false)
+Testing 141
+bool(false)
+Testing 142
+bool(false)
+Testing 143
+bool(false)
+Testing 144
+bool(false)
+Testing 145
+bool(false)
+Testing 146
+bool(false)
+Testing 147
+bool(false)
+Testing 148
+bool(false)
+Testing 149
+bool(false)
+Testing 150
+bool(false)
+Testing 151
+bool(false)
+Testing 152
+bool(false)
+Testing 153
+bool(false)
+Testing 154
+bool(false)
+Testing 155
+bool(false)
+Testing 156
+bool(false)
+Testing 157
+bool(false)
+Testing 158
+bool(false)
+Testing 159
+bool(false)
+Testing 160
+bool(false)
+Testing 161
+bool(false)
+Testing 162
+bool(false)
+Testing 163
+bool(false)
+Testing 164
+bool(false)
+Testing 165
+bool(false)
+Testing 166
+bool(false)
+Testing 167
+bool(false)
+Testing 168
+bool(false)
+Testing 169
+bool(false)
+Testing 170
+bool(false)
+Testing 171
+bool(false)
+Testing 172
+bool(false)
+Testing 173
+bool(false)
+Testing 174
+bool(false)
+Testing 175
+bool(false)
+Testing 176
+bool(false)
+Testing 177
+bool(false)
+Testing 178
+bool(false)
+Testing 179
+bool(false)
+Testing 180
+bool(false)
+Testing 181
+bool(false)
+Testing 182
+bool(false)
+Testing 183
+bool(false)
+Testing 184
+bool(false)
+Testing 185
+bool(false)
+Testing 186
+bool(false)
+Testing 187
+bool(false)
+Testing 188
+bool(false)
+Testing 189
+bool(false)
+Testing 190
+bool(false)
+Testing 191
+bool(false)
+Testing 192
+bool(false)
+Testing 193
+bool(false)
+Testing 194
+bool(false)
+Testing 195
+bool(false)
+Testing 196
+bool(false)
+Testing 197
+bool(false)
+Testing 198
+bool(false)
+Testing 199
+bool(false)
+Testing 200
+bool(false)
+Testing 201
+bool(false)
+Testing 202
+bool(false)
+Testing 203
+bool(false)
+Testing 204
+bool(false)
+Testing 205
+bool(false)
+Testing 206
+bool(false)
+Testing 207
+bool(false)
+Testing 208
+bool(false)
+Testing 209
+bool(false)
+Testing 210
+bool(false)
+Testing 211
+bool(false)
+Testing 212
+bool(false)
+Testing 213
+bool(false)
+Testing 214
+bool(false)
+Testing 215
+bool(false)
+Testing 216
+bool(false)
+Testing 217
+bool(false)
+Testing 218
+bool(false)
+Testing 219
+bool(false)
+Testing 220
+bool(false)
+Testing 221
+bool(false)
+Testing 222
+bool(false)
+Testing 223
+bool(false)
+Testing 224
+bool(false)
+Testing 225
+bool(false)
+Testing 226
+bool(false)
+Testing 227
+bool(false)
+Testing 228
+bool(false)
+Testing 229
+bool(false)
+Testing 230
+bool(false)
+Testing 231
+bool(false)
+Testing 232
+bool(false)
+Testing 233
+bool(false)
+Testing 234
+bool(false)
+Testing 235
+bool(false)
+Testing 236
+bool(false)
+Testing 237
+bool(false)
+Testing 238
+bool(false)
+Testing 239
+bool(false)
+Testing 240
+bool(false)
+Testing 241
+bool(false)
+Testing 242
+bool(false)
+Testing 243
+bool(false)
+Testing 244
+bool(false)
+Testing 245
+bool(false)
+Testing 246
+bool(false)
+Testing 247
+bool(false)
+Testing 248
+bool(false)
+Testing 249
+bool(false)
+Testing 250
+bool(false)
+Testing 251
+bool(false)
+Testing 252
+bool(false)
+Testing 253
+bool(false)
+Testing 254
+bool(false)
+Testing 255
+bool(false)
+--CLEAN--
+
diff --git a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt
index 8d0939cdf1bc7..55b6613c1c711 100644
--- a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt
+++ b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt
@@ -12,7 +12,7 @@ $batch_file_content = <<
diff --git a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt
index a1e39d7ef9ba0..0b186e977bf3f 100644
--- a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt
+++ b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt
@@ -12,7 +12,7 @@ $batch_file_content = <<^()!.exe
--CLEAN--
diff --git a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt
index 69f12d7b358d2..e9bcd3bf24b96 100644
--- a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt
+++ b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt
@@ -12,7 +12,7 @@ $batch_file_content = <<
diff --git a/ext/standard/tests/strings/setlocale_variation3.phpt b/ext/standard/tests/strings/setlocale_variation3.phpt
index 551eece086fc0..30f281a502006 100644
--- a/ext/standard/tests/strings/setlocale_variation3.phpt
+++ b/ext/standard/tests/strings/setlocale_variation3.phpt
@@ -47,7 +47,7 @@ if($locale_info_before == $locale_info_after){
echo "\nDone\n";
?>
---EXPECT--
+--EXPECTF--
*** Testing setlocale() : usage variations - setting system locale = 0 ***
Locale info, before setting the locale
array(18) {
@@ -84,17 +84,13 @@ array(18) {
["n_sign_posn"]=>
int(1)
["grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
["mon_grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
}
@@ -134,17 +130,13 @@ array(18) {
["n_sign_posn"]=>
int(1)
["grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
["mon_grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
}
diff --git a/ext/standard/tests/strings/setlocale_variation4.phpt b/ext/standard/tests/strings/setlocale_variation4.phpt
index 91171543bb694..ca469759dbd15 100644
--- a/ext/standard/tests/strings/setlocale_variation4.phpt
+++ b/ext/standard/tests/strings/setlocale_variation4.phpt
@@ -45,7 +45,7 @@ if($locale_info_before != $locale_info_after){
echo "\nDone\n";
?>
---EXPECT--
+--EXPECTF--
*** Testing setlocale() : usage variations - Setting system locale = null ***
Locale info, before setting the locale
array(18) {
@@ -82,17 +82,13 @@ array(18) {
["n_sign_posn"]=>
int(1)
["grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
["mon_grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
}
@@ -132,17 +128,13 @@ array(18) {
["n_sign_posn"]=>
int(1)
["grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
["mon_grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
}
diff --git a/ext/standard/tests/strings/setlocale_variation5.phpt b/ext/standard/tests/strings/setlocale_variation5.phpt
index 15f912d3841ec..50c8a03bb8785 100644
--- a/ext/standard/tests/strings/setlocale_variation5.phpt
+++ b/ext/standard/tests/strings/setlocale_variation5.phpt
@@ -49,7 +49,7 @@ if($locale_info_before != $locale_info_after){
echo "\nDone\n";
?>
---EXPECT--
+--EXPECTF--
*** Testing setlocale() : usage variations - setting system locale = "" ***
Locale info, before setting the locale
array(18) {
@@ -86,17 +86,13 @@ array(18) {
["n_sign_posn"]=>
int(1)
["grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
["mon_grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
}
@@ -136,17 +132,13 @@ array(18) {
["n_sign_posn"]=>
int(1)
["grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
["mon_grouping"]=>
- array(2) {
- [0]=>
- int(3)
- [1]=>
+ array(%d) {%A
+ [%d]=>
int(3)
}
}
diff --git a/ext/xml/xml.c b/ext/xml/xml.c
index 1e8a97c9059f4..59d50faed111e 100644
--- a/ext/xml/xml.c
+++ b/ext/xml/xml.c
@@ -1292,6 +1292,7 @@ PHP_FUNCTION(xml_parse_into_struct)
parser->level = 0;
xml_parser_free_ltags(parser);
parser->ltags = safe_emalloc(XML_MAXLEVEL, sizeof(char *), 0);
+ memset(parser->ltags, 0, XML_MAXLEVEL * sizeof(char *));
XML_SetElementHandler(parser->parser, _xml_startElementHandler, _xml_endElementHandler);
XML_SetCharacterDataHandler(parser->parser, _xml_characterDataHandler);
diff --git a/ext/xmlreader/php_xmlreader.c b/ext/xmlreader/php_xmlreader.c
index 3f88f758ba740..1b4bc6bcef4b1 100644
--- a/ext/xmlreader/php_xmlreader.c
+++ b/ext/xmlreader/php_xmlreader.c
@@ -186,19 +186,17 @@ zval *xmlreader_write_property(zend_object *object, zend_string *name, zval *val
/* {{{ */
static zend_function *xmlreader_get_method(zend_object **obj, zend_string *name, const zval *key)
{
- if (ZSTR_LEN(name) == sizeof("open") - 1
- && (ZSTR_VAL(name)[0] == 'o' || ZSTR_VAL(name)[0] == 'O')
- && (ZSTR_VAL(name)[1] == 'p' || ZSTR_VAL(name)[1] == 'P')
- && (ZSTR_VAL(name)[2] == 'e' || ZSTR_VAL(name)[2] == 'E')
- && (ZSTR_VAL(name)[3] == 'n' || ZSTR_VAL(name)[3] == 'N')) {
- return (zend_function*)&xmlreader_open_fn;
- } else if (ZSTR_LEN(name) == sizeof("xml") - 1
- && (ZSTR_VAL(name)[0] == 'x' || ZSTR_VAL(name)[0] == 'X')
- && (ZSTR_VAL(name)[1] == 'm' || ZSTR_VAL(name)[1] == 'M')
- && (ZSTR_VAL(name)[2] == 'l' || ZSTR_VAL(name)[2] == 'L')) {
- return (zend_function*)&xmlreader_xml_fn;
- }
- return zend_std_get_method(obj, name, key);;
+ zend_function *method = zend_std_get_method(obj, name, key);
+ if (method && (method->common.fn_flags & ZEND_ACC_STATIC) && method->common.type == ZEND_INTERNAL_FUNCTION) {
+ /* There are only two static internal methods and they both have overrides. */
+ if (ZSTR_LEN(name) == sizeof("xml") - 1) {
+ return (zend_function *) &xmlreader_xml_fn;
+ } else {
+ ZEND_ASSERT(ZSTR_LEN(name) == sizeof("open") - 1);
+ return (zend_function *) &xmlreader_open_fn;
+ }
+ }
+ return method;
}
/* }}} */
diff --git a/ext/xmlreader/tests/gh14183.phpt b/ext/xmlreader/tests/gh14183.phpt
new file mode 100644
index 0000000000000..2f3f81bd6628e
--- /dev/null
+++ b/ext/xmlreader/tests/gh14183.phpt
@@ -0,0 +1,24 @@
+--TEST--
+GH-14183 (XMLReader::open() can't be overridden)
+--EXTENSIONS--
+xmlreader
+--FILE--
+open('asdf'));
+?>
+--EXPECT--
+overridden
+bool(true)
+overridden
+bool(true)
diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
index d27b4543fcc01..8ae98eb106f58 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -584,6 +584,18 @@ static ZEND_FUNCTION(zend_test_is_pcre_bundled)
#endif
}
+#ifdef PHP_WIN32
+static ZEND_FUNCTION(zend_test_set_fmode)
+{
+ bool binary;
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_BOOL(binary)
+ ZEND_PARSE_PARAMETERS_END();
+
+ _fmode = binary ? _O_BINARY : _O_TEXT;
+}
+#endif
+
static zend_object *zend_test_class_new(zend_class_entry *class_type)
{
zend_object *obj = zend_objects_new(class_type);
diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php
index cf8ac50bd0629..0125b75f970ea 100644
--- a/ext/zend_test/test.stub.php
+++ b/ext/zend_test/test.stub.php
@@ -182,6 +182,10 @@ function zend_test_override_libxml_global_state(): void {}
#endif
function zend_test_is_pcre_bundled(): bool {}
+
+#if defined(PHP_WIN32)
+ function zend_test_set_fmode(bool $binary): void {}
+#endif
}
namespace ZendTestNS {
diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h
index 70439ff726164..8f3bcb1bdfb6f 100644
--- a/ext/zend_test/test_arginfo.h
+++ b/ext/zend_test/test_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 420711ec6f040d38bde450a169bf1186f8531191 */
+ * Stub hash: b0964f7eabf91dc0fbffdee87257ee4e58dab303 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
ZEND_END_ARG_INFO()
@@ -112,6 +112,12 @@ ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_is_pcre_bundled, 0, 0, _IS_BOOL, 0)
ZEND_END_ARG_INFO()
+#if defined(PHP_WIN32)
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_set_fmode, 0, 1, IS_VOID, 0)
+ ZEND_ARG_TYPE_INFO(0, binary, _IS_BOOL, 0)
+ZEND_END_ARG_INFO()
+#endif
+
#define arginfo_ZendTestNS2_namespaced_func arginfo_zend_test_is_pcre_bundled
#define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_test_void_return
@@ -216,6 +222,9 @@ static ZEND_FUNCTION(zend_test_crash);
static ZEND_FUNCTION(zend_test_override_libxml_global_state);
#endif
static ZEND_FUNCTION(zend_test_is_pcre_bundled);
+#if defined(PHP_WIN32)
+static ZEND_FUNCTION(zend_test_set_fmode);
+#endif
static ZEND_FUNCTION(ZendTestNS2_namespaced_func);
static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func);
static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func);
@@ -275,6 +284,9 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(zend_test_override_libxml_global_state, arginfo_zend_test_override_libxml_global_state)
#endif
ZEND_FE(zend_test_is_pcre_bundled, arginfo_zend_test_is_pcre_bundled)
+#if defined(PHP_WIN32)
+ ZEND_FE(zend_test_set_fmode, arginfo_zend_test_set_fmode)
+#endif
ZEND_NS_FALIAS("ZendTestNS2", namespaced_func, ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func)
ZEND_NS_DEP_FALIAS("ZendTestNS2", namespaced_deprecated_func, ZendTestNS2_namespaced_deprecated_func, arginfo_ZendTestNS2_namespaced_deprecated_func)
ZEND_NS_FALIAS("ZendTestNS2", namespaced_aliased_func, zend_test_void_return, arginfo_ZendTestNS2_namespaced_aliased_func)
diff --git a/ext/zend_test/tests/gh13970.phpt b/ext/zend_test/tests/gh13970.phpt
new file mode 100644
index 0000000000000..f6b68f8970234
--- /dev/null
+++ b/ext/zend_test/tests/gh13970.phpt
@@ -0,0 +1,22 @@
+--TEST--
+GH-13970 (Incorrect validation of #[\Attribute]'s first parameter)
+--EXTENSIONS--
+zend_test
+--FILE--
+getAttributes()[0]->newInstance());
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Attribute::__construct(): Argument #1 ($flags) must be of type int, ZendTestUnitEnum given
diff --git a/main/fastcgi.c b/main/fastcgi.c
index df309df9fdc70..18eb4b394bc2a 100644
--- a/main/fastcgi.c
+++ b/main/fastcgi.c
@@ -744,7 +744,7 @@ int fcgi_listen(const char *path, int backlog)
memset(&sa.sa_unix, 0, sizeof(sa.sa_unix));
sa.sa_unix.sun_family = AF_UNIX;
memcpy(sa.sa_unix.sun_path, path, path_len + 1);
- sock_len = (size_t)(((struct sockaddr_un *)0)->sun_path) + path_len;
+ sock_len = XtOffsetOf(struct sockaddr_un, sun_path) + path_len;
#ifdef HAVE_SOCKADDR_UN_SUN_LEN
sa.sa_unix.sun_len = sock_len;
#endif
@@ -965,9 +965,9 @@ static inline ssize_t safe_read(fcgi_request *req, const void *buf, size_t count
tmp = count - n;
if (!req->tcp) {
- unsigned int in_len = tmp > UINT_MAX ? UINT_MAX : (unsigned int)tmp;
+ unsigned int in_len = tmp > INT_MAX ? INT_MAX : (unsigned int)tmp;
- ret = read(req->fd, ((char*)buf)+n, in_len);
+ ret = _read(req->fd, ((char*)buf)+n, in_len);
} else {
int in_len = tmp > INT_MAX ? INT_MAX : (int)tmp;
diff --git a/main/php_version.h b/main/php_version.h
index a17d49197da51..eadeebaab93af 100644
--- a/main/php_version.h
+++ b/main/php_version.h
@@ -2,7 +2,7 @@
/* edit configure.ac to change version number */
#define PHP_MAJOR_VERSION 8
#define PHP_MINOR_VERSION 2
-#define PHP_RELEASE_VERSION 19
-#define PHP_EXTRA_VERSION "-dev"
-#define PHP_VERSION "8.2.19-dev"
-#define PHP_VERSION_ID 80219
+#define PHP_RELEASE_VERSION 20
+#define PHP_EXTRA_VERSION ""
+#define PHP_VERSION "8.2.20"
+#define PHP_VERSION_ID 80220
diff --git a/php.ini-development b/php.ini-development
index 61cd33e89504c..2bb471298ecb6 100644
--- a/php.ini-development
+++ b/php.ini-development
@@ -75,7 +75,7 @@
; php.ini-production contains settings which hold security, performance and
; best practices at its core. But please be aware, these settings may break
-; compatibility with older or less security conscience applications. We
+; compatibility with older or less security-conscious applications. We
; recommending using the production ini in production and testing environments.
; php.ini-development is very similar to its production variant, except it is
diff --git a/php.ini-production b/php.ini-production
index 2189660c02c48..4fcd47bff3509 100644
--- a/php.ini-production
+++ b/php.ini-production
@@ -75,7 +75,7 @@
; php.ini-production contains settings which hold security, performance and
; best practices at its core. But please be aware, these settings may break
-; compatibility with older or less security conscience applications. We
+; compatibility with older or less security-conscious applications. We
; recommending using the production ini in production and testing environments.
; php.ini-development is very similar to its production variant, except it is
diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c
index b45468031fcd0..01fdebae0bde1 100644
--- a/sapi/cgi/cgi_main.c
+++ b/sapi/cgi/cgi_main.c
@@ -486,9 +486,9 @@ static size_t sapi_cgi_read_post(char *buffer, size_t count_bytes)
while (read_bytes < count_bytes) {
#ifdef PHP_WIN32
size_t diff = count_bytes - read_bytes;
- unsigned int to_read = (diff > UINT_MAX) ? UINT_MAX : (unsigned int)diff;
+ unsigned int to_read = (diff > INT_MAX) ? INT_MAX : (unsigned int)diff;
- tmp_read_bytes = read(STDIN_FILENO, buffer + read_bytes, to_read);
+ tmp_read_bytes = _read(STDIN_FILENO, buffer + read_bytes, to_read);
#else
tmp_read_bytes = read(STDIN_FILENO, buffer + read_bytes, count_bytes - read_bytes);
#endif
@@ -1796,8 +1796,13 @@ int main(int argc, char *argv[])
}
}
+ /* Apache CGI will pass the query string to the command line if it doesn't contain a '='.
+ * This can create an issue where a malicious request can pass command line arguments to
+ * the executable. Ideally we skip argument parsing when we're in cgi or fastcgi mode,
+ * but that breaks PHP scripts on Linux with a hashbang: `#!/php-cgi -d option=value`.
+ * Therefore, this code only prevents passing arguments if the query string starts with a '-'.
+ * Similarly, scripts spawned in subprocesses on Windows may have the same issue. */
if((query_string = getenv("QUERY_STRING")) != NULL && strchr(query_string, '=') == NULL) {
- /* we've got query string that has no = - apache CGI will pass it to command line */
unsigned char *p;
decoded_query_string = strdup(query_string);
php_url_decode(decoded_query_string, strlen(decoded_query_string));
@@ -1807,6 +1812,22 @@ int main(int argc, char *argv[])
if(*p == '-') {
skip_getopt = 1;
}
+
+ /* On Windows we have to take into account the "best fit" mapping behaviour. */
+#ifdef PHP_WIN32
+ if (*p >= 0x80) {
+ wchar_t wide_buf[1];
+ wide_buf[0] = *p;
+ char char_buf[4];
+ size_t wide_buf_len = sizeof(wide_buf) / sizeof(wide_buf[0]);
+ size_t char_buf_len = sizeof(char_buf) / sizeof(char_buf[0]);
+ if (WideCharToMultiByte(CP_ACP, 0, wide_buf, wide_buf_len, char_buf, char_buf_len, NULL, NULL) == 0
+ || char_buf[0] == '-') {
+ skip_getopt = 1;
+ }
+ }
+#endif
+
free(decoded_query_string);
}
diff --git a/sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt b/sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt
new file mode 100644
index 0000000000000..fd2fcdfbf897d
--- /dev/null
+++ b/sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt
@@ -0,0 +1,38 @@
+--TEST--
+GHSA-3qgc-jrrr-25jv
+--SKIPIF--
+
+--FILE--
+';
+file_put_contents($filename, $script);
+
+$php = get_cgi_path();
+reset_env_vars();
+
+putenv("SERVER_NAME=Test");
+putenv("SCRIPT_FILENAME=$filename");
+putenv("QUERY_STRING=%ads");
+putenv("REDIRECT_STATUS=1");
+
+passthru("$php -s");
+
+?>
+--CLEAN--
+
+--EXPECTF--
+X-Powered-By: PHP/%s
+Content-type: %s
+
+hello world
diff --git a/sapi/cli/tests/gh14189.phpt b/sapi/cli/tests/gh14189.phpt
new file mode 100644
index 0000000000000..5c0a8b252bf73
--- /dev/null
+++ b/sapi/cli/tests/gh14189.phpt
@@ -0,0 +1,44 @@
+--TEST--
+GH-14189 (PHP Interactive shell input state incorrectly handles quoted heredoc literals.)
+--EXTENSIONS--
+readline
+--SKIPIF--
+
+--FILE--
+
+--EXPECT--
+Interactive shell
+
+php > $test = <<<"EOF"
+<<< > foo
+<<< > bar
+<<< > baz
+<<< > EOF;
+php > echo $test;
+foo
+bar
+baz
+php > exit
diff --git a/sapi/fpm/fpm/fpm_systemd.c b/sapi/fpm/fpm/fpm_systemd.c
index 175312412330f..d5858ac780d64 100644
--- a/sapi/fpm/fpm/fpm_systemd.c
+++ b/sapi/fpm/fpm/fpm_systemd.c
@@ -29,13 +29,13 @@ static void fpm_systemd(void)
}
/*
- zlog(ZLOG_DEBUG, "systemd %s (Processes active:%d, idle:%d, Requests:%lu, slow:%lu, Traffic:%.3greq/sec)",
+ zlog(ZLOG_DEBUG, "systemd %s (Processes active:%d, idle:%d, Requests:%lu, slow:%lu, Traffic:%.2freq/sec)",
fpm_global_config.systemd_watchdog ? "watchdog" : "heartbeat",
active, idle, requests, slow_req, ((float)requests - last) * 1000.0 / fpm_global_config.systemd_interval);
*/
if (0 > sd_notifyf(0, "READY=1\n%s"
- "STATUS=Processes active: %d, idle: %d, Requests: %lu, slow: %lu, Traffic: %.3greq/sec",
+ "STATUS=Processes active: %d, idle: %d, Requests: %lu, slow: %lu, Traffic: %.2freq/sec",
fpm_global_config.systemd_watchdog ? "WATCHDOG=1\n" : "",
active, idle, requests, slow_req, ((float)requests - last) * 1000.0 / fpm_global_config.systemd_interval)) {
zlog(ZLOG_NOTICE, "failed to notify status to systemd");
diff --git a/tests/lang/bug38579.phpt b/tests/lang/bug38579.phpt
index 0d58eac52969b..1e6e351ee3e30 100644
--- a/tests/lang/bug38579.phpt
+++ b/tests/lang/bug38579.phpt
@@ -2,7 +2,7 @@
Bug #38579 (include_once() may include the same file twice)
--SKIPIF--