diff options
author | Christoph M. Becker <cmbecker69@gmx.de> | 2021-01-05 15:52:38 +0100 |
---|---|---|
committer | Christoph M. Becker <cmbecker69@gmx.de> | 2021-01-05 23:40:24 +0100 |
commit | d1b1c043988277b7c0d46ec7c953418cbfbb2608 (patch) | |
tree | 5992e04701eb45397c12bf6568f9825457166b7d | |
parent | 5c963731e2eeed47815c6f23ec917988c3fe4121 (diff) | |
download | php-git-d1b1c043988277b7c0d46ec7c953418cbfbb2608.tar.gz |
Fix #77565: Incorrect locator detection in ZIP-based phars
We must not assume that the first end of central dir signature in a ZIP
archive actually designates the end of central directory record, since
the data in the archive may contain arbitrary byte patterns. Thus, we
better search from the end of the data, what is also slightly more
efficient.
There is, however, no way to detect the end of central directory
signature by searching from the end of the ZIP archive with absolute
certainty, since the signature could be part of the trailing comment.
To mitigate, we check that the comment length fits to the found
position, but that might still not be the correct position in rare
cases.
Closes GH-6507.
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | ext/phar/tests/bug69441.phpt | 2 | ||||
-rw-r--r-- | ext/phar/tests/bug77565.phpt | 13 | ||||
-rw-r--r-- | ext/phar/tests/bug77565.zip | bin | 0 -> 318 bytes | |||
-rw-r--r-- | ext/phar/tests/zip/corrupt_003.phpt | 2 | ||||
-rw-r--r-- | ext/phar/zip.c | 97 |
6 files changed, 77 insertions, 40 deletions
@@ -20,6 +20,9 @@ PHP NEWS . Fixed bug #77935 (Crash in mysqlnd_fetch_stmt_row_cursor when calling an SP with a cursor). (Nikita) +- Phar: + . Fixed bug #77565 (Incorrect locator detection in ZIP-based phars). (cmb) + 07 Jan 2021, PHP 7.4.14 - Core: diff --git a/ext/phar/tests/bug69441.phpt b/ext/phar/tests/bug69441.phpt index 91e2dcf146..7150af0f34 100644 --- a/ext/phar/tests/bug69441.phpt +++ b/ext/phar/tests/bug69441.phpt @@ -14,7 +14,7 @@ $r = new Phar($fname, 0); ==DONE== --EXPECTF-- -UnexpectedValueException: phar error: corrupted central directory entry, no magic signature in zip-based phar "%sbug69441.phar" in %sbug69441.php:%d +UnexpectedValueException: phar error: end of central directory not found in zip-based phar "%sbug69441.phar" in %sbug69441.php:%d Stack trace: #0 %s%ebug69441.php(%d): Phar->__construct('%s', 0) #1 {main} diff --git a/ext/phar/tests/bug77565.phpt b/ext/phar/tests/bug77565.phpt new file mode 100644 index 0000000000..e15e7ae647 --- /dev/null +++ b/ext/phar/tests/bug77565.phpt @@ -0,0 +1,13 @@ +--TEST-- +Bug #77565 (Incorrect locator detection in ZIP-based phars) +--SKIPIF-- +<?php +if (!extension_loaded('phar')) die('skip phar extension not available'); +?> +--FILE-- +<?php +$phar = new PharData(__DIR__ . '/bug77565.zip'); +var_dump($phar['1.zip']->getFilename()); +?> +--EXPECT-- +string(5) "1.zip" diff --git a/ext/phar/tests/bug77565.zip b/ext/phar/tests/bug77565.zip Binary files differnew file mode 100644 index 0000000000..134d571d7c --- /dev/null +++ b/ext/phar/tests/bug77565.zip diff --git a/ext/phar/tests/zip/corrupt_003.phpt b/ext/phar/tests/zip/corrupt_003.phpt index e076a4eb8f..9f5bcceeec 100644 --- a/ext/phar/tests/zip/corrupt_003.phpt +++ b/ext/phar/tests/zip/corrupt_003.phpt @@ -12,5 +12,5 @@ try { ?> ===DONE=== --EXPECTF-- -phar error: corrupt zip archive, zip file comment truncated in zip-based phar "%sfilecomment.zip" +phar error: end of central directory not found in zip-based phar "%sfilecomment.zip" ===DONE=== diff --git a/ext/phar/zip.c b/ext/phar/zip.c index 0bf873aff1..c52e87647d 100644 --- a/ext/phar/zip.c +++ b/ext/phar/zip.c @@ -161,6 +161,29 @@ static void phar_zip_u2d_time(time_t time, char *dtime, char *ddate) /* {{{ */ } /* }}} */ +static char *phar_find_eocd(const char *s, size_t n) +{ + const char *end = s + n + sizeof("PK\5\6") - 1 - sizeof(phar_zip_dir_end); + + /* search backwards for end of central directory signatures */ + do { + uint16_t comment_len; + const char *eocd_start = zend_memnrstr(s, "PK\5\6", sizeof("PK\5\6") - 1, end); + + if (eocd_start == NULL) { + return NULL; + } + ZEND_ASSERT(eocd_start + sizeof(phar_zip_dir_end) <= s + n); + comment_len = PHAR_GET_16(((phar_zip_dir_end *) eocd_start)->comment_len); + if (eocd_start + sizeof(phar_zip_dir_end) + comment_len == s + n) { + /* we can't be sure, but this looks like the proper EOCD signature */ + return (char *) eocd_start; + } + end = eocd_start; + } while (end > s); + return NULL; +} + /** * Does not check for a previously opened phar in the cache. * @@ -205,57 +228,55 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia return FAILURE; } - while ((p=(char *) memchr(p + 1, 'P', (size_t) (size - (p + 1 - buf)))) != NULL) { - if ((p - buf) + sizeof(locator) <= (size_t)size && !memcmp(p + 1, "K\5\6", 3)) { - memcpy((void *)&locator, (void *) p, sizeof(locator)); - if (PHAR_GET_16(locator.centraldisk) != 0 || PHAR_GET_16(locator.disknumber) != 0) { - /* split archives not handled */ - php_stream_close(fp); - if (error) { - spprintf(error, 4096, "phar error: split archives spanning multiple zips cannot be processed in zip-based phar \"%s\"", fname); - } - return FAILURE; + if ((p = phar_find_eocd(buf, size)) != NULL) { + memcpy((void *)&locator, (void *) p, sizeof(locator)); + if (PHAR_GET_16(locator.centraldisk) != 0 || PHAR_GET_16(locator.disknumber) != 0) { + /* split archives not handled */ + php_stream_close(fp); + if (error) { + spprintf(error, 4096, "phar error: split archives spanning multiple zips cannot be processed in zip-based phar \"%s\"", fname); } + return FAILURE; + } - if (PHAR_GET_16(locator.counthere) != PHAR_GET_16(locator.count)) { - if (error) { - spprintf(error, 4096, "phar error: corrupt zip archive, conflicting file count in end of central directory record in zip-based phar \"%s\"", fname); - } - php_stream_close(fp); - return FAILURE; + if (PHAR_GET_16(locator.counthere) != PHAR_GET_16(locator.count)) { + if (error) { + spprintf(error, 4096, "phar error: corrupt zip archive, conflicting file count in end of central directory record in zip-based phar \"%s\"", fname); } + php_stream_close(fp); + return FAILURE; + } - mydata = pecalloc(1, sizeof(phar_archive_data), PHAR_G(persist)); - mydata->is_persistent = PHAR_G(persist); + mydata = pecalloc(1, sizeof(phar_archive_data), PHAR_G(persist)); + mydata->is_persistent = PHAR_G(persist); - /* read in archive comment, if any */ - if (PHAR_GET_16(locator.comment_len)) { + /* read in archive comment, if any */ + if (PHAR_GET_16(locator.comment_len)) { - metadata = p + sizeof(locator); + metadata = p + sizeof(locator); - if (PHAR_GET_16(locator.comment_len) != size - (metadata - buf)) { - if (error) { - spprintf(error, 4096, "phar error: corrupt zip archive, zip file comment truncated in zip-based phar \"%s\"", fname); - } - php_stream_close(fp); - pefree(mydata, mydata->is_persistent); - return FAILURE; + if (PHAR_GET_16(locator.comment_len) != size - (metadata - buf)) { + if (error) { + spprintf(error, 4096, "phar error: corrupt zip archive, zip file comment truncated in zip-based phar \"%s\"", fname); } + php_stream_close(fp); + pefree(mydata, mydata->is_persistent); + return FAILURE; + } - mydata->metadata_len = PHAR_GET_16(locator.comment_len); + mydata->metadata_len = PHAR_GET_16(locator.comment_len); - if (phar_parse_metadata(&metadata, &mydata->metadata, PHAR_GET_16(locator.comment_len)) == FAILURE) { - mydata->metadata_len = 0; - /* if not valid serialized data, it is a regular string */ + if (phar_parse_metadata(&metadata, &mydata->metadata, PHAR_GET_16(locator.comment_len)) == FAILURE) { + mydata->metadata_len = 0; + /* if not valid serialized data, it is a regular string */ - ZVAL_NEW_STR(&mydata->metadata, zend_string_init(metadata, PHAR_GET_16(locator.comment_len), mydata->is_persistent)); - } - } else { - ZVAL_UNDEF(&mydata->metadata); + ZVAL_NEW_STR(&mydata->metadata, zend_string_init(metadata, PHAR_GET_16(locator.comment_len), mydata->is_persistent)); } - - goto foundit; + } else { + ZVAL_UNDEF(&mydata->metadata); } + + goto foundit; } php_stream_close(fp); |