Skip to content

Commit 78986a6

Browse files
committed
Merge branch 'PHP-8.2' into PHP-8.3
* PHP-8.2: Fix GH-10344: imagettfbbox(): Could not find/open font UNC path Fix GH-13037: PharData incorrectly extracts zip file
2 parents 31e8cea + 4a48729 commit 78986a6

File tree

6 files changed

+102
-19
lines changed

6 files changed

+102
-19
lines changed

NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ PHP NEWS
1818
. Fixed bug GH-12996 (Incorrect SCRIPT_NAME with Apache ProxyPassMatch when
1919
plus in path). (Jakub Zelenka)
2020

21+
- GD:
22+
. Fixed bug GH-10344 (imagettfbbox(): Could not find/open font UNC path).
23+
(nielsdos)
24+
2125
- LibXML:
2226
. Fix crashes with entity references and predefined entities. (nielsdos)
2327

@@ -41,6 +45,7 @@ PHP NEWS
4145

4246
- Phar:
4347
. Fixed bug #71465 (PHAR doesn't know about litespeed). (nielsdos)
48+
. Fixed bug GH-13037 (PharData incorrectly extracts zip file). (nielsdos)
4449

4550
- Random:
4651
. Fixed bug GH-13138 (Randomizer::pickArrayKeys() does not detect broken

ext/gd/libgd/gdft.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,17 @@ static void *fontFetch (char **error, void *key)
401401
#ifdef NETWARE
402402
if (*name == '/' || (name[0] != 0 && strstr(name, ":/"))) {
403403
#else
404-
if (*name == '/' || (name[0] != 0 && name[1] == ':' && (name[2] == '/' || name[2] == '\\'))) {
404+
/* Actual length doesn't matter, just the minimum does up to length 2. */
405+
unsigned int min_length = 0;
406+
if (name[0] != '\0') {
407+
if (name[1] != '\0') {
408+
min_length = 2;
409+
} else {
410+
min_length = 1;
411+
}
412+
}
413+
ZEND_IGNORE_VALUE(min_length); /* On Posix systems this may be unused */
414+
if (IS_ABSOLUTE_PATH(name, min_length)) {
405415
#endif
406416
snprintf(fullname, sizeof(fullname) - 1, "%s", name);
407417
if (access(fullname, R_OK) == 0) {

ext/gd/tests/gh10344.phpt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
GH-10344 (imagettfbbox(): Could not find/open font UNC path)
3+
--EXTENSIONS--
4+
gd
5+
--SKIPIF--
6+
<?php
7+
if (PHP_OS_FAMILY != 'Windows') {
8+
die("skip windows only test");
9+
}
10+
if (!GD_BUNDLED) {
11+
die("skip Not (yet) fixed in libgd itself");
12+
}
13+
if (!function_exists("imagettfbbox")) {
14+
die("skip ttf support unavailable");
15+
}
16+
if (!is_readable("\\\\localhost\\c$\\Windows\\Fonts\\arial.ttf")) {
17+
die("skip font not readable");
18+
}
19+
?>
20+
--FILE--
21+
<?php
22+
23+
$font = '\\\\localhost\\c$\\Windows\\Fonts\\arial.ttf';
24+
var_dump(count(imagettfbbox(10, 0, $font, 'hello')));
25+
26+
?>
27+
--EXPECT--
28+
int(8)

ext/phar/tests/zip/files/gh13037.zip

164 Bytes
Binary file not shown.

ext/phar/tests/zip/gh13037.phpt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
GH-13037 (PharData incorrectly extracts zip file)
3+
--EXTENSIONS--
4+
phar
5+
--FILE--
6+
<?php
7+
$phar = new PharData(__DIR__ . '/files/gh13037.zip');
8+
$phar->extractTo(__DIR__ . '/out_gh13037/');
9+
echo file_get_contents(__DIR__ . '/out_gh13037/test');
10+
?>
11+
--CLEAN--
12+
<?php
13+
@unlink(__DIR__ . '/out_gh13037/test');
14+
@rmdir(__DIR__ . '/out_gh13037');
15+
?>
16+
--EXPECT--
17+
hello

ext/phar/zip.c

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)