1 /*
2  *  Copyright (C) 2007-2008 Sourcefire Inc.
3  *
4  *  Authors: Alberto Wu
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License version 2 as
8  *  published by the Free Software Foundation.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18  *  MA 02110-1301, USA.
19  */
20 
21 #if HAVE_CONFIG_H
22 #include "clamav-config.h"
23 #endif
24 
25 #include <stdio.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <string.h>
30 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33 
34 #include "clamav.h"
35 #include "others.h"
36 #include "nsis_bzlib.h"
37 /* #include "zlib.h" */
38 #include "nsis_zlib.h"
39 #include "lzma_iface.h"
40 #include "matcher.h"
41 #include "scanners.h"
42 #include "nulsft.h" /* SHUT UP GCC -Wextra */
43 #include "fmap.h"
44 
45 #define EC32(x) le32_to_host(x)
46 
47 enum {
48     COMP_NOT_DETECTED,
49     COMP_BZIP2,
50     COMP_LZMA,
51     COMP_ZLIB,
52     COMP_NOCOMP
53 };
54 
55 struct nsis_st {
56     size_t curpos;
57     int ofd;
58     int opened;
59     off_t off;
60     off_t fullsz;
61     char *dir;
62     uint32_t asz;
63     uint32_t hsz;
64     uint32_t fno;
65     uint8_t comp;
66     uint8_t solid;
67     uint8_t freecomp;
68     uint8_t eof;
69     struct stream_state nsis;
70     nsis_bzstream bz;
71     struct CLI_LZMA lz;
72     /*   z_stream z; */
73     nsis_z_stream z;
74     const unsigned char *freeme;
75     fmap_t *map;
76     char ofn[1024];
77 };
78 
79 #define LINESTR(x) #x
80 #define LINESTR2(x) LINESTR(x)
81 #define __AT__ " at "__FILE__ \
82                ":" LINESTR2(__LINE__)
83 
nsis_init(struct nsis_st * n)84 static int nsis_init(struct nsis_st *n)
85 {
86     switch (n->comp) {
87         case COMP_BZIP2:
88             memset(&n->bz, 0, sizeof(nsis_bzstream));
89             if (nsis_BZ2_bzDecompressInit(&n->bz, 0, 0) != BZ_OK)
90                 return CL_EUNPACK;
91             n->freecomp = 1;
92             break;
93         case COMP_LZMA:
94             memset(&n->lz, 0, sizeof(struct CLI_LZMA));
95             if (cli_LzmaInit(&n->lz, 0xffffffffffffffffULL) != LZMA_RESULT_OK)
96                 return CL_EUNPACK;
97             n->freecomp = 1;
98             break;
99         case COMP_ZLIB:
100             memset(&n->z, 0, sizeof(z_stream));
101             /*     inflateInit2(&n->z, -MAX_WBITS); */
102             /*     n->freecomp=1; */
103             nsis_inflateInit(&n->z);
104             n->freecomp = 0;
105     }
106     return CL_SUCCESS;
107 }
108 
nsis_shutdown(struct nsis_st * n)109 static void nsis_shutdown(struct nsis_st *n)
110 {
111     if (!n->freecomp)
112         return;
113 
114     switch (n->comp) {
115         case COMP_BZIP2:
116             nsis_BZ2_bzDecompressEnd(&n->bz);
117             break;
118         case COMP_LZMA:
119             cli_LzmaShutdown(&n->lz);
120             break;
121         case COMP_ZLIB:
122             /*     inflateEnd(&n->z); */
123             break;
124     }
125 
126     n->freecomp = 0;
127 }
128 
nsis_decomp(struct nsis_st * n)129 static int nsis_decomp(struct nsis_st *n)
130 {
131     int ret = CL_EFORMAT;
132     switch (n->comp) {
133         case COMP_BZIP2:
134             n->bz.avail_in  = n->nsis.avail_in;
135             n->bz.next_in   = n->nsis.next_in;
136             n->bz.avail_out = n->nsis.avail_out;
137             n->bz.next_out  = n->nsis.next_out;
138             switch (nsis_BZ2_bzDecompress(&n->bz)) {
139                 case BZ_OK:
140                     ret = CL_SUCCESS;
141                     break;
142                 case BZ_STREAM_END:
143                     ret = CL_BREAK;
144             }
145             n->nsis.avail_in  = n->bz.avail_in;
146             n->nsis.next_in   = n->bz.next_in;
147             n->nsis.avail_out = n->bz.avail_out;
148             n->nsis.next_out  = n->bz.next_out;
149             break;
150         case COMP_LZMA:
151             n->lz.avail_in  = n->nsis.avail_in;
152             n->lz.next_in   = n->nsis.next_in;
153             n->lz.avail_out = n->nsis.avail_out;
154             n->lz.next_out  = n->nsis.next_out;
155             switch (cli_LzmaDecode(&n->lz)) {
156                 case LZMA_RESULT_OK:
157                     ret = CL_SUCCESS;
158                     break;
159                 case LZMA_STREAM_END:
160                     ret = CL_BREAK;
161             }
162             n->nsis.avail_in  = n->lz.avail_in;
163             n->nsis.next_in   = n->lz.next_in;
164             n->nsis.avail_out = n->lz.avail_out;
165             n->nsis.next_out  = n->lz.next_out;
166             break;
167         case COMP_ZLIB:
168             n->z.avail_in  = n->nsis.avail_in;
169             n->z.next_in   = n->nsis.next_in;
170             n->z.avail_out = n->nsis.avail_out;
171             n->z.next_out  = n->nsis.next_out;
172             /*  switch (inflate(&n->z, Z_NO_FLUSH)) { */
173             switch (nsis_inflate(&n->z)) {
174                 case Z_OK:
175                     ret = CL_SUCCESS;
176                     break;
177                 case Z_STREAM_END:
178                     ret = CL_BREAK;
179             }
180             n->nsis.avail_in  = n->z.avail_in;
181             n->nsis.next_in   = n->z.next_in;
182             n->nsis.avail_out = n->z.avail_out;
183             n->nsis.next_out  = n->z.next_out;
184             break;
185     }
186     return ret;
187 }
188 
nsis_unpack_next(struct nsis_st * n,cli_ctx * ctx)189 static int nsis_unpack_next(struct nsis_st *n, cli_ctx *ctx)
190 {
191     const unsigned char *ibuf;
192     uint32_t size, loops;
193     int ret, gotsome = 0;
194     unsigned char obuf[BUFSIZ];
195 
196     if (n->eof) {
197         cli_dbgmsg("NSIS: extraction complete\n");
198         return CL_BREAK;
199     }
200 
201     if ((ret = cli_checklimits("NSIS", ctx, 0, 0, 0)) != CL_CLEAN)
202         return ret;
203 
204     if (n->fno)
205         snprintf(n->ofn, 1023, "%s" PATHSEP "content.%.3u", n->dir, n->fno);
206     else
207         snprintf(n->ofn, 1023, "%s" PATHSEP "headers", n->dir);
208 
209     n->fno++;
210     n->opened = 0;
211 
212     if (!n->solid) {
213         if (fmap_readn(n->map, &size, n->curpos, 4) != 4) {
214             cli_dbgmsg("NSIS: reached EOF - extraction complete\n");
215             return CL_BREAK;
216         }
217         n->curpos += 4;
218         if (n->asz == 4) {
219             cli_dbgmsg("NSIS: reached CRC - extraction complete\n");
220             return CL_BREAK;
221         }
222         loops = EC32(size);
223         if (!(size = (loops & ~0x80000000))) {
224             cli_dbgmsg("NSIS: empty file found\n");
225             return CL_SUCCESS;
226         }
227         if (n->asz < 4 || size > n->asz - 4) {
228             cli_dbgmsg("NSIS: next file is outside the archive\n");
229             return CL_BREAK;
230         }
231 
232         n->asz -= size + 4;
233 
234         if ((ret = cli_checklimits("NSIS", ctx, size, 0, 0)) != CL_CLEAN) {
235             n->curpos += size;
236             return ret;
237         }
238         if (!(ibuf = fmap_need_off_once(n->map, n->curpos, size))) {
239             cli_dbgmsg("NSIS: cannot read %u bytes"__AT__
240                        "\n",
241                        size);
242             return CL_EREAD;
243         }
244         if ((n->ofd = open(n->ofn, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600)) == -1) {
245             cli_errmsg("NSIS: unable to create output file %s - aborting.", n->ofn);
246             return CL_ECREAT;
247         }
248         n->opened = 1;
249         n->curpos += size;
250         if (loops == size) {
251 
252             if (cli_writen(n->ofd, ibuf, size) != size) {
253                 cli_dbgmsg("NSIS: cannot write output file"__AT__
254                            "\n");
255                 close(n->ofd);
256                 return CL_EWRITE;
257             }
258         } else {
259             if ((ret = nsis_init(n)) != CL_SUCCESS) {
260                 cli_dbgmsg("NSIS: decompressor init failed"__AT__
261                            "\n");
262                 close(n->ofd);
263                 return ret;
264             }
265 
266             n->nsis.avail_in  = size;
267             n->nsis.next_in   = (void *)ibuf;
268             n->nsis.next_out  = obuf;
269             n->nsis.avail_out = BUFSIZ;
270             loops             = 0;
271 
272             while ((ret = nsis_decomp(n)) == CL_SUCCESS) {
273                 if ((size = n->nsis.next_out - obuf) > 0) {
274                     gotsome = 1;
275                     if (cli_writen(n->ofd, obuf, size) != size) {
276                         cli_dbgmsg("NSIS: cannot write output file"__AT__
277                                    "\n");
278                         close(n->ofd);
279                         nsis_shutdown(n);
280                         return CL_EWRITE;
281                     }
282                     n->nsis.next_out  = obuf;
283                     n->nsis.avail_out = BUFSIZ;
284                     loops             = 0;
285                     if ((ret = cli_checklimits("NSIS", ctx, size, 0, 0)) != CL_CLEAN) {
286                         close(n->ofd);
287                         nsis_shutdown(n);
288                         return ret;
289                     }
290                 } else if (++loops > 20) {
291                     cli_dbgmsg("NSIS: xs looping, breaking out"__AT__
292                                "\n");
293                     ret = CL_EFORMAT;
294                     break;
295                 }
296             }
297 
298             nsis_shutdown(n);
299 
300             if ((n->nsis.next_out - obuf) > 0) {
301                 gotsome = 1;
302                 if (cli_writen(n->ofd, obuf, (size_t)(n->nsis.next_out - obuf)) != (size_t)(n->nsis.next_out - obuf)) {
303                     cli_dbgmsg("NSIS: cannot write output file"__AT__
304                                "\n");
305                     close(n->ofd);
306                     return CL_EWRITE;
307                 }
308             }
309 
310             if (ret != CL_SUCCESS && ret != CL_BREAK) {
311                 cli_dbgmsg("NSIS: bad stream"__AT__
312                            "\n");
313                 if (gotsome) {
314                     ret = CL_SUCCESS;
315                 } else {
316                     ret = CL_EMAXSIZE;
317                     close(n->ofd);
318                 }
319                 return ret;
320             }
321         }
322 
323         return CL_SUCCESS;
324 
325     } else {
326         if (!n->freeme) {
327             if ((ret = nsis_init(n)) != CL_SUCCESS) {
328                 cli_dbgmsg("NSIS: decompressor init failed\n");
329                 return ret;
330             }
331             if (!(n->freeme = fmap_need_off_once(n->map, n->curpos, n->asz))) {
332                 cli_dbgmsg("NSIS: cannot read %u bytes"__AT__
333                            "\n",
334                            n->asz);
335                 return CL_EREAD;
336             }
337             n->nsis.next_in  = (void *)n->freeme;
338             n->nsis.avail_in = n->asz;
339         }
340 
341         if (n->nsis.avail_in <= 4) {
342             cli_dbgmsg("NSIS: extraction complete\n");
343             return CL_BREAK;
344         }
345         n->nsis.next_out  = obuf;
346         n->nsis.avail_out = 4;
347         loops             = 0;
348 
349         while ((ret = nsis_decomp(n)) == CL_SUCCESS) {
350             if (n->nsis.next_out - obuf == 4) break;
351             if (++loops > 20) {
352                 cli_dbgmsg("NSIS: xs looping, breaking out"__AT__
353                            "\n");
354                 ret = CL_BREAK;
355                 break;
356             }
357         }
358 
359         if (ret != CL_SUCCESS) {
360             cli_dbgmsg("NSIS: bad stream"__AT__
361                        "\n");
362             return CL_EFORMAT;
363         }
364 
365         size = cli_readint32(obuf);
366         if ((ret = cli_checklimits("NSIS", ctx, size, 0, 0)) != CL_CLEAN) {
367             return ret;
368         }
369 
370         if (size == 0) {
371             cli_dbgmsg("NSIS: Empty file found.\n");
372             return CL_SUCCESS;
373         }
374 
375         n->nsis.next_out  = obuf;
376         n->nsis.avail_out = MIN(BUFSIZ, size);
377         loops             = 0;
378 
379         if ((n->ofd = open(n->ofn, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600)) == -1) {
380             cli_errmsg("NSIS: unable to create output file %s - aborting.", n->ofn);
381             return CL_ECREAT;
382         }
383         n->opened = 1;
384 
385         while (size && (ret = nsis_decomp(n)) == CL_SUCCESS) {
386             unsigned int wsz;
387             if ((wsz = n->nsis.next_out - obuf) > 0) {
388                 gotsome = 1;
389                 if (cli_writen(n->ofd, obuf, wsz) != wsz) {
390                     cli_dbgmsg("NSIS: cannot write output file"__AT__
391                                "\n");
392                     close(n->ofd);
393                     return CL_EWRITE;
394                 }
395                 size -= wsz;
396                 loops             = 0;
397                 n->nsis.next_out  = obuf;
398                 n->nsis.avail_out = MIN(size, BUFSIZ);
399             } else if (++loops > 20) {
400                 cli_dbgmsg("NSIS: xs looping, breaking out"__AT__
401                            "\n");
402                 ret = CL_EFORMAT;
403                 break;
404             }
405         }
406 
407         if ((n->nsis.next_out - obuf) > 0) {
408             gotsome = 1;
409             if (cli_writen(n->ofd, obuf, (size_t)(n->nsis.next_out - obuf)) != (size_t)(n->nsis.next_out - obuf)) {
410                 cli_dbgmsg("NSIS: cannot write output file"__AT__
411                            "\n");
412                 close(n->ofd);
413                 return CL_EWRITE;
414             }
415         }
416 
417         if (ret == CL_EFORMAT) {
418             cli_dbgmsg("NSIS: bad stream"__AT__
419                        "\n");
420             if (!gotsome) {
421                 close(n->ofd);
422                 return CL_EMAXSIZE;
423             }
424         }
425 
426         if (ret == CL_EFORMAT || ret == CL_BREAK) {
427             n->eof = 1;
428         } else if (ret != CL_SUCCESS) {
429             cli_dbgmsg("NSIS: bad stream"__AT__
430                        "\n");
431             close(n->ofd);
432             return CL_EFORMAT;
433         }
434         return CL_SUCCESS;
435     }
436 }
437 
nsis_detcomp(const char * b)438 static uint8_t nsis_detcomp(const char *b)
439 {
440     if (*b == '1') return COMP_BZIP2;
441     if ((cli_readint32(b) & ~0x80000000) == 0x5d) return COMP_LZMA;
442     return COMP_ZLIB;
443 }
444 
nsis_headers(struct nsis_st * n,cli_ctx * ctx)445 static int nsis_headers(struct nsis_st *n, cli_ctx *ctx)
446 {
447     const char *buf;
448     uint32_t pos;
449     int i;
450     uint8_t comps[] = {0, 0, 0, 0}, trunc = 0;
451 
452     if (!(buf = fmap_need_off_once(n->map, n->off, 0x1c)))
453         return CL_EREAD;
454 
455     n->hsz    = (uint32_t)cli_readint32(buf + 0x14);
456     n->asz    = (uint32_t)cli_readint32(buf + 0x18);
457     n->fullsz = n->map->len;
458 
459     cli_dbgmsg("NSIS: Header info - Flags=%x, Header size=%x, Archive size=%x\n", cli_readint32(buf), n->hsz, n->asz);
460 
461     if (n->fullsz - n->off < (off_t)n->asz) {
462         cli_dbgmsg("NSIS: Possibly truncated file\n");
463         n->asz = n->fullsz - n->off;
464         trunc++;
465     } else if (n->fullsz - n->off != (off_t)n->asz) {
466         cli_dbgmsg("NSIS: Overlays found\n");
467     }
468 
469     n->asz -= 0x1c;
470     buf += 0x1c;
471 
472     /* Guess if solid */
473     for (i = 0, pos = 0; pos < n->asz - 4; i++) {
474         int32_t nextsz;
475         if (!(buf = fmap_need_ptr_once(n->map, (void *)buf, 4))) return CL_EREAD;
476         nextsz = cli_readint32(buf);
477         if (!i) n->comp = nsis_detcomp(buf);
478         buf += 4;
479         if (nextsz & 0x80000000) {
480             nextsz &= ~0x80000000;
481             if (!(buf = fmap_need_ptr_once(n->map, (void *)buf, 4))) return CL_EREAD;
482             comps[nsis_detcomp(buf)]++;
483             nextsz -= 4;
484             pos += 4;
485             buf += 4;
486         }
487         if ((pos += 4 + nextsz) > n->asz) {
488             n->solid = 1;
489             break;
490         }
491 
492         buf += nextsz;
493     }
494 
495     if (trunc && i >= 2) n->solid = 0;
496 
497     cli_dbgmsg("NSIS: solid compression%s detected\n", (n->solid) ? "" : " not");
498 
499     /* Guess the compression method */
500     if (!n->solid) {
501         cli_dbgmsg("NSIS: bzip2 %u - lzma %u - zlib %u\n", comps[1], comps[2], comps[3]);
502         n->comp = (comps[1] < comps[2]) ? (comps[2] < comps[3] ? COMP_ZLIB : COMP_LZMA) : (comps[1] < comps[3] ? COMP_ZLIB : COMP_BZIP2);
503     }
504 
505     n->curpos = n->off + 0x1c;
506     return nsis_unpack_next(n, ctx);
507 }
508 
cli_nsis_unpack(struct nsis_st * n,cli_ctx * ctx)509 static int cli_nsis_unpack(struct nsis_st *n, cli_ctx *ctx)
510 {
511     return (n->fno) ? nsis_unpack_next(n, ctx) : nsis_headers(n, ctx);
512 }
513 
cli_scannulsft(cli_ctx * ctx,off_t offset)514 int cli_scannulsft(cli_ctx *ctx, off_t offset)
515 {
516     int ret;
517     struct nsis_st nsist;
518 
519     cli_dbgmsg("in scannulsft()\n");
520 
521     memset(&nsist, 0, sizeof(struct nsis_st));
522 
523     nsist.off = offset;
524     if (!(nsist.dir = cli_gentemp_with_prefix(ctx->sub_tmpdir, "nulsft-tmp")))
525         return CL_ETMPDIR;
526     if (mkdir(nsist.dir, 0700)) {
527         cli_dbgmsg("NSIS: Can't create temporary directory %s\n", nsist.dir);
528         free(nsist.dir);
529         return CL_ETMPDIR;
530     }
531 
532     nsist.map = ctx->fmap;
533     if (ctx->engine->keeptmp) cli_dbgmsg("NSIS: Extracting files to %s\n", nsist.dir);
534 
535     do {
536         ret = cli_nsis_unpack(&nsist, ctx);
537         if (ret == CL_SUCCESS && nsist.opened == 0) {
538             /* Don't scan a non-existent file */
539             continue;
540         }
541         if (ret == CL_SUCCESS) {
542             cli_dbgmsg("NSIS: Successully extracted file #%u\n", nsist.fno);
543             if (lseek(nsist.ofd, 0, SEEK_SET) == -1) {
544                 cli_dbgmsg("NSIS: call to lseek() failed\n");
545                 free(nsist.dir);
546                 return CL_ESEEK;
547             }
548             if (nsist.fno == 1)
549                 ret = cli_scan_desc(nsist.ofd, ctx, 0, 0, NULL, AC_SCAN_VIR, NULL, NULL); /// TODO: Extract file names
550             else
551                 ret = cli_magic_scan_desc(nsist.ofd, nsist.ofn, ctx, NULL); /// TODO: Extract file names
552             close(nsist.ofd);
553             if (!ctx->engine->keeptmp)
554                 if (cli_unlink(nsist.ofn)) ret = CL_EUNLINK;
555         } else if (ret == CL_EMAXSIZE) {
556             ret = nsist.solid ? CL_BREAK : CL_SUCCESS;
557         }
558     } while (ret == CL_SUCCESS);
559 
560     if (ret == CL_BREAK)
561         ret = CL_CLEAN;
562 
563     nsis_shutdown(&nsist);
564 
565     if (!ctx->engine->keeptmp)
566         cli_rmdirs(nsist.dir);
567 
568     free(nsist.dir);
569 
570     return ret;
571 }
572