From 4ef300ff5ee309394e78bd92d6cb9cd47cf4195a Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Mon, 25 Jul 2022 15:58:59 +0200 Subject: [PATCH] Fix #81726: phar wrapper: DOS when using quine gzip file The phar wrapper needs to uncompress the file; the uncompressed file might be compressed, so the wrapper implementation loops. This raises potential DOS issues regarding too deep or even infinite recursion (the latter are called compressed file quines[1]). We avoid that by introducing a recursion limit; we choose the somewhat arbitrary limit `3`. This issue has been reported by real_as3617 and gPayl0ad. [1] --- NEWS | 2 ++ ext/phar/phar.c | 16 +++++++++++----- ext/phar/tests/bug81726.gz | Bin 0 -> 204 bytes ext/phar/tests/bug81726.phpt | 14 ++++++++++++++ 4 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 ext/phar/tests/bug81726.gz create mode 100644 ext/phar/tests/bug81726.phpt diff --git a/NEWS b/NEWS index 94c8fea4ef7..415628f2dcb 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,8 @@ PHP NEWS ?? ??? ????, PHP 8.1.11 - Core: + . Fixed bug #81726: phar wrapper: DOS when using quine gzip file. + (CVE-2022-31628). (cmb) . Fixed bug GH-9323 (Crash in ZEND_RETURN/GC/zend_call_function) (Tim Starling) . Fixed bug GH-9361 (Segmentation fault on script exit #9379). (cmb, diff --git a/ext/phar/phar.c b/ext/phar/phar.c index bc08e4edde0..1d5815c6d55 100644 --- a/ext/phar/phar.c +++ b/ext/phar/phar.c @@ -1626,7 +1626,8 @@ static int phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char const char zip_magic[] = "PK\x03\x04"; const char gz_magic[] = "\x1f\x8b\x08"; const char bz_magic[] = "BZh"; - char *pos, test = '\0'; + char *pos; + int recursion_count = 3; // arbitrary limit to avoid too deep or even infinite recursion const int window_size = 1024; char buffer[1024 + sizeof(token)]; /* a 1024 byte window + the size of the halt_compiler token (moving window) */ const zend_long readsize = sizeof(buffer) - sizeof(token); @@ -1654,8 +1655,7 @@ static int phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated entry)") } - if (!test) { - test = '\1'; + if (recursion_count) { pos = buffer+tokenlen; if (!memcmp(pos, gz_magic, 3)) { char err = 0; @@ -1715,7 +1715,10 @@ static int phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char compression = PHAR_FILE_COMPRESSED_GZ; /* now, start over */ - test = '\0'; + if (!--recursion_count) { + MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\""); + break; + } continue; } else if (!memcmp(pos, bz_magic, 3)) { php_stream_filter *filter; @@ -1753,7 +1756,10 @@ static int phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char compression = PHAR_FILE_COMPRESSED_BZ2; /* now, start over */ - test = '\0'; + if (!--recursion_count) { + MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\""); + break; + } continue; } diff --git a/ext/phar/tests/bug81726.gz b/ext/phar/tests/bug81726.gz new file mode 100644 index 0000000000000000000000000000000000000000..67b41ba3b6567fb242296fbb5db1504dd562c285 GIT binary patch literal 204 zcmb2|=HOref&Yc2nR%&t=~WC25)9A(%i|Dt5_1=0XJBRc^&i0ka~VV!USk*QP&~o_ nktKo!bjW{@YyLCvGJN{)1lEBn)?BbahT(tZ|CMKe>QFcU?_ozX literal 0 HcmV?d00001 diff --git a/ext/phar/tests/bug81726.phpt b/ext/phar/tests/bug81726.phpt new file mode 100644 index 00000000000..11a148c28ea --- /dev/null +++ b/ext/phar/tests/bug81726.phpt @@ -0,0 +1,14 @@ +--TEST-- +Bug #81726 (phar wrapper: DOS when using quine gzip file) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Warning: fopen(phar://%s): failed to open stream: unable to decompress gzipped phar archive "%s" in %s on line %d +bool(false)