[php-src] Issue #24740: ext/phar: tar .phar/.metadata* entries have no upper bound on claimed size, unlike signature.bin/alias.txt -- unfixed sibling
of GH-22556

From: Date: Mon, 26 Oct 2026 13:17:53 +0000
Subject: [php-src] Issue #24740: ext/phar: tar .phar/.metadata* entries have no upper bound on claimed size, unlike signature.bin/alias.txt -- unfixed sibling
of GH-22556
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-253213@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/24740 Author: kunwaratulhax0r ### Description 857ac7f0b3 (#24423, fixing GH-22556 "OOM DoS via oversized ././@LongLink entry in TAR phar") added an upper bound on the size claimed by a tar @LongLink entry, because a tar header's 12-byte octal "size" field can claim up to UINT32_MAX - 511 (~4GB) for something that should never legitimately be that large. Phar's tar-format metadata handling has the exact same shape and was not covered by that fix. Tar has no native "archive comment" field the way ZIP does, so phar stores its own metadata blob in a regular tar entry under the magic filename .phar/.metadata.bin (or .phar/.metadata/<entry>/.metadata.bin), using that entry's ordinary file-content "size" field as the metadata length: ```c static zend_result phar_tar_process_metadata(phar_entry_info *entry, php_stream *fp) /* {{{ */ { char *metadata; size_t save = php_stream_tell(fp), read; phar_entry_info *mentry; metadata = (char *) safe_emalloc(1, entry->uncompressed_filesize, 1); read = php_stream_read(fp, metadata, entry->uncompressed_filesize); ... ``` entry->uncompressed_filesize comes straight from the tar header's raw "size" field with **no check of any kind** before this allocation -- not > totalsize, not an absolute cap. Compare with the two other magic filenames handled in the same parse loop, both of which already have an explicit small sanity cap before use: - .phar/signature.bin: if (size > 511) { ...fail... } - .phar/alias.txt: if (size > 511) { ...fail... } .phar/.metadata* is the one magic-filename handler missing this. The allocation happens unconditionally, before the subsequent php_stream_read() has any chance to discover the stream doesn't actually contain that many bytes -- so the file doesn't need to contain the claimed amount of data, just a single 512-byte header claiming it does. For context on why the other two phar archive formats aren't affected by the same shape: ZIP stores phar metadata in the ZIP entry's native comment field, which is inherently 16-bit-bounded by the ZIP format itself (PHAR_GET_16(zipentry.comment_len), max 65535). The native .phar format validates its metadata length against the already-fully-read-into-memory manifest buffer before use (ext/phar/phar.c, if (len > (size_t)(endbuffer - buffer))). Only the tar format's convention reuses an inherently-large file-content-size field with no additional bound. Minimal reproducer: a hand-crafted 1,536-byte tar file (one 512-byte ustar header for an entry named .phar/.metadata.bin with the size field set to 2147483136, followed by two all-zero 512-byte end-of-archive blocks, no actual content): ```php <?php $phar = new PharData('/path/to/malicious_metadata.tar'); ``` Resulted in this output (memory_limit=16M): ``` Hard memory limit of 33554432 bytes exhausted at ext/phar/tar.c:215 (tried to allocate 2147483648 bytes) in ... on line 2 ``` But I expected this output instead: a clean "corrupted tar file (invalid entry size)"-style rejection, the same way the already-fixed @LongLink case and the signature.bin/alias.txt cases reject an oversized claim, without ever attempting the allocation. A 1,536-byte file on disk produces a 2,147,483,648-byte allocation *attempt* -- about a 1.4-million-times amplification between crafted-file size and requested-allocation size. Confirmed present, unmodified, on PHP-8.3, PHP-8.4, PHP-8.5, and master. **Suggested fix:** add the same kind of bound @LongLink now has (or reuse the existing > 511-style cap the sibling signature.bin/alias.txt handlers already use) in phar_tar_process_metadata() before the safe_emalloc() call. ### PHP Version ``` PHP 8.7.0-dev (cli) (built: Oct 26 2026 09:57:01) (NTS DEBUG) Copyright © The PHP Group and Contributors Zend Engine v4.7.0-dev, Copyright © Zend by Perforce with Zend OPcache v8.7.0-dev, Copyright ©, by Zend by Perforce ``` Also confirmed present on the PHP-8.3, PHP-8.4, and PHP-8.5 branches via direct content check. ### Operating System Ubuntu 24.04 (Docker), but this is pure parsing logic, not OS-specific.

« previous php.bugs (#253213) next »