From e7c8e6cde021afd637ea535b0641a1851e57fb2a Mon Sep 17 00:00:00 2001
From: Stanislav Malyshev <stas@php.net>
Date: Mon, 12 Nov 2018 14:02:26 -0800
Subject: [PATCH] Fix bug #77143 - add more checks to buffer reads

---
 NEWS                         |   4 ++++
 ext/phar/phar.c              |  30 +++++++++++++++++++++---------
 ext/phar/tests/bug73768.phpt |   2 +-
 ext/phar/tests/bug77143.phar | Bin 0 -> 50 bytes
 ext/phar/tests/bug77143.phpt |  18 ++++++++++++++++++
 5 files changed, 44 insertions(+), 10 deletions(-)
 create mode 100644 ext/phar/tests/bug77143.phar
 create mode 100644 ext/phar/tests/bug77143.phpt

diff -Nur php-7.2.10/NEWS php-7.2.10_bak/NEWS
--- php-7.2.10/NEWS	2018-09-11 15:06:00.000000000 +0800
+++ php-7.2.10_bak/NEWS	2019-04-04 17:41:54.869000000 +0800
@@ -136,6 +136,10 @@
   . Fixed bug #76477 (Opcache causes empty return value).
     (Nikita, Laruence)
 
+- Phar:
+  . Fixed bug #77143 (Heap Buffer Overflow (READ: 4) in phar_parse_pharfile).
+    (Stas)
+
 - PGSQL:
   . Fixed bug #76548 (pg_fetch_result did not fetch the next row). (Anatol)

diff -Nur php-7.2.10/ext/phar/phar.c php-7.2.10_bak/ext/phar/phar.c
--- php-7.2.10/ext/phar/phar.c	2019-04-04 17:39:04.158000000 +0800
+++ php-7.2.10_bak/ext/phar/phar.c	2019-04-04 17:49:51.807000000 +0800
@@ -643,6 +643,18 @@
 /* }}}*/
 
 /**
+ * Size of fixed fields in the manifest.
+ * See: http://php.net/manual/en/phar.fileformat.phar.php
+ */
+#define MANIFEST_FIXED_LEN 18
+
+#define SAFE_PHAR_GET_32(buffer, endbuffer, var) \
+   if (UNEXPECTED(buffer + 4 > endbuffer)) { \
+	   MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest header)"); \
+   } \
+   PHAR_GET_32(buffer, var);
+
+/**
  * Does not check for a previously opened phar in the cache.
  *
  * Parse a new one and add it to the cache, returning either SUCCESS or
@@ -725,7 +737,7 @@
 	savebuf = buffer;
 	endbuffer = buffer + manifest_len;
 
-	if (manifest_len < 10 || manifest_len != php_stream_read(fp, buffer, manifest_len)) {
+    if (manifest_len < MANIFEST_FIXED_LEN || manifest_len != php_stream_read(fp, buffer, manifest_len)) {
 		MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest header)")
 	}
 
@@ -750,7 +762,7 @@
 		return FAILURE;
 	}
 
-	PHAR_GET_32(buffer, manifest_flags);
+	SAFE_PHAR_GET_32(buffer, endbuffer, manifest_flags);
 
 	manifest_flags &= ~PHAR_HDR_COMPRESSION_MASK;
 	manifest_flags &= ~PHAR_FILE_COMPRESSION_MASK;
@@ -970,13 +982,13 @@
 	}
 
 	/* extract alias */
-	PHAR_GET_32(buffer, tmp_len);
+	SAFE_PHAR_GET_32(buffer, endbuffer, tmp_len);
 
 	if (buffer + tmp_len > endbuffer) {
 		MAPPHAR_FAIL("internal corruption of phar \"%s\" (buffer overrun)");
 	}
 
-	if (manifest_len < 10 + tmp_len) {
+	if (manifest_len < MANIFEST_FIXED_LEN + tmp_len) {
 		MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest header)")
 	}
 
@@ -1014,7 +1026,7 @@
 	}
 
 	/* we have 5 32-bit items plus 1 byte at least */
-	if (manifest_count > ((manifest_len - 10 - tmp_len) / (5 * 4 + 1))) {
+	if (manifest_count > ((manifest_len - MANIFEST_FIXED_LEN - tmp_len) / (5 * 4 + 1))) {
 		/* prevent serious memory issues */
 		MAPPHAR_FAIL("internal corruption of phar \"%s\" (too many manifest entries for size of manifest)")
 	}
@@ -1023,12 +1035,12 @@
 	mydata->is_persistent = PHAR_G(persist);
 
 	/* check whether we have meta data, zero check works regardless of byte order */
-	PHAR_GET_32(buffer, len);
+	SAFE_PHAR_GET_32(buffer, endbuffer, len);
 	if (mydata->is_persistent) {
 		mydata->metadata_len = len;
-		if(!len) {
+		if (!len) {
 			/* FIXME: not sure why this is needed but removing it breaks tests */
-			PHAR_GET_32(buffer, len);
+			SAFE_PHAR_GET_32(buffer, endbuffer, len);
 		}
 	}
 	if(len > (size_t)(endbuffer - buffer)) {
diff -Nur php-7.2.10/ext/phar/tests/bug73768.phpt php-7.2.10_bak/ext/phar/tests/bug73768.phpt
--- php-7.2.10/ext/phar/tests/bug73768.phpt	2018-09-11 15:06:03.000000000 +0800
+++ php-7.2.10_bak/ext/phar/tests/bug73768.phpt	2019-04-04 17:50:51.796000000 +0800
@@ -13,4 +13,4 @@
 }
 ?>
 --EXPECTF--
-cannot load phar "%sbug73768.phar" with implicit alias "" under different alias "alias.phar"
+internal corruption of phar "%sbug73768.phar" (truncated manifest header)
diff --git a/ext/phar/tests/bug77143.phpt b/ext/phar/tests/bug77143.phpt
new file mode 100644
index 0000000..f9f80fc
--- /dev/null
+++ b/ext/phar/tests/bug77143.phpt
@@ -0,0 +1,18 @@
+--TEST--
+PHP bug #77143: Heap Buffer Overflow (READ: 4) in phar_parse_pharfile
+--INI--
+phar.readonly=0
+--SKIPIF--
+<?php if (!extension_loaded("phar")) die("skip"); ?>
+--FILE--
+<?php
+chdir(__DIR__);
+try {
+var_dump(new Phar('bug77143.phar',0,'project.phar'));
+echo "OK\n";
+} catch(UnexpectedValueException $e) {
+	echo $e->getMessage();
+}
+?>
+--EXPECTF--
+internal corruption of phar "%sbug77143.phar" (truncated manifest header)
-- 
2.1.4

