diff options
Diffstat (limited to 'ext/phar/zip.c')
-rw-r--r-- | ext/phar/zip.c | 196 |
1 files changed, 190 insertions, 6 deletions
diff --git a/ext/phar/zip.c b/ext/phar/zip.c index ee365d0d1..383561ab8 100644 --- a/ext/phar/zip.c +++ b/ext/phar/zip.c @@ -172,6 +172,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, int fname_len, char *alias, phar_archive_data *mydata = NULL; phar_entry_info entry = {0}; char *p = buf, *ext, *actual_alias = NULL; + char *metadata = NULL; size = php_stream_tell(fp); @@ -222,7 +223,6 @@ int phar_parse_zipfile(php_stream *fp, char *fname, int fname_len, char *alias, /* read in archive comment, if any */ if (PHAR_GET_16(locator.comment_len)) { - char *metadata; metadata = p + sizeof(locator); @@ -299,6 +299,30 @@ foundit: entry.is_zip = 1; entry.fp_type = PHAR_FP; entry.is_persistent = mydata->is_persistent; +#define PHAR_ZIP_FAIL_FREE(errmsg, save) \ + zend_hash_destroy(&mydata->manifest); \ + mydata->manifest.arBuckets = 0; \ + zend_hash_destroy(&mydata->mounted_dirs); \ + mydata->mounted_dirs.arBuckets = 0; \ + zend_hash_destroy(&mydata->virtual_dirs); \ + mydata->virtual_dirs.arBuckets = 0; \ + php_stream_close(fp); \ + if (mydata->metadata) { \ + zval_dtor(mydata->metadata); \ + } \ + if (mydata->signature) { \ + efree(mydata->signature); \ + } \ + if (error) { \ + spprintf(error, 4096, "phar error: %s in zip-based phar \"%s\"", errmsg, mydata->fname); \ + } \ + pefree(mydata->fname, mydata->is_persistent); \ + if (mydata->alias) { \ + pefree(mydata->alias, mydata->is_persistent); \ + } \ + pefree(mydata, mydata->is_persistent); \ + efree(save); \ + return FAILURE; #define PHAR_ZIP_FAIL(errmsg) \ zend_hash_destroy(&mydata->manifest); \ mydata->manifest.arBuckets = 0; \ @@ -310,6 +334,9 @@ foundit: if (mydata->metadata) { \ zval_dtor(mydata->metadata); \ } \ + if (mydata->signature) { \ + efree(mydata->signature); \ + } \ if (error) { \ spprintf(error, 4096, "phar error: %s in zip-based phar \"%s\"", errmsg, mydata->fname); \ } \ @@ -323,6 +350,7 @@ foundit: /* add each central directory item to the manifest */ for (i = 0; i < PHAR_GET_16(locator.count); ++i) { phar_zip_central_dir_file zipentry; + off_t beforeus = php_stream_tell(fp); if (sizeof(zipentry) != php_stream_read(fp, (char *) &zipentry, sizeof(zipentry))) { PHAR_ZIP_FAIL("unable to read central directory entry, truncated"); @@ -374,6 +402,58 @@ foundit: entry.is_dir = 0; } + if (entry.filename_len == sizeof(".phar/signature.bin")-1 && !strncmp(entry.filename, ".phar/signature.bin", sizeof(".phar/signature.bin")-1)) { + size_t read; + php_stream *sigfile; + off_t now; + char *sig; + + now = php_stream_tell(fp); + pefree(entry.filename, entry.is_persistent); + sigfile = php_stream_fopen_tmpfile(); + + php_stream_seek(fp, 0, SEEK_SET); + /* copy file contents + local headers and zip comment, if any, to be hashed for signature */ + phar_stream_copy_to_stream(fp, sigfile, entry.header_offset, NULL); + /* seek to central directory */ + php_stream_seek(fp, PHAR_GET_32(locator.cdir_offset), SEEK_SET); + /* copy central directory header */ + phar_stream_copy_to_stream(fp, sigfile, beforeus - PHAR_GET_32(locator.cdir_offset), NULL); + if (metadata) { + php_stream_write(sigfile, metadata, PHAR_GET_16(locator.comment_len)); + } + php_stream_seek(fp, sizeof(phar_zip_file_header) + entry.header_offset + entry.filename_len + PHAR_GET_16(zipentry.extra_len), SEEK_SET); + sig = (char *) emalloc(entry.uncompressed_filesize); + read = php_stream_read(fp, sig, entry.uncompressed_filesize); + if (read != entry.uncompressed_filesize) { + php_stream_close(sigfile); + efree(sig); + PHAR_ZIP_FAIL("signature cannot be read"); + } + mydata->sig_flags = PHAR_GET_32(sig); + if (FAILURE == phar_verify_signature(sigfile, php_stream_tell(sigfile), mydata->sig_flags, sig + 8, entry.uncompressed_filesize - 8, fname, &mydata->signature, &mydata->sig_len, error TSRMLS_CC)) { + efree(sig); + if (error) { + char *save; + php_stream_close(sigfile); + spprintf(&save, 4096, "signature cannot be verified: %s", *error); + efree(*error); + PHAR_ZIP_FAIL_FREE(save, save); + } else { + php_stream_close(sigfile); + PHAR_ZIP_FAIL("signature cannot be verified"); + } + } + php_stream_close(sigfile); + efree(sig); + /* signature checked out, let's ensure this is the last file in the phar */ + if (i != PHAR_GET_16(locator.count) - 1) { + PHAR_ZIP_FAIL("entries exist after signature, invalid phar"); + } + + continue; + } + phar_add_virtual_dirs(mydata, entry.filename, entry.filename_len TSRMLS_CC); if (PHAR_GET_16(zipentry.extra_len)) { @@ -520,7 +600,11 @@ foundit: php_stream_filter_append(&fp->readfilters, filter); +#if PHP_MAJOR_VERSION >= 6 + if (!(entry.uncompressed_filesize = php_stream_copy_to_mem(fp, (void **) &actual_alias, entry.uncompressed_filesize, 0)) || !actual_alias) { +#else if (!(entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)) || !actual_alias) { +#endif pefree(entry.filename, entry.is_persistent); #if PHP_VERSION_ID < 50207 PHAR_ZIP_FAIL("unable to read in alias, truncated (PHP 5.2.7 and newer has a potential fix for this problem)"); @@ -541,7 +625,11 @@ foundit: php_stream_filter_append(&fp->readfilters, filter); +#if PHP_MAJOR_VERSION >= 6 + if (!(entry.uncompressed_filesize = php_stream_copy_to_mem(fp, (void **) &actual_alias, entry.uncompressed_filesize, 0)) || !actual_alias) { +#else if (!(entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)) || !actual_alias) { +#endif pefree(entry.filename, entry.is_persistent); #if PHP_VERSION_ID < 50207 PHAR_ZIP_FAIL("unable to read in alias, truncated (PHP 5.2.7 and newer has a potential fix for this problem)"); @@ -552,7 +640,11 @@ foundit: php_stream_filter_flush(filter, 1); php_stream_filter_remove(filter, 1 TSRMLS_CC); } else { +#if PHP_MAJOR_VERSION >= 6 + if (!(entry.uncompressed_filesize = php_stream_copy_to_mem(fp, (void **) &actual_alias, entry.uncompressed_filesize, 0)) || !actual_alias) { +#else if (!(entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)) || !actual_alias) { +#endif pefree(entry.filename, entry.is_persistent); PHAR_ZIP_FAIL("unable to read in alias, truncated"); } @@ -1000,6 +1092,76 @@ continue_dir: } /* }}} */ +static int phar_zip_applysignature(phar_archive_data *phar, struct _phar_zip_pass *pass, + smart_str *metadata TSRMLS_DC) /* {{{ */ +{ + /* add signature for executable tars or tars explicitly set with setSignatureAlgorithm */ + if (!phar->is_data || phar->sig_flags) { + int signature_length; + char *signature, sigbuf[8]; + phar_entry_info entry = {0}; + php_stream *newfile; + off_t tell, st; + + newfile = php_stream_fopen_tmpfile(); + st = tell = php_stream_tell(pass->filefp); + /* copy the local files, central directory, and the zip comment to generate the hash */ + php_stream_seek(pass->filefp, 0, SEEK_SET); + phar_stream_copy_to_stream(pass->filefp, newfile, tell, NULL); + tell = php_stream_tell(pass->centralfp); + php_stream_seek(pass->centralfp, 0, SEEK_SET); + phar_stream_copy_to_stream(pass->centralfp, newfile, tell, NULL); + if (metadata->c) { + php_stream_write(newfile, metadata->c, metadata->len); + } + + if (FAILURE == phar_create_signature(phar, newfile, &signature, &signature_length, pass->error TSRMLS_CC)) { + if (pass->error) { + char *save = *(pass->error); + spprintf(pass->error, 0, "phar error: unable to write signature to zip-based phar: %s", save); + efree(save); + } + + php_stream_close(newfile); + return FAILURE; + } + + entry.filename = ".phar/signature.bin"; + entry.filename_len = sizeof(".phar/signature.bin")-1; + entry.fp = php_stream_fopen_tmpfile(); + entry.fp_type = PHAR_MOD; + entry.is_modified = 1; + + PHAR_SET_32(sigbuf, phar->sig_flags); + PHAR_SET_32(sigbuf + 4, signature_length); + + if (8 != (int)php_stream_write(entry.fp, sigbuf, 8) || signature_length != (int)php_stream_write(entry.fp, signature, signature_length)) { + efree(signature); + if (pass->error) { + spprintf(pass->error, 0, "phar error: unable to write signature to zip-based phar %s", phar->fname); + } + + php_stream_close(newfile); + return FAILURE; + } + + efree(signature); + entry.uncompressed_filesize = entry.compressed_filesize = signature_length + 8; + entry.phar = phar; + /* throw out return value and write the signature */ + phar_zip_changed_apply((void *)&entry, (void *)pass TSRMLS_CC); + php_stream_close(newfile); + + if (pass->error && *(pass->error)) { + /* error is set by writeheaders */ + php_stream_close(newfile); + return FAILURE; + } + } /* signature */ + return SUCCESS; +} +/* }}} */ + int phar_zip_flush(phar_archive_data *phar, char *user_stub, long len, int defaultstub, char **error TSRMLS_DC) /* {{{ */ { char *pos; @@ -1084,7 +1246,11 @@ int phar_zip_flush(phar_archive_data *phar, char *user_stub, long len, int defau user_stub = 0; +#if PHP_MAJOR_VERSION >= 6 + if (!(len = php_stream_copy_to_mem(stubfile, (void **) &user_stub, len, 0)) || !user_stub) { +#else if (!(len = php_stream_copy_to_mem(stubfile, &user_stub, len, 0)) || !user_stub) { +#endif if (error) { spprintf(error, 0, "unable to read resource to copy stub to new zip-based phar \"%s\"", phar->fname); } @@ -1214,10 +1380,24 @@ fperror: memset(&eocd, 0, sizeof(eocd)); strncpy(eocd.signature, "PK\5\6", 4); - PHAR_SET_16(eocd.counthere, zend_hash_num_elements(&phar->manifest)); - PHAR_SET_16(eocd.count, zend_hash_num_elements(&phar->manifest)); + if (!phar->is_data && !phar->sig_flags) { + phar->sig_flags = PHAR_SIG_SHA1; + } + if (phar->sig_flags) { + PHAR_SET_16(eocd.counthere, zend_hash_num_elements(&phar->manifest) + 1); + PHAR_SET_16(eocd.count, zend_hash_num_elements(&phar->manifest) + 1); + } else { + PHAR_SET_16(eocd.counthere, zend_hash_num_elements(&phar->manifest)); + PHAR_SET_16(eocd.count, zend_hash_num_elements(&phar->manifest)); + } zend_hash_apply_with_argument(&phar->manifest, phar_zip_changed_apply, (void *) &pass TSRMLS_CC); + if (phar->metadata) { + /* set phar metadata */ + PHP_VAR_SERIALIZE_INIT(metadata_hash); + php_var_serialize(&main_metadata_str, &phar->metadata, &metadata_hash TSRMLS_CC); + PHP_VAR_SERIALIZE_DESTROY(metadata_hash); + } if (temperr) { if (error) { spprintf(error, 4096, "phar zip flush of \"%s\" failed: %s", phar->fname, temperr); @@ -1226,6 +1406,9 @@ fperror: temperror: php_stream_close(pass.centralfp); nocentralerror: + if (phar->metadata) { + smart_str_free(&main_metadata_str); + } php_stream_close(pass.filefp); if (closeoldfile) { php_stream_close(oldfile); @@ -1233,6 +1416,10 @@ nocentralerror: return EOF; } + if (FAILURE == phar_zip_applysignature(phar, &pass, &main_metadata_str TSRMLS_CC)) { + goto temperror; + } + /* save zip */ cdir_size = php_stream_tell(pass.centralfp); cdir_offset = php_stream_tell(pass.filefp); @@ -1255,9 +1442,6 @@ nocentralerror: if (phar->metadata) { /* set phar metadata */ - PHP_VAR_SERIALIZE_INIT(metadata_hash); - php_var_serialize(&main_metadata_str, &phar->metadata, &metadata_hash TSRMLS_CC); - PHP_VAR_SERIALIZE_DESTROY(metadata_hash); PHAR_SET_16(eocd.comment_len, main_metadata_str.len); if (sizeof(eocd) != php_stream_write(pass.filefp, (char *)&eocd, sizeof(eocd))) { |