Skip to content

Commit 32344c4

Browse files
nielsdosdixyes
andcommitted
Fix stream double free in phar
The copy function does two things wrong: - The error recovery logic is a hack that temporarily moves the fp pointer to cfp, even though it's not compressed. The respective error recovery it talks about is not present in the code, nor is it necessary. This is the direct cause of the double free in the original reproducer. Fixing this makes it crash in another location though. - The link following logic is inconsistent and illogical. It cannot be a link at this point. The root cause, after fixing the above issues, is that the file pointers are not reset properly for the copy. The file pointer need to be the original ones to perform the copy from the right source, but after that they need to be set properly to NULL (because fp_type == PHAR_FP). Closes GH-19035. Co-authored-by: Yun Dou <[email protected]>
1 parent 4aac98f commit 32344c4

File tree

3 files changed

+51
-15
lines changed

3 files changed

+51
-15
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ PHP NEWS
3737
. Fixed bug GH-18958 (Fatal error during shutdown after pcntl_rfork() or
3838
pcntl_forkx() with zend-max-execution-timers). (Arnaud)
3939

40+
- Phar:
41+
. Fix stream double free in phar. (nielsdos, dixyes)
42+
4043
- SOAP:
4144
. Fixed bug GH-18990, bug #81029, bug #47314 (SOAP HTTP socket not closing
4245
on object destruction). (nielsdos)

ext/phar/phar_object.c

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,7 +1926,8 @@ static int phar_copy_file_contents(phar_entry_info *entry, php_stream *fp) /* {{
19261926
{
19271927
char *error;
19281928
zend_off_t offset;
1929-
phar_entry_info *link;
1929+
1930+
ZEND_ASSERT(!entry->link);
19301931

19311932
if (FAILURE == phar_open_entry_fp(entry, &error, 1)) {
19321933
if (error) {
@@ -1941,26 +1942,14 @@ static int phar_copy_file_contents(phar_entry_info *entry, php_stream *fp) /* {{
19411942
}
19421943

19431944
/* copy old contents in entirety */
1944-
phar_seek_efp(entry, 0, SEEK_SET, 0, 1);
1945+
phar_seek_efp(entry, 0, SEEK_SET, 0, 0);
19451946
offset = php_stream_tell(fp);
1946-
link = phar_get_link_source(entry);
1947-
1948-
if (!link) {
1949-
link = entry;
1950-
}
1951-
1952-
if (SUCCESS != php_stream_copy_to_stream_ex(phar_get_efp(link, 0), fp, link->uncompressed_filesize, NULL)) {
1947+
if (SUCCESS != php_stream_copy_to_stream_ex(phar_get_efp(entry, 0), fp, entry->uncompressed_filesize, NULL)) {
19531948
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0,
19541949
"Cannot convert phar archive \"%s\", unable to copy entry \"%s\" contents", entry->phar->fname, entry->filename);
19551950
return FAILURE;
19561951
}
19571952

1958-
if (entry->fp_type == PHAR_MOD) {
1959-
/* save for potential restore on error */
1960-
entry->cfp = entry->fp;
1961-
entry->fp = NULL;
1962-
}
1963-
19641953
/* set new location of file contents */
19651954
entry->fp_type = PHAR_FP;
19661955
entry->offset = offset;
@@ -2299,6 +2288,10 @@ static zend_object *phar_convert_to_other(phar_archive_data *source, int convert
22992288
return NULL;
23002289
}
23012290
no_copy:
2291+
/* Reset file pointers, they have to be reset here such that if a copy happens the original
2292+
* source fp can be accessed. */
2293+
newentry.fp = NULL;
2294+
newentry.cfp = NULL;
23022295
newentry.filename = estrndup(newentry.filename, newentry.filename_len);
23032296

23042297
phar_metadata_tracker_clone(&newentry.metadata_tracker);

ext/phar/tests/gh18953.phpt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
GH-18953 (Phar: Stream double free)
3+
--EXTENSIONS--
4+
phar
5+
--INI--
6+
phar.readonly=0
7+
--FILE--
8+
<?php
9+
10+
$phar = new Phar(__DIR__ . "/gh18953.phar");
11+
$phar->addFromString("file", str_repeat("123", random_int(1, 1)));
12+
$phar->addEmptyDir("dir");
13+
$phar2 = $phar->compress(Phar::GZ);
14+
15+
var_dump($phar["dir"]);
16+
var_dump($phar2["dir"]);
17+
var_dump($phar["file"]->openFile()->fread(100));
18+
var_dump($phar2["file"]->openFile()->fread(100));
19+
20+
?>
21+
--CLEAN--
22+
<?php
23+
@unlink(__DIR__ . "/gh18953.phar");
24+
@unlink(__DIR__ . "/gh18953.phar.gz");
25+
?>
26+
--EXPECTF--
27+
object(PharFileInfo)#%d (2) {
28+
["pathName":"SplFileInfo":private]=>
29+
string(%d) "%sphar%sdir"
30+
["fileName":"SplFileInfo":private]=>
31+
string(3) "dir"
32+
}
33+
object(PharFileInfo)#%d (2) {
34+
["pathName":"SplFileInfo":private]=>
35+
string(%d) "%sphar.gz%sdir"
36+
["fileName":"SplFileInfo":private]=>
37+
string(3) "dir"
38+
}
39+
string(3) "123"
40+
string(3) "123"

0 commit comments

Comments
 (0)