CVE-2016-7414: Improper Restriction of Operations within the Bounds of a Memory Buffer (CWE-119)
value: The ZIP signature-verification feature in PHP before 5.6.26 and 7.x before 7.0.11 does not ensure that the uncompressed_filesize field is large enough, which allows remote attackers to cause a denial of service (out-of-bounds memory access) or possibly have unspecified other impact via a crafted PHAR archive, related to ext/phar/util.c and ext/phar/zip.c.
CWE description: The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
CVSS v3 base score: 9.8
Published: 2016-09-17T21:59Z
Repository: https://github.com/php/php-src
Language: C
Files changed: ext/phar/tests/bug72928.phpt, ext/phar/util.c, ext/phar/zip.c
Commit date: 2016-09-12 17:04:19 +0200
Commit message: Fix bug #72928 - Out of bound when verify signature of zip phar in phar_parse_zipfile

(cherry picked from commit 19484ab77466f99c78fc0e677f7e03da0584d6a2)

Diff: diff --git a/ext/phar/tests/bug72928.phpt b/ext/phar/tests/bug72928.phpt
new file mode 100644
index 00000000000..8e6a95418cf
--- /dev/null
+++ b/ext/phar/tests/bug72928.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Phar: #72928 (Out of bound when verify signature of zip phar in phar_parse_zipfile)
+--SKIPIF--
+<?php if (!extension_loaded("phar")) die("skip"); ?>
+--FILE--
+<?php
+chdir(__DIR__);
+try {
+$phar = new PharData('bug72928.zip');
+var_dump($phar);
+} catch(UnexpectedValueException $e) {
+	print $e->getMessage()."\n";
+}
+?>
+DONE
+--EXPECTF--
+phar error: signature cannot be read in zip-based phar "%sbug72928.zip"
+DONE
\ No newline at end of file
diff --git a/ext/phar/tests/bug72928.zip b/ext/phar/tests/bug72928.zip
new file mode 100644
index 00000000000..c480c5f537c
Binary files /dev/null and b/ext/phar/tests/bug72928.zip differ
diff --git a/ext/phar/util.c b/ext/phar/util.c
index eca33efc385..08d58b9301b 100644
--- a/ext/phar/util.c
+++ b/ext/phar/util.c
@@ -1607,10 +1607,17 @@ int phar_verify_signature(php_stream *fp, size_t end_of_phar, php_uint32 sig_typ
 #ifdef PHAR_HASH_OK
 		case PHAR_SIG_SHA512: {
 			unsigned char digest[64];
 			PHP_SHA512_CTX context;
 
+			if (sig_len < sizeof(digest)) {
+				if (error) {
+					spprintf(error, 0, "broken signature");
+				}
+				return FAILURE;
+			}
+
 			PHP_SHA512Init(&context);
 			read_len = end_of_phar;
 
 			if (read_len > sizeof(buf)) {
 				read_size = sizeof(buf);
@@ -1640,10 +1647,17 @@ int phar_verify_signature(php_stream *fp, size_t end_of_phar, php_uint32 sig_typ
 		}
 		case PHAR_SIG_SHA256: {
 			unsigned char digest[32];
 			PHP_SHA256_CTX context;
 
+			if (sig_len < sizeof(digest)) {
+				if (error) {
+					spprintf(error, 0, "broken signature");
+				}
+				return FAILURE;
+			}
+
 			PHP_SHA256Init(&context);
 			read_len = end_of_phar;
 
 			if (read_len > sizeof(buf)) {
 				read_size = sizeof(buf);
@@ -1681,10 +1695,17 @@ int phar_verify_signature(php_stream *fp, size_t end_of_phar, php_uint32 sig_typ
 #endif
 		case PHAR_SIG_SHA1: {
 			unsigned char digest[20];
 			PHP_SHA1_CTX  context;
 
+			if (sig_len < sizeof(digest)) {
+				if (error) {
+					spprintf(error, 0, "broken signature");
+				}
+				return FAILURE;
+			}
+
 			PHP_SHA1Init(&context);
 			read_len = end_of_phar;
 
 			if (read_len > sizeof(buf)) {
 				read_size = sizeof(buf);
@@ -1714,10 +1735,17 @@ int phar_verify_signature(php_stream *fp, size_t end_of_phar, php_uint32 sig_typ
 		}
 		case PHAR_SIG_MD5: {
 			unsigned char digest[16];
 			PHP_MD5_CTX   context;
 
+			if (sig_len < sizeof(digest)) {
+				if (error) {
+					spprintf(error, 0, "broken signature");
+				}
+				return FAILURE;
+			}
+
 			PHP_MD5Init(&context);
 			read_len = end_of_phar;
 
 			if (read_len > sizeof(buf)) {
 				read_size = sizeof(buf);
diff --git a/ext/phar/zip.c b/ext/phar/zip.c
index 764f2b133d6..8f695dd49ee 100644
--- a/ext/phar/zip.c
+++ b/ext/phar/zip.c
@@ -416,11 +416,11 @@ int phar_parse_zipfile(php_stream *fp, char *fname, int fname_len, char *alias,
 				php_stream_write(sigfile, metadata, PHAR_GET_16(locator.comment_len));
 			}
 			php_stream_seek(fp, sizeof(phar_zip_file_header) + entry.header_offset + entry.filename_len + PHAR_GET_16(zipentry.extra_len), SEEK_SET);
 			sig = (char *) emalloc(entry.uncompressed_filesize);
 			read = php_stream_read(fp, sig, entry.uncompressed_filesize);
-			if (read != entry.uncompressed_filesize) {
+			if (read != entry.uncompressed_filesize || read <= 8) {
 				php_stream_close(sigfile);
 				efree(sig);
 				PHAR_ZIP_FAIL("signature cannot be read");
 			}
 			mydata->sig_flags = PHAR_GET_32(sig);

Vulnerable code: 			if (read != entry.uncompressed_filesize) {
Fixed code: --TEST--
Phar: #72928 (Out of bound when verify signature of zip phar in phar_parse_zipfile)
--SKIPIF--
<?php if (!extension_loaded("phar")) die("skip"); ?>
--FILE--
<?php
chdir(__DIR__);
try {
$phar = new PharData('bug72928.zip');
var_dump($phar);
} catch(UnexpectedValueException $e) {
	print $e->getMessage()."\n";
}
?>
DONE
--EXPECTF--
phar error: signature cannot be read in zip-based phar "%sbug72928.zip"
DONE
			if (sig_len < sizeof(digest)) {
				if (error) {
					spprintf(error, 0, "broken signature");
				}
				return FAILURE;
			}

			if (sig_len < sizeof(digest)) {
				if (error) {
					spprintf(error, 0, "broken signature");
				}
				return FAILURE;
			}

			if (sig_len < sizeof(digest)) {
				if (error) {
					spprintf(error, 0, "broken signature");
				}
				return FAILURE;
			}

			if (sig_len < sizeof(digest)) {
				if (error) {
					spprintf(error, 0, "broken signature");
				}
				return FAILURE;
			}

			if (read != entry.uncompressed_filesize || read <= 8) {