From 48028bc53f704366040db01d596dcba37008b46c Mon Sep 17 00:00:00 2001 From: Gabriel Cordeiro Date: Sat, 7 Mar 2026 18:21:44 -0300 Subject: [PATCH 1/2] Fixed GH-21376: gzfile and readgzfile no longer detect corrupted gzip data When gzip data is corrupted, gzread() can return positive byte count while gzerror() indicates Z_DATA_ERROR. The zlib stream wrapper now checks gzerror() after each gzread() and returns -1 so that gzfile() returns an empty array and readgzfile() returns -1 as in previous PHP versions. readgzfile() also now uses ssize_t for php_stream_passthru() result so that -1 is correctly returned on error instead of being cast to a large size_t. --- NEWS | 4 ++++ ext/zlib/tests/bug21376_corrupt_gz.phpt | 24 ++++++++++++++++++++++++ ext/zlib/zlib.c | 4 ++-- ext/zlib/zlib_fopen_wrapper.c | 11 +++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 ext/zlib/tests/bug21376_corrupt_gz.phpt diff --git a/NEWS b/NEWS index 5437086c8e7dd..405d6fe5f47c0 100644 --- a/NEWS +++ b/NEWS @@ -320,6 +320,10 @@ PHP NEWS . Fixed bug GH-20439 (xml_set_default_handler() does not properly handle special characters in attributes when passing data to callback). (ndossche) +- Zlib: + . Fixed bug GH-21376 (gzfile and readgzfile no longer detect corrupted gzip + data). (GabrielCordeiroBarrosoTeles) + - Zip: . Fix crash in property existence test. (ndossche) . Don't truncate return value of zip_fread() with user sizes. (ndossche) diff --git a/ext/zlib/tests/bug21376_corrupt_gz.phpt b/ext/zlib/tests/bug21376_corrupt_gz.phpt new file mode 100644 index 0000000000000..ffe779e9bd129 --- /dev/null +++ b/ext/zlib/tests/bug21376_corrupt_gz.phpt @@ -0,0 +1,24 @@ +--TEST-- +Bug #21376: gzfile and readgzfile must detect corrupted gzip data +--EXTENSIONS-- +zlib +--FILE-- + +--EXPECT-- +array(0) { +} +int(-1) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index e73e168708ad7..b7e5ebaf626aa 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -677,7 +677,7 @@ PHP_FUNCTION(readgzfile) size_t filename_len; int flags = REPORT_ERRORS; php_stream *stream; - size_t size; + ssize_t size; bool use_include_path = false; if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|b", &filename, &filename_len, &use_include_path) == FAILURE) { @@ -695,7 +695,7 @@ PHP_FUNCTION(readgzfile) } size = php_stream_passthru(stream); php_stream_close(stream); - RETURN_LONG(size); + RETURN_LONG((zend_long) size); } /* }}} */ diff --git a/ext/zlib/zlib_fopen_wrapper.c b/ext/zlib/zlib_fopen_wrapper.c index 7420c0cc6ff2e..030f4e9ac4ab1 100644 --- a/ext/zlib/zlib_fopen_wrapper.c +++ b/ext/zlib/zlib_fopen_wrapper.c @@ -65,6 +65,17 @@ static ssize_t php_gziop_read(php_stream *stream, char *buf, size_t count) return read; } + /* Corrupt gzip data: gzread() may return bytes while gzerror indicates Z_DATA_ERROR */ + if (read > 0) { + int zerr; + gzerror(self->gz_file, &zerr); + if (zerr == Z_DATA_ERROR || zerr == Z_STREAM_ERROR) { + stream->eof = 1; + php_gziop_report_errors(stream, chunk_size, "Read"); + return -1; + } + } + total_read += read; buf += read; } while (count > 0 && !stream->eof); From 7e3369126e9ff49333d4661fb1ef9e3d27b2b5de Mon Sep 17 00:00:00 2001 From: GabrielCordeiroBarrosoTeles Date: Sat, 7 Mar 2026 19:09:37 -0300 Subject: [PATCH 2/2] Fix readgzfile() on Windows: check gzerror when gzread returns 0 On Windows, zlib may return 0 bytes for corrupted gzip while setting Z_DATA_ERROR. Check gzerror() for read >= 0 so that case is treated as failure and readgzfile() returns -1 (GH-21376). --- ext/zlib/zlib.c | 13 +++++++++++++ ext/zlib/zlib_fopen_wrapper.c | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index b7e5ebaf626aa..6cac86183d40a 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -27,6 +27,7 @@ #include "php_ini.h" #include "ext/standard/info.h" #include "ext/standard/file.h" +#include "Zend/zend_virtual_cwd.h" #include "php_zlib.h" #include "zlib_arginfo.h" @@ -694,6 +695,18 @@ PHP_FUNCTION(readgzfile) RETURN_FALSE; } size = php_stream_passthru(stream); + /* On Windows, corrupt gzip may yield 0 from passthru; stream eof is already set so a further read returns 0 (not -1). Treat 0 bytes read from a non-empty file as error. */ + if (size == 0) { + char buf[1]; + if (php_stream_read(stream, buf, 1) < 0) { + size = -1; + } else { + zend_stat_t st = {0}; + if (VCWD_STAT(filename, &st) == 0 && st.st_size > 0) { + size = -1; + } + } + } php_stream_close(stream); RETURN_LONG((zend_long) size); } diff --git a/ext/zlib/zlib_fopen_wrapper.c b/ext/zlib/zlib_fopen_wrapper.c index 030f4e9ac4ab1..a0b4f3d17ab1d 100644 --- a/ext/zlib/zlib_fopen_wrapper.c +++ b/ext/zlib/zlib_fopen_wrapper.c @@ -65,8 +65,8 @@ static ssize_t php_gziop_read(php_stream *stream, char *buf, size_t count) return read; } - /* Corrupt gzip data: gzread() may return bytes while gzerror indicates Z_DATA_ERROR */ - if (read > 0) { + /* Corrupt gzip data: gzread() may return 0 or bytes while gzerror indicates Z_DATA_ERROR (e.g. on Windows) */ + if (read >= 0) { int zerr; gzerror(self->gz_file, &zerr); if (zerr == Z_DATA_ERROR || zerr == Z_STREAM_ERROR) {