1 /*
2  *  Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3  *  Copyright (C) 2011-2013 Sourcefire, Inc.
4  *
5  *  Authors: aCaB <acab@clamav.net>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  *  MA 02110-1301, USA.
20  */
21 
22 #include <string.h>
23 
24 #include "clamav.h"
25 #include "scanners.h"
26 #include "iso9660.h"
27 #include "fmap.h"
28 #include "str.h"
29 #include "entconv.h"
30 #include "hashtab.h"
31 
32 typedef struct {
33     cli_ctx *ctx;
34     size_t base_offset;
35     unsigned int blocksz;
36     unsigned int sectsz;
37     unsigned int fileno;
38     unsigned int joliet;
39     char buf[260];
40     struct cli_hashset dir_blocks;
41 } iso9660_t;
42 
needblock(const iso9660_t * iso,unsigned int block,int temp)43 static const void *needblock(const iso9660_t *iso, unsigned int block, int temp)
44 {
45     cli_ctx *ctx = iso->ctx;
46     size_t loff;
47     unsigned int blocks_per_sect = (2048 / iso->blocksz);
48     if (block > ((ctx->fmap->len - iso->base_offset) / iso->sectsz) * blocks_per_sect)
49         return NULL;                                  /* Block is out of file */
50     loff = (block / blocks_per_sect) * iso->sectsz;   /* logical sector */
51     loff += (block % blocks_per_sect) * iso->blocksz; /* logical block within the sector */
52     if (temp)
53         return fmap_need_off_once(ctx->fmap, iso->base_offset + loff, iso->blocksz);
54     return fmap_need_off(ctx->fmap, iso->base_offset + loff, iso->blocksz);
55 }
56 
iso_scan_file(const iso9660_t * iso,unsigned int block,unsigned int len)57 static int iso_scan_file(const iso9660_t *iso, unsigned int block, unsigned int len)
58 {
59     char *tmpf;
60     int fd, ret = CL_SUCCESS;
61 
62     if (cli_gentempfd(iso->ctx->sub_tmpdir, &tmpf, &fd) != CL_SUCCESS)
63         return CL_ETMPFILE;
64 
65     cli_dbgmsg("iso_scan_file: dumping to %s\n", tmpf);
66     while (len) {
67         const void *buf   = needblock(iso, block, 1);
68         unsigned int todo = MIN(len, iso->blocksz);
69         if (!buf) {
70             /* Block outside file */
71             cli_dbgmsg("iso_scan_file: cannot dump block outside file, ISO may be truncated\n");
72             ret = CL_EFORMAT;
73             break;
74         }
75         if (cli_writen(fd, buf, todo) != todo) {
76             cli_warnmsg("iso_scan_file: Can't write to file %s\n", tmpf);
77             ret = CL_EWRITE;
78             break;
79         }
80         len -= todo;
81         block++;
82     }
83 
84     if (!len)
85         ret = cli_magic_scan_desc(fd, tmpf, iso->ctx, iso->buf);
86 
87     close(fd);
88     if (!iso->ctx->engine->keeptmp) {
89         if (cli_unlink(tmpf)) {
90             ret = CL_EUNLINK;
91         }
92     }
93 
94     free(tmpf);
95     return ret;
96 }
97 
iso_string(iso9660_t * iso,const void * src,unsigned int len)98 static char *iso_string(iso9660_t *iso, const void *src, unsigned int len)
99 {
100     if (iso->joliet) {
101         char *utf8;
102         const char *uutf8;
103         if (len > (sizeof(iso->buf) - 2))
104             len = sizeof(iso->buf) - 2;
105         memcpy(iso->buf, src, len);
106         iso->buf[len]     = '\0';
107         iso->buf[len + 1] = '\0';
108         utf8              = cli_utf16_to_utf8(iso->buf, len, E_UTF16_BE);
109         uutf8             = utf8 ? utf8 : "";
110         strncpy(iso->buf, uutf8, sizeof(iso->buf));
111         iso->buf[sizeof(iso->buf) - 1] = '\0';
112         free(utf8);
113     } else {
114         memcpy(iso->buf, src, len);
115         iso->buf[len] = '\0';
116     }
117     return iso->buf;
118 }
119 
iso_parse_dir(iso9660_t * iso,unsigned int block,unsigned int len)120 static int iso_parse_dir(iso9660_t *iso, unsigned int block, unsigned int len)
121 {
122     cli_ctx *ctx      = iso->ctx;
123     int ret           = CL_CLEAN;
124     int viruses_found = 0;
125 
126     if (len < 34) {
127         cli_dbgmsg("iso_parse_dir: Directory too small, skipping\n");
128         return CL_CLEAN;
129     }
130 
131     for (; len && ret == CL_CLEAN; block++, len -= MIN(len, iso->blocksz)) {
132         const uint8_t *dir, *dir_orig;
133         unsigned int dirsz;
134 
135         if (iso->dir_blocks.count > 1024) {
136             cli_dbgmsg("iso_parse_dir: Breaking out due to too many dir records\n");
137             return CL_BREAK;
138         }
139 
140         if (cli_hashset_contains(&iso->dir_blocks, block))
141             continue;
142 
143         if ((ret = cli_hashset_addkey(&iso->dir_blocks, block)) != CL_CLEAN)
144             return ret;
145 
146         dir = dir_orig = needblock(iso, block, 0);
147         if (!dir)
148             return CL_CLEAN;
149 
150         for (dirsz = MIN(iso->blocksz, len);;) {
151             unsigned int entrysz = *dir, fileoff, filesz;
152             char *sep;
153 
154             if (!dirsz || !entrysz) /* continuing on next block, if any */
155                 break;
156             if (entrysz > dirsz) { /* record size overlaps onto the next sector, no point in looking in there */
157                 cli_dbgmsg("iso_parse_dir: Directory entry overflow, breaking out %u %u\n", entrysz, dirsz);
158                 len = 0;
159                 break;
160             }
161             if (entrysz < 34) { /* this shouldn't happen really*/
162                 cli_dbgmsg("iso_parse_dir: Too short directory entry, attempting to skip\n");
163                 dirsz -= entrysz;
164                 dir += entrysz;
165                 continue;
166             }
167             filesz = dir[32];
168             if (filesz == 1 && (dir[33] == 0 || dir[33] == 1)) { /* skip "." and ".." */
169                 dirsz -= entrysz;
170                 dir += entrysz;
171                 continue;
172             }
173 
174             if (filesz + 33 > dirsz) {
175                 cli_dbgmsg("iso_parse_dir: Directory entry name overflow, clamping\n");
176                 filesz = dirsz - 33;
177             }
178             iso_string(iso, &dir[33], filesz);
179             sep = memchr(iso->buf, ';', filesz);
180             if (sep)
181                 *sep = '\0';
182             else
183                 iso->buf[filesz] = '\0';
184             fileoff = cli_readint32(dir + 2);
185             fileoff += dir[1];
186             filesz = cli_readint32(dir + 10);
187 
188             cli_dbgmsg("iso_parse_dir: %s '%s': off %x - size %x - flags %x - unit size %x - gap size %x - volume %u\n", (dir[25] & 2) ? "Directory" : "File", iso->buf, fileoff, filesz, dir[25], dir[26], dir[27], cli_readint32(&dir[28]) & 0xffff);
189             ret = cli_matchmeta(ctx, iso->buf, filesz, filesz, 0, 0, 0, NULL);
190             if (ret == CL_VIRUS) {
191                 viruses_found = 1;
192                 if (!SCAN_ALLMATCHES)
193                     break;
194                 ret = CL_CLEAN;
195             }
196 
197             if (dir[26] || dir[27])
198                 cli_dbgmsg("iso_parse_dir: Skipping interleaved file\n");
199             else {
200                 /* TODO Handle multi-extent ? */
201                 if (dir[25] & 2) {
202                     ret = iso_parse_dir(iso, fileoff, filesz);
203                 } else {
204                     if (cli_checklimits("ISO9660", ctx, filesz, 0, 0) != CL_SUCCESS)
205                         cli_dbgmsg("iso_parse_dir: Skipping overlimit file\n");
206                     else
207                         ret = iso_scan_file(iso, fileoff, filesz);
208                 }
209                 if (ret == CL_VIRUS) {
210                     viruses_found = 1;
211                     if (!SCAN_ALLMATCHES)
212                         break;
213                     ret = CL_CLEAN;
214                 }
215             }
216             dirsz -= entrysz;
217             dir += entrysz;
218         }
219 
220         fmap_unneed_ptr(ctx->fmap, dir_orig, iso->blocksz);
221     }
222     if (viruses_found == 1)
223         return CL_VIRUS;
224     return ret;
225 }
226 
cli_scaniso(cli_ctx * ctx,size_t offset)227 int cli_scaniso(cli_ctx *ctx, size_t offset)
228 {
229     const uint8_t *privol, *next;
230     iso9660_t iso;
231     int i;
232 
233     if (offset < 32768)
234         return CL_CLEAN; /* Need 16 sectors at least 2048 bytes long */
235 
236     privol = fmap_need_off(ctx->fmap, offset, 2448 + 6);
237     if (!privol)
238         return CL_CLEAN;
239 
240     next = (uint8_t *)cli_memstr((char *)privol + 2049, 2448 + 6 - 2049, "CD001", 5);
241     if (!next)
242         return CL_CLEAN; /* Find next volume descriptor */
243 
244     iso.sectsz = (next - privol) - 1;
245     if (iso.sectsz * 16 > offset)
246         return CL_CLEAN; /* Need room for 16 system sectors */
247 
248     iso.blocksz = cli_readint32(privol + 128) & 0xffff;
249     if (iso.blocksz != 512 && iso.blocksz != 1024 && iso.blocksz != 2048)
250         return CL_CLEAN; /* Likely not a cdrom image */
251 
252     iso.base_offset = offset - iso.sectsz * 16;
253     iso.joliet      = 0;
254 
255     for (i = 16; i < 32; i++) { /* scan for a joliet secondary volume descriptor */
256         next = fmap_need_off_once(ctx->fmap, iso.base_offset + i * iso.sectsz, 2048);
257         if (!next)
258             break; /* Out of disk */
259         if (*next == 0xff || memcmp(next + 1, "CD001", 5))
260             break; /* Not a volume descriptor */
261         if (*next != 2)
262             continue; /* Not a secondary volume descriptor */
263         if (next[88] != 0x25 || next[89] != 0x2f)
264             continue; /* Not a joliet descriptor */
265         if (next[156 + 26] || next[156 + 27])
266             continue; /* Root is interleaved so we fallback to the primary descriptor */
267         switch (next[90]) {
268             case 0x40: /* Level 1 */
269                 iso.joliet = 1;
270                 break;
271             case 0x43: /* Level 2 */
272                 iso.joliet = 2;
273                 break;
274             case 0x45: /* Level 3 */
275                 iso.joliet = 3;
276                 break;
277             default: /* Not Joliet */
278                 continue;
279         }
280         break;
281     }
282 
283     /* TODO rr, el torito, udf ? */
284 
285     /* NOTE: freeing sector now. it is still safe to access as we don't alloc anymore */
286     fmap_unneed_off(ctx->fmap, offset, 2448);
287     if (iso.joliet)
288         privol = next;
289 
290     cli_dbgmsg("in cli_scaniso\n");
291     if (cli_debug_flag) {
292         cli_dbgmsg("cli_scaniso: Raw sector size: %u\n", iso.sectsz);
293         cli_dbgmsg("cli_scaniso: Block size: %u\n", iso.blocksz);
294 
295         cli_dbgmsg("cli_scaniso: Volume descriptor version: %u\n", privol[6]);
296 
297 #define ISOSTRING(src, len) iso_string(&iso, (src), (len))
298         cli_dbgmsg("cli_scaniso: System: %s\n", ISOSTRING(privol + 8, 32));
299         cli_dbgmsg("cli_scaniso: Volume: %s\n", ISOSTRING(privol + 40, 32));
300 
301         cli_dbgmsg("cli_scaniso: Volume space size: 0x%x blocks\n", cli_readint32(&privol[80]));
302         cli_dbgmsg("cli_scaniso: Volume %u of %u\n", cli_readint32(privol + 124) & 0xffff, cli_readint32(privol + 120) & 0xffff);
303 
304         cli_dbgmsg("cli_scaniso: Volume Set: %s\n", ISOSTRING(privol + 190, 128));
305         cli_dbgmsg("cli_scaniso: Publisher: %s\n", ISOSTRING(privol + 318, 128));
306         cli_dbgmsg("cli_scaniso: Data Preparer: %s\n", ISOSTRING(privol + 446, 128));
307         cli_dbgmsg("cli_scaniso: Application: %s\n", ISOSTRING(privol + 574, 128));
308 
309 #define ISOTIME(s, n) cli_dbgmsg("cli_scaniso: "s                         \
310                                  ": %c%c%c%c-%c%c-%c%c %c%c:%c%c:%c%c\n", \
311                                  privol[n], privol[n + 1], privol[n + 2], privol[n + 3], privol[n + 4], privol[n + 5], privol[n + 6], privol[n + 7], privol[n + 8], privol[n + 9], privol[n + 10], privol[n + 11], privol[n + 12], privol[n + 13])
312         ISOTIME("Volume creation time", 813);
313         ISOTIME("Volume modification time", 830);
314         ISOTIME("Volume expiration time", 847);
315         ISOTIME("Volume effective time", 864);
316 
317         cli_dbgmsg("cli_scaniso: Path table size: 0x%x\n", cli_readint32(privol + 132) & 0xffff);
318         cli_dbgmsg("cli_scaniso: LSB Path Table: 0x%x\n", cli_readint32(privol + 140));
319         cli_dbgmsg("cli_scaniso: Opt LSB Path Table: 0x%x\n", cli_readint32(privol + 144));
320         cli_dbgmsg("cli_scaniso: MSB Path Table: 0x%x\n", cbswap32(cli_readint32(privol + 148)));
321         cli_dbgmsg("cli_scaniso: Opt MSB Path Table: 0x%x\n", cbswap32(cli_readint32(privol + 152)));
322         cli_dbgmsg("cli_scaniso: File Structure Version: %u\n", privol[881]);
323 
324         if (iso.joliet)
325             cli_dbgmsg("cli_scaniso: Joliet level %u\n", iso.joliet);
326     }
327 
328     if (privol[156 + 26] || privol[156 + 27]) {
329         cli_dbgmsg("cli_scaniso: Interleaved root directory is not supported\n");
330         return CL_CLEAN;
331     }
332 
333     iso.ctx = ctx;
334     i       = cli_hashset_init(&iso.dir_blocks, 1024, 80);
335     if (i != CL_SUCCESS)
336         return i;
337     i = iso_parse_dir(&iso, cli_readint32(privol + 156 + 2) + privol[156 + 1], cli_readint32(privol + 156 + 10));
338     cli_hashset_destroy(&iso.dir_blocks);
339     if (i == CL_BREAK)
340         return CL_CLEAN;
341     return i;
342 }
343