Skip to content

Commit 455df08

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 455df08

1 file changed

Lines changed: 42 additions & 14 deletions

File tree

‎ext/phar/tar.c‎

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,22 @@ 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
152+
#if SIZEOF_ZEND_LONG == 4
153+
|| size > ZEND_LONG_MAX
154+
#endif
155+
|| (size_t) offset > totalsize || size > totalsize - (size_t) offset) {
156+
return false;
157+
}
158+
159+
return php_stream_seek(fp, size, SEEK_CUR) == 0
160+
&& php_stream_tell(fp) == offset + (zend_off_t) size;
161+
}
162+
147163
bool phar_is_tar(const char *buf, const char *fname) /* {{{ */
148164
{
149165
tar_header *header = (tar_header *) buf;
@@ -260,6 +276,8 @@ zend_result phar_parse_tarfile(
260276
char buf[512], *actual_alias = NULL, *p;
261277
phar_entry_info entry = {0};
262278
size_t pos = 0, read, totalsize;
279+
zend_off_t end;
280+
php_stream_statbuf ssb;
263281
tar_header *hdr;
264282
uint32_t sum1, sum2, size, old;
265283
phar_archive_data *myphar, *actual;
@@ -270,9 +288,17 @@ zend_result phar_parse_tarfile(
270288
*error = NULL;
271289
}
272290

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

278304
if (read != sizeof(buf)) {
@@ -324,7 +350,8 @@ zend_result phar_parse_tarfile(
324350
}
325351
}
326352

327-
if (!phar_tar_size(hdr->size, sizeof(hdr->size), &size)) {
353+
if (!phar_tar_size(hdr->size, sizeof(hdr->size), &size)
354+
|| (uint64_t) size + 511 > (uint64_t) ZEND_LONG_MAX) {
328355
if (error) {
329356
spprintf(error, 4096, "phar error: \"%s\" is a corrupted tar file (invalid entry size)", fname);
330357
}
@@ -397,12 +424,17 @@ zend_result phar_parse_tarfile(
397424
goto bail;
398425
}
399426
myphar->sig_len = sig_len;
400-
php_stream_seek(fp, curloc + 512, SEEK_SET);
427+
if (curloc < 0 || (size_t) curloc > totalsize
428+
|| totalsize - (size_t) curloc < 512
429+
|| php_stream_seek(fp, curloc + 512, SEEK_SET) != 0) {
430+
if (error) {
431+
spprintf(error, 4096, "phar error: \"%s\" is a corrupted tar file (truncated)", fname);
432+
}
433+
goto bail;
434+
}
401435
/* signature checked out, let's ensure this is the last file in the phar */
402436
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) {
437+
if (!phar_tar_skip(fp, 512, totalsize)) {
406438
if (error) {
407439
spprintf(error, 4096, "phar error: \"%s\" is a corrupted tar file (truncated)", fname);
408440
}
@@ -470,9 +502,7 @@ zend_result phar_parse_tarfile(
470502
/* skip blank stuff */
471503
size = ((size+511)&~511) - size;
472504

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) {
505+
if (!phar_tar_skip(fp, size, totalsize)) {
476506
zend_string_free(entry.filename);
477507
if (error) {
478508
spprintf(error, 4096, "phar error: \"%s\" is a corrupted tar file (truncated)", fname);
@@ -663,9 +693,7 @@ zend_result phar_parse_tarfile(
663693

664694
if (phar_tar_type_has_data(hdr->typeflag) && size > 0) {
665695
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) {
696+
if (!phar_tar_skip(fp, size, totalsize)) {
669697
if (error) {
670698
spprintf(error, 4096, "phar error: \"%s\" is a corrupted tar file (truncated)", fname);
671699
}

0 commit comments

Comments
 (0)