diff --git a/NEWS b/NEWS index aed9bd06181a..6a6b51fe9575 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,83 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.3.22 +03 Jul 2025, PHP 8.3.23 + +- Core: + . Fixed GH-18695 (zend_ast_export() - float number is not preserved). + (Oleg Efimov) + . Do not delete main chunk in zend_gc. (danog, Arnaud) + . Fix compile issues with zend_alloc and some non-default options. (nielsdos) + +- Curl: + . Fix memory leak when setting a list via curl_setopt fails. (nielsdos) + . Fix incorrect OpenSSL version detection. (Peter Kokot) + +- Date: + . Fix leaks with multiple calls to DatePeriod iterator current(). (nielsdos) + +- FPM: + . Fixed GH-18662 (fpm_get_status segfault). (txuna) + +- Hash: + . Fixed bug GH-14551 (PGO build fails with xxhash). (nielsdos) + +- Intl: + . Fix memory leak in intl_datetime_decompose() on failure. (nielsdos) + . Fix memory leak in locale lookup on failure. (nielsdos) + +- ODBC: + . Fix memory leak on php_odbc_fetch_hash() failure. (nielsdos) + +- Opcache: + . Fixed bug GH-18743 (Incompatibility in Inline TLS Assembly on Alpine 3.22). + (nielsdos, Arnaud) + +- OpenSSL: + . Fix memory leak of X509_STORE in php_openssl_setup_verify() on failure. + (nielsdos) + . Fixed bug #74796 (Requests through http proxy set peer name). + (Jakub Zelenka) + +- PGSQL: + . Fixed GHSA-hrwm-9436-5mv3 (pgsql extension does not check for errors during + escaping). (CVE-2025-1735) (Jakub Zelenka) + +- Phar: + . Add missing filter cleanups on phar failure. (nielsdos) + . Fixed bug GH-18642 (Signed integer overflow in ext/phar fseek). (nielsdos) + +- PHPDBG: + . Fix 'phpdbg --help' segfault on shutdown with USE_ZEND_ALLOC=0. (nielsdos) + +- PDO ODBC: + . Fix memory leak if WideCharToMultiByte() fails. (nielsdos) + +- PGSQL: + . Fix warning not being emitted when failure to cancel a query with + pg_cancel_query(). (Girgias) + +- Random: + . Fix reference type confusion and leak in user random engine. + (nielsdos, timwolla) + +- Readline: + . Fix memory leak when calloc() fails in php_readline_completion_cb(). + (nielsdos) + +- SOAP: + . Fix memory leaks in php_http.c when call_user_function() fails. (nielsdos) + . Fixed GHSA-453j-q27h-5p8x (NULL Pointer Dereference in PHP SOAP Extension + via Large XML Namespace Prefix). (CVE-2025-6491) (Lekssays, nielsdos) + +- Standard: + . Fixed GHSA-3cr5-j632-f35r (Null byte termination in hostnames). + (CVE-2025-1220) (Jakub Zelenka) + +- Tidy: + . Fix memory leak in tidy output handler on error. (nielsdos) + . Fix tidyOptIsReadonly deprecation, using tidyOptGetCategory. (David Carlier) + +05 Jun 2025, PHP 8.3.22 - Core: . Fixed GH-18480 (array_splice with large values for offset/length arguments). @@ -42,6 +119,10 @@ PHP NEWS - PDO_OCI: . Fixed bug GH-18494 (PDO OCI segfault in statement GC). (nielsdos) +- Sockets: + - Fixed bug GH-18617 (socket_import_file_descriptor return value + unchecked). (David Carlier) + - SPL: . Fixed bug GH-18421 (Integer overflow with large numbers in LimitIterator). (nielsdos) diff --git a/Zend/tests/ast/ast_serialize_floats.phpt b/Zend/tests/ast/ast_serialize_floats.phpt new file mode 100644 index 000000000000..164b8b03338c --- /dev/null +++ b/Zend/tests/ast/ast_serialize_floats.phpt @@ -0,0 +1,26 @@ +--TEST-- +Serialization of floats are correct +--INI-- +zend.assertions=1 +--FILE-- +getMessage(), ' failed', PHP_EOL; +} +try { + assert(!is_float(1.1)); +} catch (AssertionError $e) { + echo 'assert(): ', $e->getMessage(), ' failed', PHP_EOL; +} +try { + assert(!is_float(1234.5678)); +} catch (AssertionError $e) { + echo 'assert(): ', $e->getMessage(), ' failed', PHP_EOL; +} +?> +--EXPECT-- +assert(): assert(!is_float(0.0)) failed +assert(): assert(!is_float(1.1)) failed +assert(): assert(!is_float(1234.5678)) failed diff --git a/Zend/tests/gh18756.phpt b/Zend/tests/gh18756.phpt new file mode 100644 index 000000000000..6e112d906049 --- /dev/null +++ b/Zend/tests/gh18756.phpt @@ -0,0 +1,13 @@ +--TEST-- +Bug GH-18756: Zend MM may delete the main chunk +--EXTENSIONS-- +zend_test +--FILE-- + +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/zend.h b/Zend/zend.h index 99b88992475b..ad06ee8d4ad4 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.3.22-dev" +#define ZEND_VERSION "4.3.23" #define ZEND_ENGINE_3 diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index d531270d445e..47e9967a1e29 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2047,7 +2047,7 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) i++; } } - if (chunk->free_pages == ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE) { + if (chunk->free_pages == ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE && chunk != heap->main_chunk) { zend_mm_chunk *next_chunk = chunk->next; zend_mm_delete_chunk(heap, chunk); @@ -2274,7 +2274,9 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent) /* Make sure the heap free below does not use tracked_free(). */ heap->custom_heap.std._free = free; } +#if ZEND_MM_STAT heap->size = 0; +#endif } if (full) { @@ -2820,6 +2822,7 @@ static zend_always_inline zval *tracked_get_size_zv(zend_mm_heap *heap, void *pt } static zend_always_inline void tracked_check_limit(zend_mm_heap *heap, size_t add_size) { +#if ZEND_MM_STAT if (add_size > heap->limit - heap->size && !heap->overflow) { #if ZEND_DEBUG zend_mm_safe_error(heap, @@ -2831,6 +2834,7 @@ static zend_always_inline void tracked_check_limit(zend_mm_heap *heap, size_t ad heap->limit, add_size); #endif } +#endif } static void *tracked_malloc(size_t size) @@ -2844,7 +2848,9 @@ static void *tracked_malloc(size_t size) } tracked_add(heap, ptr, size); +#if ZEND_MM_STAT heap->size += size; +#endif return ptr; } @@ -2855,7 +2861,9 @@ static void tracked_free(void *ptr) { zend_mm_heap *heap = AG(mm_heap); zval *size_zv = tracked_get_size_zv(heap, ptr); +#if ZEND_MM_STAT heap->size -= Z_LVAL_P(size_zv); +#endif zend_hash_del_bucket(heap->tracked_allocs, (Bucket *) size_zv); free(ptr); } @@ -2880,7 +2888,9 @@ static void *tracked_realloc(void *ptr, size_t new_size) { ptr = __zend_realloc(ptr, new_size); tracked_add(heap, ptr, new_size); +#if ZEND_MM_STAT heap->size += new_size - old_size; +#endif return ptr; } @@ -3043,7 +3053,7 @@ ZEND_API zend_mm_storage *zend_mm_get_storage(zend_mm_heap *heap) #if ZEND_MM_STORAGE return heap->storage; #else - return NULL + return NULL; #endif } diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 0fb50e2eae1f..f8c4ca17a9b9 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1563,7 +1563,7 @@ static ZEND_COLD void zend_ast_export_zval(smart_str *str, zval *zv, int priorit break; case IS_DOUBLE: smart_str_append_double( - str, Z_DVAL_P(zv), (int) EG(precision), /* zero_fraction */ false); + str, Z_DVAL_P(zv), (int) EG(precision), /* zero_fraction */ true); break; case IS_STRING: smart_str_appendc(str, '\''); diff --git a/Zend/zend_cpuinfo.h b/Zend/zend_cpuinfo.h index 9d221c59e541..5cc161eab994 100644 --- a/Zend/zend_cpuinfo.h +++ b/Zend/zend_cpuinfo.h @@ -126,58 +126,86 @@ ZEND_API int zend_cpu_supports(zend_cpu_feature feature); * functions */ ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_sse2(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("sse2"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_sse3(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("sse3"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_ssse3(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("ssse3"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_sse41(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("sse4.1"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_sse42(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("sse4.2"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_avx(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("avx"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_avx2(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("avx2"); +#endif } #if PHP_HAVE_AVX512_SUPPORTS diff --git a/configure.ac b/configure.ac index 278dbd110d24..cd0b8820006c 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.3.22-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.3.23],[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 diff --git a/ext/curl/config.m4 b/ext/curl/config.m4 index 3b11739654bd..c0325f990ad1 100644 --- a/ext/curl/config.m4 +++ b/ext/curl/config.m4 @@ -28,6 +28,7 @@ if test "$PHP_CURL" != "no"; then AC_MSG_CHECKING([for libcurl linked against old openssl]) AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include #include #include @@ -39,9 +40,18 @@ int main(int argc, char *argv[]) const char *ptr = data->ssl_version; while(*ptr == ' ') ++ptr; - if (strncasecmp(ptr, "OpenSSL/1.1", sizeof("OpenSSL/1.1")-1) == 0) { - /* New OpenSSL version */ - return 3; + int major, minor; + if (sscanf(ptr, "OpenSSL/%d", &major) == 1) { + if (major >= 3) { + /* OpenSSL version 3 or later */ + return 4; + } + } + if (sscanf(ptr, "OpenSSL/%d.%d", &major, &minor) == 2) { + if (major > 1 || (major == 1 && minor >= 1)) { + /* OpenSSL version 1.1 or later */ + return 3; + } } if (strncasecmp(ptr, "OpenSSL", sizeof("OpenSSL")-1) == 0) { /* Old OpenSSL version */ @@ -56,11 +66,7 @@ int main(int argc, char *argv[]) ]])],[ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_CURL_OLD_OPENSSL], [1], [Have cURL with old OpenSSL]) - PKG_CHECK_MODULES([OPENSSL], [openssl], [ - PHP_EVAL_LIBLINE($OPENSSL_LIBS, CURL_SHARED_LIBADD) - PHP_EVAL_INCLINE($OPENSSL_CFLAGS) - AC_CHECK_HEADERS([openssl/crypto.h]) - ], []) + PHP_SETUP_OPENSSL(CURL_SHARED_LIBADD,[AC_CHECK_HEADERS([openssl/crypto.h])],[]) ], [ AC_MSG_RESULT([no]) ], [ diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 1a270a1c32ce..61d830e8abfe 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -2220,12 +2220,14 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue ZEND_HASH_FOREACH_VAL(ph, current) { ZVAL_DEREF(current); val = zval_get_tmp_string(current, &tmp_val); - slist = curl_slist_append(slist, ZSTR_VAL(val)); + struct curl_slist *new_slist = curl_slist_append(slist, ZSTR_VAL(val)); zend_tmp_string_release(tmp_val); - if (!slist) { + if (!new_slist) { + curl_slist_free_all(slist); php_error_docref(NULL, E_WARNING, "Could not build curl_slist"); return FAILURE; } + slist = new_slist; } ZEND_HASH_FOREACH_END(); if (slist) { diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 2347fd55706f..910149efae65 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -1607,6 +1607,7 @@ static zval *date_period_it_current_data(zend_object_iterator *iter) php_date_obj *newdateobj; /* Create new object */ + zval_ptr_dtor(&iterator->current); php_date_instantiate(get_base_date_class(object->start_ce), &iterator->current); newdateobj = Z_PHPDATE_P(&iterator->current); newdateobj->time = timelib_time_ctor(); diff --git a/ext/date/tests/multiple_calls_date_period_iterator_current.phpt b/ext/date/tests/multiple_calls_date_period_iterator_current.phpt new file mode 100644 index 000000000000..b0e90873e612 --- /dev/null +++ b/ext/date/tests/multiple_calls_date_period_iterator_current.phpt @@ -0,0 +1,42 @@ +--TEST-- +Multiple calls to DatePeriod iterator current() leak objects +--FILE-- +getIterator(); +var_dump($iter->current()); +var_dump($iter->current()); +$iter->current()->setTimestamp(0); +var_dump($iter->current()); + +?> +--EXPECT-- +object(DateTime)#9 (3) { + ["date"]=> + string(26) "2018-12-31 00:00:00.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(3) "UTC" +} +object(DateTime)#9 (3) { + ["date"]=> + string(26) "2018-12-31 00:00:00.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(3) "UTC" +} +object(DateTime)#9 (3) { + ["date"]=> + string(26) "2018-12-31 00:00:00.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(3) "UTC" +} diff --git a/ext/enchant/tests/broker_dict_exists.phpt b/ext/enchant/tests/broker_dict_exists.phpt index c4ca5d91eb3b..494d2a539b3c 100644 --- a/ext/enchant/tests/broker_dict_exists.phpt +++ b/ext/enchant/tests/broker_dict_exists.phpt @@ -6,8 +6,7 @@ marcosptf - enchant --SKIPIF-- --FILE-- --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- --FILE-- --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- enchant --SKIPIF-- --FILE-- ")) die('skip libenchant v1 only'); ?> --FILE-- diff --git a/ext/enchant/tests/dict_add_to_personal.phpt b/ext/enchant/tests/dict_add_to_personal.phpt index 1fe6c1ff4a47..54d2be5e5d17 100644 --- a/ext/enchant/tests/dict_add_to_personal.phpt +++ b/ext/enchant/tests/dict_add_to_personal.phpt @@ -6,8 +6,7 @@ marcosptf - enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- ")) die('skip libenchant v1 only'); ?> --FILE-- diff --git a/ext/enchant/tests/invalidobj.phpt b/ext/enchant/tests/invalidobj.phpt index 8dbfc2e1f9a6..d9f60075754d 100644 --- a/ext/enchant/tests/invalidobj.phpt +++ b/ext/enchant/tests/invalidobj.phpt @@ -2,10 +2,6 @@ invalid object raise exception() function --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- --FILE-- data_file.c */ -const unsigned char php_magic_database[7955032] = { +const unsigned char php_magic_database[7955032] ZEND_NONSTRING = { 0x1C, 0x04, 0x1E, 0xF1, 0x12, 0x00, 0x00, 0x00, 0xD2, 0x3B, 0x00, 0x00, 0xD2, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/ext/fileinfo/libmagic/apprentice.c b/ext/fileinfo/libmagic/apprentice.c index 5a5ae197467b..922adcf55f1c 100644 --- a/ext/fileinfo/libmagic/apprentice.c +++ b/ext/fileinfo/libmagic/apprentice.c @@ -185,6 +185,12 @@ private struct { { NULL, 0, NULL } }; +#if __has_attribute(nonstring) +# define ZEND_NONSTRING __attribute__((nonstring)) +#else +# define ZEND_NONSTRING +#endif + #include "../data_file.c" #ifdef COMPILE_ONLY diff --git a/ext/hash/hash_xxhash.c b/ext/hash/hash_xxhash.c index 24da754d8835..070bd06bff07 100644 --- a/ext/hash/hash_xxhash.c +++ b/ext/hash/hash_xxhash.c @@ -154,7 +154,7 @@ const php_hash_ops php_hash_xxh3_64_ops = { typedef XXH_errorcode (*xxh3_reset_with_secret_func_t)(XXH3_state_t*, const void*, size_t); typedef XXH_errorcode (*xxh3_reset_with_seed_func_t)(XXH3_state_t*, XXH64_hash_t); -zend_always_inline static void _PHP_XXH3_Init(PHP_XXH3_64_CTX *ctx, HashTable *args, +static void _PHP_XXH3_Init(PHP_XXH3_64_CTX *ctx, HashTable *args, xxh3_reset_with_seed_func_t func_init_seed, xxh3_reset_with_secret_func_t func_init_secret, const char* algo_name) { memset(&ctx->s, 0, sizeof ctx->s); diff --git a/ext/hash/xxhash/xxhash.h b/ext/hash/xxhash/xxhash.h index 8e816c0584eb..5874c9a1f97b 100644 --- a/ext/hash/xxhash/xxhash.h +++ b/ext/hash/xxhash/xxhash.h @@ -931,7 +931,7 @@ XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2); /******* Canonical representation *******/ typedef struct { unsigned char digest[sizeof(XXH128_hash_t)]; } XXH128_canonical_t; -XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash); +static zend_always_inline void XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash); XXH_PUBLIC_API XXH128_hash_t XXH128_hashFromCanonical(const XXH128_canonical_t* src); @@ -5503,7 +5503,7 @@ XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2) /*====== Canonical representation ======*/ /*! @ingroup xxh3_family */ -XXH_PUBLIC_API void +static zend_always_inline void XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash) { XXH_STATIC_ASSERT(sizeof(XXH128_canonical_t) == sizeof(XXH128_hash_t)); diff --git a/ext/intl/common/common_date.cpp b/ext/intl/common/common_date.cpp index 2a52b7e63be5..a412ddcdffcd 100644 --- a/ext/intl/common/common_date.cpp +++ b/ext/intl/common/common_date.cpp @@ -118,6 +118,7 @@ U_CFUNC int intl_datetime_decompose(zval *z, double *millis, TimeZone **tz, ZVAL_STRING(&zfuncname, "getTimestamp"); if (call_user_function(NULL, z, &zfuncname, &retval, 0, NULL) != SUCCESS || Z_TYPE(retval) != IS_LONG) { + zval_ptr_dtor(&retval); spprintf(&message, 0, "%s: error calling ::getTimeStamp() on the " "object", func); intl_errors_set(err, U_INTERNAL_PROGRAM_ERROR, diff --git a/ext/intl/locale/locale_methods.c b/ext/intl/locale/locale_methods.c index f810a61b6be1..44ae9a901a56 100644 --- a/ext/intl/locale/locale_methods.c +++ b/ext/intl/locale/locale_methods.c @@ -1499,6 +1499,7 @@ static zend_string* lookup_loc_range(const char* loc_range, HashTable* hash_arr, zend_string_release_ex(can_loc_range, 0); } if(result == 0) { + efree(cur_loc_range); intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0); LOOKUP_CLEAN_RETURN(NULL); } diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index a1b7e7322a5d..fecb8846400a 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -984,8 +984,6 @@ PHP_FUNCTION(ldap_connect) RETURN_FALSE; } - object_init_ex(return_value, ldap_link_ce); - ld = Z_LDAP_LINK_P(return_value); { int rc = LDAP_SUCCESS; @@ -1008,13 +1006,17 @@ PHP_FUNCTION(ldap_connect) /* ensure all pending TLS options are applied in a new context */ if (ldap_set_option(NULL, LDAP_OPT_X_TLS_NEWCTX, &val) != LDAP_OPT_SUCCESS) { - zval_ptr_dtor(return_value); + if (url != host) { + efree(url); + } php_error_docref(NULL, E_WARNING, "Could not create new security context"); RETURN_FALSE; } LDAPG(tls_newctx) = false; } #endif + object_init_ex(return_value, ldap_link_ce); + ld = Z_LDAP_LINK_P(return_value); #ifdef LDAP_API_FEATURE_X_OPENLDAP /* ldap_init() is deprecated, use ldap_initialize() instead. @@ -1027,6 +1029,9 @@ PHP_FUNCTION(ldap_connect) ldap = ldap_init(host, port); if (ldap == NULL) { zval_ptr_dtor(return_value); + if (url != host) { + efree(url); + } php_error_docref(NULL, E_WARNING, "Could not create session handle"); RETURN_FALSE; } diff --git a/ext/ldap/tests/ldap_connect_port_error.phpt b/ext/ldap/tests/ldap_connect_port_error.phpt new file mode 100644 index 000000000000..55d41d395b13 --- /dev/null +++ b/ext/ldap/tests/ldap_connect_port_error.phpt @@ -0,0 +1,24 @@ +--TEST-- +ldap_connect() - Connection errors +--EXTENSIONS-- +ldap +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +getMessage(), PHP_EOL; +} + +try { + ldap_connect("nope://$host", 0); +} catch (\ValueError $e) { + echo $e->getMessage(), PHP_EOL; +} +?> +--EXPECT-- +ldap_connect(): Argument #2 ($port) must be between 1 and 65535 +ldap_connect(): Argument #2 ($port) must be between 1 and 65535 diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c index 579b5e989bd3..77ba85fe12ae 100644 --- a/ext/odbc/php_odbc.c +++ b/ext/odbc/php_odbc.c @@ -1370,6 +1370,7 @@ static void php_odbc_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int result_type) if (rc == SQL_ERROR) { odbc_sql_error(result->conn_ptr, result->stmt, "SQLGetData"); efree(buf); + zval_ptr_dtor(return_value); RETURN_FALSE; } diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index 1f1abb59a1c2..7061f6b2b73a 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -2910,7 +2910,7 @@ static int zend_jit_setup(void) __asm__( "leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n" - : "=a" (ti)); + : "=D" (ti)); tsrm_tls_offset = ti[1]; tsrm_tls_index = ti[0] * 8; #elif defined(__FreeBSD__) @@ -2918,7 +2918,7 @@ static int zend_jit_setup(void) __asm__( "leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n" - : "=a" (ti)); + : "=D" (ti)); tsrm_tls_offset = ti[1]; /* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/bf56e8b9c8639ac4447d223b83cdc128107cc3cd/libexec/rtld-elf/rtld.c#L5260) */ tsrm_tls_index = (ti[0] + 1) * 8; @@ -2927,7 +2927,7 @@ static int zend_jit_setup(void) __asm__( "leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n" - : "=a" (ti)); + : "=D" (ti)); tsrm_tls_offset = ti[1]; tsrm_tls_index = ti[0] * 16; #endif diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index c978859b7ec0..718f946ad176 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -2421,6 +2421,7 @@ static X509_STORE *php_openssl_setup_verify(zval *calist, uint32_t arg_num) ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(calist), item) { zend_string *str = zval_try_get_string(item); if (UNEXPECTED(!str)) { + X509_STORE_free(store); return NULL; } diff --git a/ext/openssl/tests/bug74796.phpt b/ext/openssl/tests/bug74796.phpt new file mode 100644 index 000000000000..b3f594d5e60f --- /dev/null +++ b/ext/openssl/tests/bug74796.phpt @@ -0,0 +1,178 @@ +--TEST-- +Bug #74796: TLS encryption fails behind HTTP proxy +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + [ + 'SNI_server_certs' => [ + "cs.php.net" => __DIR__ . "/sni_server_cs.pem", + "uk.php.net" => __DIR__ . "/sni_server_uk.pem", + "us.php.net" => __DIR__ . "/sni_server_us.pem" + ] + ]]); + + $server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr, $serverFlags, $ctx); + phpt_notify_server_start($server); + + for ($i=0; $i < 3; $i++) { + $conn = stream_socket_accept($server, 3); + fwrite($conn, "HTTP/1.0 200 OK\r\n\r\nHello from server $i"); + fclose($conn); + } + + phpt_wait(); +CODE; + +$proxyCode = <<<'CODE' + function parse_sni_from_client_hello($data) { + $sni = null; + + if (strlen($data) < 5 || ord($data[0]) != 0x16) return null; + + $session_id_len = ord($data[43]); + $ptr = 44 + $session_id_len; + + // Cipher suites length + $cipher_suites_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]); + $ptr += 2 + $cipher_suites_len; + + // Compression methods length + $compression_methods_len = ord($data[$ptr]); + $ptr += 1 + $compression_methods_len; + + // Extensions length + if ($ptr + 2 > strlen($data)) return null; + $extensions_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]); + $ptr += 2; + + $extensions_end = $ptr + $extensions_len; + + while ($ptr + 4 <= $extensions_end) { + $ext_type = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]); + $ext_len = (ord($data[$ptr+2]) << 8) | ord($data[$ptr+3]); + $ptr += 4; + + if ($ext_type === 0x00) { // SNI extension + if ($ptr + 2 > strlen($data)) break; + $name_list_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]); + $ptr += 2; + + if ($ptr + 3 > strlen($data)) break; + $name_type = ord($data[$ptr]); + $name_len = (ord($data[$ptr+1]) << 8) | ord($data[$ptr+2]); + $ptr += 3; + + if ($name_type === 0) { // host_name type + $sni = substr($data, $ptr, $name_len); + break; + } + } + + $ptr += $ext_len; + } + + return $sni; + } + + $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; + $server = stream_socket_server("tcp://127.0.0.1:0", $errornum, $errorstr, $flags); + phpt_notify_server_start($server); + + for ($i=0; $i < 3; $i++) { + $upstream = stream_socket_client("tcp://{{ ADDR }}", $errornum, $errorstr, 30, STREAM_CLIENT_CONNECT); + stream_set_blocking($upstream, false); + + $conn = stream_socket_accept($server); + stream_set_blocking($conn, true); + + // reading CONNECT request headers + while (($line = fgets($conn)) !== false) { + if (rtrim($line) === '') break; // empty line means end of headers + } + + // successful CONNECT response + fwrite($conn, "HTTP/1.0 200 Connection established\r\n\r\n"); + + // tunnel data + stream_set_blocking($conn, false); + $firstRead = true; + while (!feof($conn) && !feof($upstream)) { + $clientData = fread($conn, 8192); + if ($clientData !== false && $clientData !== '') { + if ($firstRead) { + $sni = parse_sni_from_client_hello($clientData); + if ($sni !== null) { + file_put_contents(__DIR__ . "/bug74796_proxy_sni.log", $sni . "\n", FILE_APPEND); + } + $firstRead = false; + } + fwrite($upstream, $clientData); + } + + $serverData = fread($upstream, 8192); + if ($serverData !== false && $serverData !== '') { + fwrite($conn, $serverData); + } + } + fclose($conn); + fclose($upstream); + phpt_wait(); + } +CODE; + +$clientCode = <<<'CODE' + $clientCtx = stream_context_create([ + 'ssl' => [ + 'cafile' => __DIR__ . '/sni_server_ca.pem', + 'verify_peer' => true, + 'verify_peer_name' => true, + ], + "http" => [ + "proxy" => "tcp://{{ ADDR }}" + ], + ]); + + // servers + $hosts = ["cs.php.net", "uk.php.net", "us.php.net"]; + foreach ($hosts as $host) { + var_dump(file_get_contents("https://$host/", false, $clientCtx)); + var_dump(stream_context_get_options($clientCtx)['ssl']['peer_name'] ?? null); + phpt_notify('proxy'); + } + + echo file_get_contents(__DIR__ . "/bug74796_proxy_sni.log"); + + phpt_notify('server'); +CODE; + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, [ + 'server' => $serverCode, + 'proxy' => $proxyCode, +]); +?> +--CLEAN-- + +--EXPECT-- +string(19) "Hello from server 0" +NULL +string(19) "Hello from server 1" +NULL +string(19) "Hello from server 2" +NULL +cs.php.net +uk.php.net +us.php.net diff --git a/ext/openssl/tests/memory_leak_x509_store.phpt b/ext/openssl/tests/memory_leak_x509_store.phpt new file mode 100644 index 000000000000..bc9b113602a3 --- /dev/null +++ b/ext/openssl/tests/memory_leak_x509_store.phpt @@ -0,0 +1,22 @@ +--TEST-- +Memory leak of X509_STORE in php_openssl_setup_verify() on failure +--EXTENSIONS-- +openssl +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +stop diff --git a/ext/pdo/pdo_sqlstate.c b/ext/pdo/pdo_sqlstate.c index 5858566a818e..a5e23890cee7 100644 --- a/ext/pdo/pdo_sqlstate.c +++ b/ext/pdo/pdo_sqlstate.c @@ -24,8 +24,14 @@ #include "php_pdo.h" #include "php_pdo_driver.h" +#if __has_attribute(nonstring) +# define ZEND_NONSTRING __attribute__((nonstring)) +#else +# define ZEND_NONSTRING +#endif + struct pdo_sqlstate_info { - const char state[5]; + const char state[5] ZEND_NONSTRING; const char *desc; }; diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c index 4bf7162ea06e..9f7ab24f8fad 100644 --- a/ext/pdo_odbc/odbc_stmt.c +++ b/ext/pdo_odbc/odbc_stmt.c @@ -104,6 +104,7 @@ static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, zval *result) zend_string *str = zend_string_alloc(ret, 0); ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) Z_STRVAL_P(result), Z_STRLEN_P(result)/sizeof(WCHAR), ZSTR_VAL(str), ZSTR_LEN(str), NULL, NULL); if (ret == 0) { + zend_string_efree(str); return PDO_ODBC_CONV_FAIL; } diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index 46b3f25f4086..1cccfd2ab07a 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -353,11 +353,15 @@ static zend_string* pgsql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo zend_string *quoted_str; pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; size_t tmp_len; + int err; switch (paramtype) { case PDO_PARAM_LOB: /* escapedlen returned by PQescapeBytea() accounts for trailing 0 */ escaped = PQescapeByteaConn(H->server, (unsigned char *)ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &tmp_len); + if (escaped == NULL) { + return NULL; + } quotedlen = tmp_len + 1; quoted = emalloc(quotedlen + 1); memcpy(quoted+1, escaped, quotedlen-2); @@ -369,7 +373,11 @@ static zend_string* pgsql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo default: quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); quoted[0] = '\''; - quotedlen = PQescapeStringConn(H->server, quoted + 1, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), NULL); + quotedlen = PQescapeStringConn(H->server, quoted + 1, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &err); + if (err) { + efree(quoted); + return NULL; + } quoted[quotedlen + 1] = '\''; quoted[quotedlen + 2] = '\0'; quotedlen += 2; diff --git a/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt b/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt new file mode 100644 index 000000000000..8566a26753b4 --- /dev/null +++ b/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt @@ -0,0 +1,24 @@ +--TEST-- +#GHSA-hrwm-9436-5mv3: pdo_pgsql extension does not check for errors during escaping +--EXTENSIONS-- +pdo +pdo_pgsql +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +$invalid = "ABC\xff\x30';"; +var_dump($db->quote($invalid)); + +?> +--EXPECT-- +bool(false) diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index e955eed0928c..11ce814cbec0 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -3269,8 +3269,14 @@ PHP_FUNCTION(pg_escape_string) to = zend_string_safe_alloc(ZSTR_LEN(from), 2, 0, 0); if (link) { + int err; pgsql = link->conn; - ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), NULL); + ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), &err); + if (err) { + zend_argument_value_error(ZEND_NUM_ARGS(), "Escaping string failed"); + zend_string_efree(to); + RETURN_THROWS(); + } } else { ZSTR_LEN(to) = PQescapeString(ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from)); @@ -3313,6 +3319,10 @@ PHP_FUNCTION(pg_escape_bytea) } else { to = (char *)PQescapeBytea((unsigned char *)ZSTR_VAL(from), ZSTR_LEN(from), &to_len); } + if (to == NULL) { + zend_argument_value_error(ZEND_NUM_ARGS(), "Escape failure"); + RETURN_THROWS(); + } RETVAL_STRINGL(to, to_len-1); /* to_len includes additional '\0' */ PQfreemem(to); @@ -3576,8 +3586,14 @@ static void php_pgsql_do_async(INTERNAL_FUNCTION_PARAMETERS, int entry_type) int rc; c = PQgetCancel(pgsql); + /* PQcancel + * The return value of PQcancel is 1 if the cancel request was successfully dispatched and 0 if not. + * If not, errbuf is filled with an explanatory error message. + * errbuf must be a char array of size errbufsize (the recommended size is 256 bytes). + * https://www.postgresql.org/docs/current/libpq-cancel.html#LIBPQ-PQCANCEL + */ RETVAL_LONG((rc = PQcancel(c, err, sizeof(err)))); - if (rc < 0) { + if (rc == 0) { zend_error(E_WARNING, "cannot cancel the query: %s", err); } while ((pgsql_result = PQgetResult(pgsql))) { @@ -4239,7 +4255,7 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string char *escaped; smart_str querystr = {0}; size_t new_len; - int i, num_rows; + int i, num_rows, err; zval elem; ZEND_ASSERT(ZSTR_LEN(table_name) != 0); @@ -4277,7 +4293,14 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string "WHERE a.attnum > 0 AND c.relname = '"); } escaped = (char *)safe_emalloc(strlen(tmp_name2), 2, 1); - new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), NULL); + new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), &err); + if (err) { + php_error_docref(NULL, E_WARNING, "Escaping table name '%s' failed", ZSTR_VAL(table_name)); + efree(src); + efree(escaped); + smart_str_free(&querystr); + return FAILURE; + } if (new_len) { smart_str_appendl(&querystr, escaped, new_len); } @@ -4285,7 +4308,14 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string smart_str_appends(&querystr, "' AND n.nspname = '"); escaped = (char *)safe_emalloc(strlen(tmp_name), 2, 1); - new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), NULL); + new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), &err); + if (err) { + php_error_docref(NULL, E_WARNING, "Escaping table namespace '%s' failed", ZSTR_VAL(table_name)); + efree(src); + efree(escaped); + smart_str_free(&querystr); + return FAILURE; + } if (new_len) { smart_str_appendl(&querystr, escaped, new_len); } @@ -4546,7 +4576,7 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string * { zend_string *field = NULL; zval meta, *def, *type, *not_null, *has_default, *is_enum, *val, new_val; - int err = 0, skip_field; + int err = 0, escape_err = 0, skip_field; php_pgsql_data_type data_type; ZEND_ASSERT(pg_link != NULL); @@ -4798,8 +4828,13 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string * /* PostgreSQL ignores \0 */ str = zend_string_alloc(Z_STRLEN_P(val) * 2, 0); /* better to use PGSQLescapeLiteral since PGescapeStringConn does not handle special \ */ - ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); - ZVAL_STR(&new_val, php_pgsql_add_quotes(str)); + ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), + Z_STRVAL_P(val), Z_STRLEN_P(val), &escape_err); + if (escape_err) { + err = 1; + } else { + ZVAL_STR(&new_val, php_pgsql_add_quotes(str)); + } zend_string_release_ex(str, false); } break; @@ -4822,7 +4857,14 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string * } PGSQL_CONV_CHECK_IGNORE(); if (err) { - zend_type_error("%s(): Field \"%s\" must be of type string|null, %s given", get_active_function_name(), ZSTR_VAL(field), Z_STRVAL_P(type)); + if (escape_err) { + php_error_docref(NULL, E_NOTICE, + "String value escaping failed for PostgreSQL '%s' (%s)", + Z_STRVAL_P(type), ZSTR_VAL(field)); + } else { + zend_type_error("%s(): Field \"%s\" must be of type string|null, %s given", + get_active_function_name(), ZSTR_VAL(field), Z_STRVAL_P(type)); + } } break; @@ -5097,6 +5139,11 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string * zend_string *tmp_zstr; tmp = PQescapeByteaConn(pg_link, (unsigned char *)Z_STRVAL_P(val), Z_STRLEN_P(val), &to_len); + if (tmp == NULL) { + php_error_docref(NULL, E_NOTICE, "Escaping value failed for %s field (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + err = 1; + break; + } tmp_zstr = zend_string_init((char *)tmp, to_len - 1, false); /* PQescapeBytea's to_len includes additional '\0' */ PQfreemem(tmp); @@ -5175,6 +5222,12 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string * zend_hash_update(Z_ARRVAL_P(result), field, &new_val); } else { char *escaped = PQescapeIdentifier(pg_link, ZSTR_VAL(field), ZSTR_LEN(field)); + if (escaped == NULL) { + /* This cannot fail because of invalid string but only due to failed memory allocation */ + php_error_docref(NULL, E_NOTICE, "Escaping field '%s' failed", ZSTR_VAL(field)); + err = 1; + break; + } add_assoc_zval(result, escaped, &new_val); PQfreemem(escaped); } @@ -5253,7 +5306,7 @@ static bool do_exec(smart_str *querystr, ExecStatusType expect, PGconn *pg_link, } /* }}} */ -static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const zend_string *table) /* {{{ */ +static inline zend_result build_tablename(smart_str *querystr, PGconn *pg_link, const zend_string *table) /* {{{ */ { /* schema.table should be "schema"."table" */ const char *dot = memchr(ZSTR_VAL(table), '.', ZSTR_LEN(table)); @@ -5263,6 +5316,10 @@ static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const z smart_str_appendl(querystr, ZSTR_VAL(table), len); } else { char *escaped = PQescapeIdentifier(pg_link, ZSTR_VAL(table), len); + if (escaped == NULL) { + php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", ZSTR_VAL(table)); + return FAILURE; + } smart_str_appends(querystr, escaped); PQfreemem(escaped); } @@ -5275,11 +5332,17 @@ static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const z smart_str_appendl(querystr, after_dot, len); } else { char *escaped = PQescapeIdentifier(pg_link, after_dot, len); + if (escaped == NULL) { + php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", ZSTR_VAL(table)); + return FAILURE; + } smart_str_appendc(querystr, '.'); smart_str_appends(querystr, escaped); PQfreemem(escaped); } } + + return SUCCESS; } /* }}} */ @@ -5300,7 +5363,9 @@ PHP_PGSQL_API zend_result php_pgsql_insert(PGconn *pg_link, const zend_string *t ZVAL_UNDEF(&converted); if (zend_hash_num_elements(Z_ARRVAL_P(var_array)) == 0) { smart_str_appends(&querystr, "INSERT INTO "); - build_tablename(&querystr, pg_link, table); + if (build_tablename(&querystr, pg_link, table) == FAILURE) { + goto cleanup; + } smart_str_appends(&querystr, " DEFAULT VALUES"); goto no_values; @@ -5316,7 +5381,9 @@ PHP_PGSQL_API zend_result php_pgsql_insert(PGconn *pg_link, const zend_string *t } smart_str_appends(&querystr, "INSERT INTO "); - build_tablename(&querystr, pg_link, table); + if (build_tablename(&querystr, pg_link, table) == FAILURE) { + goto cleanup; + } smart_str_appends(&querystr, " ("); ZEND_HASH_FOREACH_STR_KEY(Z_ARRVAL_P(var_array), fld) { @@ -5326,6 +5393,10 @@ PHP_PGSQL_API zend_result php_pgsql_insert(PGconn *pg_link, const zend_string *t } if (opt & PGSQL_DML_ESCAPE) { tmp = PQescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1); + if (tmp == NULL) { + php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld)); + goto cleanup; + } smart_str_appends(&querystr, tmp); PQfreemem(tmp); } else { @@ -5337,15 +5408,19 @@ PHP_PGSQL_API zend_result php_pgsql_insert(PGconn *pg_link, const zend_string *t smart_str_appends(&querystr, ") VALUES ("); /* make values string */ - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(var_array), val) { + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(var_array), fld, val) { /* we can avoid the key_type check here, because we tested it in the other loop */ switch (Z_TYPE_P(val)) { case IS_STRING: if (opt & PGSQL_DML_ESCAPE) { - size_t new_len; - char *tmp; - tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1); - new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); + int error; + char *tmp = safe_emalloc(Z_STRLEN_P(val), 2, 1); + size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error); + if (error) { + php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld)); + efree(tmp); + goto cleanup; + } smart_str_appendc(&querystr, '\''); smart_str_appendl(&querystr, tmp, new_len); smart_str_appendc(&querystr, '\''); @@ -5501,6 +5576,10 @@ static inline int build_assignment_string(PGconn *pg_link, smart_str *querystr, } if (opt & PGSQL_DML_ESCAPE) { char *tmp = PQescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1); + if (tmp == NULL) { + php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld)); + return -1; + } smart_str_appends(querystr, tmp); PQfreemem(tmp); } else { @@ -5516,8 +5595,14 @@ static inline int build_assignment_string(PGconn *pg_link, smart_str *querystr, switch (Z_TYPE_P(val)) { case IS_STRING: if (opt & PGSQL_DML_ESCAPE) { + int error; char *tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1); - size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); + size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error); + if (error) { + php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld)); + efree(tmp); + return -1; + } smart_str_appendc(querystr, '\''); smart_str_appendl(querystr, tmp, new_len); smart_str_appendc(querystr, '\''); @@ -5585,7 +5670,9 @@ PHP_PGSQL_API zend_result php_pgsql_update(PGconn *pg_link, const zend_string *t } smart_str_appends(&querystr, "UPDATE "); - build_tablename(&querystr, pg_link, table); + if (build_tablename(&querystr, pg_link, table) == FAILURE) { + goto cleanup; + } smart_str_appends(&querystr, " SET "); if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(var_array), 0, ",", 1, opt)) @@ -5688,7 +5775,9 @@ PHP_PGSQL_API zend_result php_pgsql_delete(PGconn *pg_link, const zend_string *t } smart_str_appends(&querystr, "DELETE FROM "); - build_tablename(&querystr, pg_link, table); + if (build_tablename(&querystr, pg_link, table) == FAILURE) { + goto cleanup; + } smart_str_appends(&querystr, " WHERE "); if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt)) @@ -5828,7 +5917,9 @@ PHP_PGSQL_API zend_result php_pgsql_select(PGconn *pg_link, const zend_string *t } smart_str_appends(&querystr, "SELECT * FROM "); - build_tablename(&querystr, pg_link, table); + if (build_tablename(&querystr, pg_link, table) == FAILURE) { + goto cleanup; + } smart_str_appends(&querystr, " WHERE "); if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt)) diff --git a/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt b/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt new file mode 100644 index 000000000000..c1c5e05dce62 --- /dev/null +++ b/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt @@ -0,0 +1,64 @@ +--TEST-- +#GHSA-hrwm-9436-5mv3: pgsql extension does not check for errors during escaping +--EXTENSIONS-- +pgsql +--SKIPIF-- + +--FILE-- + 'test'])); // table name str escape in php_pgsql_meta_data +var_dump(pg_insert($db, "$invalid.tbl", ['bar' => 'test'])); // schema name str escape in php_pgsql_meta_data +var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', ['bar' => $invalid])); // converted value str escape in php_pgsql_convert +var_dump(pg_insert($db, $invalid, [])); // ident escape in build_tablename +var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', [$invalid => 'foo'], $flags)); // ident escape for field php_pgsql_insert +var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', ['bar' => $invalid], $flags)); // str escape for field value in php_pgsql_insert +var_dump(pg_update($db, 'ghsa_hrmw_9436_5mv3', ['bar' => 'val'], [$invalid => 'test'], $flags)); // ident escape in build_assignment_string +var_dump(pg_update($db, 'ghsa_hrmw_9436_5mv3', ['bar' => 'val'], ['bar' => $invalid], $flags)); // invalid str escape in build_assignment_string +var_dump(pg_escape_literal($db, $invalid)); // pg_escape_literal escape +var_dump(pg_escape_identifier($db, $invalid)); // pg_escape_identifier escape + +?> +--EXPECTF-- + +Warning: pg_insert(): Escaping table name 'ABC%s';' failed in %s on line %d +bool(false) + +Warning: pg_insert(): Escaping table namespace 'ABC%s';.tbl' failed in %s on line %d +bool(false) + +Notice: pg_insert(): String value escaping failed for PostgreSQL 'text' (bar) in %s on line %d +bool(false) + +Notice: pg_insert(): Failed to escape table name 'ABC%s';' in %s on line %d +bool(false) + +Notice: pg_insert(): Failed to escape field 'ABC%s';' in %s on line %d +bool(false) + +Notice: pg_insert(): Failed to escape field 'bar' value in %s on line %d +bool(false) + +Notice: pg_update(): Failed to escape field 'ABC%s';' in %s on line %d +bool(false) + +Notice: pg_update(): Failed to escape field 'bar' value in %s on line %d +bool(false) + +Warning: pg_escape_literal(): Failed to escape in %s on line %d +bool(false) + +Warning: pg_escape_identifier(): Failed to escape in %s on line %d +bool(false) diff --git a/ext/phar/phar.c b/ext/phar/phar.c index dfdbbb8fb839..125fc8470361 100644 --- a/ext/phar/phar.c +++ b/ext/phar/phar.c @@ -1704,6 +1704,7 @@ static int phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char php_stream_filter_append(&temp->writefilters, filter); if (SUCCESS != php_stream_copy_to_stream_ex(fp, temp, PHP_STREAM_COPY_ALL, NULL)) { + php_stream_filter_remove(filter, 1); if (err) { php_stream_close(temp); MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\", ext/zlib is buggy in PHP versions older than 5.2.6") @@ -1750,6 +1751,7 @@ static int phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char php_stream_filter_append(&temp->writefilters, filter); if (SUCCESS != php_stream_copy_to_stream_ex(fp, temp, PHP_STREAM_COPY_ALL, NULL)) { + php_stream_filter_remove(filter, 1); php_stream_close(temp); MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\" to temporary file") } @@ -2833,6 +2835,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv } entry->cfp = shared_cfp; if (!entry->cfp) { + php_stream_filter_free(filter); if (error) { spprintf(error, 0, "unable to create temporary file"); } @@ -2847,6 +2850,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv entry->header_offset = php_stream_tell(entry->cfp); php_stream_flush(file); if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) { + php_stream_filter_free(filter); if (closeoldfile) { php_stream_close(oldfile); } @@ -2858,6 +2862,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv } php_stream_filter_append((&entry->cfp->writefilters), filter); if (SUCCESS != php_stream_copy_to_stream_ex(file, entry->cfp, entry->uncompressed_filesize, NULL)) { + php_stream_filter_remove(filter, 1); if (closeoldfile) { php_stream_close(oldfile); } diff --git a/ext/phar/stream.c b/ext/phar/stream.c index b53d4297c422..fee100cc31a1 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -405,7 +405,7 @@ static int phar_stream_seek(php_stream *stream, zend_off_t offset, int whence, z phar_entry_data *data = (phar_entry_data *)stream->abstract; phar_entry_info *entry; int res; - zend_off_t temp; + zend_ulong temp; if (data->internal_file->link) { entry = phar_get_link_source(data->internal_file); @@ -415,26 +415,28 @@ static int phar_stream_seek(php_stream *stream, zend_off_t offset, int whence, z switch (whence) { case SEEK_END : - temp = data->zero + entry->uncompressed_filesize + offset; + temp = (zend_ulong) data->zero + (zend_ulong) entry->uncompressed_filesize + (zend_ulong) offset; break; case SEEK_CUR : - temp = data->zero + data->position + offset; + temp = (zend_ulong) data->zero + (zend_ulong) data->position + (zend_ulong) offset; break; case SEEK_SET : - temp = data->zero + offset; + temp = (zend_ulong) data->zero + (zend_ulong) offset; break; default: temp = 0; } - if (temp > data->zero + (zend_off_t) entry->uncompressed_filesize) { - *newoffset = -1; + + zend_off_t temp_signed = (zend_off_t) temp; + if (temp_signed > data->zero + (zend_off_t) entry->uncompressed_filesize) { + *newoffset = -1; /* FIXME: this will invalidate the ZEND_ASSERT(stream->position >= 0); assertion in streams.c */ return -1; } - if (temp < data->zero) { - *newoffset = -1; + if (temp_signed < data->zero) { + *newoffset = -1; /* FIXME: this will invalidate the ZEND_ASSERT(stream->position >= 0); assertion in streams.c */ return -1; } - res = php_stream_seek(data->fp, temp, SEEK_SET); + res = php_stream_seek(data->fp, temp_signed, SEEK_SET); *newoffset = php_stream_tell(data->fp) - data->zero; data->position = *newoffset; return res; diff --git a/ext/phar/tests/gh18642.phpt b/ext/phar/tests/gh18642.phpt new file mode 100644 index 000000000000..a6872f7a62d8 --- /dev/null +++ b/ext/phar/tests/gh18642.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-18642 (Signed integer overflow in ext/phar fseek) +--EXTENSIONS-- +phar +--INI-- +phar.require_hash=0 +--FILE-- +setInfoClass('MyFile'); +$f = $phar['a.php']; +var_dump($f->fseek(PHP_INT_MAX)); +var_dump($f->fseek(0)); +var_dump($f->fseek(PHP_INT_MIN, SEEK_END)); +var_dump($f->fseek(0, SEEK_SET)); +var_dump($f->fseek(1, SEEK_CUR)); +var_dump($f->fseek(PHP_INT_MAX, SEEK_CUR)); +?> +--EXPECT-- +int(-1) +int(0) +int(-1) +int(0) +int(0) +int(-1) diff --git a/ext/phar/zip.c b/ext/phar/zip.c index c1f7d47c761c..87681c69959a 100644 --- a/ext/phar/zip.c +++ b/ext/phar/zip.c @@ -642,6 +642,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia } if (!entry.uncompressed_filesize || !actual_alias) { + php_stream_filter_remove(filter, 1); pefree(entry.filename, entry.is_persistent); PHAR_ZIP_FAIL("unable to read in alias, truncated"); } @@ -674,6 +675,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia } if (!entry.uncompressed_filesize || !actual_alias) { + php_stream_filter_remove(filter, 1); pefree(entry.filename, entry.is_persistent); PHAR_ZIP_FAIL("unable to read in alias, truncated"); } @@ -968,6 +970,7 @@ static int phar_zip_changed_apply_int(phar_entry_info *entry, void *arg) /* {{{ entry->cfp = php_stream_fopen_tmpfile(); if (!entry->cfp) { + php_stream_filter_free(filter); spprintf(p->error, 0, "unable to create temporary file for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname); return ZEND_HASH_APPLY_STOP; } @@ -975,6 +978,7 @@ static int phar_zip_changed_apply_int(phar_entry_info *entry, void *arg) /* {{{ php_stream_flush(efp); if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) { + php_stream_filter_free(filter); spprintf(p->error, 0, "unable to seek to start of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname); return ZEND_HASH_APPLY_STOP; } @@ -982,6 +986,7 @@ static int phar_zip_changed_apply_int(phar_entry_info *entry, void *arg) /* {{{ php_stream_filter_append((&entry->cfp->writefilters), filter); if (SUCCESS != php_stream_copy_to_stream_ex(efp, entry->cfp, entry->uncompressed_filesize, NULL)) { + php_stream_filter_remove(filter, 1); spprintf(p->error, 0, "unable to copy compressed file contents of file \"%s\" while creating new phar \"%s\"", entry->filename, entry->phar->fname); return ZEND_HASH_APPLY_STOP; } diff --git a/ext/random/engine_user.c b/ext/random/engine_user.c index b45924d3bb7d..ce68521c129a 100644 --- a/ext/random/engine_user.c +++ b/ext/random/engine_user.c @@ -27,6 +27,7 @@ static uint64_t generate(php_random_status *status) uint64_t result = 0; size_t size; zval retval; + zend_string *zstr; zend_call_known_instance_method_with_0_params(s->generate_method, s->object, &retval); @@ -34,8 +35,14 @@ static uint64_t generate(php_random_status *status) return 0; } + if (UNEXPECTED(Z_ISREF(retval))) { + zstr = Z_STR_P(Z_REFVAL(retval)); + } else { + zstr = Z_STR(retval); + } + /* Store generated size in a state */ - size = Z_STRLEN(retval); + size = ZSTR_LEN(zstr); /* Guard for over 64-bit results */ if (size > sizeof(uint64_t)) { @@ -46,11 +53,10 @@ static uint64_t generate(php_random_status *status) if (size > 0) { /* Endianness safe copy */ for (size_t i = 0; i < size; i++) { - result += ((uint64_t) (unsigned char) Z_STRVAL(retval)[i]) << (8 * i); + result += ((uint64_t) (unsigned char) ZSTR_VAL(zstr)[i]) << (8 * i); } } else { zend_throw_error(random_ce_Random_BrokenRandomEngineError, "A random engine must return a non-empty string"); - return 0; } zval_ptr_dtor(&retval); diff --git a/ext/random/tests/02_engine/user_reference_return.phpt b/ext/random/tests/02_engine/user_reference_return.phpt new file mode 100644 index 000000000000..f54e65bed755 --- /dev/null +++ b/ext/random/tests/02_engine/user_reference_return.phpt @@ -0,0 +1,25 @@ +--TEST-- +Random: Engine: User: Returning by reference works +--FILE-- +field; + } +} + +$randomizer = new Randomizer(new ReferenceEngine()); + +var_dump($randomizer->getBytes(64)); + +?> +--EXPECT-- +string(64) "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd" diff --git a/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt b/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt index 6aec7180b1ee..fe402b82b57b 100644 --- a/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt +++ b/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt @@ -10,7 +10,8 @@ final class EmptyStringEngine implements Engine { public function generate(): string { - return ''; + // Create a non-interned empty string. + return preg_replace('/./s', '', random_bytes(4)); } } diff --git a/ext/readline/readline.c b/ext/readline/readline.c index 1bd5e2fd6059..4da9f3595152 100644 --- a/ext/readline/readline.c +++ b/ext/readline/readline.c @@ -458,13 +458,14 @@ char **php_readline_completion_cb(const char *text, int start, int end) /* libedit will read matches[2] */ matches = calloc(3, sizeof(char *)); if (!matches) { - return NULL; + goto out; } matches[0] = strdup(""); } } } +out: zval_ptr_dtor(¶ms[0]); zval_ptr_dtor(&_readline_array); diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index 00aa54c83efd..c908bb4d8ff1 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -416,6 +416,7 @@ int make_http_soap_request(zval *this_ptr, } else { zval_ptr_dtor(¶ms[0]); zval_ptr_dtor(&func); + zval_ptr_dtor(&retval); if (request != buf) { zend_string_release_ex(request, 0); } @@ -1314,6 +1315,7 @@ int make_http_soap_request(zval *this_ptr, } else { zval_ptr_dtor(¶ms[0]); zval_ptr_dtor(&func); + zval_ptr_dtor(&retval); efree(content_encoding); zend_string_release_ex(http_headers, 0); zend_string_release_ex(http_body, 0); diff --git a/ext/soap/php_schema.c b/ext/soap/php_schema.c index 423714545ae3..2fc0eacc530f 100644 --- a/ext/soap/php_schema.c +++ b/ext/soap/php_schema.c @@ -1872,7 +1872,7 @@ static int schema_attribute(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr attrType, sdl } else { xmlNsPtr nsPtr = attr_find_ns(attr); - if (strncmp((char*)nsPtr->href, SCHEMA_NAMESPACE, sizeof(SCHEMA_NAMESPACE))) { + if (nsPtr && strncmp((char*)nsPtr->href, SCHEMA_NAMESPACE, sizeof(SCHEMA_NAMESPACE))) { smart_str key2 = {0}; sdlExtraAttributePtr ext; xmlNsPtr nsptr; diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 3f8a50ba6ed2..fc51e32658f3 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -3980,8 +3980,10 @@ static xmlNodePtr serialize_zval(zval *val, sdlParamPtr param, char *paramName, } xmlParam = master_to_xml(enc, val, style, parent); zval_ptr_dtor(&defval); - if (!strcmp((char*)xmlParam->name, "BOGUS")) { - xmlNodeSetName(xmlParam, BAD_CAST(paramName)); + if (xmlParam != NULL) { + if (xmlParam->name == NULL || strcmp((char*)xmlParam->name, "BOGUS") == 0) { + xmlNodeSetName(xmlParam, BAD_CAST(paramName)); + } } return xmlParam; } diff --git a/ext/soap/tests/soap_qname_crash.phpt b/ext/soap/tests/soap_qname_crash.phpt new file mode 100644 index 000000000000..bcf01d574fab --- /dev/null +++ b/ext/soap/tests/soap_qname_crash.phpt @@ -0,0 +1,48 @@ +--TEST-- +Test SoapClient with excessively large QName prefix in SoapVar +--EXTENSIONS-- +soap +--SKIPIF-- + +--INI-- +memory_limit=6144M +--FILE-- + 'http://127.0.0.1/', + 'uri' => 'urn:dummy', + 'trace' => 1, + 'exceptions' => true, +]; +$client = new TestSoapClient(null, $options); +$client->__soapCall("DummyFunction", [$var]); +?> +--EXPECT-- +Attempting to create SoapVar with very large QName +Attempting encoding + +value diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index 4059758f4471..d03ef8ef68d5 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -1457,7 +1457,11 @@ void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) object_init_ex(&elem, socket_ce); php_socket *sock = Z_SOCKET_P(&elem); - socket_import_file_descriptor(fd, sock); + if (!socket_import_file_descriptor(fd, sock)) { + do_to_zval_err(ctx, "error getting protocol descriptor %d: getsockopt() call failed with errno %d", fd, errno); + zval_ptr_dtor(&elem); + return; + } } else { php_stream *stream = php_stream_fopen_from_fd(fd, "rw", NULL); php_stream_to_zval(stream, &elem); diff --git a/ext/standard/crypt_sha256.c b/ext/standard/crypt_sha256.c index 9e86db6020cd..3e99bedc5412 100644 --- a/ext/standard/crypt_sha256.c +++ b/ext/standard/crypt_sha256.c @@ -317,8 +317,14 @@ static const char sha256_rounds_prefix[] = "rounds="; /* Maximum number of rounds. */ #define ROUNDS_MAX 999999999 +#if __has_attribute(nonstring) +# define ZEND_NONSTRING __attribute__((nonstring)) +#else +# define ZEND_NONSTRING +#endif + /* Table with characters for base64 transformation. */ -static const char b64t[64] = +static const char b64t[64] ZEND_NONSTRING = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; char * php_sha256_crypt_r(const char *key, const char *salt, char *buffer, int buflen) diff --git a/ext/standard/crypt_sha512.c b/ext/standard/crypt_sha512.c index 6ead7f2964a4..ae0eaecdca9a 100644 --- a/ext/standard/crypt_sha512.c +++ b/ext/standard/crypt_sha512.c @@ -350,8 +350,14 @@ static const char sha512_rounds_prefix[] = "rounds="; /* Maximum number of rounds. */ #define ROUNDS_MAX 999999999 +#if __has_attribute(nonstring) +# define ZEND_NONSTRING __attribute__((nonstring)) +#else +# define ZEND_NONSTRING +#endif + /* Table with characters for base64 transformation. */ -static const char b64t[64] = +static const char b64t[64] ZEND_NONSTRING = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; diff --git a/ext/standard/fsock.c b/ext/standard/fsock.c index cb7a471e935a..2b9e00a57554 100644 --- a/ext/standard/fsock.c +++ b/ext/standard/fsock.c @@ -23,6 +23,28 @@ #include "php_network.h" #include "file.h" +static size_t php_fsockopen_format_host_port(char **message, const char *prefix, size_t prefix_len, + const char *host, size_t host_len, zend_long port) +{ + char portbuf[32]; + int portlen = snprintf(portbuf, sizeof(portbuf), ":" ZEND_LONG_FMT, port); + size_t total_len = prefix_len + host_len + portlen; + + char *result = emalloc(total_len + 1); + + if (prefix_len > 0) { + memcpy(result, prefix, prefix_len); + } + memcpy(result + prefix_len, host, host_len); + memcpy(result + prefix_len + host_len, portbuf, portlen); + + result[total_len] = '\0'; + + *message = result; + + return total_len; +} + /* {{{ php_fsockopen() */ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) @@ -62,11 +84,12 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) } if (persistent) { - spprintf(&hashkey, 0, "pfsockopen__%s:" ZEND_LONG_FMT, host, port); + php_fsockopen_format_host_port(&hashkey, "pfsockopen__", strlen("pfsockopen__"), host, + host_len, port); } if (port > 0) { - hostname_len = spprintf(&hostname, 0, "%s:" ZEND_LONG_FMT, host, port); + hostname_len = php_fsockopen_format_host_port(&hostname, "", 0, host, host_len, port); } else { hostname_len = host_len; hostname = host; diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index b4d065dd0b62..040ee4eabf78 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -470,12 +470,14 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (stream && use_proxy && use_ssl) { smart_str header = {0}; + bool reset_ssl_peer_name = false; /* Set peer_name or name verification will try to use the proxy server name */ if (!context || (tmpzval = php_stream_context_get_option(context, "ssl", "peer_name")) == NULL) { ZVAL_STR_COPY(&ssl_proxy_peer_name, resource->host); php_stream_context_set_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name", &ssl_proxy_peer_name); zval_ptr_dtor(&ssl_proxy_peer_name); + reset_ssl_peer_name = true; } smart_str_appendl(&header, "CONNECT ", sizeof("CONNECT ")-1); @@ -572,6 +574,10 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, stream = NULL; } } + + if (reset_ssl_peer_name) { + php_stream_context_unset_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name"); + } } php_stream_http_response_header_info_init(&header_info); diff --git a/ext/standard/tests/file/copy_variation2-win32-mb.phpt b/ext/standard/tests/file/copy_variation2-win32-mb.phpt index 4251a24e54cf..67d84ee3e32c 100644 --- a/ext/standard/tests/file/copy_variation2-win32-mb.phpt +++ b/ext/standard/tests/file/copy_variation2-win32-mb.phpt @@ -22,24 +22,24 @@ fclose($file_handle); $dest_files = array( /* File names containing special(non-alpha numeric) characters */ - "_copy_variation2.tmp", - "@copy_variation2.tmp", - "#copy_variation2.tmp", - "+copy_variation2.tmp", - "?copy_variation2.tmp", - ">copy_variation2.tmp", - "!copy_variation2.tmp", - "©_variation2.tmp", - "(copy_variation2.tmp", - ":copy_variation2.tmp", - ";copy_variation2.tmp", - "=copy_variation2.tmp", - "[copy_variation2.tmp", - "^copy_variation2.tmp", - "{copy_variation2.tmp", - "|copy_variation2.tmp", - "~copy_variation2.tmp", - "\$copy_variation2.tmp" + "_copy_variation2_mb.tmp", + "@copy_variation2_mb.tmp", + "#copy_variation2_mb.tmp", + "+copy_variation2_mb.tmp", + "?copy_variation2_mb.tmp", + ">copy_variation2_mb.tmp", + "!copy_variation2_mb.tmp", + "©_variation2_mb.tmp", + "(copy_variation2_mb.tmp", + ":copy_variation2_mb.tmp", + ";copy_variation2_mb.tmp", + "=copy_variation2_mb.tmp", + "[copy_variation2_mb.tmp", + "^copy_variation2_mb.tmp", + "{copy_variation2_mb.tmp", + "|copy_variation2_mb.tmp", + "~copy_variation2_mb.tmp", + "\$copy_variation2_mb.tmp" ); echo "Size of the source file before copy operation => "; @@ -90,28 +90,28 @@ Size of the source file before copy operation => int(1500) -- Iteration 1 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/_copy_variation2.tmp +Destination file name => %s/_copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 2 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/@copy_variation2.tmp +Destination file name => %s/@copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 3 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/#copy_variation2.tmp +Destination file name => %s/#copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 4 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/+copy_variation2.tmp +Destination file name => %s/+copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) @@ -130,21 +130,21 @@ Existence of destination file => bool(false) -- Iteration 7 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/!copy_variation2.tmp +Destination file name => %s/!copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 8 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/©_variation2.tmp +Destination file name => %s/©_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 9 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/(copy_variation2.tmp +Destination file name => %s/(copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) @@ -157,35 +157,35 @@ Existence of destination file => bool(false) -- Iteration 11 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/;copy_variation2.tmp +Destination file name => %s/;copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 12 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/=copy_variation2.tmp +Destination file name => %s/=copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 13 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/[copy_variation2.tmp +Destination file name => %s/[copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 14 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/^copy_variation2.tmp +Destination file name => %s/^copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 15 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/{copy_variation2.tmp +Destination file name => %s/{copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) @@ -198,14 +198,14 @@ Existence of destination file => bool(false) -- Iteration 17 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/~copy_variation2.tmp +Destination file name => %s/~copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 18 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/$copy_variation2.tmp +Destination file name => %s/$copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) *** Done *** diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt index 37a47df060a1..f3302d77c1d6 100644 --- a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt @@ -44,7 +44,7 @@ array(6) { [0]=> string(14) "GET / HTTP/1.1" [1]=> - string(21) "Host: 127.0.0.1:%d" + string(%d) "Host: 127.0.0.1:%d" [2]=> string(17) "Connection: close" [3]=> diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt index 6c84679ff63b..30d20f855419 100644 --- a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt @@ -44,7 +44,7 @@ array(7) { [0]=> string(14) "GET / HTTP/1.1" [1]=> - string(21) "Host: 127.0.0.1:%d" + string(%d) "Host: 127.0.0.1:%d" [2]=> string(17) "Connection: close" [3]=> diff --git a/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt new file mode 100644 index 000000000000..7556c3be94cc --- /dev/null +++ b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt @@ -0,0 +1,21 @@ +--TEST-- +GHSA-3cr5-j632-f35r: Null byte termination in fsockopen() +--FILE-- + +--EXPECTF-- + +Warning: fsockopen(): Unable to connect to localhost:%d (The hostname must not contain null bytes) in %s +bool(false) diff --git a/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt new file mode 100644 index 000000000000..52f9263c99aa --- /dev/null +++ b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt @@ -0,0 +1,26 @@ +--TEST-- +GHSA-3cr5-j632-f35r: Null byte termination in stream_socket_client() +--FILE-- + +--EXPECTF-- + +Warning: stream_socket_client(): Unable to connect to tcp://localhost\0.example.com:%d (The hostname must not contain null bytes) in %s +bool(false) diff --git a/ext/tidy/config.m4 b/ext/tidy/config.m4 index bc0976a1dd9b..569cb3672944 100644 --- a/ext/tidy/config.m4 +++ b/ext/tidy/config.m4 @@ -62,6 +62,11 @@ if test "$PHP_TIDY" != "no"; then AC_DEFINE(HAVE_TIDYRELEASEDATE,1,[ ]) ], [], []) + PHP_CHECK_LIBRARY($TIDY_LIB_NAME,tidyOptGetCategory, + [ + AC_DEFINE(HAVE_TIDYOPTGETCATEGORY,1,[ ]) + ], [], []) + PHP_ADD_LIBRARY_WITH_PATH($TIDY_LIB_NAME, $TIDY_LIBDIR, TIDY_SHARED_LIBADD) PHP_ADD_INCLUDE($TIDY_INCDIR) diff --git a/ext/tidy/tidy.c b/ext/tidy/tidy.c index 831fcb381539..a42e2bc20377 100644 --- a/ext/tidy/tidy.c +++ b/ext/tidy/tidy.c @@ -232,7 +232,11 @@ static int _php_tidy_set_tidy_opt(TidyDoc doc, char *optname, zval *value) return FAILURE; } +#if defined(HAVE_TIDYOPTGETCATEGORY) + if (tidyOptGetCategory(opt) == TidyInternalCategory) { +#else if (tidyOptIsReadOnly(opt)) { +#endif php_error_docref(NULL, E_WARNING, "Attempting to set read-only option \"%s\"", optname); return FAILURE; } @@ -965,6 +969,11 @@ static int php_tidy_output_handler(void **nothing, php_output_context *output_co TidyBuffer inbuf, outbuf, errbuf; if (TG(clean_output) && (output_context->op & PHP_OUTPUT_HANDLER_START) && (output_context->op & PHP_OUTPUT_HANDLER_FINAL)) { + if (ZEND_SIZE_T_UINT_OVFL(output_context->in.used)) { + php_error_docref(NULL, E_WARNING, "Input string is too long"); + return status; + } + doc = tidyCreate(); tidyBufInit(&errbuf); @@ -972,11 +981,6 @@ static int php_tidy_output_handler(void **nothing, php_output_context *output_co tidyOptSetBool(doc, TidyForceOutput, yes); tidyOptSetBool(doc, TidyMark, no); - if (ZEND_SIZE_T_UINT_OVFL(output_context->in.used)) { - php_error_docref(NULL, E_WARNING, "Input string is too long"); - return status; - } - TIDY_SET_DEFAULT_CONFIG(doc); tidyBufInit(&inbuf); diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index a7dd604d89ef..04ece8bd2537 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1516,3 +1516,13 @@ static PHP_FUNCTION(zend_test_create_throwing_resource) zend_resource *res = zend_register_resource(NULL, le_throwing_resource); ZVAL_RES(return_value, res); } + +static PHP_FUNCTION(zend_test_gh18756) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zend_mm_heap *heap = zend_mm_startup(); + zend_mm_gc(heap); + zend_mm_gc(heap); + zend_mm_shutdown(heap, true, false); +} diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index c9477eef5271..f9cb93b5a1cc 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -262,6 +262,8 @@ function zend_test_cast_fread($stream): void {} function zend_test_is_zend_ptr(int $addr): bool {} function zend_test_log_err_debug(string $str): void {} + + function zend_test_gh18756(): void {} } namespace ZendTestNS { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 5947a6587bbe..c7e3df5c58d2 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: 9ddaf4d659226c55d49221c71702fa373d42695e */ + * Stub hash: 2f161861ab09b6b5b594dc2db7c2c9df49d76aa7 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -162,6 +162,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_log_err_debug, 0, 1, I ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0) ZEND_END_ARG_INFO() +#define arginfo_zend_test_gh18756 arginfo_zend_test_void_return + #define arginfo_ZendTestNS2_namespaced_func arginfo_zend_test_is_pcre_bundled #define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_test_void_return @@ -292,6 +294,7 @@ static ZEND_FUNCTION(zend_test_set_fmode); static ZEND_FUNCTION(zend_test_cast_fread); static ZEND_FUNCTION(zend_test_is_zend_ptr); static ZEND_FUNCTION(zend_test_log_err_debug); +static ZEND_FUNCTION(zend_test_gh18756); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -372,6 +375,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_cast_fread, arginfo_zend_test_cast_fread) ZEND_FE(zend_test_is_zend_ptr, arginfo_zend_test_is_zend_ptr) ZEND_FE(zend_test_log_err_debug, arginfo_zend_test_log_err_debug) + ZEND_FE(zend_test_gh18756, arginfo_zend_test_gh18756) 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/main/network.c b/main/network.c index d4938a4a08c1..8de81a6271a2 100644 --- a/main/network.c +++ b/main/network.c @@ -227,7 +227,7 @@ PHPAPI int php_network_getaddresses(const char *host, int socktype, struct socka for (n = 1; (sai = sai->ai_next) != NULL; n++) ; - *sal = safe_emalloc((n + 1), sizeof(*sal), 0); + *sal = safe_emalloc((n + 1), sizeof(**sal), 0); sai = res; sap = *sal; @@ -266,7 +266,7 @@ PHPAPI int php_network_getaddresses(const char *host, int socktype, struct socka in = *((struct in_addr *) host_info->h_addr); } - *sal = safe_emalloc(2, sizeof(*sal), 0); + *sal = safe_emalloc(2, sizeof(**sal), 0); sap = *sal; *sap = emalloc(sizeof(struct sockaddr_in)); (*sap)->sa_family = AF_INET; diff --git a/main/php_version.h b/main/php_version.h index 6cec820ba471..b7139a67d477 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 3 -#define PHP_RELEASE_VERSION 22 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.3.22-dev" -#define PHP_VERSION_ID 80322 +#define PHP_RELEASE_VERSION 23 +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.3.23" +#define PHP_VERSION_ID 80323 diff --git a/main/streams/php_stream_context.h b/main/streams/php_stream_context.h index d4ebe29bc162..56a1f5374711 100644 --- a/main/streams/php_stream_context.h +++ b/main/streams/php_stream_context.h @@ -59,7 +59,8 @@ PHPAPI zval *php_stream_context_get_option(php_stream_context *context, const char *wrappername, const char *optionname); PHPAPI void php_stream_context_set_option(php_stream_context *context, const char *wrappername, const char *optionname, zval *optionvalue); - +void php_stream_context_unset_option(php_stream_context *context, + const char *wrappername, const char *optionname); PHPAPI php_stream_notifier *php_stream_notification_alloc(void); PHPAPI void php_stream_notification_free(php_stream_notifier *notifier); END_EXTERN_C() diff --git a/main/streams/streams.c b/main/streams/streams.c index 4e0aaa53b443..4f9c88e4774c 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2432,6 +2432,20 @@ PHPAPI void php_stream_context_set_option(php_stream_context *context, SEPARATE_ARRAY(wrapperhash); zend_hash_str_update(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname), optionvalue); } + +void php_stream_context_unset_option(php_stream_context *context, + const char *wrappername, const char *optionname) +{ + zval *wrapperhash; + + wrapperhash = zend_hash_str_find(Z_ARRVAL(context->options), wrappername, strlen(wrappername)); + if (NULL == wrapperhash) { + return; + } + SEPARATE_ARRAY(&context->options); + SEPARATE_ARRAY(wrapperhash); + zend_hash_str_del(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname)); +} /* }}} */ /* {{{ php_stream_dirent_alphasort */ diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c index 606d1499456a..b1d89bc44cb2 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -616,12 +616,15 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po char *colon; char *host = NULL; -#ifdef HAVE_IPV6 - char *p; + if (memchr(str, '\0', str_len)) { + *err = ZSTR_INIT_LITERAL("The hostname must not contain null bytes", 0); + return NULL; + } +#ifdef HAVE_IPV6 if (*(str) == '[' && str_len > 1) { /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ - p = memchr(str + 1, ']', str_len - 2); + char *p = memchr(str + 1, ']', str_len - 2); if (!p || *(p + 1) != ':') { if (get_err) { *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); diff --git a/sapi/fpm/fpm/fpm_request.c b/sapi/fpm/fpm/fpm_request.c index 0eb75884d367..9ea7d8aeaaa3 100644 --- a/sapi/fpm/fpm/fpm_request.c +++ b/sapi/fpm/fpm/fpm_request.c @@ -22,6 +22,7 @@ #include "zlog.h" static const char *requests_stages[] = { + [FPM_REQUEST_CREATING] = "Creating", [FPM_REQUEST_ACCEPTING] = "Idle", [FPM_REQUEST_READING_HEADERS] = "Reading headers", [FPM_REQUEST_INFO] = "Getting request information", diff --git a/sapi/fpm/fpm/fpm_request.h b/sapi/fpm/fpm/fpm_request.h index c1cde0111be4..1dcc7f78902f 100644 --- a/sapi/fpm/fpm/fpm_request.h +++ b/sapi/fpm/fpm/fpm_request.h @@ -25,7 +25,8 @@ const char *fpm_request_get_stage_name(int stage); int fpm_request_last_activity(struct fpm_child_s *child, struct timeval *tv); enum fpm_request_stage_e { - FPM_REQUEST_ACCEPTING = 1, + FPM_REQUEST_CREATING, + FPM_REQUEST_ACCEPTING, FPM_REQUEST_READING_HEADERS, FPM_REQUEST_INFO, FPM_REQUEST_EXECUTING, diff --git a/sapi/phpdbg/phpdbg.c b/sapi/phpdbg/phpdbg.c index 5a4dd6acdbe2..f27d87de8418 100644 --- a/sapi/phpdbg/phpdbg.c +++ b/sapi/phpdbg/phpdbg.c @@ -179,12 +179,6 @@ static PHP_MSHUTDOWN_FUNCTION(phpdbg) /* {{{ */ phpdbg_notice("Script ended normally"); } - /* hack to restore mm_heap->use_custom_heap in order to receive memory leak info */ - if (use_mm_wrappers) { - /* ASSUMING that mm_heap->use_custom_heap is the first element of the struct ... */ - *(int *) zend_mm_get_heap() = 0; - } - if (PHPDBG_G(buffer)) { free(PHPDBG_G(buffer)); PHPDBG_G(buffer) = NULL;