Skip to content

Commit b8ff8c0

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

File tree

5 files changed

+97
-19
lines changed

5 files changed

+97
-19
lines changed

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
@@ -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

Comments
 (0)