@@ -416,8 +416,6 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
416416 entry .timestamp = phar_zip_d2u_time (zipentry .timestamp , zipentry .datestamp );
417417 entry .flags = PHAR_ENT_PERM_DEF_FILE ;
418418 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 );
421419
422420 if (PHAR_GET_16 (zipentry .flags ) & PHAR_ZIP_FLAG_ENCRYPTED ) {
423421 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
447445 entry .is_dir = 0 ;
448446 }
449447
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+
450484 if (entry .filename_len == sizeof (".phar/signature.bin" )- 1 && !strncmp (entry .filename , ".phar/signature.bin" , sizeof (".phar/signature.bin" )- 1 )) {
451485 size_t read ;
452486 php_stream * sigfile ;
@@ -474,7 +508,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
474508 if (metadata ) {
475509 php_stream_write (sigfile , metadata , PHAR_GET_16 (locator .comment_len ));
476510 }
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 );
478512 sig = (char * ) emalloc (entry .uncompressed_filesize );
479513 read = php_stream_read (fp , sig , entry .uncompressed_filesize );
480514 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
592626
593627 if (!actual_alias && entry .filename_len == sizeof (".phar/alias.txt" )- 1 && !strncmp (entry .filename , ".phar/alias.txt" , sizeof (".phar/alias.txt" )- 1 )) {
594628 php_stream_filter * filter ;
595- zend_off_t saveloc ;
596- /* verify local file header */
597- phar_zip_file_header local ;
598629
599630 /* 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- }
607631
608632 /* 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 )) {
610635 pefree (entry .filename , entry .is_persistent );
611636 PHAR_ZIP_FAIL ("phar error: internal corruption of zip-based phar (local header of alias does not match central directory)" );
612637 }
613638
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 );
617640 php_stream_seek (fp , entry .offset , SEEK_SET );
618641 /* these next lines should be for php < 5.2.6 after 5.3 filters are fixed */
619642 fp -> writepos = 0 ;
@@ -709,7 +732,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
709732 }
710733
711734 /* return to central directory parsing */
712- php_stream_seek (fp , saveloc , SEEK_SET );
735+ php_stream_seek (fp , restore_pos , SEEK_SET );
713736 }
714737
715738 phar_set_inode (& entry );
0 commit comments