Commit d975bbb6c48 for php.net
commit d975bbb6c48df0f49b4fc85165beedca81b3ca42
Merge: 6db10b644cc 8ceef7f8129
Author: David Carlier <devnexen@gmail.com>
Date: Fri Sep 18 22:19:38 2026 +0100
Merge branch 'PHP-8.5'
* PHP-8.5:
ext/zip: ZipArchive::close() use-after-free from a progress or cancel callback.
# Conflicts:
# ext/zip/php_zip.c
# ext/zip/php_zip.h
diff --cc ext/zip/php_zip.c
index 93004531dac,12d004cc27d..88fdcaa9b03
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@@ -634,73 -632,7 +634,85 @@@ static char * php_zipobj_get_zip_commen
}
/* }}} */
-int php_zip_glob(char *pattern, int pattern_len, zend_long flags, zval *return_value) /* {{{ */
++static bool php_zipobj_closing(ze_zip_object *obj) /* {{{ */
++{
++ if (obj->archive && obj->archive->close) {
++ zend_throw_error(NULL, "Already being closed");
++ return true;
++ }
++ return false;
++}
++/* }}} */
++
+/* Close and free the zip_t. If the archive was opened as a string, the
+ * final contents of the archive will be assigned to *out_str and that
+ * string will afterwards be owned by the caller.
+ *
+ * If out_str is NULL, the final string contents, if any, will be discarded. */
+static bool php_zipobj_close(ze_zip_object *obj, zend_string **out_str) /* {{{ */
+{
+ php_zip_archive *archive = obj->archive;
+ struct zip *intern = archive ? archive->za : NULL;
+ bool bailout = false;
+ bool success = false;
+
+ if (intern) {
++ archive->close = true;
+ int err = zip_close(intern);
++ archive->close = false;
+ if (err) {
+ php_error_docref(NULL, E_WARNING, "%s", zip_strerror(intern));
+ /* Save error for property reader */
+ zip_error_t *ziperr = zip_get_error(intern);
+ obj->err_zip = zip_error_code_zip(ziperr);
+ obj->err_sys = zip_error_code_system(ziperr);
+ zip_error_fini(ziperr);
+ zip_discard(intern);
+ } else {
+ obj->err_zip = 0;
+ obj->err_sys = 0;
+ }
+ success = !err;
+ }
+
+ /* if we have a filename, we need to free it */
+ if (obj->filename) {
+ /* clear cache as empty zip are not created but deleted */
+ php_clear_stat_cache(1, obj->filename, obj->filename_len);
+
+ efree(obj->filename);
+ obj->filename = NULL;
+ obj->filename_len = 0;
+ }
+
+ if (archive && archive->out_str) {
+ if (out_str) {
+ *out_str = archive->out_str;
+ } else {
+ zend_string_release(archive->out_str);
+ }
+ archive->out_str = NULL;
+ } else {
+ ZEND_ASSERT(!out_str);
+ }
+
+ if (archive) {
+ archive->za = NULL;
+ bailout = archive->bailout_callback;
+ archive->bailout_callback = false;
+ obj->archive = NULL;
+ bailout |= php_zip_archive_release(archive);
+ }
+
+ if (bailout) {
+ zend_bailout();
+ }
+
+ return success;
+}
+/* }}} */
+
+static int php_zip_glob(zend_string *spattern, zend_long flags, zval *return_value) /* {{{ */
{
int cwd_skip = 0;
#ifdef ZTS
@@@ -1599,11 -1535,35 +1611,16 @@@ PHP_METHOD(ZipArchive, open
RETURN_FALSE;
}
- if (ze_obj->archive) {
- /* we already have an opened zip, free it */
- if (ze_obj->archive->close) {
- efree(resolved_path);
- zend_throw_error(NULL, "Already being closed");
- RETURN_THROWS();
- }
- intern = ze_obj->archive->za;
- ze_obj->archive->close = true;
- err = zip_close(intern);
- ze_obj->archive->close = false;
- if (err != 0) {
- php_error_docref(NULL, E_WARNING, "Empty string as source");
- efree(resolved_path);
- RETURN_FALSE;
- }
- php_zip_object_detach_archive(ze_obj, intern);
- }
- if (ze_obj->filename) {
- efree(ze_obj->filename);
- ze_obj->filename = NULL;
++ if (php_zipobj_closing(ze_obj)) {
++ efree(resolved_path);
++ RETURN_THROWS();
+ }
+
+ /* If we already have an opened zip, free it */
+ php_zipobj_close(ze_obj, NULL);
+
/* open for write without option to empty the archive */
-#ifdef ZIP_RDONLY
if ((flags & (ZIP_TRUNCATE | ZIP_RDONLY)) == 0) {
-#else
- if ((flags & ZIP_TRUNCATE) == 0) {
-#endif
zend_stat_t st = {0};
/* exists and is empty */
@@@ -1628,58 -1588,6 +1645,62 @@@
}
/* }}} */
+/* {{{ Create new zip from a string, or a create an empty zip to be saved to a string */
+PHP_METHOD(ZipArchive, openString)
+{
+ zend_string *buffer = NULL;
+ zend_long flags = 0;
+ zval *self = ZEND_THIS;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "|Sl", &buffer, &flags) == FAILURE) {
+ RETURN_THROWS();
+ }
+
+ if (!buffer) {
+ buffer = ZSTR_EMPTY_ALLOC();
+ }
+
+ ze_zip_object *ze_obj = Z_ZIP_P(self);
+ php_zip_archive *archive;
+
++ if (php_zipobj_closing(ze_obj)) {
++ RETURN_THROWS();
++ }
++
+ php_zipobj_close(ze_obj, NULL);
+
+ zip_error_t err;
+ zip_error_init(&err);
+
+ archive = php_zip_archive_create(NULL);
+ zip_source_t * zip_source = php_zip_create_string_source(buffer, &archive->out_str, &err);
+
+ if (!zip_source) {
+ ze_obj->err_zip = zip_error_code_zip(&err);
+ ze_obj->err_sys = zip_error_code_system(&err);
+ zip_error_fini(&err);
+ php_zip_archive_release(archive);
+ RETURN_LONG(ze_obj->err_zip);
+ }
+
+ struct zip *intern = zip_open_from_source(zip_source, flags, &err);
+ if (!intern) {
+ ze_obj->err_zip = zip_error_code_zip(&err);
+ ze_obj->err_sys = zip_error_code_system(&err);
+ zip_error_fini(&err);
+ zip_source_free(zip_source);
+ php_zip_archive_release(archive);
+ RETURN_LONG(ze_obj->err_zip);
+ }
+
+ archive->za = intern;
+ archive->from_string = true;
+ ze_obj->archive = archive;
+ zip_error_fini(&err);
+ RETURN_TRUE;
+}
+/* }}} */
+
/* {{{ Set the password for the active archive */
PHP_METHOD(ZipArchive, setPassword)
{
@@@ -1712,34 -1629,50 +1733,42 @@@ PHP_METHOD(ZipArchive, close
ZIP_FROM_OBJECT(intern, self);
- ze_obj = Z_ZIP_P(self);
-
- if (ze_obj->archive->close) {
- zend_throw_error(NULL, "Already being closed");
++ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
- ze_obj->archive->close = true;
- err = zip_close(intern);
- ze_obj->archive->close = false;
- if (err) {
- php_error_docref(NULL, E_WARNING, "%s", zip_strerror(intern));
- /* Save error for property reader */
- #if LIBZIP_VERSION_MAJOR < 1
- zip_error_get(intern, &ze_obj->err_zip, &ze_obj->err_sys);
- #else
- {
- zip_error_t *ziperr;
+ RETURN_BOOL(php_zipobj_close(Z_ZIP_P(self), NULL));
+}
+/* }}} */
- ziperr = zip_get_error(intern);
- ze_obj->err_zip = zip_error_code_zip(ziperr);
- ze_obj->err_sys = zip_error_code_system(ziperr);
- zip_error_fini(ziperr);
- }
- #endif
- zip_discard(intern);
- } else {
- ze_obj->err_zip = 0;
- ze_obj->err_sys = 0;
- }
+/* {{{ close the zip archive and get the result as a string */
+PHP_METHOD(ZipArchive, closeString)
+{
+ struct zip *intern;
+ zval *self = ZEND_THIS;
- /* clear cache as empty zip are not created but deleted */
- php_clear_stat_cache(1, ze_obj->filename, ze_obj->filename_len);
+ ZEND_PARSE_PARAMETERS_NONE();
- efree(ze_obj->filename);
- ze_obj->filename = NULL;
- ze_obj->filename_len = 0;
- php_zip_object_detach_archive(ze_obj, intern);
+ ZIP_FROM_OBJECT(intern, self);
- if (!err) {
- RETURN_TRUE;
- } else {
- RETURN_FALSE;
+ if (!Z_ZIP_P(self)->archive->from_string) {
+ zend_throw_error(NULL, "ZipArchive::closeString can only be called on "
+ "an archive opened with ZipArchive::openString");
+ RETURN_THROWS();
+ }
+
++ if (php_zipobj_closing(Z_ZIP_P(self))) {
++ RETURN_THROWS();
++ }
++
+ zend_string *ret = NULL;
+ bool success = php_zipobj_close(Z_ZIP_P(self), &ret);
+ ZEND_ASSERT(ret);
+ if (success) {
+ RETURN_STR(ret);
}
+ zend_string_release(ret);
+ RETURN_FALSE;
}
/* }}} */
diff --cc ext/zip/php_zip.h
index a67d2042d7c,38773659012..18f09b20850
--- a/ext/zip/php_zip.h
+++ b/ext/zip/php_zip.h
@@@ -69,9 -70,10 +69,10 @@@ typedef struct _ze_zip_read_rsrc
typedef struct _php_zip_archive {
struct zip *za;
uint32_t refcount;
- /* libzip reads buffers until the archive is closed, can outlive the object. */
- char **buffers;
- int buffers_cnt;
+ zend_string *out_str;
+ bool from_string;
+ bool bailout_callback;
+ bool close;
#ifdef HAVE_PROGRESS_CALLBACK
zend_fcall_info_cache progress_callback;
#endif