Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ext/phar/phar.c
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,14 @@ static zend_result phar_parse_pharfile(php_stream *fp, char *fname, size_t fname

sig_ptr = sig_buf;
PHAR_GET_32(sig_ptr, signature_len);
if (signature_len > 1024 * 1024) {
efree(savebuf);
php_stream_close(fp);
if (error) {
spprintf(error, 0, "phar \"%s\" openssl signature length is too large", fname);
}
return FAILURE;
}
sig = (char *) emalloc(signature_len);
whence = signature_len + 4;
whence = -whence;
Expand Down
12 changes: 7 additions & 5 deletions ext/phar/phar_object.c
Original file line number Diff line number Diff line change
Expand Up @@ -3591,6 +3591,11 @@ PHP_METHOD(Phar, offsetGet)
if (!(entry = phar_get_entry_info_dir(phar_obj->archive, ZSTR_VAL(file_name), ZSTR_LEN(file_name), 1, &error, 0))) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist%s%s", ZSTR_VAL(file_name), error?", ":"", error?error:"");
} else {
if (entry->is_temp_dir) {
efree(entry->filename);
efree(entry);
}

if (zend_string_equals_literal(file_name, ".phar/stub.php")) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot get stub \".phar/stub.php\" directly in phar \"%s\", use getStub", phar_obj->archive->fname);
RETURN_THROWS();
Expand All @@ -3606,11 +3611,6 @@ PHP_METHOD(Phar, offsetGet)
RETURN_THROWS();
}

if (entry->is_temp_dir) {
efree(entry->filename);
efree(entry);
}

zend_string *sfname = strpprintf(0, "phar://%s/%s", phar_obj->archive->fname, ZSTR_VAL(file_name));
zval zfname;
ZVAL_NEW_STR(&zfname, sfname);
Expand Down Expand Up @@ -3681,11 +3681,13 @@ static void phar_add_file(phar_archive_data **pphar, zend_string *file_name, con
size_t written_len = php_stream_write(data->fp, ZSTR_VAL(content), ZSTR_LEN(content));
if (written_len != contents_len) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s could not be written to", filename);
phar_entry_delref(data);
goto finish;
}
} else {
if (!(php_stream_from_zval_no_verify(contents_file, zresource))) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s could not be written to", filename);
phar_entry_delref(data);
goto finish;
}
php_stream_copy_to_stream_ex(contents_file, data->fp, PHP_STREAM_COPY_ALL, &contents_len);
Expand Down
29 changes: 29 additions & 0 deletions ext/phar/tests/gh21798-add-file-delref.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
GH-21798: phar_add_file must call phar_entry_delref on write error paths
--EXTENSIONS--
phar
--INI--
phar.readonly=0
phar.require_hash=0
--FILE--
<?php
// Regression baseline: verify addFile and addFromString succeed and
// phar_entry_delref is not skipped on the happy path.
$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar';

$phar = new Phar($fname);
$phar->addFromString('hello.txt', 'hello world');
$phar->addFromString('empty.txt', '');
unset($phar);

$phar = new Phar($fname);
echo $phar['hello.txt']->getContent() . "\n";
echo ($phar->offsetExists('empty.txt') ? 'empty exists' : 'missing') . "\n";
echo "no crash\n";
?>
--CLEAN--
<?php @unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar'); ?>
--EXPECT--
hello world
empty exists
no crash
39 changes: 39 additions & 0 deletions ext/phar/tests/gh21798-offsetget-temp-entry.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
--TEST--
GH-21798: Phar::offsetGet() must free is_temp_dir entry before rejecting .phar/* paths
--EXTENSIONS--
phar
--INI--
phar.readonly=0
phar.require_hash=0
--FILE--
<?php
$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar';
$phar = new Phar($fname);
$phar->addFromString('index.php', '<?php echo "ok"; ?>');
unset($phar);

$phar = new Phar($fname);
try {
$phar->offsetGet('.phar/stub.php');
} catch (BadMethodCallException $e) {
echo $e->getMessage() . "\n";
}
try {
$phar->offsetGet('.phar/alias.txt');
} catch (BadMethodCallException $e) {
echo $e->getMessage() . "\n";
}
try {
$phar->offsetGet('.phar/internal');
} catch (BadMethodCallException $e) {
echo $e->getMessage() . "\n";
}
echo "no crash\n";
?>
--CLEAN--
<?php @unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar'); ?>
--EXPECT--
Entry .phar/stub.php does not exist
Entry .phar/alias.txt does not exist
Entry .phar/internal does not exist
no crash
39 changes: 39 additions & 0 deletions ext/phar/tests/gh21798-sig-len-cap.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
--TEST--
GH-21798: phar rejects oversized OpenSSL signature length field
--EXTENSIONS--
phar
--SKIPIF--
<?php
if (!in_array("OpenSSL", Phar::getSupportedSignatures())) die("skip openssl support required");
?>
--INI--
phar.require_hash=0
phar.readonly=0
--FILE--
<?php
// The openssl phar signature layout (end of file):
// [sig bytes: signature_len bytes]
// [signature_len: 4 bytes LE] <- offset -12 from EOF
// [sig_flags: 4 bytes] <- offset -8
// ["GBMB": 4 bytes magic] <- offset -4
$src = __DIR__ . '/files/openssl.phar';
$tmp = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar';
copy($src, $tmp);

$fp = fopen($tmp, 'r+b');
fseek($fp, -12, SEEK_END);
fwrite($fp, pack('V', 0x00200001)); // 2 MiB + 1 — exceeds 1 MiB cap
fclose($fp);

try {
$phar = new Phar($tmp);
} catch (UnexpectedValueException $e) {
echo $e->getMessage() . "\n";
}
echo "done\n";
?>
--CLEAN--
<?php @unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar'); ?>
--EXPECTF--
phar "%s" openssl signature length is too large
done
Loading