make.dangerous.tar.php.inc   [plain text]


<?php
// stolen from PEAR2_Pyrus_Developer_Creator_Tar by Greg Beaver, the original author, for use in unit tests
// this tarmaker makes a malicious tar with a header designed to overflow the buffer
class danger_tarmaker
{
    /**
     * Path to archive file
     *
     * @var string
     */
    protected $archive;
    /**
     * Temporary stream used for creating the archive
     *
     * @var stream
     */
    protected $tmp;
    protected $path;
    protected $compress;
    function __construct($path, $compress = 'zlib')
    {
        $this->compress = $compress;
        if ($compress === 'bz2' && !function_exists('bzopen')) {
            throw new PEAR2_Pyrus_Developer_Creator_Exception(
                'bzip2 extension not available');
        }
        if ($compress === 'zlib' && !function_exists('gzopen')) {
            throw new PEAR2_Pyrus_Developer_Creator_Exception(
                'zlib extension not available');
        }
        $this->path = $path;
    }

    /**
     * save a file inside this package
     * 
     * This code is modified from Vincent Lascaux's File_Archive
     * package, which is licensed under the LGPL license.
     * @param string relative path within the package
     * @param string|resource file contents or open file handle
     */
    function addFile($path, $fileOrStream, $stat = null)
    {
        clearstatcache();
        if ($stat === null) {
            if (is_resource($fileOrStream)) {
                $stat = fstat($fileOrStream);
            } else {
                $stat = array(
                    'mode' => 0x8000 + 0644,
                    'uid' => 0,
                    'gid' => 0,
                    'size' => strlen($fileOrStream),
                    'mtime' => time(),
                );
            }
        }

        $link = null;
        if ($stat['mode'] & 0x4000) {
            $type = 5;        // Directory
        } else if ($stat['mode'] & 0x8000) {
            $type = 0;        // Regular
        } else if ($stat['mode'] & 0xA000) {
            $type = 1;        // Link
            $link = @readlink($current);
        } else {
            $type = 9;        // Unknown
        }

        $filePrefix = '';
        if (strlen($path) > 255) {
            throw new Exception(
                "$path is too long, must be 255 characters or less"
            );
        } else if (strlen($path) > 100) {
            $filePrefix = substr($path, 0, strlen($path)-100);
            $path = substr($path, -100);
        }

        $block = pack('a100a8a8a8a12A12',
                $path,
                '12345678', // have a mode that allows the name to overflow
                sprintf('%6s ',decoct($stat['uid'])),
                sprintf('%6s ',decoct($stat['gid'])),
                sprintf('%11s ',decoct($stat['size'])),
                sprintf('%11s ',decoct($stat['mtime']))
            );

        $blockend = pack('a1a100a6a2a32a32a8a8a155a12',
            $type,
            $link,
            'ustar',
            '00',
            'Pyrus',
            'Pyrus',
            '',
            '',
            $filePrefix,
            '123456789abc'); // malicious block

        $checkheader = array_merge(str_split($block), str_split($blockend));
        if (!function_exists('_pear2tarchecksum')) {
            function _pear2tarchecksum($a, $b) {return $a + ord($b);}
        }
        $checksum = 256; // 8 * ord(' ');
        $checksum += array_reduce($checkheader, '_pear2tarchecksum');

        $checksum = pack('a8', sprintf('%6s ', decoct($checksum)));

        fwrite($this->tmp, (binary)$block . $checksum . $blockend, 512);
        if (is_resource($fileOrStream)) {
            stream_copy_to_stream($fileOrStream, $this->tmp);
            if ($stat['size'] % 512) {
                fwrite($this->tmp, (binary)str_repeat("\0", 512 - $stat['size'] % 512));
            }
        } else {
            fwrite($this->tmp, (binary)$fileOrStream);
            if (strlen($fileOrStream) % 512) {
                fwrite($this->tmp, (binary)str_repeat("\0", 512 - strlen($fileOrStream) % 512));
            }
        }
    }

    /**
     * Initialize the package creator
     */
    function init()
    {
        switch ($this->compress) {
            case 'zlib' :
                $this->tmp = gzopen($this->path, 'wb');
                break;
            case 'bz2' :
                $this->tmp = bzopen($this->path, 'w');
                break;
            case 'none' :
                $this->tmp = fopen($this->path, 'wb');
                break;
            default :
                throw new Exception(
                    'unknown compression type ' . $this->compress);
        }
    }

    /**
     * Create an internal directory, creating parent directories as needed
     * 
     * @param string $dir
     */
    function mkdir($dir)
    {
        $this->addFile($dir, "", array(
                    'mode' => 0x4000 + 0644,
                    'uid' => 0,
                    'gid' => 0,
                    'size' => 0,
                    'mtime' => time(),
                ));
    }

    /**
     * Finish saving the package
     */
    function close()
    {
        fwrite($this->tmp, pack('a1024', ''));
        fclose($this->tmp);
    }
}