1<?php
2// stolen from PEAR2_Pyrus_Developer_Creator_Tar by Greg Beaver, the original author, for use in unit tests
3// this tarmaker makes a malicious tar with a header designed to overflow the buffer
4class danger_tarmaker
5{
6    /**
7     * Path to archive file
8     *
9     * @var string
10     */
11    protected $archive;
12    /**
13     * Temporary stream used for creating the archive
14     *
15     * @var stream
16     */
17    protected $tmp;
18    protected $path;
19    protected $compress;
20    function __construct($path, $compress = 'zlib')
21    {
22        $this->compress = $compress;
23        if ($compress === 'bz2' && !function_exists('bzopen')) {
24            throw new PEAR2_Pyrus_Developer_Creator_Exception(
25                'bzip2 extension not available');
26        }
27        if ($compress === 'zlib' && !function_exists('gzopen')) {
28            throw new PEAR2_Pyrus_Developer_Creator_Exception(
29                'zlib extension not available');
30        }
31        $this->path = $path;
32    }
33
34    /**
35     * save a file inside this package
36     *
37     * This code is modified from Vincent Lascaux's File_Archive
38     * package, which is licensed under the LGPL license.
39     * @param string relative path within the package
40     * @param string|resource file contents or open file handle
41     */
42    function addFile($path, $fileOrStream, $stat = null)
43    {
44        clearstatcache();
45        if ($stat === null) {
46            if (is_resource($fileOrStream)) {
47                $stat = fstat($fileOrStream);
48            } else {
49                $stat = array(
50                    'mode' => 0x8000 + 0644,
51                    'uid' => 0,
52                    'gid' => 0,
53                    'size' => strlen($fileOrStream),
54                    'mtime' => time(),
55                );
56            }
57        }
58
59        $link = null;
60        if ($stat['mode'] & 0x4000) {
61            $type = 5;        // Directory
62        } else if ($stat['mode'] & 0x8000) {
63            $type = 0;        // Regular
64        } else if ($stat['mode'] & 0xA000) {
65            $type = 1;        // Link
66            $link = @readlink($current);
67        } else {
68            $type = 9;        // Unknown
69        }
70
71        $filePrefix = '';
72        if (strlen($path) > 255) {
73            throw new Exception(
74                "$path is too long, must be 255 characters or less"
75            );
76        } else if (strlen($path) > 100) {
77            $filePrefix = substr($path, 0, strlen($path)-100);
78            $path = substr($path, -100);
79        }
80
81        $block = pack('a100a8a8a8a12A12',
82                $path,
83                '12345678', // have a mode that allows the name to overflow
84                sprintf('%6s ',decoct($stat['uid'])),
85                sprintf('%6s ',decoct($stat['gid'])),
86                sprintf('%11s ',decoct($stat['size'])),
87                sprintf('%11s ',decoct($stat['mtime']))
88            );
89
90        $blockend = pack('a1a100a6a2a32a32a8a8a155a12',
91            $type,
92            $link,
93            'ustar',
94            '00',
95            'Pyrus',
96            'Pyrus',
97            '',
98            '',
99            $filePrefix,
100            '123456789abc'); // malicious block
101
102        $checkheader = array_merge(str_split($block), str_split($blockend));
103        if (!function_exists('_pear2tarchecksum')) {
104            function _pear2tarchecksum($a, $b) {return $a + ord($b);}
105        }
106        $checksum = 256; // 8 * ord(' ');
107        $checksum += array_reduce($checkheader, '_pear2tarchecksum');
108
109        $checksum = pack('a8', sprintf('%6s ', decoct($checksum)));
110
111        fwrite($this->tmp, (binary)$block . $checksum . $blockend, 512);
112        if (is_resource($fileOrStream)) {
113            stream_copy_to_stream($fileOrStream, $this->tmp);
114            if ($stat['size'] % 512) {
115                fwrite($this->tmp, (binary)str_repeat("\0", 512 - $stat['size'] % 512));
116            }
117        } else {
118            fwrite($this->tmp, (binary)$fileOrStream);
119            if (strlen($fileOrStream) % 512) {
120                fwrite($this->tmp, (binary)str_repeat("\0", 512 - strlen($fileOrStream) % 512));
121            }
122        }
123    }
124
125    /**
126     * Initialize the package creator
127     */
128    function init()
129    {
130        switch ($this->compress) {
131            case 'zlib' :
132                $this->tmp = gzopen($this->path, 'wb');
133                break;
134            case 'bz2' :
135                $this->tmp = bzopen($this->path, 'w');
136                break;
137            case 'none' :
138                $this->tmp = fopen($this->path, 'wb');
139                break;
140            default :
141                throw new Exception(
142                    'unknown compression type ' . $this->compress);
143        }
144    }
145
146    /**
147     * Create an internal directory, creating parent directories as needed
148     *
149     * @param string $dir
150     */
151    function mkdir($dir)
152    {
153        $this->addFile($dir, "", array(
154                    'mode' => 0x4000 + 0644,
155                    'uid' => 0,
156                    'gid' => 0,
157                    'size' => 0,
158                    'mtime' => time(),
159                ));
160    }
161
162    /**
163     * Finish saving the package
164     */
165    function close()
166    {
167        fwrite($this->tmp, pack('a1024', ''));
168        fclose($this->tmp);
169    }
170}
171