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