Skip to content

ext/phar: 32-bit/64-bit divergence parsing tar entries with size ≥ 2 GiB (uint32_t → zend_off_t) - #24242

Open
PavlNekrasov wants to merge 1 commit into
php:masterfrom
PavlNekrasov:fix/phar-tar-32bit-size-bounds
Open

PavlNekrasov wants to merge 1 commit into
php:masterfrom
PavlNekrasov:fix/phar-tar-32bit-size-bounds

Conversation

@PavlNekrasov

Copy link
Copy Markdown

Description:

The tar header's size field is read as uint32_t and passed as-is to php_stream_seek() (offset is signed zend_off_t). On 32-bit, zend_off_t is int32, so sizes with the high bit set wrap into a negative offset — UBSan flags it, getSize() returns a negative value, and the corrupt/truncated check never triggers. On 64-bit the same size fits and the entry is correctly rejected as truncated.

Reproducer:

php -d display_errors=1 -r '
var_dump(PHP_INT_SIZE);
try {
    $phar = new PharData("/test.tar");
    echo "Parsed, entries: ", count($phar), PHP_EOL;
    foreach ($phar as $file) {
        echo "Name:     ", $file->getFilename(), PHP_EOL;
        echo "Size:     ", $file->getSize(), PHP_EOL;
        echo "Path:     ", $file->getPathname(), PHP_EOL;
        echo "RealPath: ", $file->getRealPath(), PHP_EOL;
        echo "Readable: ", var_export($file->isReadable(), true), PHP_EOL;
        echo "--- content (first 64 bytes) ---", PHP_EOL;
        $fp = $file->openFile("r");
        echo bin2hex($fp->fread(64)), PHP_EOL;
    }
} catch (Throwable $e) {
    echo get_class($e), ": ", $e->getMessage(), PHP_EOL;
}'

With test.tar (entry bigfile, header size 0xC0000000 = 3221225472):

32-bit:

int(4)
Parsed, entries: 1
Name:     bigfile
Size:     -1073741824
Path:     phar:///test.tar/bigfile
RealPath:
Readable: true

UBSan:

# SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /php-8.3.31/ext/phar/tar.c:576:21 
# /php-8.3.31/ext/phar/tar.c:581:4: runtime error: implicit conversion from type 'uint32_t' (aka 'unsigned int') of value 3221225472 (32-bit, unsigned) to type 'zend_off_t' (aka 'int') changed the value to -1073741824 (32-bit, signed)

64-bit:

int(8)
UnexpectedValueException: phar error: "/host/test.tar" is a corrupted tar file (truncated)

Env: PHP 8.3.31; 32-bit and 64-bit builds.

Found by Linux Verification Center (linuxtesting.org) with SVACE

Comment thread ext/phar/tar.c Outdated
{
zend_off_t offset = php_stream_tell(fp);

if (offset < 0 || (uint64_t) size > (uint64_t) ZEND_LONG_MAX

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is causing CI error. size is uint32_t which is impossible to be bigger than ZEND_LONG_MAX in 64 bit

Tar entry sizes at or above 2 GiB cannot be represented by the signed
zend_off_t used by php_stream_seek() on 32-bit systems. In addition,
rounding an entry size to a 512-byte boundary can overflow uint32_t, and
unchecked php_stream_tell() failures can be converted to valid size_t
values.

Reject archive and entry sizes outside the supported stream offset range,
check rounding before it occurs, and validate data skips against the
archive length.

Signed-off-by: Pavel Nekrasov <p.nekrasov@fobos-nt.ru>
Signed-off-by: Georgij Tsarin <crystarm@altlinux.org>
@PavlNekrasov
PavlNekrasov force-pushed the fix/phar-tar-32bit-size-bounds branch from 2008b88 to 455df08 Compare October 11, 2026 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants