Skip to content

Commit 449361a

Browse files
committed
Fix GH-21333: use-after-free when unlinking entries during iteration of a compressed phar.
close GH-21334
1 parent d08d80c commit 449361a

File tree

6 files changed

+50
-3
lines changed

6 files changed

+50
-3
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ PHP NEWS
1313
. Fixed re-entrancy issue on php_pcre_match_impl, php_pcre_replace_impl,
1414
php_pcre_split_impl, and php_pcre_grep_impl. (David Carlier)
1515

16+
- Phar:
17+
. Fixed bug GH-21333 (use after free when unlinking entries during iteration
18+
of a compressed phar). (David Carlier)
19+
1620
- SNMP:
1721
. Fixed bug GH-21336 (SNMP::setSecurity() undefined behavior with
1822
NULL arguments). (David Carlier)

ext/phar/phar.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2461,7 +2461,7 @@ static int phar_flush_clean_deleted_apply(zval *zv) /* {{{ */
24612461
{
24622462
phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv);
24632463

2464-
if (entry->fp_refcount <= 0 && entry->is_deleted) {
2464+
if (entry->is_deleted && phar_entry_can_remove(entry)) {
24652465
return ZEND_HASH_APPLY_REMOVE;
24662466
} else {
24672467
return ZEND_HASH_APPLY_KEEP;

ext/phar/phar_internal.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,11 @@ static inline void phar_set_inode(phar_entry_info *entry) /* {{{ */
400400
}
401401
/* }}} */
402402

403+
static inline bool phar_entry_can_remove(phar_entry_info *entry)
404+
{
405+
return entry->fp_refcount == 0 && entry->fileinfo_lock_count == 0;
406+
}
407+
403408
void phar_request_initialize(void);
404409

405410
void phar_object_init(void);

ext/phar/tar.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ static int phar_tar_writeheaders_int(phar_entry_info *entry, void *argument) /*
730730
}
731731

732732
if (entry->is_deleted) {
733-
if (entry->fp_refcount <= 0 && entry->fileinfo_lock_count == 0) {
733+
if (phar_entry_can_remove(entry)) {
734734
return ZEND_HASH_APPLY_REMOVE;
735735
} else {
736736
/* we can't delete this in-memory until it is closed */

ext/phar/tests/gh21333.phpt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
GH-21333 (UAF when unlinking entries during iteration of a compressed phar)
3+
--CREDITS--
4+
YuanchengJiang
5+
--EXTENSIONS--
6+
phar
7+
zlib
8+
--INI--
9+
phar.readonly=0
10+
--FILE--
11+
<?php
12+
$phar_path = __DIR__ . "/gh21333.phar";
13+
$phar = new Phar($phar_path);
14+
$phar->addFromString("file", "initial_content");
15+
$phar->addEmptyDir("dir");
16+
17+
$phar2 = $phar->compress(Phar::GZ);
18+
19+
$tmp_src = __DIR__ . "/gh21333.tmp";
20+
file_put_contents($tmp_src, str_repeat("A", 100));
21+
22+
foreach ($phar2 as $item) {
23+
@copy($tmp_src, $item);
24+
@unlink($item);
25+
}
26+
27+
$garbage = get_defined_vars();
28+
29+
echo "Done\n";
30+
?>
31+
--CLEAN--
32+
<?php
33+
@unlink(__DIR__ . "/gh21333.phar");
34+
@unlink(__DIR__ . "/gh21333.phar.gz");
35+
@unlink(__DIR__ . "/gh21333.tmp");
36+
?>
37+
--EXPECT--
38+
Done

ext/phar/zip.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,7 @@ static int phar_zip_changed_apply_int(phar_entry_info *entry, void *arg) /* {{{
865865
}
866866

867867
if (entry->is_deleted) {
868-
if (entry->fp_refcount <= 0 && entry->fileinfo_lock_count == 0) {
868+
if (phar_entry_can_remove(entry)) {
869869
return ZEND_HASH_APPLY_REMOVE;
870870
} else {
871871
/* we can't delete this in-memory until it is closed */

0 commit comments

Comments
 (0)