From 48b19a8eded16782e64718ea4058af99f2a8d7a3 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Thu, 13 Nov 2025 22:41:23 +0100 Subject: [PATCH 1/5] xml: Use safe_emalloc() correctly Fortunately, libxml won't allow _at this point in time_ to have more than INT_MAX/5 attributes, so this doesn't cause issues right now. However, if this limit is ever raised then it can cause an integer overflow which will cause a heap overflow. So future-proof this code by properly using safe_emalloc(). Closes GH-20472. --- ext/xml/compat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/xml/compat.c b/ext/xml/compat.c index 25add45f0340..df241e5b684f 100644 --- a/ext/xml/compat.c +++ b/ext/xml/compat.c @@ -111,7 +111,7 @@ _start_element_handler_ns(void *user, const xmlChar *name, const xmlChar *prefix if (attributes != NULL) { xmlChar *qualified_name_attr = NULL; - attrs = safe_emalloc((nb_attributes * 2) + 1, sizeof(int *), 0); + attrs = safe_emalloc(nb_attributes, 2 * sizeof(int *), sizeof(int *)); for (i = 0; i < nb_attributes; i += 1) { From 7263cb61e05d0227061db6e9c34f443d3276d9d9 Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Sat, 15 Nov 2025 05:48:34 -0600 Subject: [PATCH 2/5] [skip ci] Fix destructuring typo in NEWS and UPGRADING (#20488) --- NEWS | 2 +- UPGRADING | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 26b6a59cb5d3..494915f3d923 100644 --- a/NEWS +++ b/NEWS @@ -289,7 +289,7 @@ PHP NEWS 11 Sep 2025, PHP 8.5.0beta3 - Core: - . Destructing non-array values (other than NULL) using [] or list() now + . Destructuring non-array values (other than NULL) using [] or list() now emits a warning. (Girgias) . Fixed bug GH-19637 (Incorrect Closure scope for FCC in constant expression). (timwolla) diff --git a/UPGRADING b/UPGRADING index f95ca587594a..94b272c69c70 100644 --- a/UPGRADING +++ b/UPGRADING @@ -52,7 +52,7 @@ PHP 8.5 UPGRADE NOTES . The disable_classes INI setting has been removed as it causes various engine assumptions to be broken. RFC: https://wiki.php.net/rfc/deprecations_php_8_5#remove_disable_classes_ini_setting - . Destructing non-array values (other than NULL) using [] or list() now + . Destructuring non-array values (other than NULL) using [] or list() now emits a warning. RFC: https://wiki.php.net/rfc/warnings-php-8-5#destructuring_non-array_values . A warning is now emitted when casting floats (or strings that look like From 2f9d86b67787592c2532939df4f91957cd3df7c4 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Thu, 13 Nov 2025 23:06:21 +0100 Subject: [PATCH 3/5] phar: Fix broken return value of fflush() for phar file entries The flush functions always return EOF, even in the success path. The success path should return 0 to indicate success. Closes GH-20474. --- NEWS | 1 + ext/phar/phar.c | 2 +- ext/phar/tar.c | 2 +- .../fflush_phar_file_report_success.phpt | 25 +++++++++++++++++++ ext/phar/zip.c | 2 +- 5 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 ext/phar/tests/fflush_phar_file_report_success.phpt diff --git a/NEWS b/NEWS index 52cfb5ee6385..a7b5e126e3a4 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,7 @@ PHP NEWS - Phar: . Fixed bug GH-20442 (Phar does not respect case-insensitiveness of __halt_compiler() when reading stub). (ndossche, TimWolla) + . Fix broken return value of fflush() for phar file entries. (ndossche) - PHPDBG: . Fixed ZPP type violation in phpdbg_get_executable() and phpdbg_end_oplog(). diff --git a/ext/phar/phar.c b/ext/phar/phar.c index aa9a8821d8e8..a63a8dd8eb6d 100644 --- a/ext/phar/phar.c +++ b/ext/phar/phar.c @@ -3225,7 +3225,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv return EOF; } - return EOF; + return 0; cleanup: if (shared_cfp != NULL) { diff --git a/ext/phar/tar.c b/ext/phar/tar.c index e675b8694321..3bbaad596826 100644 --- a/ext/phar/tar.c +++ b/ext/phar/tar.c @@ -1378,6 +1378,6 @@ int phar_tar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int php_stream_close(newfile); } } - return EOF; + return 0; } /* }}} */ diff --git a/ext/phar/tests/fflush_phar_file_report_success.phpt b/ext/phar/tests/fflush_phar_file_report_success.phpt new file mode 100644 index 000000000000..615f4e58e567 --- /dev/null +++ b/ext/phar/tests/fflush_phar_file_report_success.phpt @@ -0,0 +1,25 @@ +--TEST-- +fflush() on phar file should report success +--EXTENSIONS-- +phar +--INI-- +phar.readonly=0 +--FILE-- +addFromString('test', 'contents'); +unset($phar); + +$f = fopen('phar://' . __DIR__.'/fflush_phar_file_report_success.phar/test', 'w'); +var_dump(fflush($f)); +var_dump(fclose($f)); + +?> +--CLEAN-- + +--EXPECT-- +bool(true) +bool(true) diff --git a/ext/phar/zip.c b/ext/phar/zip.c index 63c56108ed98..b5133063e446 100644 --- a/ext/phar/zip.c +++ b/ext/phar/zip.c @@ -1542,6 +1542,6 @@ int phar_zip_flush(phar_archive_data *phar, char *user_stub, zend_long len, int if (closeoldfile) { php_stream_close(oldfile); } - return EOF; + return 0; } /* }}} */ From fd5c14e682a12a681ebeae66208cd3e09c8952d1 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Sat, 15 Nov 2025 13:57:47 +0100 Subject: [PATCH 4/5] Revert "ext/phar: Voidify flush function as it always returns EOL" This reverts commit 2513258a2b4e83a5c864cc1152914414d6565375. --- ext/phar/phar.c | 41 ++++++++++++++++++------------------ ext/phar/phar_internal.h | 8 +++---- ext/phar/stream.c | 5 +++-- ext/phar/tar.c | 45 ++++++++++++++++++++-------------------- ext/phar/zip.c | 29 +++++++++++++------------- 5 files changed, 66 insertions(+), 62 deletions(-) diff --git a/ext/phar/phar.c b/ext/phar/phar.c index d6c402eefaee..c01ffc9b6e0d 100644 --- a/ext/phar/phar.c +++ b/ext/phar/phar.c @@ -2496,8 +2496,8 @@ zend_string *phar_create_default_stub(const char *index_php, const char *web_ind } /* }}} */ -void phar_flush(phar_archive_data *phar, char **error) { - phar_flush_ex(phar, NULL, false, error); +int phar_flush(phar_archive_data *phar, char **error) { + return phar_flush_ex(phar, NULL, false, error); } /** @@ -2505,7 +2505,7 @@ void phar_flush(phar_archive_data *phar, char **error) { * * if user_stub is NULL the default or existing stub should be used */ -void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error) /* {{{ */ +int phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error) /* {{{ */ { static const char halt_stub[] = "__HALT_COMPILER();"; @@ -2533,7 +2533,7 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa if (error) { spprintf(error, 0, "internal error: attempt to flush cached zip-based phar \"%s\"", phar->fname); } - return; + return EOF; } if (error) { @@ -2541,23 +2541,21 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa } if (!zend_hash_num_elements(&phar->manifest) && !user_stub) { - return; + return EOF; } zend_hash_clean(&phar->virtual_dirs); if (phar->is_zip) { - phar_zip_flush(phar, user_stub, is_default_stub, error); - return; + return phar_zip_flush(phar, user_stub, is_default_stub, error); } if (phar->is_tar) { - phar_tar_flush(phar, user_stub, is_default_stub, error); - return; + return phar_tar_flush(phar, user_stub, is_default_stub, error); } if (PHAR_G(readonly)) { - return; + return EOF; } if (phar->fp && !phar->is_brandnew) { @@ -2576,7 +2574,7 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa if (must_close_old_file) { php_stream_close(oldfile); } - return; + return EOF; } if (user_stub) { @@ -2590,7 +2588,7 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa if (error) { spprintf(error, 0, "illegal stub for phar \"%s\" (__HALT_COMPILER(); is missing)", phar->fname); } - return; + return EOF; } size_t len = pos - ZSTR_VAL(user_stub) + strlen(halt_stub); @@ -2608,7 +2606,7 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa if (error) { spprintf(error, 0, "unable to create stub from string in new phar \"%s\"", phar->fname); } - return; + return EOF; } phar->halt_offset = len + end_sequence_len; } else { @@ -2638,7 +2636,7 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa if (new_stub) { zend_string_free(new_stub); } - return; + return EOF; } if (new_stub) { zend_string_free(new_stub); @@ -2734,7 +2732,7 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa if (error) { spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); } - return; + return EOF; } newcrc32 = php_crc32_bulk_init(); php_crc32_stream_bulk_update(&newcrc32, file, entry->uncompressed_filesize); @@ -2760,7 +2758,7 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa spprintf(error, 0, "unable to bzip2 compress file \"%s\" to new phar \"%s\"", entry->filename, phar->fname); } } - return; + return EOF; } /* create new file that holds the compressed versions */ @@ -3086,7 +3084,7 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa php_stream_close(oldfile); } php_stream_close(newfile); - return; + return EOF; } php_stream_write(newfile, digest, digest_len); @@ -3138,7 +3136,7 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa if (error) { spprintf(error, 4096, "unable to open new phar \"%s\" for writing", phar->fname); } - return; + return EOF; } if (phar->flags & PHAR_FILE_COMPRESSED_GZ) { @@ -3154,7 +3152,7 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa if (error) { spprintf(error, 4096, "unable to compress all contents of phar \"%s\" using zlib, PHP versions older than 5.2.6 have a buggy zlib", phar->fname); } - return; + return EOF; } php_stream_filter_append(&phar->fp->writefilters, filter); @@ -3184,9 +3182,10 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa if (error) { spprintf(error, 0, "unable to seek to __HALT_COMPILER(); in new phar \"%s\"", phar->fname); } + return EOF; } - return; + return EOF; cleanup: if (shared_cfp != NULL) { @@ -3198,6 +3197,8 @@ void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_defa entry->header_offset = 0; } } ZEND_HASH_FOREACH_END(); + + return EOF; } /* }}} */ diff --git a/ext/phar/phar_internal.h b/ext/phar/phar_internal.h index 2f9ae7c1c84e..d8e319eeb6e7 100644 --- a/ext/phar/phar_internal.h +++ b/ext/phar/phar_internal.h @@ -447,12 +447,12 @@ zend_result phar_copy_on_write(phar_archive_data **pphar); bool phar_is_tar(char *buf, char *fname); zend_result phar_parse_tarfile(php_stream* fp, char *fname, size_t fname_len, char *alias, size_t alias_len, phar_archive_data** pphar, uint32_t compression, char **error); zend_result phar_open_or_create_tar(char *fname, size_t fname_len, char *alias, size_t alias_len, int is_data, uint32_t options, phar_archive_data** pphar, char **error); -void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error); +int phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error); /* zip functions in zip.c */ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alias, size_t alias_len, phar_archive_data** pphar, char **error); int phar_open_or_create_zip(char *fname, size_t fname_len, char *alias, size_t alias_len, int is_data, uint32_t options, phar_archive_data** pphar, char **error); -void phar_zip_flush(phar_archive_data *archive, zend_string *user_stub, bool is_default_stub, char **error); +int phar_zip_flush(phar_archive_data *archive, zend_string *user_stub, bool is_default_stub, char **error); #ifdef PHAR_MAIN extern const php_stream_wrapper php_stream_phar_wrapper; @@ -468,8 +468,8 @@ phar_entry_info *phar_get_entry_info(phar_archive_data *phar, char *path, size_t phar_entry_info *phar_get_entry_info_dir(phar_archive_data *phar, char *path, size_t path_len, char dir, char **error, int security); phar_entry_data *phar_get_or_create_entry_data(char *fname, size_t fname_len, char *path, size_t path_len, const char *mode, char allow_dir, char **error, int security); zend_result phar_get_entry_data(phar_entry_data **ret, char *fname, size_t fname_len, char *path, size_t path_len, const char *mode, char allow_dir, char **error, int security); -void phar_flush_ex(phar_archive_data *archive, zend_string *user_stub, bool is_default_stub, char **error); -void phar_flush(phar_archive_data *archive, char **error); +int phar_flush_ex(phar_archive_data *archive, zend_string *user_stub, bool is_default_stub, char **error); +int phar_flush(phar_archive_data *archive, char **error); zend_result phar_detect_phar_fname_ext(const char *filename, size_t filename_len, const char **ext_str, size_t *ext_len, int executable, int for_create, int is_complete); zend_result phar_split_fname(const char *filename, size_t filename_len, char **arch, size_t *arch_len, char **entry, size_t *entry_len, int executable, int for_create); diff --git a/ext/phar/stream.c b/ext/phar/stream.c index 41f1ed9c0dde..9a7fd3763e32 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -471,16 +471,17 @@ static ssize_t phar_stream_write(php_stream *stream, const char *buf, size_t cou static int phar_stream_flush(php_stream *stream) /* {{{ */ { char *error; + int ret; phar_entry_data *data = (phar_entry_data *) stream->abstract; if (data->internal_file->is_modified) { data->internal_file->timestamp = time(0); - phar_flush(data->phar, &error); + ret = phar_flush(data->phar, &error); if (error) { php_stream_wrapper_log_error(stream->wrapper, REPORT_ERRORS, "%s", error); efree(error); } - return EOF; + return ret; } else { return EOF; } diff --git a/ext/phar/tar.c b/ext/phar/tar.c index 63a9cdbf88fd..d53d3a7107e8 100644 --- a/ext/phar/tar.c +++ b/ext/phar/tar.c @@ -959,7 +959,7 @@ static int phar_tar_setupmetadata(zval *zv, void *argument) /* {{{ */ } /* }}} */ -void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error) /* {{{ */ +int phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error) /* {{{ */ { static const char newstub[] = "fname); } - return; + return EOF; } if (phar->is_data) { @@ -1001,7 +1001,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (entry.fp == NULL) { efree(entry.filename); spprintf(error, 0, "phar error: unable to create temporary file"); - return; + return -1; } if (phar->alias_len != php_stream_write(entry.fp, phar->alias, phar->alias_len)) { if (error) { @@ -1009,7 +1009,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def } php_stream_close(entry.fp); efree(entry.filename); - return; + return EOF; } entry.uncompressed_filesize = phar->alias_len; @@ -1029,7 +1029,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (error) { spprintf(error, 0, "illegal stub for tar-based phar \"%s\"", phar->fname); } - return; + return EOF; } size_t len = pos - ZSTR_VAL(user_stub) + strlen(halt_stub); @@ -1039,7 +1039,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def entry.fp = php_stream_fopen_tmpfile(); if (entry.fp == NULL) { spprintf(error, 0, "phar error: unable to create temporary file"); - return; + return EOF; } entry.uncompressed_filesize = len + end_sequence_len; @@ -1051,7 +1051,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def spprintf(error, 0, "unable to create stub from string in new tar-based phar \"%s\"", phar->fname); } php_stream_close(entry.fp); - return; + return EOF; } entry.filename = estrndup(".phar/stub.php", sizeof(".phar/stub.php")-1); @@ -1062,14 +1062,14 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def entry.fp = php_stream_fopen_tmpfile(); if (entry.fp == NULL) { spprintf(error, 0, "phar error: unable to create temporary file"); - return; + return EOF; } if (sizeof(newstub)-1 != php_stream_write(entry.fp, newstub, sizeof(newstub)-1)) { php_stream_close(entry.fp); if (error) { spprintf(error, 0, "unable to %s stub in%star-based phar \"%s\", failed", user_stub ? "overwrite" : "create", user_stub ? " " : " new ", phar->fname); } - return; + return EOF; } entry.uncompressed_filesize = entry.compressed_filesize = sizeof(newstub) - 1; @@ -1084,7 +1084,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (error) { spprintf(error, 0, "unable to create stub in tar-based phar \"%s\"", phar->fname); } - return; + return EOF; } } else { php_stream_close(entry.fp); @@ -1112,7 +1112,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (must_close_old_file) { php_stream_close(oldfile); } - return; + return EOF; } pass.old = oldfile; @@ -1128,7 +1128,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (must_close_old_file) { php_stream_close(oldfile); } - return; + return EOF; } } else { phar_entry_info newentry = {0}; @@ -1144,7 +1144,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (must_close_old_file) { php_stream_close(oldfile); } - return; + return EOF; } if (ZEND_HASH_APPLY_KEEP != phar_tar_setmetadata(&phar->metadata_tracker, mentry, error)) { @@ -1152,7 +1152,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (must_close_old_file) { php_stream_close(oldfile); } - return; + return EOF; } } } @@ -1166,7 +1166,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def /* on error in the hash iterator above, error is set */ php_stream_close(newfile); - return; + return EOF; } zend_hash_apply_with_argument(&phar->manifest, phar_tar_writeheaders, (void *) &pass); @@ -1195,7 +1195,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def } php_stream_close(newfile); - return; + return EOF; } entry.filename = ".phar/signature.bin"; @@ -1209,7 +1209,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def php_stream_close(oldfile); } php_stream_close(newfile); - return; + return EOF; } #ifdef WORDS_BIGENDIAN # define PHAR_SET_32(destination, source) do { \ @@ -1235,7 +1235,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def php_stream_close(oldfile); } php_stream_close(newfile); - return; + return EOF; } efree(signature); @@ -1249,7 +1249,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def } /* error is set by writeheaders */ php_stream_close(newfile); - return; + return EOF; } } /* signature */ @@ -1265,7 +1265,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def /* on error in the hash iterator above, error is set */ if (error && *error) { php_stream_close(newfile); - return; + return EOF; } if (phar->fp && pass.free_fp) { @@ -1292,7 +1292,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (error) { spprintf(error, 0, "unable to open new phar \"%s\" for writing", phar->fname); } - return; + return EOF; } if (phar->flags & PHAR_FILE_COMPRESSED_GZ) { @@ -1316,7 +1316,7 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (error) { spprintf(error, 4096, "unable to compress all contents of phar \"%s\" using zlib, PHP versions older than 5.2.6 have a buggy zlib", phar->fname); } - return; + return EOF; } php_stream_filter_append(&phar->fp->writefilters, filter); @@ -1343,5 +1343,6 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def php_stream_close(newfile); } } + return EOF; } /* }}} */ diff --git a/ext/phar/zip.c b/ext/phar/zip.c index 026fe3935c5e..5bb0e6986bbd 100644 --- a/ext/phar/zip.c +++ b/ext/phar/zip.c @@ -1236,7 +1236,7 @@ static int phar_zip_applysignature(phar_archive_data *phar, struct _phar_zip_pas } /* }}} */ -void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error) /* {{{ */ +int phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error) /* {{{ */ { static const char newstub[] = "fname); } - return; + return EOF; } if (phar->is_data) { @@ -1273,14 +1273,14 @@ void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def entry.fp = php_stream_fopen_tmpfile(); if (entry.fp == NULL) { spprintf(error, 0, "phar error: unable to create temporary file"); - return; + return EOF; } if (phar->alias_len != php_stream_write(entry.fp, phar->alias, phar->alias_len)) { php_stream_close(entry.fp); if (error) { spprintf(error, 0, "unable to set alias in zip-based phar \"%s\"", phar->fname); } - return; + return EOF; } entry.uncompressed_filesize = entry.compressed_filesize = phar->alias_len; @@ -1295,7 +1295,7 @@ void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def /* register alias */ if (phar->alias_len) { if (FAILURE == phar_get_archive(&phar, phar->fname, phar->fname_len, phar->alias, phar->alias_len, error)) { - return; + return EOF; } } @@ -1307,7 +1307,7 @@ void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (error) { spprintf(error, 0, "illegal stub for zip-based phar \"%s\"", phar->fname); } - return; + return EOF; } size_t len = pos - ZSTR_VAL(user_stub) + strlen(halt_stub); @@ -1317,7 +1317,7 @@ void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def entry.fp = php_stream_fopen_tmpfile(); if (entry.fp == NULL) { spprintf(error, 0, "phar error: unable to create temporary file"); - return; + return EOF; } entry.uncompressed_filesize = len + end_sequence_len; @@ -1329,7 +1329,7 @@ void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def spprintf(error, 0, "unable to create stub from string in new zip-based phar \"%s\"", phar->fname); } php_stream_close(entry.fp); - return; + return EOF; } entry.filename = estrndup(".phar/stub.php", sizeof(".phar/stub.php")-1); @@ -1341,14 +1341,14 @@ void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def entry.fp = php_stream_fopen_tmpfile(); if (entry.fp == NULL) { spprintf(error, 0, "phar error: unable to create temporary file"); - return; + return EOF; } if (sizeof(newstub)-1 != php_stream_write(entry.fp, newstub, sizeof(newstub)-1)) { php_stream_close(entry.fp); if (error) { spprintf(error, 0, "unable to %s stub in%szip-based phar \"%s\", failed", user_stub ? "overwrite" : "create", user_stub ? " " : " new ", phar->fname); } - return; + return EOF; } entry.uncompressed_filesize = entry.compressed_filesize = sizeof(newstub) - 1; @@ -1363,7 +1363,7 @@ void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (error) { spprintf(error, 0, "unable to create stub in zip-based phar \"%s\"", phar->fname); } - return; + return EOF; } } else { php_stream_close(entry.fp); @@ -1395,7 +1395,7 @@ void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (error) { spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to open temporary file", phar->fname); } - return; + return EOF; } pass.centralfp = php_stream_fopen_tmpfile(); @@ -1435,7 +1435,7 @@ void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (must_close_old_file) { php_stream_close(oldfile); } - return; + return EOF; } if (FAILURE == phar_zip_applysignature(phar, &pass)) { @@ -1516,7 +1516,7 @@ void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (error) { spprintf(error, 4096, "unable to open new phar \"%s\" for writing", phar->fname); } - return; + return EOF; } php_stream_rewind(pass.filefp); php_stream_copy_to_stream_ex(pass.filefp, phar->fp, PHP_STREAM_COPY_ALL, NULL); @@ -1527,5 +1527,6 @@ void phar_zip_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def if (must_close_old_file) { php_stream_close(oldfile); } + return EOF; } /* }}} */ From d9e40372fcd62be0ee1c954a1a2172ab054815da Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Thu, 13 Nov 2025 23:19:04 +0100 Subject: [PATCH 5/5] Fix assertion failure when fseeking a phar file out of bounds In 61884c3b52 I added these FIXME comments after I noticed that this would cause an assertion failure. At that time I did not yet know what to do here. I took a look at the code now and other streams return -1 and leave the file position untouched. So we do the same for phar. This fixes the assertion failure and subsequent crashes, but also changes one test output. However, I believe the new test output is correct. Closes GH-20475. --- NEWS | 1 + ext/phar/stream.c | 2 -- ext/phar/tests/022.phpt | 14 ++++++------- ext/phar/tests/fseek_outside_bounds.phpt | 26 ++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 ext/phar/tests/fseek_outside_bounds.phpt diff --git a/NEWS b/NEWS index a7b5e126e3a4..c9ff9ac461d4 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,7 @@ PHP NEWS . Fixed bug GH-20442 (Phar does not respect case-insensitiveness of __halt_compiler() when reading stub). (ndossche, TimWolla) . Fix broken return value of fflush() for phar file entries. (ndossche) + . Fix assertion failure when fseeking a phar file out of bounds. (ndossche) - PHPDBG: . Fixed ZPP type violation in phpdbg_get_executable() and phpdbg_end_oplog(). diff --git a/ext/phar/stream.c b/ext/phar/stream.c index a0049f9a6b1f..1151b520bba0 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -429,11 +429,9 @@ static int phar_stream_seek(php_stream *stream, zend_off_t offset, int whence, z 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_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_signed, SEEK_SET); diff --git a/ext/phar/tests/022.phpt b/ext/phar/tests/022.phpt index 5363a65be942..c484c4d3c06d 100644 --- a/ext/phar/tests/022.phpt +++ b/ext/phar/tests/022.phpt @@ -80,28 +80,28 @@ int(1) fseek($fp, -1, SEEK_END)int(0) int(6) fseek($fp, -8, SEEK_END)int(-1) -bool(false) +int(6) fseek($fp, -7, SEEK_END)int(0) int(0) fseek($fp, 0, SEEK_END)int(0) int(7) fseek($fp, 1, SEEK_END)int(-1) -bool(false) +int(7) fseek($fp, -8, SEEK_END)int(-1) -bool(false) +int(7) fseek($fp, 6)int(0) int(6) fseek($fp, 8)int(-1) -bool(false) +int(6) fseek($fp, -1)int(-1) -bool(false) +int(6) next int(4) fseek($fp, -5, SEEK_CUR)int(-1) -bool(false) +int(4) int(4) fseek($fp, 5, SEEK_CUR)int(-1) -bool(false) +int(4) int(4) fseek($fp, -4, SEEK_CUR)int(0) int(0) diff --git a/ext/phar/tests/fseek_outside_bounds.phpt b/ext/phar/tests/fseek_outside_bounds.phpt new file mode 100644 index 000000000000..0a35abb360d5 --- /dev/null +++ b/ext/phar/tests/fseek_outside_bounds.phpt @@ -0,0 +1,26 @@ +--TEST-- +Assertion failure when fseeking outside of bounds of phar file +--EXTENSIONS-- +phar +--INI-- +phar.require_hash=0 +--FILE-- +setInfoClass('SplFileObject'); +$f = $phar['a.php']; +var_dump($f->fseek(1, SEEK_SET)); +var_dump($f->fseek(999999, SEEK_SET)); +var_dump($f->fseek(999999, SEEK_CUR)); +var_dump($f->ftell()); +var_dump($f->fseek(1, SEEK_CUR)); +var_dump($f->fread(3)); +?> +--EXPECT-- +int(0) +int(-1) +int(-1) +int(1) +int(0) +string(3) "php"