Skip to content

Commit 383e343

Browse files
committed
Proper fix
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).
1 parent d29a540 commit 383e343

File tree

5 files changed

+36
-82
lines changed

5 files changed

+36
-82
lines changed

ext/phar/phar_object.c

Lines changed: 8 additions & 22 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;
@@ -2298,14 +2287,11 @@ static zend_object *phar_convert_to_other(phar_archive_data *source, int convert
22982287
/* exception already thrown */
22992288
return NULL;
23002289
}
2301-
2302-
if (newentry.fp == NULL) {
2303-
/* entry->fp may be moved to cfp in phar_copy_file_contents
2304-
* and be free'd later in phar_flush_ex
2305-
* If so, zero fp to avoid double free in rshutdown. */
2306-
entry->fp = NULL;
2307-
}
23082290
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;
23092295
newentry.filename = estrndup(newentry.filename, newentry.filename_len);
23102296

23112297
phar_metadata_tracker_clone(&newentry.metadata_tracker);

ext/phar/tests/gh18953.phpt

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,43 @@
11
--TEST--
2-
Phar: Stream double free
2+
GH-18953 (Phar: Stream double free)
33
--EXTENSIONS--
44
phar
55
--INI--
66
phar.readonly=0
7-
--ENV--
8-
USE_ZEND_ALLOC=0
97
--FILE--
108
<?php
119

12-
declare(strict_types=1);
13-
14-
require __DIR__ . '/gh18953/autoload.inc';
15-
16-
// cleaning
17-
@unlink("gh18953.phar");
18-
@unlink("gh18953.phar.gz");
19-
20-
// create a phar
21-
$phar = new Phar("gh18953.phar");
22-
$phar->startBuffering();
23-
// add any dir
10+
$phar = new Phar(__DIR__ . "/gh18953.phar");
11+
$phar->addFromString("file", str_repeat("123", random_int(1, 1)));
2412
$phar->addEmptyDir("dir");
25-
$phar->stopBuffering();
26-
// compress
27-
$phar->compress(Phar::GZ);
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));
2819

29-
// this increases the chance of reproducing the problem
30-
// even with this, it's not always reproducible
31-
$obj1 = new NS1\Class1();
32-
$obj2 = new NS1\Class1();
33-
$obj2 = new NS1\Class1();
34-
$obj2 = new NS1\Class1();
35-
$obj2 = new NS1\Class1();
20+
unset($file);
21+
unset($phar);
3622

37-
echo "Done" . PHP_EOL;
3823
?>
3924
--CLEAN--
4025
<?php
41-
@unlink("gh18953.phar");
42-
@unlink("gh18953.phar.gz");
26+
@unlink(__DIR__ . "/gh18953.phar");
27+
@unlink(__DIR__ . "/gh18953.phar.gz");
4328
?>
44-
--EXPECT--
45-
Done
29+
--EXPECTF--
30+
object(PharFileInfo)#%d (2) {
31+
["pathName":"SplFileInfo":private]=>
32+
string(%d) "%sphar%sdir"
33+
["fileName":"SplFileInfo":private]=>
34+
string(3) "dir"
35+
}
36+
object(PharFileInfo)#%d (2) {
37+
["pathName":"SplFileInfo":private]=>
38+
string(%d) "%sphar.gz%sdir"
39+
["fileName":"SplFileInfo":private]=>
40+
string(3) "dir"
41+
}
42+
string(3) "123"
43+
string(3) "123"

ext/phar/tests/gh18953/autoload.inc

Lines changed: 0 additions & 10 deletions
This file was deleted.

ext/phar/tests/gh18953/src/NS1/Class1.inc

Lines changed: 0 additions & 11 deletions
This file was deleted.

ext/phar/tests/gh18953/src/NS2/Interface1.inc

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)