source: clamav/trunk/libclamav/blob.c

Last change on this file was 1834, checked in by Yuri Dario, 8 years ago

clamav: import current vendor version into trunk.

File size: 13.7 KB
Line 
1/*
2 * Copyright (C) 2015 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3 * Copyright (C) 2007-2008 Sourcefire, Inc.
4 *
5 * Authors: Nigel Horne
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#if HAVE_CONFIG_H
23#include "clamav-config.h"
24#endif
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <errno.h>
30#include <fcntl.h>
31#include <sys/types.h>
32#include <sys/stat.h>
33
34#ifdef HAVE_SYS_PARAM_H
35#include <sys/param.h> /* for NAME_MAX */
36#endif
37
38#ifdef C_DARWIN
39#include <sys/types.h>
40#endif
41
42#ifdef HAVE_UNISTD_H
43#include <unistd.h>
44#endif
45
46#include "others.h"
47#include "mbox.h"
48#include "matcher.h"
49#include "scanners.h"
50#include "filetypes.h"
51
52#include <assert.h>
53
54/* Scehduled for rewite in 0.94 (bb#804). Disabling for now */
55/* #define MAX_SCAN_SIZE 20*1024 /\* */
56/* * The performance benefit of scanning */
57/* * early disappears on medium and */
58/* * large sized files */
59/* *\/ */
60
61static const char *blobGetFilename(const blob *b);
62
63blob *
64blobCreate(void)
65{
66#ifdef CL_DEBUG
67 blob *b = (blob *)cli_calloc(1, sizeof(blob));
68 if(b)
69 b->magic = BLOBCLASS;
70 cli_dbgmsg("blobCreate\n");
71 return b;
72#else
73 return (blob *)cli_calloc(1, sizeof(blob));
74#endif
75}
76
77void
78blobDestroy(blob *b)
79{
80#ifdef CL_DEBUG
81 cli_dbgmsg("blobDestroy %d\n", b->magic);
82#else
83 cli_dbgmsg("blobDestroy\n");
84#endif
85
86 assert(b != NULL);
87 assert(b->magic == BLOBCLASS);
88
89 if(b->name)
90 free(b->name);
91 if(b->data)
92 free(b->data);
93#ifdef CL_DEBUG
94 b->magic = INVALIDCLASS;
95#endif
96 free(b);
97}
98
99void
100blobArrayDestroy(blob *blobList[], int n)
101{
102 assert(blobList != NULL);
103
104 while(--n >= 0) {
105 cli_dbgmsg("blobArrayDestroy: %d\n", n);
106 if(blobList[n]) {
107 blobDestroy(blobList[n]);
108 blobList[n] = NULL;
109 }
110 }
111}
112
113/*
114 * No longer needed to be growable, so turn into a normal memory area which
115 * the caller must free. The passed blob is destroyed
116 */
117void *
118blobToMem(blob *b)
119{
120 void *ret;
121
122 assert(b != NULL);
123 assert(b->magic == BLOBCLASS);
124
125 if(!b->isClosed)
126 blobClose(b);
127 if(b->name)
128 free(b->name);
129#ifdef CL_DEBUG
130 b->magic = INVALIDCLASS;
131#endif
132 ret = (void *)b->data;
133 free(b);
134
135 return ret;
136}
137
138/*ARGSUSED*/
139void
140blobSetFilename(blob *b, const char *dir, const char *filename)
141{
142 assert(b != NULL);
143 assert(b->magic == BLOBCLASS);
144 assert(filename != NULL);
145
146 UNUSEDPARAM(dir);
147
148 cli_dbgmsg("blobSetFilename: %s\n", filename);
149
150 if(b->name)
151 free(b->name);
152
153 b->name = cli_strdup(filename);
154
155 if(b->name)
156 sanitiseName(b->name);
157}
158
159static const char *
160blobGetFilename(const blob *b)
161{
162 assert(b != NULL);
163 assert(b->magic == BLOBCLASS);
164
165 return b->name;
166}
167
168/*
169 * Returns <0 for failure
170 */
171int
172blobAddData(blob *b, const unsigned char *data, size_t len)
173{
174#if HAVE_CLI_GETPAGESIZE
175 static int pagesize;
176 int growth;
177#endif
178
179 assert(b != NULL);
180 assert(b->magic == BLOBCLASS);
181 assert(data != NULL);
182
183 if(len == 0)
184 return 0;
185
186 if(b->isClosed) {
187 /*
188 * Should be cli_dbgmsg, but I want to see them for now,
189 * and cli_dbgmsg doesn't support debug levels
190 */
191 cli_warnmsg("Reopening closed blob\n");
192 b->isClosed = 0;
193 }
194 /*
195 * The payoff here is between reducing the number of calls to
196 * malloc/realloc and not overallocating memory. A lot of machines
197 * are more tight with memory than one may imagine which is why
198 * we don't just allocate a *huge* amount and be done with it. Closing
199 * the blob helps because that reclaims memory. If you know the maximum
200 * size of a blob before you start adding data, use blobGrow() that's
201 * the most optimum
202 */
203#if HAVE_CLI_GETPAGESIZE
204 if(pagesize == 0) {
205 pagesize = cli_getpagesize();
206 if(pagesize == 0)
207 pagesize = 4096;
208 }
209 growth = pagesize;
210 if(len >= (size_t)pagesize)
211 growth = ((len / pagesize) + 1) * pagesize;
212
213 /*cli_dbgmsg("blobGrow: b->size %lu, b->len %lu, len %lu, growth = %u\n",
214 b->size, b->len, len, growth);*/
215
216 if(b->data == NULL) {
217 assert(b->len == 0);
218 assert(b->size == 0);
219
220 b->size = growth;
221 b->data = cli_malloc(growth);
222 } else if(b->size < b->len + (off_t)len) {
223 unsigned char *p = cli_realloc(b->data, b->size + growth);
224
225 if(p == NULL)
226 return -1;
227
228 b->size += growth;
229 b->data = p;
230 }
231#else
232 if(b->data == NULL) {
233 assert(b->len == 0);
234 assert(b->size == 0);
235
236 b->size = (off_t)len * 4;
237 b->data = cli_malloc(b->size);
238 } else if(b->size < b->len + (off_t)len) {
239 unsigned char *p = cli_realloc(b->data, b->size + (len * 4));
240
241 if(p == NULL)
242 return -1;
243
244 b->size += (off_t)len * 4;
245 b->data = p;
246 }
247#endif
248
249 if(b->data) {
250 memcpy(&b->data[b->len], data, len);
251 b->len += (off_t)len;
252 }
253 return 0;
254}
255
256unsigned char *
257blobGetData(const blob *b)
258{
259 assert(b != NULL);
260 assert(b->magic == BLOBCLASS);
261
262 if(b->len == 0)
263 return NULL;
264 return b->data;
265}
266
267size_t
268blobGetDataSize(const blob *b)
269{
270 assert(b != NULL);
271 assert(b->magic == BLOBCLASS);
272
273 return b->len;
274}
275
276void
277blobClose(blob *b)
278{
279 assert(b != NULL);
280 assert(b->magic == BLOBCLASS);
281
282 if(b->isClosed) {
283 cli_warnmsg("Attempt to close a previously closed blob\n");
284 return;
285 }
286
287 /*
288 * Nothing more is going to be added to this blob. If it'll save more
289 * than a trivial amount (say 64 bytes) of memory, shrink the allocation
290 */
291 if((b->size - b->len) >= 64) {
292 if(b->len == 0) { /* Not likely */
293 free(b->data);
294 b->data = NULL;
295 cli_dbgmsg("blobClose: recovered all %lu bytes\n",
296 (unsigned long)b->size);
297 b->size = 0;
298 } else {
299 unsigned char *ptr = cli_realloc(b->data, b->len);
300
301 if(ptr == NULL)
302 return;
303
304 cli_dbgmsg("blobClose: recovered %lu bytes from %lu\n",
305 (unsigned long)(b->size - b->len),
306 (unsigned long)b->size);
307 b->size = b->len;
308 b->data = ptr;
309 }
310 }
311 b->isClosed = 1;
312}
313
314/*
315 * Returns 0 if the blobs are the same
316 */
317int
318blobcmp(const blob *b1, const blob *b2)
319{
320 size_t s1, s2;
321
322 assert(b1 != NULL);
323 assert(b2 != NULL);
324
325 if(b1 == b2)
326 return 0;
327
328 s1 = blobGetDataSize(b1);
329 s2 = blobGetDataSize(b2);
330
331 if(s1 != s2)
332 return 1;
333
334 if((s1 == 0) && (s2 == 0))
335 return 0;
336
337 return memcmp(blobGetData(b1), blobGetData(b2), s1);
338}
339
340/*
341 * Return clamav return code
342 */
343int
344blobGrow(blob *b, size_t len)
345{
346 assert(b != NULL);
347 assert(b->magic == BLOBCLASS);
348
349 if(len == 0)
350 return CL_SUCCESS;
351
352 if(b->isClosed) {
353 /*
354 * Should be cli_dbgmsg, but I want to see them for now,
355 * and cli_dbgmsg doesn't support debug levels
356 */
357 cli_warnmsg("Growing closed blob\n");
358 b->isClosed = 0;
359 }
360 if(b->data == NULL) {
361 assert(b->len == 0);
362 assert(b->size == 0);
363
364 b->data = cli_malloc(len);
365 if(b->data)
366 b->size = (off_t)len;
367 } else {
368 unsigned char *ptr = cli_realloc(b->data, b->size + len);
369
370 if(ptr) {
371 b->size += (off_t)len;
372 b->data = ptr;
373 }
374 }
375
376 return (b->data) ? CL_SUCCESS : CL_EMEM;
377}
378
379fileblob *
380fileblobCreate(void)
381{
382#ifdef CL_DEBUG
383 fileblob *fb = (fileblob *)cli_calloc(1, sizeof(fileblob));
384 if(fb)
385 fb->b.magic = BLOBCLASS;
386 cli_dbgmsg("blobCreate\n");
387 return fb;
388#else
389 return (fileblob *)cli_calloc(1, sizeof(fileblob));
390#endif
391}
392
393/*
394 * Returns CL_CLEAN or CL_VIRUS. Destroys the fileblob and removes the file
395 * if possible
396 */
397int
398fileblobScanAndDestroy(fileblob *fb)
399{
400 switch(fileblobScan(fb)) {
401 case CL_VIRUS:
402 fileblobDestructiveDestroy(fb);
403 return CL_VIRUS;
404 case CL_BREAK:
405 fileblobDestructiveDestroy(fb);
406 return CL_CLEAN;
407 default:
408 fileblobDestroy(fb);
409 return CL_CLEAN;
410 }
411}
412
413/*
414 * Destroy the fileblob, and remove the file associated with it
415 */
416void
417fileblobDestructiveDestroy(fileblob *fb)
418{
419 if(fb->fp && fb->fullname) {
420 fclose(fb->fp);
421 cli_dbgmsg("fileblobDestructiveDestroy: %s\n", fb->fullname);
422 if(!fb->ctx || !fb->ctx->engine->keeptmp)
423 cli_unlink(fb->fullname);
424 free(fb->fullname);
425 fb->fp = NULL;
426 fb->fullname = NULL;
427 }
428 if(fb->b.name) {
429 free(fb->b.name);
430 fb->b.name = NULL;
431 }
432 fileblobDestroy(fb);
433}
434
435/*
436 * Destroy the fileblob, and remove the file associated with it if that file is
437 * empty
438 */
439void
440fileblobDestroy(fileblob *fb)
441{
442 assert(fb != NULL);
443 assert(fb->b.magic == BLOBCLASS);
444
445 if(fb->b.name && fb->fp) {
446 fclose(fb->fp);
447 if(fb->fullname) {
448 cli_dbgmsg("fileblobDestroy: %s\n", fb->fullname);
449 if(!fb->isNotEmpty) {
450 cli_dbgmsg("fileblobDestroy: not saving empty file\n");
451 cli_unlink(fb->fullname);
452 }
453 }
454 free(fb->b.name);
455
456 assert(fb->b.data == NULL);
457 } else if(fb->b.data) {
458 free(fb->b.data);
459 if(fb->b.name) {
460 cli_errmsg("fileblobDestroy: %s not saved: report to http://bugs.clamav.net\n",
461 (fb->fullname) ? fb->fullname : fb->b.name);
462 free(fb->b.name);
463 } else
464 cli_errmsg("fileblobDestroy: file not saved (%lu bytes): report to http://bugs.clamav.net\n",
465 (unsigned long)fb->b.len);
466 }
467 if(fb->fullname)
468 free(fb->fullname);
469#ifdef CL_DEBUG
470 fb->b.magic = INVALIDCLASS;
471#endif
472 free(fb);
473}
474
475void
476fileblobPartialSet(fileblob *fb, const char *fullname, const char *arg)
477{
478 UNUSEDPARAM(arg);
479
480 if(fb->b.name)
481 return;
482
483 assert(fullname != NULL);
484
485 cli_dbgmsg("fileblobPartialSet: saving to %s\n", fullname);
486
487 fb->fd = open(fullname, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY|O_EXCL, 0600);
488 if(fb->fd < 0) {
489 cli_errmsg("fileblobPartialSet: unable to create file: %s\n",fullname);
490 return;
491 }
492 fb->fp = fdopen(fb->fd, "wb");
493
494 if(fb->fp == NULL) {
495 cli_errmsg("fileblobSetFilename: fdopen failed\n");
496 close(fb->fd);
497 return;
498 }
499 blobSetFilename(&fb->b, fb->ctx ? fb->ctx->engine->tmpdir : NULL, fullname);
500 if(fb->b.data)
501 if(fileblobAddData(fb, fb->b.data, fb->b.len) == 0) {
502 free(fb->b.data);
503 fb->b.data = NULL;
504 fb->b.len = fb->b.size = 0;
505 fb->isNotEmpty = 1;
506 }
507 fb->fullname = cli_strdup(fullname);
508}
509
510void
511fileblobSetFilename(fileblob *fb, const char *dir, const char *filename)
512{
513 char *fullname;
514
515 if(fb->b.name)
516 return;
517
518 assert(filename != NULL);
519 assert(dir != NULL);
520
521 blobSetFilename(&fb->b, dir, filename);
522
523 /*
524 * Reload the filename, it may be different from the one we've
525 * asked for, e.g. '/'s taken out
526 */
527 filename = blobGetFilename(&fb->b);
528
529 assert(filename != NULL);
530
531 if (cli_gentempfd(dir, &fullname, &fb->fd)!=CL_SUCCESS) return;
532
533 cli_dbgmsg("fileblobSetFilename: file %s saved to %s\n", filename, fullname);
534
535 fb->fp = fdopen(fb->fd, "wb");
536
537 if(fb->fp == NULL) {
538 cli_errmsg("fileblobSetFilename: fdopen failed\n");
539 close(fb->fd);
540 free(fullname);
541 return;
542 }
543 if(fb->b.data)
544 if(fileblobAddData(fb, fb->b.data, fb->b.len) == 0) {
545 free(fb->b.data);
546 fb->b.data = NULL;
547 fb->b.len = fb->b.size = 0;
548 fb->isNotEmpty = 1;
549 }
550 fb->fullname = fullname;
551}
552
553int
554fileblobAddData(fileblob *fb, const unsigned char *data, size_t len)
555{
556 if(len == 0)
557 return 0;
558
559 assert(data != NULL);
560
561 if(fb->fp) {
562#if defined(MAX_SCAN_SIZE) && (MAX_SCAN_SIZE > 0)
563 const cli_ctx *ctx = fb->ctx;
564
565 if(fb->isInfected) /* pretend all was written */
566 return 0;
567 if(ctx) {
568 int do_scan = 1;
569
570 if(cli_checklimits("fileblobAddData", ctx, fb->bytes_scanned, 0, 0)!=CL_CLEAN)
571 do_scan = 0;
572
573 if(fb->bytes_scanned > MAX_SCAN_SIZE)
574 do_scan = 0;
575 if(do_scan) {
576 if(ctx->scanned)
577 *ctx->scanned += (unsigned long)len / CL_COUNT_PRECISION;
578 fb->bytes_scanned += (unsigned long)len;
579
580 if((len > 5) && cli_updatelimits(ctx, len)==CL_CLEAN && (cli_scanbuff(data, (unsigned int)len, 0, ctx->virname, ctx->engine, CL_TYPE_BINARY_DATA, NULL) == CL_VIRUS)) {
581 cli_dbgmsg("fileblobAddData: found %s\n", cli_get_last_virus_str(ctx->virname));
582 fb->isInfected = 1;
583 }
584 }
585 }
586#endif
587
588 if(fwrite(data, len, 1, fb->fp) != 1) {
589 cli_errmsg("fileblobAddData: Can't write %lu bytes to temporary file %s\n",
590 (unsigned long)len, fb->b.name);
591 return -1;
592 }
593 fb->isNotEmpty = 1;
594 return 0;
595 }
596 return blobAddData(&(fb->b), data, len);
597}
598
599const char *
600fileblobGetFilename(const fileblob *fb)
601{
602 return blobGetFilename(&(fb->b));
603}
604
605void
606fileblobSetCTX(fileblob *fb, cli_ctx *ctx)
607{
608 fb->ctx = ctx;
609}
610
611/*
612 * Performs a full scan on the fileblob, returning ClamAV status:
613 * CL_BREAK means clean
614 * CL_CLEAN means unknown
615 * CL_VIRUS means infected
616 */
617int
618fileblobScan(const fileblob *fb)
619{
620 int rc;
621 STATBUF sb;
622
623 if(fb->isInfected)
624 return CL_VIRUS;
625 if(fb->fp == NULL || fb->fullname == NULL) {
626 /* shouldn't happen, scan called before fileblobSetFilename */
627 cli_warnmsg("fileblobScan, fullname == NULL\n");
628 return CL_ENULLARG; /* there is no CL_UNKNOWN */
629 }
630 if(fb->ctx == NULL) {
631 /* fileblobSetCTX hasn't been called */
632 cli_dbgmsg("fileblobScan, ctx == NULL\n");
633 return CL_CLEAN; /* there is no CL_UNKNOWN */
634 }
635
636 fflush(fb->fp);
637 lseek(fb->fd, 0, SEEK_SET);
638 FSTAT(fb->fd, &sb);
639 if(cli_matchmeta(fb->ctx, fb->b.name, sb.st_size, sb.st_size, 0, 0, 0, NULL) == CL_VIRUS)
640 return CL_VIRUS;
641
642 rc = cli_magic_scandesc(fb->fd, fb->ctx);
643 if(rc == CL_VIRUS) {
644 cli_dbgmsg("%s is infected\n", fb->fullname);
645 return CL_VIRUS;
646 }
647 cli_dbgmsg("%s is clean\n", fb->fullname);
648 return CL_BREAK;
649}
650
651/*
652 * Doesn't perform a full scan just lets the caller know if something suspicious has
653 * been seen yet
654 */
655int
656fileblobInfected(const fileblob *fb)
657{
658 return fb->isInfected;
659}
660
661/*
662 * Different operating systems allow different characters in their filenames
663 * FIXME: What does QNX want? There is no #ifdef C_QNX, but if there were
664 * it may be best to treat it like MSDOS
665 */
666void
667sanitiseName(char *name)
668{
669 char c;
670 while((c = *name)) {
671 if(c!='.' && c!='_' && (c>'z' || c<'0' || (c>'9' && c<'A') || (c>'Z' && c<'a')))
672 *name = '_';
673 name++;
674 }
675}
Note: See TracBrowser for help on using the repository browser.