Skip to content

Commit 1bb2a4f

Browse files
committed
Fix #53467: Phar cannot compress large archives
When Phars are flushed, a new temporary file is created for each entry which should be compressed, and the `compressed_filesize` is retrieved. Afterwards, the Phar manifest is written, and only after that the files are copied to the actual Phar. So for each such entry there is an open temp file, what easily exceeds the limit. Therefore, we use a single temporary file for all entries, and store the start offset in the otherwise unused `header_offset` member. We ensure that the `cfp` members are properly set to NULL even if flushing fails, to avoid use after free scenarios. This solution is based on a suggestion by @lserni[1]. Closes GH-6643. [1] <box-project/box2#80 (comment)>
1 parent 3d09626 commit 1bb2a4f

File tree

2 files changed

+42
-17
lines changed

2 files changed

+42
-17
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ PHP NEWS
99
. Fixed bug #75850 (Unclear error message wrt. __halt_compiler() w/o
1010
semicolon) (cmb)
1111
. Fixed bug #70091 (Phar does not mark UTF-8 filenames in ZIP archives). (cmb)
12+
. Fixed bug #53467 (Phar cannot compress large archives). (cmb, lserni)
1213

1314
- Standard:
1415
. Fixed bug #80654 (file_get_contents() maxlen fails above (2**31)-1 bytes).

ext/phar/phar.c

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,6 +2508,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
25082508
smart_str main_metadata_str = {0};
25092509
int free_user_stub, free_fp = 1, free_ufp = 1;
25102510
int manifest_hack = 0;
2511+
php_stream *shared_cfp = NULL;
25112512

25122513
if (phar->is_persistent) {
25132514
if (error) {
@@ -2788,10 +2789,13 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
27882789
return EOF;
27892790
}
27902791

2791-
/* create new file that holds the compressed version */
2792+
/* create new file that holds the compressed versions */
27922793
/* work around inability to specify freedom in write and strictness
27932794
in read count */
2794-
entry->cfp = php_stream_fopen_tmpfile();
2795+
if (shared_cfp == NULL) {
2796+
shared_cfp = php_stream_fopen_tmpfile();
2797+
}
2798+
entry->cfp = shared_cfp;
27952799
if (!entry->cfp) {
27962800
if (error) {
27972801
spprintf(error, 0, "unable to create temporary file");
@@ -2800,8 +2804,11 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
28002804
php_stream_close(oldfile);
28012805
}
28022806
php_stream_close(newfile);
2803-
return EOF;
2807+
goto cleanup;
28042808
}
2809+
/* for real phars, header_offset is unused; we misuse it here to store the offset in the temp file */
2810+
ZEND_ASSERT(entry->header_offset == 0);
2811+
entry->header_offset = php_stream_tell(entry->cfp);
28052812
php_stream_flush(file);
28062813
if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
28072814
if (closeoldfile) {
@@ -2811,7 +2818,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
28112818
if (error) {
28122819
spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
28132820
}
2814-
return EOF;
2821+
goto cleanup;
28152822
}
28162823
php_stream_filter_append((&entry->cfp->writefilters), filter);
28172824
if (SUCCESS != php_stream_copy_to_stream_ex(file, entry->cfp, entry->uncompressed_filesize, NULL)) {
@@ -2822,15 +2829,14 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
28222829
if (error) {
28232830
spprintf(error, 0, "unable to copy compressed file contents of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
28242831
}
2825-
return EOF;
2832+
goto cleanup;
28262833
}
28272834
php_stream_filter_flush(filter, 1);
28282835
php_stream_flush(entry->cfp);
28292836
php_stream_filter_remove(filter, 1);
28302837
php_stream_seek(entry->cfp, 0, SEEK_END);
2831-
entry->compressed_filesize = (uint32_t) php_stream_tell(entry->cfp);
2838+
entry->compressed_filesize = ((uint32_t) php_stream_tell(entry->cfp)) - entry->header_offset;
28322839
/* generate crc on compressed file */
2833-
php_stream_rewind(entry->cfp);
28342840
entry->old_flags = entry->flags;
28352841
entry->is_modified = 1;
28362842
global_flags |= (entry->flags & PHAR_ENT_COMPRESSION_MASK);
@@ -2886,7 +2892,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
28862892
spprintf(error, 0, "unable to write manifest header of new phar \"%s\"", phar->fname);
28872893
}
28882894

2889-
return EOF;
2895+
goto cleanup;
28902896
}
28912897

