Open
Description
Description
The following code:
<?php
$a = new Phar(__DIR__ . '/a.phar');
$a->addFromString(
'foo.php',
<<<'EOT'
<?php
echo "this is foo.php\n";
EOT
);
$a->addFromString(
'bar.php',
<<<'EOT'
<?php
echo "this is bar.php\n";
EOT
);
$a->setStub(
<<<'EOT'
<?php
Phar::mapPhar('a.phar');
include 'phar://a.phar/foo.php';
rename('b.phar', 'a.phar');
include 'phar://a.phar/bar.php';
__HALT_COMPILER(); ?>
EOT
);
$a = new Phar(__DIR__ . '/b.phar');
$a->addFromString(
'bar.php',
<<<'EOT'
<?php
/* Shift the offsets compared to a.phar. */
echo "this is bar.php\n";
EOT
);
$a->addFromString(
'foo.php',
<<<'EOT'
<?php
echo "this is foo.php\n";
EOT
);
$a->setStub(
<<<'EOT'
<?php
Phar::mapPhar('b.phar');
include 'phar://b.phar/foo.php';
rename('a.phar', 'b.phar');
include 'phar://b.phar/bar.php';
__HALT_COMPILER(); ?>
EOT
);
Running php a.phar
resulted in this output:
this is foo.php
PHP Parse error: Unterminated comment starting line 2 in phar:///*redacted*/a.phar/bar.php on line 2
But I expected this output instead:
this is foo.php
this is bar.php
The reason for this appears to be that the file offsets and lengths are cached during the mapPhar()
, but each time the phar is opened with phar://X.phar
it is reopened from disk without validating that it's still the same phar / instead of reopening it from a cached file descriptor.
openat(AT_FDCWD, "*redacted*/a.phar", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0664, st_size=343, ...}) = 0
lseek(4, 0, SEEK_CUR) = 0
lseek(4, 0, SEEK_SET) = 0
read(4, "<?php\nPhar::mapPhar('a.phar');\n\n"..., 8192) = 343
read(4, "", 8192) = 0
lseek(4, 144, SEEK_SET) = 144
read(4, " ?>\r\nX\0\0\0\2\0\0\0\21\0\0\0\1\0\0\0\0\0\0\0\0\0\7\0\0\0b"..., 8192) = 199
lseek(4, 149, SEEK_SET) = 149
read(4, "X\0\0\0\2\0\0\0\21\0\0\0\1\0\0\0\0\0\0\0\0\0\7\0\0\0bar.ph"..., 8192) = 194
lseek(4, -8, SEEK_END) = 335
read(4, "\3\0\0\0GBMB", 8192) = 8
lseek(4, -40, SEEK_END) = 303
read(4, "\250\267o[\342\272\367\303\221>\361\307kf&\257\376\335\360;\356=`\244\270#\335\353\277\253\345$"..., 8192) = 40
lseek(4, 0, SEEK_SET) = 0
read(4, "<?php\nPhar::mapPhar('a.phar');\n\n"..., 8192) = 343
ioctl(3, TCGETS, 0x7ffc358e61c0) = -1 ENOTTY (Inappropriate ioctl for device)
fstat(3, {st_mode=S_IFREG|0664, st_size=343, ...}) = 0
fstat(3, {st_mode=S_IFREG|0664, st_size=343, ...}) = 0
read(3, "<?php\nPhar::mapPhar('a.phar');\n\n"..., 4096) = 343
lseek(4, 272, SEEK_SET) = 272
read(4, "<?php\necho \"this is foo.php\\n\";\250"..., 8192) = 71
close(4) = 0
write(1, "this is foo.php\n", 16this is foo.php
) = 16
rename("b.phar", "a.phar") = 0
newfstatat(AT_FDCWD, "*redacted*/a.phar", {st_mode=S_IFREG|0664, st_size=387, ...}, AT_SYMLINK_NOFOLLOW) = 0
newfstatat(AT_FDCWD, "*redacted*", {st_mode=S_IFDIR|0775, st_size=4096, ...}, AT_SYMLINK_NOFOLLOW) = 0
newfstatat(AT_FDCWD, "*redacted*", {st_mode=S_IFDIR|0775, st_size=4096, ...}, AT_SYMLINK_NOFOLLOW) = 0
newfstatat(AT_FDCWD, "*redacted*", {st_mode=S_IFDIR|0755, st_size=4096, ...}, AT_SYMLINK_NOFOLLOW) = 0
newfstatat(AT_FDCWD, "*redacted*", {st_mode=S_IFDIR|0755, st_size=4096, ...}, AT_SYMLINK_NOFOLLOW) = 0
openat(AT_FDCWD, "*redacted*/a.phar", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0664, st_size=387, ...}) = 0
lseek(4, 0, SEEK_CUR) = 0
lseek(4, 241, SEEK_SET) = 241
read(4, "<?php\n/* Shift the offsets compa"..., 8192) = 146
close(4) = 0
write(2, "PHP Parse error: Unterminated c"..., 122PHP Parse error: Unterminated comment starting line 2 in phar://*redacted*/a.phar/bar.php on line 2
) = 122
close(3) = 0
This is an issue for self-updating phars where the autoloader needs to load a class after the update has been performed.
PHP Version
PHP 8.3.14
Operating System
Ubuntu 24.04