@@ -416,8 +416,6 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
416
416
entry .timestamp = phar_zip_d2u_time (zipentry .timestamp , zipentry .datestamp );
417
417
entry .flags = PHAR_ENT_PERM_DEF_FILE ;
418
418
entry .header_offset = PHAR_GET_32 (zipentry .offset );
419
- entry .offset = entry .offset_abs = PHAR_GET_32 (zipentry .offset ) + sizeof (phar_zip_file_header ) + PHAR_GET_16 (zipentry .filename_len ) +
420
- PHAR_GET_16 (zipentry .extra_len );
421
419
422
420
if (PHAR_GET_16 (zipentry .flags ) & PHAR_ZIP_FLAG_ENCRYPTED ) {
423
421
PHAR_ZIP_FAIL ("Cannot process encrypted zip files" );
@@ -447,6 +445,42 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
447
445
entry .is_dir = 0 ;
448
446
}
449
447
448
+ phar_zip_file_header local ; /* Warning: only filled in when the entry is not a directory! */
449
+ if (!entry .is_dir ) {
450
+ /* A file has a central directory entry, and a local file header. Both of these contain the filename
451
+ * and the extra field data. However, at least the extra field data does not have to match between the two!
452
+ * This happens for example for the "Extended Timestamp extra field" where the local header has 2 extra fields
453
+ * in comparison to the central header. So we have to use the local header to find the right offset to the file
454
+ * contents, otherwise we're reading some garbage bytes before reading the actual file contents. */
455
+ zend_off_t current_central_dir_pos = php_stream_tell (fp );
456
+
457
+ php_stream_seek (fp , entry .header_offset , SEEK_SET );
458
+ if (sizeof (local ) != php_stream_read (fp , (char * ) & local , sizeof (local ))) {
459
+ pefree (entry .filename , entry .is_persistent );
460
+ PHAR_ZIP_FAIL ("phar error: internal corruption (cannot read local file header)" );
461
+ }
462
+ php_stream_seek (fp , current_central_dir_pos , SEEK_SET );
463
+
464
+ /* verify local header
465
+ * Note: normally I'd check the crc32, and file sizes too here, but that breaks tests zip/bug48791.phpt & zip/odt.phpt,
466
+ * suggesting that something may be wrong with those files or the assumption doesn't hold. Anyway, the other checks
467
+ * _are_ performed for the alias file as was done in the past too. */
468
+ if (entry .filename_len != PHAR_GET_16 (local .filename_len )) {
469
+ pefree (entry .filename , entry .is_persistent );
470
+ PHAR_ZIP_FAIL ("phar error: internal corruption (local file header does not match central directory)" );
471
+ }
472
+
473
+ entry .offset = entry .offset_abs = entry .header_offset
474
+ + sizeof (phar_zip_file_header )
475
+ + entry .filename_len
476
+ + PHAR_GET_16 (local .extra_len );
477
+ } else {
478
+ entry .offset = entry .offset_abs = entry .header_offset
479
+ + sizeof (phar_zip_file_header )
480
+ + entry .filename_len
481
+ + PHAR_GET_16 (zipentry .extra_len );
482
+ }
483
+
450
484
if (entry .filename_len == sizeof (".phar/signature.bin" )- 1 && !strncmp (entry .filename , ".phar/signature.bin" , sizeof (".phar/signature.bin" )- 1 )) {
451
485
size_t read ;
452
486
php_stream * sigfile ;
@@ -474,7 +508,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
474
508
if (metadata ) {
475
509
php_stream_write (sigfile , metadata , PHAR_GET_16 (locator .comment_len ));
476
510
}
477
- php_stream_seek (fp , sizeof ( phar_zip_file_header ) + entry .header_offset + entry . filename_len + PHAR_GET_16 ( zipentry . extra_len ) , SEEK_SET );
511
+ php_stream_seek (fp , entry .offset , SEEK_SET );
478
512
sig = (char * ) emalloc (entry .uncompressed_filesize );
479
513
read = php_stream_read (fp , sig , entry .uncompressed_filesize );
480
514
if (read != entry .uncompressed_filesize || read <= 8 ) {
@@ -592,28 +626,17 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
592
626
593
627
if (!actual_alias && entry .filename_len == sizeof (".phar/alias.txt" )- 1 && !strncmp (entry .filename , ".phar/alias.txt" , sizeof (".phar/alias.txt" )- 1 )) {
594
628
php_stream_filter * filter ;
595
- zend_off_t saveloc ;
596
- /* verify local file header */
597
- phar_zip_file_header local ;
598
629
599
630
/* archive alias found */
600
- saveloc = php_stream_tell (fp );
601
- php_stream_seek (fp , PHAR_GET_32 (zipentry .offset ), SEEK_SET );
602
-
603
- if (sizeof (local ) != php_stream_read (fp , (char * ) & local , sizeof (local ))) {
604
- pefree (entry .filename , entry .is_persistent );
605
- PHAR_ZIP_FAIL ("phar error: internal corruption of zip-based phar (cannot read local file header for alias)" );
606
- }
607
631
608
632
/* verify local header */
609
- if (entry .filename_len != PHAR_GET_16 (local .filename_len ) || entry .crc32 != PHAR_GET_32 (local .crc32 ) || entry .uncompressed_filesize != PHAR_GET_32 (local .uncompsize ) || entry .compressed_filesize != PHAR_GET_32 (local .compsize )) {
633
+ ZEND_ASSERT (!entry .is_dir );
634
+ if (entry .crc32 != PHAR_GET_32 (local .crc32 ) || entry .uncompressed_filesize != PHAR_GET_32 (local .uncompsize ) || entry .compressed_filesize != PHAR_GET_32 (local .compsize )) {
610
635
pefree (entry .filename , entry .is_persistent );
611
636
PHAR_ZIP_FAIL ("phar error: internal corruption of zip-based phar (local header of alias does not match central directory)" );
612
637
}
613
638
614
- /* construct actual offset to file start - local extra_len can be different from central extra_len */
615
- entry .offset = entry .offset_abs =
616
- sizeof (local ) + entry .header_offset + PHAR_GET_16 (local .filename_len ) + PHAR_GET_16 (local .extra_len );
639
+ zend_off_t restore_pos = php_stream_tell (fp );
617
640
php_stream_seek (fp , entry .offset , SEEK_SET );
618
641
/* these next lines should be for php < 5.2.6 after 5.3 filters are fixed */
619
642
fp -> writepos = 0 ;
@@ -709,7 +732,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
709
732
}
710
733
711
734
/* return to central directory parsing */
712
- php_stream_seek (fp , saveloc , SEEK_SET );
735
+ php_stream_seek (fp , restore_pos , SEEK_SET );
713
736
}
714
737
715
738
phar_set_inode (& entry );
0 commit comments