28922898
phar->alias_len = restore_alias_len;
@@ -2907,7 +2913,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
29072913
spprintf(error, 0, "unable to write manifest meta-data of new phar \"%s\"", phar->fname);
29082914
}
29092915

2910-
return EOF;
2916+
goto cleanup;
29112917
}
29122918
smart_str_free(&main_metadata_str);
29132919

@@ -2942,7 +2948,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
29422948
spprintf(error, 0, "unable to write filename of file \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname);
29432949
}
29442950
}
2945-
return EOF;
2951+
goto cleanup;
29462952
}
29472953

29482954
/* set the manifest meta-data:
@@ -2975,7 +2981,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
29752981
spprintf(error, 0, "unable to write temporary manifest of file \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname);
29762982
}
29772983

2978-
return EOF;
2984+
goto cleanup;
29792985
}
29802986
} ZEND_HASH_FOREACH_END();
29812987
/* Hack - see bug #65028, add padding byte to the end of the manifest */
@@ -2991,7 +2997,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
29912997
spprintf(error, 0, "unable to write manifest padding byte");
29922998
}
29932999

2994-
return EOF;
3000+
goto cleanup;
29953001
}
29963002
}
29973003

@@ -3004,7 +3010,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
30043010

30053011
if (entry->cfp) {
30063012
file = entry->cfp;
3007-
php_stream_rewind(file);
3013+
php_stream_seek(file, entry->header_offset, SEEK_SET);
30083014
} else {
30093015
file = phar_get_efp(entry, 0);
30103016
if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
@@ -3015,7 +3021,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
30153021
if (error) {
30163022
spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
30173023
}
3018-
return EOF;
3024+
goto cleanup;
30193025
}
30203026
}
30213027

@@ -3027,7 +3033,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
30273033
if (error) {
30283034
spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
30293035
}
3030-
return EOF;
3036+
goto cleanup;
30313037
}
30323038

30333039
/* this will have changed for all files that have either changed compression or been modified */
@@ -3044,14 +3050,14 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
30443050
spprintf(error, 0, "unable to write contents of file \"%s\" to new phar \"%s\"", entry->filename, phar->fname);
30453051
}
30463052

3047-
return EOF;
3053+
goto cleanup;
30483054
}
30493055

30503056
entry->is_modified = 0;
30513057

30523058
if (entry->cfp) {
3053-
php_stream_close(entry->cfp);
30543059
entry->cfp = NULL;
3060+
entry->header_offset = 0;
30553061
}
30563062

30573063
if (entry->fp_type == PHAR_MOD) {
@@ -3067,6 +3073,11 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
30673073
}
30683074
} ZEND_HASH_FOREACH_END();
30693075

3076+
if (shared_cfp != NULL) {
3077+
php_stream_close(shared_cfp);
3078+
shared_cfp = NULL;
3079+
}
3080+
30703081
/* append signature */
30713082
if (global_flags & PHAR_HDR_SIGNATURE) {
30723083
char sig_buf[4];
@@ -3196,6 +3207,19 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv
31963207
return EOF;
31973208
}
31983209

3210+
return EOF;
3211+
3212+
cleanup:
3213+
if (shared_cfp != NULL) {
3214+
php_stream_close(shared_cfp);
3215+
}
3216+
ZEND_HASH_FOREACH_PTR(&phar->manifest, entry) {
3217+
if (entry->cfp) {
3218+
entry->cfp = NULL;
3219+
entry->header_offset = 0;
3220+
}
3221+
} ZEND_HASH_FOREACH_END();
3222+
31993223
return EOF;
32003224
}
32013225
/* }}} */

0 commit comments

Comments
 (0)