@@ -386,8 +386,6 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
386386 entry .timestamp = phar_zip_d2u_time (zipentry .timestamp , zipentry .datestamp );
387387 entry .flags = PHAR_ENT_PERM_DEF_FILE ;
388388 entry .header_offset = PHAR_GET_32 (zipentry .offset );
389- entry .offset = entry .offset_abs = PHAR_GET_32 (zipentry .offset ) + sizeof (phar_zip_file_header ) + PHAR_GET_16 (zipentry .filename_len ) +
390- PHAR_GET_16 (zipentry .extra_len );
391389
392390 if (PHAR_GET_16 (zipentry .flags ) & PHAR_ZIP_FLAG_ENCRYPTED ) {
393391 PHAR_ZIP_FAIL ("Cannot process encrypted zip files" );
@@ -417,6 +415,42 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
417415 entry .is_dir = 0 ;
418416 }
419417
418+ phar_zip_file_header local ; /* Warning: only filled in when the entry is not a directory! */
419+ if (!entry .is_dir ) {
420+ /* A file has a central directory entry, and a local file header. Both of these contain the filename
421+ * and the extra field data. However, at least the extra field data does not have to match between the two!
422+ * This happens for example for the "Extended Timestamp extra field" where the local header has 2 extra fields
423+ * in comparison to the central header. So we have to use the local header to find the right offset to the file
424+ * contents, otherwise we're reading some garbage bytes before reading the actual file contents. */
425+ zend_off_t current_central_dir_pos = php_stream_tell (fp );
426+
427+ php_stream_seek (fp , entry .header_offset , SEEK_SET );
428+ if (sizeof (local ) != php_stream_read (fp , (char * ) & local , sizeof (local ))) {
429+ pefree (entry .filename , entry .is_persistent );
430+ PHAR_ZIP_FAIL ("phar error: internal corruption (cannot read local file header)" );
431+ }
432+ php_stream_seek (fp , current_central_dir_pos , SEEK_SET );
433+
434+ /* verify local header
435+ * Note: normally I'd check the crc32, and file sizes too here, but that breaks tests zip/bug48791.phpt & zip/odt.phpt,
436+ * suggesting that something may be wrong with those files or the assumption doesn't hold. Anyway, the other checks
437+ * _are_ performed for the alias file as was done in the past too. */
438+ if (entry .filename_len != PHAR_GET_16 (local .filename_len )) {
439+ pefree (entry .filename , entry .is_persistent );
440+ PHAR_ZIP_FAIL ("phar error: internal corruption (local file header does not match central directory)" );
441+ }
442+
443+ entry .offset = entry .offset_abs = entry .header_offset
444+ + sizeof (phar_zip_file_header )
445+ + entry .filename_len
446+ + PHAR_GET_16 (local .extra_len );
447+ } else {
448+ entry .offset = entry .offset_abs = entry .header_offset
449+ + sizeof (phar_zip_file_header )
450+ + entry .filename_len
451+ + PHAR_GET_16 (zipentry .extra_len );
452+ }
453+
420454 if (entry .filename_len == sizeof (".phar/signature.bin" )- 1 && !strncmp (entry .filename , ".phar/signature.bin" , sizeof (".phar/signature.bin" )- 1 )) {
421455 size_t read ;
422456 php_stream * sigfile ;
@@ -444,7 +478,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
444478 if (metadata ) {
445479 php_stream_write (sigfile , metadata , PHAR_GET_16 (locator .comment_len ));
446480 }
447- php_stream_seek (fp , sizeof ( phar_zip_file_header ) + entry .header_offset + entry . filename_len + PHAR_GET_16 ( zipentry . extra_len ) , SEEK_SET );
481+ php_stream_seek (fp , entry .offset , SEEK_SET );
448482 sig = (char * ) emalloc (entry .uncompressed_filesize );
449483 read = php_stream_read (fp , sig , entry .uncompressed_filesize );
450484 if (read != entry .uncompressed_filesize || read <= 8 ) {
@@ -562,28 +596,17 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
562596
563597 if (!actual_alias && entry .filename_len == sizeof (".phar/alias.txt" )- 1 && !strncmp (entry .filename , ".phar/alias.txt" , sizeof (".phar/alias.txt" )- 1 )) {
564598 php_stream_filter * filter ;
565- zend_off_t saveloc ;
566- /* verify local file header */
567- phar_zip_file_header local ;
568599
569600 /* archive alias found */
570- saveloc = php_stream_tell (fp );
571- php_stream_seek (fp , PHAR_GET_32 (zipentry .offset ), SEEK_SET );
572-
573- if (sizeof (local ) != php_stream_read (fp , (char * ) & local , sizeof (local ))) {
574- pefree (entry .filename , entry .is_persistent );
575- PHAR_ZIP_FAIL ("phar error: internal corruption of zip-based phar (cannot read local file header for alias)" );
576- }
577601
578602 /* verify local header */
579- 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 )) {
603+ ZEND_ASSERT (!entry .is_dir );
604+ if (entry .crc32 != PHAR_GET_32 (local .crc32 ) || entry .uncompressed_filesize != PHAR_GET_32 (local .uncompsize ) || entry .compressed_filesize != PHAR_GET_32 (local .compsize )) {
580605 pefree (entry .filename , entry .is_persistent );
581606 PHAR_ZIP_FAIL ("phar error: internal corruption of zip-based phar (local header of alias does not match central directory)" );
582607 }
583608
584- /* construct actual offset to file start - local extra_len can be different from central extra_len */
585- entry .offset = entry .offset_abs =
586- sizeof (local ) + entry .header_offset + PHAR_GET_16 (local .filename_len ) + PHAR_GET_16 (local .extra_len );
609+ zend_off_t restore_pos = php_stream_tell (fp );
587610 php_stream_seek (fp , entry .offset , SEEK_SET );
588611 /* these next lines should be for php < 5.2.6 after 5.3 filters are fixed */
589612 fp -> writepos = 0 ;
@@ -679,7 +702,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
679702 }
680703
681704 /* return to central directory parsing */
682- php_stream_seek (fp , saveloc , SEEK_SET );
705+ php_stream_seek (fp , restore_pos , SEEK_SET );
683706 }
684707
685708 phar_set_inode (& entry );
0 commit comments