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