Skip to content

Commit 2008b88

Browse files
committed
ext/phar: Fix tar entry size handling on 32-bit systems
Tar entry sizes at or above 2 GiB cannot be represented by the signed zend_off_t used by php_stream_seek() on 32-bit systems. In addition, rounding an entry size to a 512-byte boundary can overflow uint32_t, and unchecked php_stream_tell() failures can be converted to valid size_t values. Reject archive and entry sizes outside the supported stream offset range, check rounding before it occurs, and validate data skips against the archive length. Signed-off-by: Pavel Nekrasov <p.nekrasov@fobos-nt.ru> Signed-off-by: Georgij Tsarin <crystarm@altlinux.org>
1 parent 4e131c0 commit 2008b88

1 file changed

Lines changed: 39 additions & 14 deletions

File tree

‎ext/phar/tar.c‎

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,19 @@ static uint32_t phar_tar_checksum(const char *buf, size_t len) /* {{{ */
144144
}
145145
/* }}} */
146146

147+
static bool phar_tar_skip(php_stream *fp, uint32_t size, size_t totalsize)
148+
{
149+
zend_off_t offset = php_stream_tell(fp);
150+
151+
if (offset < 0 || (uint64_t) size > (uint64_t) ZEND_LONG_MAX
152+
|| (size_t) offset > totalsize || size > totalsize - (size_t) offset) {
153+
return false;
154+
}
155+
156+
return php_stream_seek(fp, size, SEEK_CUR) == 0
157+
&& php_stream_tell(fp) == offset + (zend_off_t) size;
158+
}
159+
147160
bool phar_is_tar(const char *buf, const char *fname) /* {{{ */
148161
{
149162
tar_header *header = (tar_header *) buf;
@@ -260,6 +273,8 @@ zend_result phar_parse_tarfile(
260273
char buf[512], *actual_alias = NULL, *p;
261274
phar_entry_info entry = {0};
262275
size_t pos = 0, read, totalsize;
276+
zend_off_t end;
277+
php_stream_statbuf ssb;
263278
tar_header *hdr;
264279
uint32_t sum1, sum2, size, old;
265280
phar_archive_data *myphar, *actual;
@@ -270,9 +285,17 @@ zend_result phar_parse_tarfile(
270285
*error = NULL;
271286
}
272287

273-
php_stream_seek(fp, 0, SEEK_END);
274-
totalsize = php_stream_tell(fp);
275-
php_stream_seek(fp, 0, SEEK_SET);
288+
if ((php_stream_stat(fp, &ssb) == 0 && ssb.sb.st_size > ZEND_LONG_MAX)
289+
|| php_stream_seek(fp, 0, SEEK_END) != 0
290+
|| (end = php_stream_tell(fp)) < 0
291+
|| php_stream_seek(fp, 0, SEEK_SET) != 0) {
292+
if (error) {
293+
spprintf(error, 4096, "phar error: tar file \"%s\" exceeds the supported stream offset range", fname);
294+
}
295+
php_stream_close(fp);
296+
return FAILURE;
297+
}
298+
totalsize = (size_t) end;
276299
read = php_stream_read(fp, buf, sizeof(buf));
277300

278301
if (read != sizeof(buf)) {
@@ -324,7 +347,8 @@ zend_result phar_parse_tarfile(
324347
}
325348
}
326349

327-
if (!phar_tar_size(hdr->size, sizeof(hdr->size), &size)) {
350+
if (!phar_tar_size(hdr->size, sizeof(hdr->size), &size)
351+
|| (uint64_t) size + 511 > (uint64_t) ZEND_LONG_MAX) {
328352
if (error) {
329353
spprintf(error, 4096, "phar error: \"%s\" is a corrupted tar file (invalid entry size)", fname);
330354
}
@@ -397,12 +421,17 @@ zend_result phar_parse_tarfile(
397421
goto bail;
398422
}
399423
myphar->sig_len = sig_len;
400-
php_stream_seek(fp, curloc + 512, SEEK_SET);
424+
if (curloc < 0 || (size_t) curloc > totalsize
425+
|| totalsize - (size_t) curloc < 512
426+
|| php_stream_seek(fp, curloc + 512, SEEK_SET) != 0) {
427+
if (error) {
428+
spprintf(error, 4096, "phar error: \"%s\" is a corrupted tar file (truncated)", fname);
429+
}
430+
goto bail;
431+
}
401432
/* signature checked out, let's ensure this is the last file in the phar */
402433
if (((hdr->typeflag == '\0') || (hdr->typeflag == TAR_FILE)) && size > 0) {
403-
/* this is not good enough - seek succeeds even on truncated tars */
404-
php_stream_seek(fp, 512, SEEK_CUR);
405-
if ((uint32_t)php_stream_tell(fp) > totalsize) {
434+
if (!phar_tar_skip(fp, 512, totalsize)) {
406435
if (error) {
407436
spprintf(error, 4096, "phar error: \"%s\" is a corrupted tar file (truncated)", fname);
408437
}
@@ -470,9 +499,7 @@ zend_result phar_parse_tarfile(
470499
/* skip blank stuff */
471500
size = ((size+511)&~511) - size;
472501

473-
/* this is not good enough - seek succeeds even on truncated tars */
474-
php_stream_seek(fp, size, SEEK_CUR);
475-
if ((uint32_t)php_stream_tell(fp) > totalsize) {
502+
if (!phar_tar_skip(fp, size, totalsize)) {
476503
zend_string_free(entry.filename);
477504
if (error) {
478505
spprintf(error, 4096, "phar error: \"%s\" is a corrupted tar file (truncated)", fname);
@@ -663,9 +690,7 @@ zend_result phar_parse_tarfile(
663690

664691
if (phar_tar_type_has_data(hdr->typeflag) && size > 0) {
665692
next:
666-
/* this is not good enough - seek succeeds even on truncated tars */
667-
php_stream_seek(fp, size, SEEK_CUR);
668-
if ((uint32_t)php_stream_tell(fp) > totalsize) {
693+
if (!phar_tar_skip(fp, size, totalsize)) {
669694
if (error) {
670695
spprintf(error, 4096, "phar error: \"%s\" is a corrupted tar file (truncated)", fname);
671696
}

0 commit comments

Comments
 (0)