You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Extracts the zip file with invisible characters such as \x14 and other control characters in a few of HUNDREDS of files.
Files that are corrupted when extracted:
Files test/wordpress/wp-includes/blocks/site-tagline/editor-rtl.min.css and test2/wordpress/wp-includes/blocks/site-tagline/editor-rtl.min.css differ
Files test/wordpress/wp-includes/blocks/site-tagline/editor.min.css and test2/wordpress/wp-includes/blocks/site-tagline/editor.min.css differ
Files test/wordpress/wp-includes/blocks/site-title/style-rtl.css and test2/wordpress/wp-includes/blocks/site-title/style-rtl.css differ
Files test/wordpress/wp-includes/blocks/site-title/style-rtl.min.css and test2/wordpress/wp-includes/blocks/site-title/style-rtl.min.css differ
Files test/wordpress/wp-includes/blocks/site-title/style.css and test2/wordpress/wp-includes/blocks/site-title/style.css differ
Files test/wordpress/wp-includes/blocks/site-title/style.min.css and test2/wordpress/wp-includes/blocks/site-title/style.min.css differ
Files test/wordpress/wp-includes/blocks/spacer/style-rtl.css and test2/wordpress/wp-includes/blocks/spacer/style-rtl.css differ
Files test/wordpress/wp-includes/blocks/spacer/style-rtl.min.css and test2/wordpress/wp-includes/blocks/spacer/style-rtl.min.css differ
The corruption only seems to happen when the compression flags of the file entry are zero, i.e. no compression is used.
What's also interesting is that it seems to read 4 bytes prior to the file, which explains why we see 4 bytes of junk at the front and 4 bytes missing at the end.
Not sure yet what's going on exactly, but I'm digging into it.
EDIT: looks like the zip file entry offset is maybe not set up properly, the entry.offset variable set up at phar_parse_zipfile seems wrong, it points to offset 12398848 for vendor/joypixels/emoji-toolkit/joypixels.json, but that's indeed 4 bytes prior to the file start.
The difference of 4 bytes comes from an inconsistency in the extra field length in the sample zip files.
In the central directory entry for the file, this is 24 bytes, but in the local header this is 28 bytes. A difference of 4.
libzip seems to use that of the local header (confirmed by compiling and debugging libzip), hence it reads at the correct offset. This has probably always been done incorrectly in phar.
Immediately following the local header for a file SHOULD be placed the compressed or stored data for the file.
Note that it does not mention consistency between the local directory and central directory.
The reason you have to add 4 to the offset in your example is because the size of the extra data field in central directory of this entry (= file header) is different than the one in local file header, and it is perfectly legal as per zip specification to have different extra data field sizes in central directory and local header. In fact the extra data field we are talking about, Extended Timestamp extra field (signature 0x5455), has an official definition which has varied lengths between the two.
And indeed that's the case here too! What an amazing file format design!
Here's a very quick and dirty proof of concept patch:
Activity
[-]PharData incorrectly extracts zip file[/-][+]```PharData``` incorrectly extracts zip file[/+]ndossche commentedon Dec 27, 2023
The corruption only seems to happen when the compression flags of the file entry are zero, i.e. no compression is used.
What's also interesting is that it seems to read 4 bytes prior to the file, which explains why we see 4 bytes of junk at the front and 4 bytes missing at the end.
Not sure yet what's going on exactly, but I'm digging into it.
EDIT: looks like the zip file entry offset is maybe not set up properly, the
entry.offsetvariable set up atphar_parse_zipfileseems wrong, it points to offset 12398848 forvendor/joypixels/emoji-toolkit/joypixels.json,but that's indeed 4 bytes prior to the file start.ndossche commentedon Dec 28, 2023
The difference of 4 bytes comes from an inconsistency in the extra field length in the sample zip files.
In the central directory entry for the file, this is 24 bytes, but in the local header this is 28 bytes. A difference of 4.
libzip seems to use that of the local header (confirmed by compiling and debugging libzip), hence it reads at the correct offset. This has probably always been done incorrectly in phar.
According to https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
Furthermore, upon finding this I googled a bit further and found this: https://stackoverflow.com/questions/58702783/finding-start-of-compressed-data-for-items-in-a-zip-with-zip4j
And indeed that's the case here too! What an amazing file format design!
Here's a very quick and dirty proof of concept patch:
I'll write up a proper patch and testcase tomorrow, it's late.
Fix phpGH-13037: PharData incorrectly extracts zip file
Merge branch 'PHP-8.2' into PHP-8.3
Merge branch 'PHP-8.3'
Fix GH-13037: PharData incorrectly extracts zip file