It is a shortcoming of existing scan APIs that it is not possible
to return an error without masking a verdict.
We presently work around this limitation by counting up detections at
the end and then overriding the error code with `CL_VIRUS`, if necessary.
The `cl_scanfile_ex()`, `cl_scandesc_ex()`, and `cl_scanmap_ex()` functions
should provide the scan verdict separately from the error code.
This introduces a new enum for recording and reporting a verdict:
`cl_verdict_t` with options:
- `CL_VERDICT_NOTHING_FOUND`
- `CL_VERDICT_TRUSTED`
- `CL_VERDICT_STRONG_INDICATOR`
- `CL_VERDICT_POTENTIALLY_UNWANTED`
Notably, the newer scan APIs may set the verdict to `CL_VERDICT_TRUSTED`
if there is a (hash-based) FP signature for a file, or in the cause where
Authenticode or similar certificate-based verification was performed, or
in the case where an application scan callback returned `CL_VERIFIED`.
CLAM-763
CLAM-865
| ... | ... |
@@ -181,7 +181,7 @@ static int print_chain(struct metachain *c, char *str, size_t len) |
| 181 | 181 |
return i == c->nchains - 1 ? 0 : 1; |
| 182 | 182 |
} |
| 183 | 183 |
|
| 184 |
-static cl_error_t post(int fd, int result, const char *virname, void *context) |
|
| 184 |
+static cl_error_t post(int fd, int result, const char *alert_name, void *context) |
|
| 185 | 185 |
{
|
| 186 | 186 |
struct clamscan_cb_data *d = context; |
| 187 | 187 |
struct metachain *c = NULL; |
| ... | ... |
@@ -196,10 +196,10 @@ static cl_error_t post(int fd, int result, const char *virname, void *context) |
| 196 | 196 |
if (c && c->nchains) {
|
| 197 | 197 |
print_chain(c, str, sizeof(str)); |
| 198 | 198 |
|
| 199 |
- if (c->level == c->lastadd && !virname) |
|
| 199 |
+ if (c->level == c->lastadd && !alert_name) |
|
| 200 | 200 |
free(c->chains[--c->nchains]); |
| 201 | 201 |
|
| 202 |
- if (virname && !c->lastvir) |
|
| 202 |
+ if (alert_name && !c->lastvir) |
|
| 203 | 203 |
c->lastvir = c->level; |
| 204 | 204 |
} |
| 205 | 205 |
|
| ... | ... |
@@ -275,7 +275,7 @@ static cl_error_t meta(const char *container_type, unsigned long fsize_container |
| 275 | 275 |
return CL_CLEAN; |
| 276 | 276 |
} |
| 277 | 277 |
|
| 278 |
-static void clamscan_virus_found_cb(int fd, const char *virname, void *context) |
|
| 278 |
+static void clamscan_virus_found_cb(int fd, const char *alert_name, void *context) |
|
| 279 | 279 |
{
|
| 280 | 280 |
struct clamscan_cb_data *data = (struct clamscan_cb_data *)context; |
| 281 | 281 |
const char *filename; |
| ... | ... |
@@ -288,7 +288,7 @@ static void clamscan_virus_found_cb(int fd, const char *virname, void *context) |
| 288 | 288 |
filename = data->filename; |
| 289 | 289 |
else |
| 290 | 290 |
filename = "(filename not set)"; |
| 291 |
- logg(LOGG_INFO, "%s: %s FOUND\n", filename, virname); |
|
| 291 |
+ logg(LOGG_INFO, "%s: %s FOUND\n", filename, alert_name); |
|
| 292 | 292 |
return; |
| 293 | 293 |
} |
| 294 | 294 |
|
| ... | ... |
@@ -299,7 +299,8 @@ static void scanfile(const char *filename, struct cl_engine *engine, const struc |
| 299 | 299 |
int included = 0; |
| 300 | 300 |
unsigned i; |
| 301 | 301 |
const struct optstruct *opt; |
| 302 |
- const char *virname = NULL; |
|
| 302 |
+ cl_verdict_t verdict = CL_VERDICT_NOTHING_FOUND; |
|
| 303 |
+ const char *alert_name = NULL; |
|
| 303 | 304 |
STATBUF sb; |
| 304 | 305 |
struct metachain chain = {0};
|
| 305 | 306 |
struct clamscan_cb_data data = {0};
|
| ... | ... |
@@ -433,43 +434,63 @@ static void scanfile(const char *filename, struct cl_engine *engine, const struc |
| 433 | 433 |
|
| 434 | 434 |
data.chain = &chain; |
| 435 | 435 |
data.filename = filename; |
| 436 |
- if (CL_VIRUS == (ret = cl_scandesc_ex( |
|
| 437 |
- fd, |
|
| 438 |
- filename, |
|
| 439 |
- &virname, |
|
| 440 |
- &info.bytes_scanned, |
|
| 441 |
- engine, options, |
|
| 442 |
- &data, |
|
| 443 |
- hash_hint, |
|
| 444 |
- hash_out, |
|
| 445 |
- hash_alg, |
|
| 446 |
- file_type_hint, |
|
| 447 |
- file_type_out))) {
|
|
| 448 |
- if (optget(opts, "archive-verbose")->enabled) {
|
|
| 449 |
- if (chain.nchains > 1) {
|
|
| 450 |
- char str[128]; |
|
| 451 |
- int toolong = print_chain(&chain, str, sizeof(str)); |
|
| 452 |
- |
|
| 453 |
- logg(LOGG_INFO, "%s%s!(%llu)%s: %s FOUND\n", str, toolong ? "..." : "", (long long unsigned)(chain.lastvir - 1), chain.chains[chain.nchains - 1], virname); |
|
| 454 |
- } else if (chain.lastvir) {
|
|
| 455 |
- logg(LOGG_INFO, "%s!(%llu): %s FOUND\n", filename, (long long unsigned)(chain.lastvir - 1), virname); |
|
| 456 |
- } |
|
| 457 |
- } |
|
| 458 |
- info.files++; |
|
| 459 |
- info.ifiles++; |
|
| 460 | 436 |
|
| 461 |
- if (bell) |
|
| 462 |
- fprintf(stderr, "\007"); |
|
| 463 |
- } else if (ret == CL_CLEAN) {
|
|
| 464 |
- if (!printinfected && printclean) |
|
| 465 |
- mprintf(LOGG_INFO, "%s: OK\n", filename); |
|
| 437 |
+ ret = cl_scandesc_ex( |
|
| 438 |
+ fd, |
|
| 439 |
+ filename, |
|
| 440 |
+ &verdict, |
|
| 441 |
+ &alert_name, |
|
| 442 |
+ &info.bytes_scanned, |
|
| 443 |
+ engine, options, |
|
| 444 |
+ &data, |
|
| 445 |
+ hash_hint, |
|
| 446 |
+ hash_out, |
|
| 447 |
+ hash_alg, |
|
| 448 |
+ file_type_hint, |
|
| 449 |
+ file_type_out); |
|
| 450 |
+ |
|
| 451 |
+ switch (verdict) {
|
|
| 452 |
+ case CL_VERDICT_NOTHING_FOUND: {
|
|
| 453 |
+ if (CL_SUCCESS == ret) {
|
|
| 454 |
+ if (!printinfected && printclean) {
|
|
| 455 |
+ mprintf(LOGG_INFO, "%s: OK\n", filename); |
|
| 456 |
+ } |
|
| 457 |
+ info.files++; |
|
| 458 |
+ } else {
|
|
| 459 |
+ if (!printinfected) |
|
| 460 |
+ logg(LOGG_INFO, "%s: %s ERROR\n", filename, cl_strerror(ret)); |
|
| 466 | 461 |
|
| 467 |
- info.files++; |
|
| 468 |
- } else {
|
|
| 469 |
- if (!printinfected) |
|
| 470 |
- logg(LOGG_INFO, "%s: %s ERROR\n", filename, cl_strerror(ret)); |
|
| 462 |
+ info.errors++; |
|
| 463 |
+ } |
|
| 464 |
+ } break; |
|
| 471 | 465 |
|
| 472 |
- info.errors++; |
|
| 466 |
+ case CL_VERDICT_TRUSTED: {
|
|
| 467 |
+ // TODO: Option to print "TRUSTED" verdict instead of "OK"? |
|
| 468 |
+ if (!printinfected && printclean) {
|
|
| 469 |
+ mprintf(LOGG_INFO, "%s: OK\n", filename); |
|
| 470 |
+ } |
|
| 471 |
+ info.files++; |
|
| 472 |
+ } break; |
|
| 473 |
+ |
|
| 474 |
+ case CL_VERDICT_STRONG_INDICATOR: |
|
| 475 |
+ case CL_VERDICT_POTENTIALLY_UNWANTED: {
|
|
| 476 |
+ if (optget(opts, "archive-verbose")->enabled) {
|
|
| 477 |
+ if (chain.nchains > 1) {
|
|
| 478 |
+ char str[128]; |
|
| 479 |
+ int toolong = print_chain(&chain, str, sizeof(str)); |
|
| 480 |
+ |
|
| 481 |
+ logg(LOGG_INFO, "%s%s!(%llu)%s: %s FOUND\n", str, toolong ? "..." : "", (long long unsigned)(chain.lastvir - 1), chain.chains[chain.nchains - 1], alert_name); |
|
| 482 |
+ } else if (chain.lastvir) {
|
|
| 483 |
+ logg(LOGG_INFO, "%s!(%llu): %s FOUND\n", filename, (long long unsigned)(chain.lastvir - 1), alert_name); |
|
| 484 |
+ } |
|
| 485 |
+ } |
|
| 486 |
+ info.files++; |
|
| 487 |
+ info.ifiles++; |
|
| 488 |
+ |
|
| 489 |
+ if (bell) {
|
|
| 490 |
+ fprintf(stderr, "\007"); |
|
| 491 |
+ } |
|
| 492 |
+ } break; |
|
| 473 | 493 |
} |
| 474 | 494 |
|
| 475 | 495 |
if (NULL != hash) {
|
| ... | ... |
@@ -488,7 +509,7 @@ done: |
| 488 | 488 |
/* |
| 489 | 489 |
* Run the action callback if the file was infected. |
| 490 | 490 |
*/ |
| 491 |
- if (ret == CL_VIRUS && action) {
|
|
| 491 |
+ if (((verdict == CL_VERDICT_STRONG_INDICATOR) || (verdict == CL_VERDICT_POTENTIALLY_UNWANTED)) && action) {
|
|
| 492 | 492 |
action(filename); |
| 493 | 493 |
} |
| 494 | 494 |
|
| ... | ... |
@@ -630,9 +651,10 @@ static int scanstdin(const struct cl_engine *engine, const struct optstruct *opt |
| 630 | 630 |
{
|
| 631 | 631 |
cl_error_t ret; |
| 632 | 632 |
|
| 633 |
- size_t fsize = 0; |
|
| 634 |
- const char *virname = NULL; |
|
| 635 |
- const char *tmpdir = NULL; |
|
| 633 |
+ size_t fsize = 0; |
|
| 634 |
+ cl_verdict_t verdict = CL_VERDICT_NOTHING_FOUND; |
|
| 635 |
+ const char *alert_name = NULL; |
|
| 636 |
+ const char *tmpdir = NULL; |
|
| 636 | 637 |
char *filename, buff[FILEBUFF]; |
| 637 | 638 |
size_t bread; |
| 638 | 639 |
FILE *fs; |
| ... | ... |
@@ -702,30 +724,51 @@ static int scanstdin(const struct cl_engine *engine, const struct optstruct *opt |
| 702 | 702 |
|
| 703 | 703 |
data.filename = "stdin"; |
| 704 | 704 |
data.chain = NULL; |
| 705 |
- if (CL_VIRUS == (ret = cl_scanfile_ex( |
|
| 706 |
- filename, |
|
| 707 |
- &virname, |
|
| 708 |
- &info.bytes_scanned, |
|
| 709 |
- engine, |
|
| 710 |
- options, |
|
| 711 |
- &data, |
|
| 712 |
- hash_hint, |
|
| 713 |
- hash_out, |
|
| 714 |
- hash_alg, |
|
| 715 |
- file_type_hint, |
|
| 716 |
- file_type_out))) {
|
|
| 717 |
- info.ifiles++; |
|
| 718 |
- |
|
| 719 |
- if (bell) |
|
| 720 |
- fprintf(stderr, "\007"); |
|
| 721 |
- } else if (ret == CL_CLEAN) {
|
|
| 722 |
- if (!printinfected) |
|
| 723 |
- mprintf(LOGG_INFO, "stdin: OK\n"); |
|
| 724 |
- } else {
|
|
| 725 |
- if (!printinfected) |
|
| 726 |
- logg(LOGG_INFO, "stdin: %s ERROR\n", cl_strerror(ret)); |
|
| 727 | 705 |
|
| 728 |
- info.errors++; |
|
| 706 |
+ ret = cl_scanfile_ex( |
|
| 707 |
+ filename, |
|
| 708 |
+ &verdict, |
|
| 709 |
+ &alert_name, |
|
| 710 |
+ &info.bytes_scanned, |
|
| 711 |
+ engine, |
|
| 712 |
+ options, |
|
| 713 |
+ &data, |
|
| 714 |
+ hash_hint, |
|
| 715 |
+ hash_out, |
|
| 716 |
+ hash_alg, |
|
| 717 |
+ file_type_hint, |
|
| 718 |
+ file_type_out); |
|
| 719 |
+ |
|
| 720 |
+ switch (verdict) {
|
|
| 721 |
+ case CL_VERDICT_NOTHING_FOUND: {
|
|
| 722 |
+ if (CL_SUCCESS == ret) {
|
|
| 723 |
+ if (!printinfected) {
|
|
| 724 |
+ mprintf(LOGG_INFO, "stdin: OK\n"); |
|
| 725 |
+ } |
|
| 726 |
+ info.files++; |
|
| 727 |
+ } else {
|
|
| 728 |
+ if (!printinfected) {
|
|
| 729 |
+ logg(LOGG_INFO, "stdin: %s ERROR\n", cl_strerror(ret)); |
|
| 730 |
+ } |
|
| 731 |
+ info.errors++; |
|
| 732 |
+ } |
|
| 733 |
+ } break; |
|
| 734 |
+ |
|
| 735 |
+ case CL_VERDICT_TRUSTED: {
|
|
| 736 |
+ // TODO: Option to print "TRUSTED" verdict instead of "OK"? |
|
| 737 |
+ if (!printinfected) {
|
|
| 738 |
+ mprintf(LOGG_INFO, "stdin: OK\n"); |
|
| 739 |
+ } |
|
| 740 |
+ } break; |
|
| 741 |
+ |
|
| 742 |
+ case CL_VERDICT_STRONG_INDICATOR: |
|
| 743 |
+ case CL_VERDICT_POTENTIALLY_UNWANTED: {
|
|
| 744 |
+ info.ifiles++; |
|
| 745 |
+ |
|
| 746 |
+ if (bell) {
|
|
| 747 |
+ fprintf(stderr, "\007"); |
|
| 748 |
+ } |
|
| 749 |
+ } break; |
|
| 729 | 750 |
} |
| 730 | 751 |
|
| 731 | 752 |
if (NULL != hash) {
|
| ... | ... |
@@ -533,8 +533,10 @@ int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *inf |
| 533 | 533 |
{
|
| 534 | 534 |
int fd; |
| 535 | 535 |
int scantype; |
| 536 |
- int ret = CL_CLEAN; |
|
| 537 |
- const char *virname = NULL; |
|
| 536 |
+ int ret = CL_CLEAN; |
|
| 537 |
+ |
|
| 538 |
+ cl_verdict_t verdict = CL_VERDICT_NOTHING_FOUND; |
|
| 539 |
+ const char *alert_name = NULL; |
|
| 538 | 540 |
|
| 539 | 541 |
logg(LOGG_DEBUG, "Scanning %s\n", filename); |
| 540 | 542 |
|
| ... | ... |
@@ -565,7 +567,8 @@ int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *inf |
| 565 | 565 |
ret = cl_scandesc_ex( |
| 566 | 566 |
fd, |
| 567 | 567 |
filename, |
| 568 |
- &virname, |
|
| 568 |
+ &verdict, |
|
| 569 |
+ &alert_name, |
|
| 569 | 570 |
&info->bytes_scanned, |
| 570 | 571 |
info->engine, |
| 571 | 572 |
info->options, |
| ... | ... |
@@ -576,11 +579,23 @@ int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *inf |
| 576 | 576 |
NULL, |
| 577 | 577 |
NULL, |
| 578 | 578 |
NULL); |
| 579 |
- if (ret == CL_VIRUS) {
|
|
| 580 |
- logg(LOGG_INFO, "%s: %s FOUND\n", filename, virname); |
|
| 581 |
- info->ifiles++; |
|
| 582 |
- } else if (scan_data->printclean) {
|
|
| 583 |
- logg(LOGG_INFO, "%s: OK \n", filename); |
|
| 579 |
+ |
|
| 580 |
+ switch (verdict) {
|
|
| 581 |
+ case CL_VERDICT_NOTHING_FOUND: {
|
|
| 582 |
+ logg(LOGG_INFO, "%s: OK \n", filename); |
|
| 583 |
+ ret = CL_CLEAN; |
|
| 584 |
+ } break; |
|
| 585 |
+ case CL_VERDICT_TRUSTED: {
|
|
| 586 |
+ // TODO: Option to print "TRUSTED" verdict instead of "OK"? |
|
| 587 |
+ logg(LOGG_INFO, "%s: OK \n", filename); |
|
| 588 |
+ ret = CL_CLEAN; |
|
| 589 |
+ } break; |
|
| 590 |
+ case CL_VERDICT_STRONG_INDICATOR: |
|
| 591 |
+ case CL_VERDICT_POTENTIALLY_UNWANTED: {
|
|
| 592 |
+ logg(LOGG_INFO, "%s: %s FOUND\n", filename, alert_name); |
|
| 593 |
+ info->ifiles++; |
|
| 594 |
+ ret = CL_VIRUS; |
|
| 595 |
+ } break; |
|
| 584 | 596 |
} |
| 585 | 597 |
} |
| 586 | 598 |
|
| ... | ... |
@@ -1008,41 +1008,76 @@ done: |
| 1008 | 1008 |
const char *cl_error_t_to_string(cl_error_t clerror) |
| 1009 | 1009 |
{
|
| 1010 | 1010 |
switch (clerror) {
|
| 1011 |
- case CL_SUCCESS: return "CL_SUCCESS"; |
|
| 1012 |
- case CL_VIRUS: return "CL_VIRUS"; |
|
| 1013 |
- case CL_ENULLARG: return "CL_ENULLARG"; |
|
| 1014 |
- case CL_EARG: return "CL_EARG"; |
|
| 1015 |
- case CL_EMALFDB: return "CL_EMALFDB"; |
|
| 1016 |
- case CL_ECVD: return "CL_ECVD"; |
|
| 1017 |
- case CL_EVERIFY: return "CL_EVERIFY"; |
|
| 1018 |
- case CL_EUNPACK: return "CL_EUNPACK"; |
|
| 1019 |
- case CL_EPARSE: return "CL_EPARSE"; |
|
| 1020 |
- case CL_EOPEN: return "CL_EOPEN"; |
|
| 1021 |
- case CL_ECREAT: return "CL_ECREAT"; |
|
| 1022 |
- case CL_EUNLINK: return "CL_EUNLINK"; |
|
| 1023 |
- case CL_ESTAT: return "CL_ESTAT"; |
|
| 1024 |
- case CL_EREAD: return "CL_EREAD"; |
|
| 1025 |
- case CL_ESEEK: return "CL_ESEEK"; |
|
| 1026 |
- case CL_EWRITE: return "CL_EWRITE"; |
|
| 1027 |
- case CL_EDUP: return "CL_EDUP"; |
|
| 1028 |
- case CL_EACCES: return "CL_EACCES"; |
|
| 1029 |
- case CL_ETMPFILE: return "CL_ETMPFILE"; |
|
| 1030 |
- case CL_ETMPDIR: return "CL_ETMPDIR"; |
|
| 1031 |
- case CL_EMAP: return "CL_EMAP"; |
|
| 1032 |
- case CL_EMEM: return "CL_EMEM"; |
|
| 1033 |
- case CL_ETIMEOUT: return "CL_ETIMEOUT"; |
|
| 1034 |
- case CL_EMAXREC: return "CL_EMAXREC"; |
|
| 1035 |
- case CL_EMAXSIZE: return "CL_EMAXSIZE"; |
|
| 1036 |
- case CL_EMAXFILES: return "CL_EMAXFILES"; |
|
| 1037 |
- case CL_EFORMAT: return "CL_EFORMAT"; |
|
| 1038 |
- case CL_EBYTECODE: return "CL_EBYTECODE"; |
|
| 1039 |
- case CL_EBYTECODE_TESTFAIL: return "CL_EBYTECODE_TESTFAIL"; |
|
| 1040 |
- case CL_ELOCK: return "CL_ELOCK"; |
|
| 1041 |
- case CL_EBUSY: return "CL_EBUSY"; |
|
| 1042 |
- case CL_ESTATE: return "CL_ESTATE"; |
|
| 1043 |
- case CL_ERROR: return "CL_ERROR"; |
|
| 1044 |
- case CL_VERIFIED: return "CL_VERIFIED"; |
|
| 1045 |
- case CL_BREAK: return "CL_BREAK"; |
|
| 1011 |
+ case CL_SUCCESS: |
|
| 1012 |
+ return "CL_SUCCESS"; |
|
| 1013 |
+ case CL_VIRUS: |
|
| 1014 |
+ return "CL_VIRUS"; |
|
| 1015 |
+ case CL_ENULLARG: |
|
| 1016 |
+ return "CL_ENULLARG"; |
|
| 1017 |
+ case CL_EARG: |
|
| 1018 |
+ return "CL_EARG"; |
|
| 1019 |
+ case CL_EMALFDB: |
|
| 1020 |
+ return "CL_EMALFDB"; |
|
| 1021 |
+ case CL_ECVD: |
|
| 1022 |
+ return "CL_ECVD"; |
|
| 1023 |
+ case CL_EVERIFY: |
|
| 1024 |
+ return "CL_EVERIFY"; |
|
| 1025 |
+ case CL_EUNPACK: |
|
| 1026 |
+ return "CL_EUNPACK"; |
|
| 1027 |
+ case CL_EPARSE: |
|
| 1028 |
+ return "CL_EPARSE"; |
|
| 1029 |
+ case CL_EOPEN: |
|
| 1030 |
+ return "CL_EOPEN"; |
|
| 1031 |
+ case CL_ECREAT: |
|
| 1032 |
+ return "CL_ECREAT"; |
|
| 1033 |
+ case CL_EUNLINK: |
|
| 1034 |
+ return "CL_EUNLINK"; |
|
| 1035 |
+ case CL_ESTAT: |
|
| 1036 |
+ return "CL_ESTAT"; |
|
| 1037 |
+ case CL_EREAD: |
|
| 1038 |
+ return "CL_EREAD"; |
|
| 1039 |
+ case CL_ESEEK: |
|
| 1040 |
+ return "CL_ESEEK"; |
|
| 1041 |
+ case CL_EWRITE: |
|
| 1042 |
+ return "CL_EWRITE"; |
|
| 1043 |
+ case CL_EDUP: |
|
| 1044 |
+ return "CL_EDUP"; |
|
| 1045 |
+ case CL_EACCES: |
|
| 1046 |
+ return "CL_EACCES"; |
|
| 1047 |
+ case CL_ETMPFILE: |
|
| 1048 |
+ return "CL_ETMPFILE"; |
|
| 1049 |
+ case CL_ETMPDIR: |
|
| 1050 |
+ return "CL_ETMPDIR"; |
|
| 1051 |
+ case CL_EMAP: |
|
| 1052 |
+ return "CL_EMAP"; |
|
| 1053 |
+ case CL_EMEM: |
|
| 1054 |
+ return "CL_EMEM"; |
|
| 1055 |
+ case CL_ETIMEOUT: |
|
| 1056 |
+ return "CL_ETIMEOUT"; |
|
| 1057 |
+ case CL_EMAXREC: |
|
| 1058 |
+ return "CL_EMAXREC"; |
|
| 1059 |
+ case CL_EMAXSIZE: |
|
| 1060 |
+ return "CL_EMAXSIZE"; |
|
| 1061 |
+ case CL_EMAXFILES: |
|
| 1062 |
+ return "CL_EMAXFILES"; |
|
| 1063 |
+ case CL_EFORMAT: |
|
| 1064 |
+ return "CL_EFORMAT"; |
|
| 1065 |
+ case CL_EBYTECODE: |
|
| 1066 |
+ return "CL_EBYTECODE"; |
|
| 1067 |
+ case CL_EBYTECODE_TESTFAIL: |
|
| 1068 |
+ return "CL_EBYTECODE_TESTFAIL"; |
|
| 1069 |
+ case CL_ELOCK: |
|
| 1070 |
+ return "CL_ELOCK"; |
|
| 1071 |
+ case CL_EBUSY: |
|
| 1072 |
+ return "CL_EBUSY"; |
|
| 1073 |
+ case CL_ESTATE: |
|
| 1074 |
+ return "CL_ESTATE"; |
|
| 1075 |
+ case CL_ERROR: |
|
| 1076 |
+ return "CL_ERROR"; |
|
| 1077 |
+ case CL_VERIFIED: |
|
| 1078 |
+ return "CL_VERIFIED"; |
|
| 1079 |
+ case CL_BREAK: |
|
| 1080 |
+ return "CL_BREAK"; |
|
| 1046 | 1081 |
default: |
| 1047 | 1082 |
return "Unknown error code"; |
| 1048 | 1083 |
} |
| ... | ... |
@@ -1195,7 +1230,10 @@ int main(int argc, char **argv) |
| 1195 | 1195 |
script_context_t *script_context = NULL; |
| 1196 | 1196 |
|
| 1197 | 1197 |
unsigned long int size = 0; |
| 1198 |
- const char *virname; |
|
| 1198 |
+ |
|
| 1199 |
+ cl_verdict_t verdict = CL_VERDICT_NOTHING_FOUND; |
|
| 1200 |
+ const char *alert_name; |
|
| 1201 |
+ |
|
| 1199 | 1202 |
struct cl_engine *engine = NULL; |
| 1200 | 1203 |
struct cl_scan_options options; |
| 1201 | 1204 |
unsigned int signo = 0; |
| ... | ... |
@@ -1336,27 +1374,21 @@ int main(int argc, char **argv) |
| 1336 | 1336 |
* Run the scan. |
| 1337 | 1337 |
* Note that the callbacks will be called during this function. |
| 1338 | 1338 |
*/ |
| 1339 |
- if (CL_VIRUS == (ret = cl_scandesc_ex( |
|
| 1340 |
- target_fd, |
|
| 1341 |
- filename, |
|
| 1342 |
- &virname, |
|
| 1343 |
- &size, |
|
| 1344 |
- engine, |
|
| 1345 |
- &options, |
|
| 1346 |
- script_context, // context, |
|
| 1347 |
- hash_hint, // hash_hint, |
|
| 1348 |
- &hash_out, // hash_out, |
|
| 1349 |
- hash_alg, // hash_alg, |
|
| 1350 |
- file_type_hint, // file_type_hint, |
|
| 1351 |
- &file_type_out // file_type_out |
|
| 1352 |
- ))) {
|
|
| 1353 |
- printf("Virus detected: %s\n", virname);
|
|
| 1354 |
- } else {
|
|
| 1355 |
- if (ret != CL_SUCCESS) {
|
|
| 1356 |
- printf("Error: %s\n", cl_strerror(ret));
|
|
| 1357 |
- goto done; |
|
| 1358 |
- } |
|
| 1359 |
- } |
|
| 1339 |
+ ret = cl_scandesc_ex( |
|
| 1340 |
+ target_fd, |
|
| 1341 |
+ filename, |
|
| 1342 |
+ &verdict, |
|
| 1343 |
+ &alert_name, |
|
| 1344 |
+ &size, |
|
| 1345 |
+ engine, |
|
| 1346 |
+ &options, |
|
| 1347 |
+ script_context, // context, |
|
| 1348 |
+ hash_hint, // hash_hint, |
|
| 1349 |
+ &hash_out, // hash_out, |
|
| 1350 |
+ hash_alg, // hash_alg, |
|
| 1351 |
+ file_type_hint, // file_type_hint, |
|
| 1352 |
+ &file_type_out // file_type_out |
|
| 1353 |
+ ); |
|
| 1360 | 1354 |
|
| 1361 | 1355 |
/* Calculate size of scanned data */ |
| 1362 | 1356 |
printf("\n");
|
| ... | ... |
@@ -1373,7 +1405,24 @@ int main(int argc, char **argv) |
| 1373 | 1373 |
} else {
|
| 1374 | 1374 |
printf("No file type provided for this file.\n");
|
| 1375 | 1375 |
} |
| 1376 |
- printf("Return code: %s (%d)\n", cl_strerror(ret), ret);
|
|
| 1376 |
+ switch (verdict) {
|
|
| 1377 |
+ case CL_VERDICT_NOTHING_FOUND: {
|
|
| 1378 |
+ printf("Verdict: Nothing found.\n");
|
|
| 1379 |
+ } break; |
|
| 1380 |
+ |
|
| 1381 |
+ case CL_VERDICT_TRUSTED: {
|
|
| 1382 |
+ printf("Verdict: Trusted.\n");
|
|
| 1383 |
+ } break; |
|
| 1384 |
+ |
|
| 1385 |
+ case CL_VERDICT_STRONG_INDICATOR: {
|
|
| 1386 |
+ printf("Verdict: Found Strong Indicator: %s\n", alert_name);
|
|
| 1387 |
+ } break; |
|
| 1388 |
+ |
|
| 1389 |
+ case CL_VERDICT_POTENTIALLY_UNWANTED: {
|
|
| 1390 |
+ printf("Verdict: Found Potentially Unwanted Indicator: %s\n", alert_name);
|
|
| 1391 |
+ } break; |
|
| 1392 |
+ } |
|
| 1393 |
+ printf("Return Code: %s (%d)\n", cl_error_t_to_string(ret), ret);
|
|
| 1377 | 1394 |
|
| 1378 | 1395 |
status = ret == CL_VIRUS ? 1 : 0; |
| 1379 | 1396 |
|
| ... | ... |
@@ -2440,5 +2440,9 @@ cl_error_t asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset |
| 2440 | 2440 |
} |
| 2441 | 2441 |
|
| 2442 | 2442 |
cli_dbgmsg("asn1_check_mscat: file with valid authenticode signature, trusted\n");
|
| 2443 |
+ |
|
| 2444 |
+ // Remove any evidence for this layer and set the verdict to trusted. |
|
| 2445 |
+ (void)cli_trust_this_layer(ctx); |
|
| 2446 |
+ |
|
| 2443 | 2447 |
return CL_VERIFIED; |
| 2444 | 2448 |
} |
| ... | ... |
@@ -81,7 +81,19 @@ extern "C" {
|
| 81 | 81 |
|
| 82 | 82 |
#define CL_COUNT_PRECISION 4096 |
| 83 | 83 |
|
| 84 |
-/* return codes */ |
|
| 84 |
+/** |
|
| 85 |
+ * @brief Scan verdicts for cl_scanmap_ex(), cl_scanfile_ex(), and cl_scandesc_ex(). |
|
| 86 |
+ */ |
|
| 87 |
+typedef enum cl_verdict_t {
|
|
| 88 |
+ CL_VERDICT_NOTHING_FOUND = 0, /**< No alerting signatures matched. */ |
|
| 89 |
+ CL_VERDICT_TRUSTED, /**< The scan target has been deemed trusted (e.g. by FP signature or Authenticode). */ |
|
| 90 |
+ CL_VERDICT_STRONG_INDICATOR, /**< One or more strong indicator signatures matched. */ |
|
| 91 |
+ CL_VERDICT_POTENTIALLY_UNWANTED, /**< One or more potentially unwanted signatures matched. */ |
|
| 92 |
+} cl_verdict_t; |
|
| 93 |
+ |
|
| 94 |
+/** |
|
| 95 |
+ * @brief Return codes used by libclamav functions. |
|
| 96 |
+ */ |
|
| 85 | 97 |
typedef enum cl_error_t {
|
| 86 | 98 |
/* libclamav specific */ |
| 87 | 99 |
CL_CLEAN = 0, |
| ... | ... |
@@ -123,7 +135,7 @@ typedef enum cl_error_t {
|
| 123 | 123 |
CL_EBUSY, |
| 124 | 124 |
CL_ESTATE, |
| 125 | 125 |
|
| 126 |
- CL_VERIFIED, /** The binary has been deemed trusted */ |
|
| 126 |
+ CL_VERIFIED, /** The scan target has been deemed trusted */ |
|
| 127 | 127 |
CL_ERROR, /** Unspecified / generic error */ |
| 128 | 128 |
|
| 129 | 129 |
/* no error codes below this line please */ |
| ... | ... |
@@ -1630,9 +1642,12 @@ extern cl_error_t cl_scandesc_callback( |
| 1630 | 1630 |
* This variant also upgrades the `scanned` output parameter to a 64-bit integer. |
| 1631 | 1631 |
* |
| 1632 | 1632 |
* @param desc File descriptor of an open file. The caller must provide this or the map. |
| 1633 |
- * @param filename (optional) Filepath of the open file descriptor or file map. |
|
| 1634 |
- * @param[out] virname Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan matches against a signature. |
|
| 1635 |
- * @param[out] scanned The (exact) number of bytes scanned. |
|
| 1633 |
+ * @param filename (Optional) Filepath of the open file descriptor or file map. |
|
| 1634 |
+ * @param[out] verdict_out A pointer to a cl_verdict_t that will be set to the scan verdict. |
|
| 1635 |
+ * You should check the verdict even if the function returns an error. |
|
| 1636 |
+ * @param[out] last_alert_out Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan |
|
| 1637 |
+ * matches against a signature. |
|
| 1638 |
+ * @param[out] scanned_out The (exact) number of bytes scanned. |
|
| 1636 | 1639 |
* @param engine The scanning engine. |
| 1637 | 1640 |
* @param scanoptions Scanning options. |
| 1638 | 1641 |
* @param[in,out] context (Optional) An application-defined context struct, opaque to libclamav. |
| ... | ... |
@@ -1653,15 +1668,16 @@ extern cl_error_t cl_scandesc_callback( |
| 1653 | 1653 |
* of the top layer as determined by ClamAV. |
| 1654 | 1654 |
* Will take the form of the standard ClamAV file type format. E.g. "CL_TYPE_PE". |
| 1655 | 1655 |
* See also: https://docs.clamav.net/appendix/FileTypes.html#file-types |
| 1656 |
- * @return cl_error_t CL_CLEAN if no signature matched. |
|
| 1657 |
- * CL_VIRUS if a signature matched. |
|
| 1658 |
- * Another CL_E* error code if an error occurred. |
|
| 1656 |
+ * @return cl_error_t CL_SUCCESS if no error occured. |
|
| 1657 |
+ * Otherwise a CL_E* error code. |
|
| 1658 |
+ * Does NOT return CL_VIRUS for a signature match. Check the `verdict_out` parameter instead. |
|
| 1659 | 1659 |
*/ |
| 1660 | 1660 |
extern cl_error_t cl_scandesc_ex( |
| 1661 | 1661 |
int desc, |
| 1662 | 1662 |
const char *filename, |
| 1663 |
- const char **virname, |
|
| 1664 |
- uint64_t *scanned, |
|
| 1663 |
+ cl_verdict_t *verdict_out, |
|
| 1664 |
+ const char **last_alert_out, |
|
| 1665 |
+ uint64_t *scanned_out, |
|
| 1665 | 1666 |
const struct cl_engine *engine, |
| 1666 | 1667 |
struct cl_scan_options *scanoptions, |
| 1667 | 1668 |
void *context, |
| ... | ... |
@@ -1727,8 +1743,11 @@ extern cl_error_t cl_scanfile_callback( |
| 1727 | 1727 |
* This variant also upgrades the `scanned` output parameter to a 64-bit integer. |
| 1728 | 1728 |
* |
| 1729 | 1729 |
* @param filename Filepath of the file to be scanned. |
| 1730 |
- * @param[out] virname Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan matches against a signature. |
|
| 1731 |
- * @param[out] scanned The exact number of bytes scanned. |
|
| 1730 |
+ * @param[out] verdict_out A pointer to a cl_verdict_t that will be set to the scan verdict. |
|
| 1731 |
+ * You should check the verdict even if the function returns an error. |
|
| 1732 |
+ * @param[out] last_alert_out Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan |
|
| 1733 |
+ * matches against a signature. |
|
| 1734 |
+ * @param[out] scanned_out The (exact) number of bytes scanned. |
|
| 1732 | 1735 |
* @param engine The scanning engine. |
| 1733 | 1736 |
* @param scanoptions Scanning options. |
| 1734 | 1737 |
* @param[in,out] context (Optional) An application-defined context struct, opaque to libclamav. |
| ... | ... |
@@ -1749,14 +1768,15 @@ extern cl_error_t cl_scanfile_callback( |
| 1749 | 1749 |
* of the top layer as determined by ClamAV. |
| 1750 | 1750 |
* Will take the form of the standard ClamAV file type format. E.g. "CL_TYPE_PE". |
| 1751 | 1751 |
* See also: https://docs.clamav.net/appendix/FileTypes.html#file-types |
| 1752 |
- * @return cl_error_t CL_CLEAN if no signature matched. |
|
| 1753 |
- * CL_VIRUS if a signature matched. |
|
| 1754 |
- * Another CL_E* error code if an error occurred. |
|
| 1752 |
+ * @return cl_error_t CL_SUCCESS if no error occured. |
|
| 1753 |
+ * Otherwise a CL_E* error code. |
|
| 1754 |
+ * Does NOT return CL_VIRUS for a signature match. Check the `verdict_out` parameter instead. |
|
| 1755 | 1755 |
*/ |
| 1756 | 1756 |
extern cl_error_t cl_scanfile_ex( |
| 1757 | 1757 |
const char *filename, |
| 1758 |
- const char **virname, |
|
| 1759 |
- uint64_t *scanned, |
|
| 1758 |
+ cl_verdict_t *verdict_out, |
|
| 1759 |
+ const char **last_alert_out, |
|
| 1760 |
+ uint64_t *scanned_out, |
|
| 1760 | 1761 |
const struct cl_engine *engine, |
| 1761 | 1762 |
struct cl_scan_options *scanoptions, |
| 1762 | 1763 |
void *context, |
| ... | ... |
@@ -1819,13 +1839,15 @@ extern cl_error_t cl_scanmap_callback( |
| 1819 | 1819 |
* This variant also upgrades the `scanned` output parameter to a 64-bit integer. |
| 1820 | 1820 |
* |
| 1821 | 1821 |
* @param map Buffer to be scanned, in form of a cl_fmap_t. |
| 1822 |
- * @param filename Name of data origin. Does not need to be an actual |
|
| 1823 |
- * file on disk. May be NULL if a name is not available. |
|
| 1824 |
- * @param[out] virname Pointer to receive the signature match name name if a |
|
| 1825 |
- * signature matched. |
|
| 1826 |
- * @param[out] scanned The exact number of bytes scanned. |
|
| 1822 |
+ * @param filename (Optional) Name of data origin. Does not need to be an actual file on disk. |
|
| 1823 |
+ * May be NULL if a name is not available. |
|
| 1824 |
+ * @param[out] verdict_out A pointer to a cl_verdict_t that will be set to the scan verdict. |
|
| 1825 |
+ * You should check the verdict even if the function returns an error. |
|
| 1826 |
+ * @param[out] last_alert_out Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan |
|
| 1827 |
+ * matches against a signature. |
|
| 1828 |
+ * @param[out] scanned_out The (exact) number of bytes scanned. |
|
| 1827 | 1829 |
* @param engine The scanning engine. |
| 1828 |
- * @param scanoptions The scanning options struct. |
|
| 1830 |
+ * @param scanoptions Scanning options. |
|
| 1829 | 1831 |
* @param[in,out] context (Optional) An application-defined context struct, opaque to libclamav. |
| 1830 | 1832 |
* May be used within your callback functions. |
| 1831 | 1833 |
* @param hash_hint (Optional) A NULL terminated string of the file hash so that |
| ... | ... |
@@ -1844,15 +1866,16 @@ extern cl_error_t cl_scanmap_callback( |
| 1844 | 1844 |
* of the top layer as determined by ClamAV. |
| 1845 | 1845 |
* Will take the form of the standard ClamAV file type format. E.g. "CL_TYPE_PE". |
| 1846 | 1846 |
* See also: https://docs.clamav.net/appendix/FileTypes.html#file-types |
| 1847 |
- * @return cl_error_t CL_CLEAN if no signature matched. |
|
| 1848 |
- * CL_VIRUS if a signature matched. |
|
| 1849 |
- * Another CL_E* error code if an error occurred. |
|
| 1847 |
+ * @return cl_error_t CL_SUCCESS if no error occured. |
|
| 1848 |
+ * Otherwise a CL_E* error code. |
|
| 1849 |
+ * Does NOT return CL_VIRUS for a signature match. Check the `verdict_out` parameter instead. |
|
| 1850 | 1850 |
*/ |
| 1851 | 1851 |
extern cl_error_t cl_scanmap_ex( |
| 1852 | 1852 |
cl_fmap_t *map, |
| 1853 | 1853 |
const char *filename, |
| 1854 |
- const char **virname, |
|
| 1855 |
- uint64_t *scanned, |
|
| 1854 |
+ cl_verdict_t *verdict_out, |
|
| 1855 |
+ const char **last_alert_out, |
|
| 1856 |
+ uint64_t *scanned_out, |
|
| 1856 | 1857 |
const struct cl_engine *engine, |
| 1857 | 1858 |
struct cl_scan_options *scanoptions, |
| 1858 | 1859 |
void *context, |
| ... | ... |
@@ -668,12 +668,20 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname) |
| 668 | 668 |
|
| 669 | 669 |
if (cli_hm_scan(hash, map->len, &virname, ctx->engine->hm_fp, hash_type) == CL_VIRUS) {
|
| 670 | 670 |
cli_dbgmsg("cli_check_fp: Found false positive detection for %s (fp sig: %s)\n", cli_hash_name(hash_type), virname);
|
| 671 |
- status = CL_CLEAN; |
|
| 671 |
+ |
|
| 672 |
+ // Remove any evidence and set the verdict to trusted for the layer where the FP hash matched, and for all contained layers. |
|
| 673 |
+ (void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level); |
|
| 674 |
+ |
|
| 675 |
+ status = CL_VERIFIED; |
|
| 672 | 676 |
goto done; |
| 673 | 677 |
} |
| 674 | 678 |
if (cli_hm_scan_wild(hash, &virname, ctx->engine->hm_fp, hash_type) == CL_VIRUS) {
|
| 675 | 679 |
cli_dbgmsg("cli_check_fp: Found false positive detection for %s (fp sig: %s)\n", cli_hash_name(hash_type), virname);
|
| 676 |
- status = CL_CLEAN; |
|
| 680 |
+ |
|
| 681 |
+ // Remove any evidence and set the verdict to trusted for the layer where the FP hash matched, and for all contained layers. |
|
| 682 |
+ (void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level); |
|
| 683 |
+ |
|
| 684 |
+ status = CL_VERIFIED; |
|
| 677 | 685 |
goto done; |
| 678 | 686 |
} |
| 679 | 687 |
|
| ... | ... |
@@ -682,7 +690,11 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname) |
| 682 | 682 |
* (associated with the .CAB file type) */ |
| 683 | 683 |
if (cli_hm_scan(hash, 1, &virname, ctx->engine->hm_fp, hash_type) == CL_VIRUS) {
|
| 684 | 684 |
cli_dbgmsg("cli_check_fp: Found .CAB false positive detection for %s via catalog file\n", cli_hash_name(hash_type));
|
| 685 |
- status = CL_CLEAN; |
|
| 685 |
+ |
|
| 686 |
+ // Remove any evidence and set the verdict to trusted for the layer where the FP hash matched, and for all contained layers. |
|
| 687 |
+ (void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level); |
|
| 688 |
+ |
|
| 689 |
+ status = CL_VERIFIED; |
|
| 686 | 690 |
goto done; |
| 687 | 691 |
} |
| 688 | 692 |
} |
| ... | ... |
@@ -1521,11 +1521,13 @@ static cl_error_t append_virus(cli_ctx *ctx, const char *virname, IndicatorType |
| 1521 | 1521 |
ctx->this_layer_evidence = ctx->recursion_stack[ctx->recursion_level].evidence; |
| 1522 | 1522 |
|
| 1523 | 1523 |
if ((ctx->fmap != NULL) && |
| 1524 |
- (ctx->recursion_stack != NULL) && |
|
| 1525 |
- (CL_VIRUS != cli_check_fp(ctx, virname))) {
|
|
| 1526 |
- // FP signature found for one of the layers. Ignore indicator. |
|
| 1527 |
- status = CL_SUCCESS; |
|
| 1528 |
- goto done; |
|
| 1524 |
+ (ctx->recursion_stack != NULL)) {
|
|
| 1525 |
+ |
|
| 1526 |
+ status = cli_check_fp(ctx, virname); |
|
| 1527 |
+ if (CL_VERIFIED == status) {
|
|
| 1528 |
+ // FP signature found for one of the layers. Ignore indicator. |
|
| 1529 |
+ goto done; |
|
| 1530 |
+ } |
|
| 1529 | 1531 |
} |
| 1530 | 1532 |
|
| 1531 | 1533 |
add_successful = evidence_add_indicator( |
| ... | ... |
@@ -2621,10 +2623,8 @@ cl_error_t cli_dispatch_scan_callback(cli_ctx *ctx, cl_scan_callback_t location) |
| 2621 | 2621 |
// So we need to remove any alerts for this layer and return CL_VERIFIED (will stop scanning this layer). |
| 2622 | 2622 |
cli_dbgmsg("dispatch_scan_callback: Layer verified clean by callback\n");
|
| 2623 | 2623 |
|
| 2624 |
- evidence_free(ctx->recursion_stack[ctx->recursion_level].evidence); |
|
| 2625 |
- ctx->recursion_stack[ctx->recursion_level].evidence = NULL; |
|
| 2626 |
- ctx->this_layer_evidence = NULL; |
|
| 2627 |
- |
|
| 2624 |
+ // Remove any evidence for this layer and set the verdict to trusted. |
|
| 2625 |
+ (void)cli_trust_this_layer(ctx); |
|
| 2628 | 2626 |
status = CL_VERIFIED; |
| 2629 | 2627 |
} break; |
| 2630 | 2628 |
|
| ... | ... |
@@ -2726,3 +2726,55 @@ uint8_t cli_set_debug_flag(uint8_t debug_flag) |
| 2726 | 2726 |
|
| 2727 | 2727 |
return was; |
| 2728 | 2728 |
} |
| 2729 |
+ |
|
| 2730 |
+cl_error_t cli_trust_this_layer(cli_ctx *ctx) |
|
| 2731 |
+{
|
|
| 2732 |
+ cl_error_t status = CL_ERROR; |
|
| 2733 |
+ |
|
| 2734 |
+ if (!ctx) {
|
|
| 2735 |
+ cli_errmsg("cli_trust_this_layer: invalid context\n");
|
|
| 2736 |
+ status = CL_ENULLARG; |
|
| 2737 |
+ goto done; |
|
| 2738 |
+ } |
|
| 2739 |
+ |
|
| 2740 |
+ if (NULL != ctx->recursion_stack[ctx->recursion_level].evidence) {
|
|
| 2741 |
+ evidence_free(ctx->recursion_stack[ctx->recursion_level].evidence); |
|
| 2742 |
+ ctx->recursion_stack[ctx->recursion_level].evidence = NULL; |
|
| 2743 |
+ ctx->this_layer_evidence = NULL; |
|
| 2744 |
+ } |
|
| 2745 |
+ |
|
| 2746 |
+ ctx->recursion_stack[ctx->recursion_level].verdict = CL_VERDICT_TRUSTED; |
|
| 2747 |
+ |
|
| 2748 |
+ status = CL_SUCCESS; |
|
| 2749 |
+ |
|
| 2750 |
+done: |
|
| 2751 |
+ return status; |
|
| 2752 |
+} |
|
| 2753 |
+ |
|
| 2754 |
+cl_error_t cli_trust_layers(cli_ctx *ctx, uint32_t start_layer, uint32_t end_layer) |
|
| 2755 |
+{
|
|
| 2756 |
+ cl_error_t status = CL_ERROR; |
|
| 2757 |
+ size_t i; |
|
| 2758 |
+ |
|
| 2759 |
+ if (!ctx) {
|
|
| 2760 |
+ cli_errmsg("cli_trust_layers: invalid context\n");
|
|
| 2761 |
+ status = CL_ENULLARG; |
|
| 2762 |
+ goto done; |
|
| 2763 |
+ } |
|
| 2764 |
+ |
|
| 2765 |
+ for (i = start_layer; i <= end_layer; i++) {
|
|
| 2766 |
+ |
|
| 2767 |
+ if (NULL != ctx->recursion_stack[i].evidence) {
|
|
| 2768 |
+ evidence_free(ctx->recursion_stack[i].evidence); |
|
| 2769 |
+ ctx->recursion_stack[i].evidence = NULL; |
|
| 2770 |
+ ctx->this_layer_evidence = NULL; |
|
| 2771 |
+ } |
|
| 2772 |
+ |
|
| 2773 |
+ ctx->recursion_stack[i].verdict = CL_VERDICT_TRUSTED; |
|
| 2774 |
+ } |
|
| 2775 |
+ |
|
| 2776 |
+ status = CL_SUCCESS; |
|
| 2777 |
+ |
|
| 2778 |
+done: |
|
| 2779 |
+ return status; |
|
| 2780 |
+} |
| ... | ... |
@@ -1323,6 +1323,24 @@ uint8_t cli_get_debug_flag(void); |
| 1323 | 1323 |
*/ |
| 1324 | 1324 |
uint8_t cli_set_debug_flag(uint8_t debug_flag); |
| 1325 | 1325 |
|
| 1326 |
+/** |
|
| 1327 |
+ * @brief Trust the current layer by removing any evidence and setting the verdict to trusted. |
|
| 1328 |
+ * |
|
| 1329 |
+ * @param ctx The scan context. |
|
| 1330 |
+ * @return cl_error_t CL_SUCCESS on success, or an error code. |
|
| 1331 |
+ */ |
|
| 1332 |
+cl_error_t cli_trust_this_layer(cli_ctx *ctx); |
|
| 1333 |
+ |
|
| 1334 |
+/** |
|
| 1335 |
+ * @brief Trust a range of layers by removing any evidence and setting the verdict to trusted. |
|
| 1336 |
+ * |
|
| 1337 |
+ * @param ctx The scan context. |
|
| 1338 |
+ * @param start_layer The layer to start trusting from (inclusive). |
|
| 1339 |
+ * @param end_layer The layer to stop trusting at (inclusive). |
|
| 1340 |
+ * @return cl_error_t CL_SUCCESS on success, or an error code. |
|
| 1341 |
+ */ |
|
| 1342 |
+cl_error_t cli_trust_layers(cli_ctx *ctx, uint32_t start_layer, uint32_t end_layer); |
|
| 1343 |
+ |
|
| 1326 | 1344 |
#ifndef CLI_SAFER_STRDUP_OR_GOTO_DONE |
| 1327 | 1345 |
/** |
| 1328 | 1346 |
* @brief Wrapper around strdup that does a NULL check. |
| ... | ... |
@@ -5655,6 +5655,10 @@ cl_error_t cli_check_auth_header(cli_ctx *ctx, struct cli_exe_info *peinfo) |
| 5655 | 5655 |
|
| 5656 | 5656 |
if (cli_hm_scan(authsha, 2, NULL, ctx->engine->hm_fp, hashtype) == CL_VIRUS) {
|
| 5657 | 5657 |
cli_dbgmsg("cli_check_auth_header: PE file trusted by catalog file (%s)\n", hashctx_name);
|
| 5658 |
+ |
|
| 5659 |
+ // Remove any evidence for this layer and set the verdict to trusted. |
|
| 5660 |
+ (void)cli_trust_this_layer(ctx); |
|
| 5661 |
+ |
|
| 5658 | 5662 |
ret = CL_VERIFIED; |
| 5659 | 5663 |
goto finish; |
| 5660 | 5664 |
} |
| ... | ... |
@@ -44,6 +44,7 @@ typedef struct cli_scan_layer {
|
| 44 | 44 |
size_t object_id; /* Unique ID for this object. */ |
| 45 | 45 |
json_object *metadata_json; /* JSON object for this recursion level, e.g. for JSON metadata. */ |
| 46 | 46 |
evidence_t evidence; /* Store signature matches for this layer and its children. */ |
| 47 |
+ cl_verdict_t verdict; /* Verdict for this layer, e.g. CL_VERDICT_STRONG_INDICATOR, CL_VERDICT_NOTHING_FOUND, CL_VERDICT_TRUSTED. */ |
|
| 47 | 48 |
char *tmpdir; /* The directory to store tmp files created when processing this layer. */ |
| 48 | 49 |
struct cli_scan_layer *parent; /* Pointer to the parent layer, if any. */ |
| 49 | 50 |
} cli_scan_layer_t; |
| ... | ... |
@@ -4380,6 +4380,10 @@ static cl_error_t dispatch_prescan_callback(clcb_pre_scan cb, cli_ctx *ctx, cons |
| 4380 | 4380 |
switch (status) {
|
| 4381 | 4381 |
case CL_BREAK: |
| 4382 | 4382 |
cli_dbgmsg("dispatch_prescan_callback: file allowed by callback\n");
|
| 4383 |
+ |
|
| 4384 |
+ // Remove any evidence for this layer and set the verdict to trusted. |
|
| 4385 |
+ (void)cli_trust_this_layer(ctx); |
|
| 4386 |
+ |
|
| 4383 | 4387 |
status = CL_VERIFIED; |
| 4384 | 4388 |
break; |
| 4385 | 4389 |
case CL_VIRUS: |
| ... | ... |
@@ -4548,9 +4552,9 @@ done: |
| 4548 | 4548 |
|
| 4549 | 4549 |
cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4550 | 4550 |
{
|
| 4551 |
- cl_error_t ret = CL_CLEAN; |
|
| 4552 |
- cl_error_t cache_check_result = CL_VIRUS; |
|
| 4553 |
- cl_error_t verdict_at_this_level = CL_CLEAN; |
|
| 4551 |
+ cl_error_t ret = CL_CLEAN; |
|
| 4552 |
+ cl_error_t cache_check_result = CL_VIRUS; |
|
| 4553 |
+ cl_verdict_t verdict_at_this_level = CL_VERDICT_NOTHING_FOUND; |
|
| 4554 | 4554 |
|
| 4555 | 4555 |
bool cache_enabled = true; |
| 4556 | 4556 |
cli_file_t dettype = 0; |
| ... | ... |
@@ -5352,15 +5356,6 @@ done: |
| 5352 | 5352 |
(void)result_should_goto_done(ctx, ret, &ret); |
| 5353 | 5353 |
|
| 5354 | 5354 |
/* |
| 5355 |
- * Determine if there was an alert for this layer (or its children). |
|
| 5356 |
- */ |
|
| 5357 |
- if (0 < evidence_num_alerts(ctx->this_layer_evidence)) {
|
|
| 5358 |
- verdict_at_this_level = CL_VIRUS; |
|
| 5359 |
- } else {
|
|
| 5360 |
- verdict_at_this_level = ret; |
|
| 5361 |
- } |
|
| 5362 |
- |
|
| 5363 |
- /* |
|
| 5364 | 5355 |
* Run the deprecated post-scan callback (if one exists) and provide the verdict for this layer. |
| 5365 | 5356 |
*/ |
| 5366 | 5357 |
cli_dbgmsg("cli_magic_scan: returning %d %s\n", ret, __AT__);
|
| ... | ... |
@@ -5369,7 +5364,7 @@ done: |
| 5369 | 5369 |
const char *virusname = NULL; |
| 5370 | 5370 |
|
| 5371 | 5371 |
// Get the last signature that matched (if any). |
| 5372 |
- if (verdict_at_this_level == CL_VIRUS) {
|
|
| 5372 |
+ if (0 < evidence_num_alerts(ctx->this_layer_evidence)) {
|
|
| 5373 | 5373 |
virusname = cli_get_last_virus(ctx); |
| 5374 | 5374 |
} |
| 5375 | 5375 |
|
| ... | ... |
@@ -5397,6 +5392,25 @@ done: |
| 5397 | 5397 |
} |
| 5398 | 5398 |
} |
| 5399 | 5399 |
|
| 5400 |
+ if (CL_VERDICT_TRUSTED == ctx->recursion_stack[ctx->recursion_level].verdict) {
|
|
| 5401 |
+ /* Remove any alerts for this layer. */ |
|
| 5402 |
+ if (NULL != ctx->recursion_stack[ctx->recursion_level].evidence) {
|
|
| 5403 |
+ evidence_free(ctx->recursion_stack[ctx->recursion_level].evidence); |
|
| 5404 |
+ ctx->recursion_stack[ctx->recursion_level].evidence = NULL; |
|
| 5405 |
+ ctx->this_layer_evidence = NULL; |
|
| 5406 |
+ } |
|
| 5407 |
+ } else {
|
|
| 5408 |
+ /* |
|
| 5409 |
+ * Update the verdict for this layer based on the scan results. |
|
| 5410 |
+ * If the verdict is CL_VERDICT_TRUSTED, then we don't change it. |
|
| 5411 |
+ */ |
|
| 5412 |
+ if (0 < evidence_num_indicators_type(ctx->this_layer_evidence, IndicatorType_Strong)) {
|
|
| 5413 |
+ ctx->recursion_stack[ctx->recursion_level].verdict = CL_VERDICT_STRONG_INDICATOR; |
|
| 5414 |
+ } else if (0 < evidence_num_indicators_type(ctx->this_layer_evidence, IndicatorType_PotentiallyUnwanted)) {
|
|
| 5415 |
+ ctx->recursion_stack[ctx->recursion_level].verdict = CL_VERDICT_POTENTIALLY_UNWANTED; |
|
| 5416 |
+ } |
|
| 5417 |
+ } |
|
| 5418 |
+ |
|
| 5400 | 5419 |
/* |
| 5401 | 5420 |
* If the verdict for this layer is "clean", we can cache it. |
| 5402 | 5421 |
* |
| ... | ... |
@@ -5404,10 +5418,15 @@ done: |
| 5404 | 5404 |
* so this may not actually cache if we exceeded limits earlier. |
| 5405 | 5405 |
* It will also check if caching is disabled. |
| 5406 | 5406 |
*/ |
| 5407 |
- if (verdict_at_this_level == CL_CLEAN) {
|
|
| 5408 |
- perf_start(ctx, PERFT_CACHE); |
|
| 5409 |
- clean_cache_add(ctx); |
|
| 5410 |
- perf_stop(ctx, PERFT_CACHE); |
|
| 5407 |
+ if ((CL_VERDICT_TRUSTED == ctx->recursion_stack[ctx->recursion_level].verdict) || |
|
| 5408 |
+ (CL_VERDICT_NOTHING_FOUND == ctx->recursion_stack[ctx->recursion_level].verdict)) {
|
|
| 5409 |
+ // Also verify we have no weak indicators before adding to the clean cache. |
|
| 5410 |
+ // Weak indicators may be used in the future to match a strong indicator. |
|
| 5411 |
+ if (evidence_num_indicators_type(ctx->this_layer_evidence, IndicatorType_Weak) == 0) {
|
|
| 5412 |
+ perf_start(ctx, PERFT_CACHE); |
|
| 5413 |
+ clean_cache_add(ctx); |
|
| 5414 |
+ perf_stop(ctx, PERFT_CACHE); |
|
| 5415 |
+ } |
|
| 5411 | 5416 |
} |
| 5412 | 5417 |
|
| 5413 | 5418 |
early_ret: |
| ... | ... |
@@ -5646,38 +5665,41 @@ cl_error_t cli_magic_scan_buff(const void *buffer, size_t length, cli_ctx *ctx, |
| 5646 | 5646 |
/** |
| 5647 | 5647 |
* @brief The main function to initiate a scan of an fmap. |
| 5648 | 5648 |
* |
| 5649 |
- * @param map File map. |
|
| 5650 |
- * @param filepath (optional, recommended) filepath of the open file descriptor or file map. |
|
| 5651 |
- * @param[out] virname Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan matches against a signature. |
|
| 5652 |
- * @param[out] scanned (Optional) The number of bytes scanned. |
|
| 5653 |
- * @param engine The scanning engine. |
|
| 5654 |
- * @param scanoptions Scanning options. |
|
| 5655 |
- * @param[in,out] context (Optional) An application-defined context struct, opaque to libclamav. |
|
| 5656 |
- * May be used within your callback functions. |
|
| 5657 |
- * @param hash_hint (Optional) A NULL terminated string of the file hash so that |
|
| 5658 |
- * libclamav does not need to calculate it. |
|
| 5659 |
- * @param[out] hash_out (Optional) A NULL terminated string of the file hash. |
|
| 5660 |
- * The caller is responsible for freeing this string. |
|
| 5661 |
- * @param hash_alg The hashing algorithm used for either `hash_hint` or `hash_out`. |
|
| 5662 |
- * Supported algorithms are "md5", "sha1", "sha2-256". |
|
| 5663 |
- * Required only if you provide a `hash_hint` or want to receive a `hash_out`. |
|
| 5664 |
- * @param file_type_hint (Optional) A NULL terminated string of the file type hint. |
|
| 5665 |
- * E.g. "pe", "elf", "zip", etc. |
|
| 5666 |
- * You may also use ClamAV type names such as "CL_TYPE_PE". |
|
| 5667 |
- * ClamAV will ignore the hint if it is not familiar with the specified type. |
|
| 5668 |
- * @param file_type_out (Optional) A NULL terminated string of the file type |
|
| 5669 |
- * of the top layer as determined by ClamAV. |
|
| 5670 |
- * Will take the form of the standard ClamAV file type format. E.g. "CL_TYPE_PE". |
|
| 5671 |
- * The caller is responsible for freeing this string. |
|
| 5672 |
- * @return cl_error_t CL_CLEAN if no signature matched. |
|
| 5673 |
- * CL_VIRUS if a signature matched. |
|
| 5674 |
- * Another CL_E* error code if an error occurred. |
|
| 5649 |
+ * @param map File map. |
|
| 5650 |
+ * @param filepath (optional, recommended) filepath of the open file descriptor or file map. |
|
| 5651 |
+ * @param[out] verdict_out A pointer to a cl_verdict_t that will be set to the scan verdict. |
|
| 5652 |
+ * You should check the verdict even if the function returns an error. |
|
| 5653 |
+ * @param[out] last_alert_out Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan matches against a signature. |
|
| 5654 |
+ * @param[out] scanned_out (Optional) The number of bytes scanned. |
|
| 5655 |
+ * @param engine The scanning engine. |
|
| 5656 |
+ * @param scanoptions Scanning options. |
|
| 5657 |
+ * @param[in,out] context (Optional) An application-defined context struct, opaque to libclamav. |
|
| 5658 |
+ * May be used within your callback functions. |
|
| 5659 |
+ * @param hash_hint (Optional) A NULL terminated string of the file hash so that |
|
| 5660 |
+ * libclamav does not need to calculate it. |
|
| 5661 |
+ * @param[out] hash_out (Optional) A NULL terminated string of the file hash. |
|
| 5662 |
+ * The caller is responsible for freeing this string. |
|
| 5663 |
+ * @param hash_alg The hashing algorithm used for either `hash_hint` or `hash_out`. |
|
| 5664 |
+ * Supported algorithms are "md5", "sha1", "sha2-256". |
|
| 5665 |
+ * Required only if you provide a `hash_hint` or want to receive a `hash_out`. |
|
| 5666 |
+ * @param file_type_hint (Optional) A NULL terminated string of the file type hint. |
|
| 5667 |
+ * E.g. "pe", "elf", "zip", etc. |
|
| 5668 |
+ * You may also use ClamAV type names such as "CL_TYPE_PE". |
|
| 5669 |
+ * ClamAV will ignore the hint if it is not familiar with the specified type. |
|
| 5670 |
+ * @param file_type_out (Optional) A NULL terminated string of the file type |
|
| 5671 |
+ * of the top layer as determined by ClamAV. |
|
| 5672 |
+ * Will take the form of the standard ClamAV file type format. E.g. "CL_TYPE_PE". |
|
| 5673 |
+ * The caller is responsible for freeing this string. |
|
| 5674 |
+ * @return cl_error_t CL_SUCCESS if no error occured. |
|
| 5675 |
+ * Otherwise a CL_E* error code. |
|
| 5676 |
+ * Does NOT return CL_VIRUS for a signature match. Check the `verdict_out` parameter instead. |
|
| 5675 | 5677 |
*/ |
| 5676 | 5678 |
static cl_error_t scan_common( |
| 5677 | 5679 |
cl_fmap_t *map, |
| 5678 | 5680 |
const char *filepath, |
| 5679 |
- const char **virname, |
|
| 5680 |
- uint64_t *scanned, |
|
| 5681 |
+ cl_verdict_t *verdict_out, |
|
| 5682 |
+ const char **last_alert_out, |
|
| 5683 |
+ uint64_t *scanned_out, |
|
| 5681 | 5684 |
const struct cl_engine *engine, |
| 5682 | 5685 |
struct cl_scan_options *scanoptions, |
| 5683 | 5686 |
void *context, |
| ... | ... |
@@ -5689,7 +5711,6 @@ static cl_error_t scan_common( |
| 5689 | 5689 |
{
|
| 5690 | 5690 |
cl_error_t status = CL_SUCCESS; |
| 5691 | 5691 |
cl_error_t ret; |
| 5692 |
- cl_error_t verdict = CL_CLEAN; |
|
| 5693 | 5692 |
|
| 5694 | 5693 |
cli_ctx ctx = {0};
|
| 5695 | 5694 |
|
| ... | ... |
@@ -5710,12 +5731,13 @@ static cl_error_t scan_common( |
| 5710 | 5710 |
// The type of the file being scanned. |
| 5711 | 5711 |
cli_file_t file_type = CL_TYPE_ANY; |
| 5712 | 5712 |
|
| 5713 |
- if (NULL == map || NULL == scanoptions || NULL == virname) {
|
|
| 5713 |
+ if (NULL == map || NULL == scanoptions || NULL == verdict_out || NULL == last_alert_out || NULL == engine) {
|
|
| 5714 | 5714 |
return CL_ENULLARG; |
| 5715 | 5715 |
} |
| 5716 | 5716 |
|
| 5717 | 5717 |
/* Initialize output variables */ |
| 5718 |
- *virname = NULL; |
|
| 5718 |
+ *verdict_out = CL_VERDICT_NOTHING_FOUND; |
|
| 5719 |
+ *last_alert_out = NULL; |
|
| 5719 | 5720 |
|
| 5720 | 5721 |
// If the caller provided a file type hint, we make a best effort to use it. |
| 5721 | 5722 |
if (file_type_hint) {
|
| ... | ... |
@@ -5776,7 +5798,7 @@ static cl_error_t scan_common( |
| 5776 | 5776 |
} |
| 5777 | 5777 |
|
| 5778 | 5778 |
ctx.engine = engine; |
| 5779 |
- ctx.scanned = scanned; |
|
| 5779 |
+ ctx.scanned = scanned_out; |
|
| 5780 | 5780 |
CLI_MALLOC_OR_GOTO_DONE(ctx.options, sizeof(struct cl_scan_options), status = CL_EMEM); |
| 5781 | 5781 |
|
| 5782 | 5782 |
memcpy(ctx.options, scanoptions, sizeof(struct cl_scan_options)); |
| ... | ... |
@@ -6043,8 +6065,8 @@ static cl_error_t scan_common( |
| 6043 | 6043 |
|
| 6044 | 6044 |
// If any alerts occurred, set the output pointer to the "latest" alert signature name. |
| 6045 | 6045 |
if (0 < evidence_num_alerts(ctx.this_layer_evidence)) {
|
| 6046 |
- *virname = cli_get_last_virus_str(&ctx); |
|
| 6047 |
- verdict = CL_VIRUS; |
|
| 6046 |
+ *last_alert_out = cli_get_last_virus_str(&ctx); |
|
| 6047 |
+ *verdict_out = CL_VERDICT_STRONG_INDICATOR; |
|
| 6048 | 6048 |
} |
| 6049 | 6049 |
|
| 6050 | 6050 |
/* |
| ... | ... |
@@ -6152,12 +6174,6 @@ static cl_error_t scan_common( |
| 6152 | 6152 |
} |
| 6153 | 6153 |
} |
| 6154 | 6154 |
|
| 6155 |
- if (verdict != CL_CLEAN) {
|
|
| 6156 |
- // Reporting "VIRUS" is more important than reporting and error, |
|
| 6157 |
- // because... unfortunately we can only do one with the current API. |
|
| 6158 |
- status = verdict; |
|
| 6159 |
- } |
|
| 6160 |
- |
|
| 6161 | 6155 |
done: |
| 6162 | 6156 |
|
| 6163 | 6157 |
if (logg_initialized) {
|
| ... | ... |
@@ -6222,10 +6238,12 @@ cl_error_t cl_scandesc( |
| 6222 | 6222 |
{
|
| 6223 | 6223 |
cl_error_t status; |
| 6224 | 6224 |
uint64_t scanned_out; |
| 6225 |
+ cl_verdict_t verdict_out = CL_VERDICT_NOTHING_FOUND; |
|
| 6225 | 6226 |
|
| 6226 | 6227 |
status = cl_scandesc_ex( |
| 6227 | 6228 |
desc, |
| 6228 | 6229 |
filename, |
| 6230 |
+ &verdict_out, |
|
| 6229 | 6231 |
virname, |
| 6230 | 6232 |
&scanned_out, |
| 6231 | 6233 |
engine, |
| ... | ... |
@@ -6247,6 +6265,12 @@ cl_error_t cl_scandesc( |
| 6247 | 6247 |
} |
| 6248 | 6248 |
} |
| 6249 | 6249 |
|
| 6250 |
+ if (verdict_out == CL_VERDICT_STRONG_INDICATOR || verdict_out == CL_VERDICT_POTENTIALLY_UNWANTED) {
|
|
| 6251 |
+ // Reporting "CL_VIRUS" is more important than reporting an error, |
|
| 6252 |
+ // because... unfortunately we can only do one with this API. |
|
| 6253 |
+ status = CL_VIRUS; |
|
| 6254 |
+ } |
|
| 6255 |
+ |
|
| 6250 | 6256 |
return status; |
| 6251 | 6257 |
} |
| 6252 | 6258 |
|
| ... | ... |
@@ -6260,13 +6284,15 @@ cl_error_t cl_scandesc_callback( |
| 6260 | 6260 |
void *context) |
| 6261 | 6261 |
{
|
| 6262 | 6262 |
cl_error_t status; |
| 6263 |
- uint64_t scanned_out; |
|
| 6263 |
+ uint64_t scanned_bytes; |
|
| 6264 |
+ cl_verdict_t verdict_out = CL_VERDICT_NOTHING_FOUND; |
|
| 6264 | 6265 |
|
| 6265 | 6266 |
status = cl_scandesc_ex( |
| 6266 | 6267 |
desc, |
| 6267 | 6268 |
filename, |
| 6269 |
+ &verdict_out, |
|
| 6268 | 6270 |
virname, |
| 6269 |
- &scanned_out, |
|
| 6271 |
+ &scanned_bytes, |
|
| 6270 | 6272 |
engine, |
| 6271 | 6273 |
scanoptions, |
| 6272 | 6274 |
context, |
| ... | ... |
@@ -6278,22 +6304,29 @@ cl_error_t cl_scandesc_callback( |
| 6278 | 6278 |
|
| 6279 | 6279 |
if (NULL != scanned) {
|
| 6280 | 6280 |
if ((SIZEOF_LONG == 4) && |
| 6281 |
- (scanned_out / CL_COUNT_PRECISION > UINT32_MAX)) {
|
|
| 6282 |
- cli_warnmsg("cl_scanfile_callback: scanned_out exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
|
| 6281 |
+ (scanned_bytes / CL_COUNT_PRECISION > UINT32_MAX)) {
|
|
| 6282 |
+ cli_warnmsg("cl_scanfile_callback: scanned_bytes exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
|
| 6283 | 6283 |
*scanned = UINT32_MAX; |
| 6284 | 6284 |
} else {
|
| 6285 |
- *scanned = (unsigned long int)(scanned_out / CL_COUNT_PRECISION); |
|
| 6285 |
+ *scanned = (unsigned long int)(scanned_bytes / CL_COUNT_PRECISION); |
|
| 6286 | 6286 |
} |
| 6287 | 6287 |
} |
| 6288 | 6288 |
|
| 6289 |
+ if (verdict_out == CL_VERDICT_STRONG_INDICATOR || verdict_out == CL_VERDICT_POTENTIALLY_UNWANTED) {
|
|
| 6290 |
+ // Reporting "CL_VIRUS" is more important than reporting an error, |
|
| 6291 |
+ // because... unfortunately we can only do one with this API. |
|
| 6292 |
+ status = CL_VIRUS; |
|
| 6293 |
+ } |
|
| 6294 |
+ |
|
| 6289 | 6295 |
return status; |
| 6290 | 6296 |
} |
| 6291 | 6297 |
|
| 6292 | 6298 |
cl_error_t cl_scandesc_ex( |
| 6293 | 6299 |
int desc, |
| 6294 | 6300 |
const char *filename, |
| 6295 |
- const char **virname, |
|
| 6296 |
- uint64_t *scanned, |
|
| 6301 |
+ cl_verdict_t *verdict_out, |
|
| 6302 |
+ const char **last_alert_out, |
|
| 6303 |
+ uint64_t *scanned_out, |
|
| 6297 | 6304 |
const struct cl_engine *engine, |
| 6298 | 6305 |
struct cl_scan_options *scanoptions, |
| 6299 | 6306 |
void *context, |
| ... | ... |
@@ -6323,8 +6356,8 @@ cl_error_t cl_scandesc_ex( |
| 6323 | 6323 |
if (scanoptions->heuristic & CL_SCAN_HEURISTIC_EXCEEDS_MAX) {
|
| 6324 | 6324 |
if (engine->cb_virus_found) {
|
| 6325 | 6325 |
engine->cb_virus_found(desc, "Heuristics.Limits.Exceeded.MaxFileSize", context); |
| 6326 |
- if (virname) {
|
|
| 6327 |
- *virname = "Heuristics.Limits.Exceeded.MaxFileSize"; |
|
| 6326 |
+ if (last_alert_out) {
|
|
| 6327 |
+ *last_alert_out = "Heuristics.Limits.Exceeded.MaxFileSize"; |
|
| 6328 | 6328 |
} |
| 6329 | 6329 |
} |
| 6330 | 6330 |
status = CL_VIRUS; |
| ... | ... |
@@ -6347,8 +6380,9 @@ cl_error_t cl_scandesc_ex( |
| 6347 | 6347 |
status = scan_common( |
| 6348 | 6348 |
map, |
| 6349 | 6349 |
filename, |
| 6350 |
- virname, |
|
| 6351 |
- scanned, |
|
| 6350 |
+ verdict_out, |
|
| 6351 |
+ last_alert_out, |
|
| 6352 |
+ scanned_out, |
|
| 6352 | 6353 |
engine, |
| 6353 | 6354 |
scanoptions, |
| 6354 | 6355 |
context, |
| ... | ... |
@@ -6379,13 +6413,15 @@ cl_error_t cl_scanmap_callback( |
| 6379 | 6379 |
void *context) |
| 6380 | 6380 |
{
|
| 6381 | 6381 |
cl_error_t status; |
| 6382 |
- uint64_t scanned_out; |
|
| 6382 |
+ uint64_t scanned_bytes; |
|
| 6383 |
+ cl_verdict_t verdict_out = CL_VERDICT_NOTHING_FOUND; |
|
| 6383 | 6384 |
|
| 6384 | 6385 |
status = cl_scanmap_ex( |
| 6385 | 6386 |
map, |
| 6386 | 6387 |
filename, |
| 6388 |
+ &verdict_out, |
|
| 6387 | 6389 |
virname, |
| 6388 |
- &scanned_out, |
|
| 6390 |
+ &scanned_bytes, |
|
| 6389 | 6391 |
engine, |
| 6390 | 6392 |
scanoptions, |
| 6391 | 6393 |
context, |
| ... | ... |
@@ -6397,22 +6433,29 @@ cl_error_t cl_scanmap_callback( |
| 6397 | 6397 |
|
| 6398 | 6398 |
if (NULL != scanned) {
|
| 6399 | 6399 |
if ((SIZEOF_LONG == 4) && |
| 6400 |
- (scanned_out / CL_COUNT_PRECISION > UINT32_MAX)) {
|
|
| 6401 |
- cli_warnmsg("cl_scanfile_callback: scanned_out exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
|
| 6400 |
+ (scanned_bytes / CL_COUNT_PRECISION > UINT32_MAX)) {
|
|
| 6401 |
+ cli_warnmsg("cl_scanfile_callback: scanned_bytes exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
|
| 6402 | 6402 |
*scanned = UINT32_MAX; |
| 6403 | 6403 |
} else {
|
| 6404 |
- *scanned = (unsigned long int)(scanned_out / CL_COUNT_PRECISION); |
|
| 6404 |
+ *scanned = (unsigned long int)(scanned_bytes / CL_COUNT_PRECISION); |
|
| 6405 | 6405 |
} |
| 6406 | 6406 |
} |
| 6407 | 6407 |
|
| 6408 |
+ if (verdict_out == CL_VERDICT_STRONG_INDICATOR || verdict_out == CL_VERDICT_POTENTIALLY_UNWANTED) {
|
|
| 6409 |
+ // Reporting "CL_VIRUS" is more important than reporting an error, |
|
| 6410 |
+ // because... unfortunately we can only do one with this API. |
|
| 6411 |
+ status = CL_VIRUS; |
|
| 6412 |
+ } |
|
| 6413 |
+ |
|
| 6408 | 6414 |
return status; |
| 6409 | 6415 |
} |
| 6410 | 6416 |
|
| 6411 | 6417 |
cl_error_t cl_scanmap_ex( |
| 6412 | 6418 |
cl_fmap_t *map, |
| 6413 | 6419 |
const char *filename, |
| 6414 |
- const char **virname, |
|
| 6415 |
- uint64_t *scanned, |
|
| 6420 |
+ cl_verdict_t *verdict_out, |
|
| 6421 |
+ const char **last_alert_out, |
|
| 6422 |
+ uint64_t *scanned_out, |
|
| 6416 | 6423 |
const struct cl_engine *engine, |
| 6417 | 6424 |
struct cl_scan_options *scanoptions, |
| 6418 | 6425 |
void *context, |
| ... | ... |
@@ -6427,8 +6470,8 @@ cl_error_t cl_scanmap_ex( |
| 6427 | 6427 |
if (scanoptions->heuristic & CL_SCAN_HEURISTIC_EXCEEDS_MAX) {
|
| 6428 | 6428 |
if (engine->cb_virus_found) {
|
| 6429 | 6429 |
engine->cb_virus_found(fmap_fd(map), "Heuristics.Limits.Exceeded.MaxFileSize", context); |
| 6430 |
- if (virname) {
|
|
| 6431 |
- *virname = "Heuristics.Limits.Exceeded.MaxFileSize"; |
|
| 6430 |
+ if (last_alert_out) {
|
|
| 6431 |
+ *last_alert_out = "Heuristics.Limits.Exceeded.MaxFileSize"; |
|
| 6432 | 6432 |
} |
| 6433 | 6433 |
} |
| 6434 | 6434 |
return CL_VIRUS; |
| ... | ... |
@@ -6444,8 +6487,9 @@ cl_error_t cl_scanmap_ex( |
| 6444 | 6444 |
return scan_common( |
| 6445 | 6445 |
map, |
| 6446 | 6446 |
filename, |
| 6447 |
- virname, |
|
| 6448 |
- scanned, |
|
| 6447 |
+ verdict_out, |
|
| 6448 |
+ last_alert_out, |
|
| 6449 |
+ scanned_out, |
|
| 6449 | 6450 |
engine, |
| 6450 | 6451 |
scanoptions, |
| 6451 | 6452 |
context, |
| ... | ... |
@@ -6485,12 +6529,14 @@ cl_error_t cl_scanfile( |
| 6485 | 6485 |
struct cl_scan_options *scanoptions) |
| 6486 | 6486 |
{
|
| 6487 | 6487 |
cl_error_t status; |
| 6488 |
- uint64_t scanned_out; |
|
| 6488 |
+ uint64_t scanned_bytes; |
|
| 6489 |
+ cl_verdict_t verdict_out = CL_VERDICT_NOTHING_FOUND; |
|
| 6489 | 6490 |
|
| 6490 | 6491 |
status = cl_scanfile_ex( |
| 6491 | 6492 |
filename, |
| 6493 |
+ &verdict_out, |
|
| 6492 | 6494 |
virname, |
| 6493 |
- &scanned_out, |
|
| 6495 |
+ &scanned_bytes, |
|
| 6494 | 6496 |
engine, |
| 6495 | 6497 |
scanoptions, |
| 6496 | 6498 |
NULL, |
| ... | ... |
@@ -6501,14 +6547,20 @@ cl_error_t cl_scanfile( |
| 6501 | 6501 |
NULL); |
| 6502 | 6502 |
|
| 6503 | 6503 |
if (NULL != scanned) {
|
| 6504 |
- if (SIZEOF_LONG == 4 && scanned_out > UINT32_MAX) {
|
|
| 6505 |
- cli_warnmsg("cl_scanfile_callback: scanned_out exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
|
| 6504 |
+ if (SIZEOF_LONG == 4 && scanned_bytes > UINT32_MAX) {
|
|
| 6505 |
+ cli_warnmsg("cl_scanfile_callback: scanned_bytes exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
|
| 6506 | 6506 |
*scanned = UINT32_MAX; |
| 6507 | 6507 |
} else {
|
| 6508 |
- *scanned = (unsigned long int)scanned_out; |
|
| 6508 |
+ *scanned = (unsigned long int)scanned_bytes; |
|
| 6509 | 6509 |
} |
| 6510 | 6510 |
} |
| 6511 | 6511 |
|
| 6512 |
+ if (verdict_out == CL_VERDICT_STRONG_INDICATOR || verdict_out == CL_VERDICT_POTENTIALLY_UNWANTED) {
|
|
| 6513 |
+ // Reporting "CL_VIRUS" is more important than reporting an error, |
|
| 6514 |
+ // because... unfortunately we can only do one with this API. |
|
| 6515 |
+ status = CL_VIRUS; |
|
| 6516 |
+ } |
|
| 6517 |
+ |
|
| 6512 | 6518 |
return status; |
| 6513 | 6519 |
} |
| 6514 | 6520 |
|
| ... | ... |
@@ -6522,9 +6574,11 @@ cl_error_t cl_scanfile_callback( |
| 6522 | 6522 |
{
|
| 6523 | 6523 |
cl_error_t status; |
| 6524 | 6524 |
uint64_t scanned_out; |
| 6525 |
+ cl_verdict_t verdict_out = CL_VERDICT_NOTHING_FOUND; |
|
| 6525 | 6526 |
|
| 6526 | 6527 |
status = cl_scanfile_ex( |
| 6527 | 6528 |
filename, |
| 6529 |
+ &verdict_out, |
|
| 6528 | 6530 |
virname, |
| 6529 | 6531 |
&scanned_out, |
| 6530 | 6532 |
engine, |
| ... | ... |
@@ -6545,13 +6599,20 @@ cl_error_t cl_scanfile_callback( |
| 6545 | 6545 |
} |
| 6546 | 6546 |
} |
| 6547 | 6547 |
|
| 6548 |
+ if (verdict_out == CL_VERDICT_STRONG_INDICATOR || verdict_out == CL_VERDICT_POTENTIALLY_UNWANTED) {
|
|
| 6549 |
+ // Reporting "CL_VIRUS" is more important than reporting an error, |
|
| 6550 |
+ // because... unfortunately we can only do one with this API. |
|
| 6551 |
+ status = CL_VIRUS; |
|
| 6552 |
+ } |
|
| 6553 |
+ |
|
| 6548 | 6554 |
return status; |
| 6549 | 6555 |
} |
| 6550 | 6556 |
|
| 6551 | 6557 |
cl_error_t cl_scanfile_ex( |
| 6552 | 6558 |
const char *filename, |
| 6553 |
- const char **virname, |
|
| 6554 |
- uint64_t *scanned, |
|
| 6559 |
+ cl_verdict_t *verdict_out, |
|
| 6560 |
+ const char **last_alert_out, |
|
| 6561 |
+ uint64_t *scanned_out, |
|
| 6555 | 6562 |
const struct cl_engine *engine, |
| 6556 | 6563 |
struct cl_scan_options *scanoptions, |
| 6557 | 6564 |
void *context, |
| ... | ... |
@@ -6582,8 +6643,9 @@ cl_error_t cl_scanfile_ex( |
| 6582 | 6582 |
ret = cl_scandesc_ex( |
| 6583 | 6583 |
fd, |
| 6584 | 6584 |
filename, |
| 6585 |
- virname, |
|
| 6586 |
- scanned, |
|
| 6585 |
+ verdict_out, |
|
| 6586 |
+ last_alert_out, |
|
| 6587 |
+ scanned_out, |
|
| 6587 | 6588 |
engine, |
| 6588 | 6589 |
scanoptions, |
| 6589 | 6590 |
context, |
| ... | ... |
@@ -154,9 +154,7 @@ pub unsafe extern "C" fn _evidence_add_child_evidence( |
| 154 | 154 |
/// Free the evidence |
| 155 | 155 |
#[no_mangle] |
| 156 | 156 |
pub extern "C" fn evidence_free(evidence: sys::evidence_t) {
|
| 157 |
- if evidence.is_null() {
|
|
| 158 |
- warn!("Attempted to free a NULL evidence pointer. Please report this at: https://github.com/Cisco-Talos/clamav/issues");
|
|
| 159 |
- } else {
|
|
| 157 |
+ if !evidence.is_null() {
|
|
| 160 | 158 |
let _ = unsafe { Box::from_raw(evidence as *mut Evidence) };
|
| 161 | 159 |
} |
| 162 | 160 |
} |
| ... | ... |
@@ -18,6 +18,16 @@ pub struct bignum_st {
|
| 18 | 18 |
_unused: [u8; 0], |
| 19 | 19 |
} |
| 20 | 20 |
pub type BIGNUM = bignum_st; |
| 21 |
+#[doc = "< No alerting signatures matched."] |
|
| 22 |
+pub const cl_verdict_t_CL_VERDICT_NOTHING_FOUND: cl_verdict_t = 0; |
|
| 23 |
+#[doc = "< The scan target has been deemed trusted (e.g. by FP signature or Authenticode)."] |
|
| 24 |
+pub const cl_verdict_t_CL_VERDICT_TRUSTED: cl_verdict_t = 1; |
|
| 25 |
+#[doc = "< One or more strong indicator signatures matched."] |
|
| 26 |
+pub const cl_verdict_t_CL_VERDICT_STRONG_INDICATOR: cl_verdict_t = 2; |
|
| 27 |
+#[doc = "< One or more potentially unwanted signatures matched."] |
|
| 28 |
+pub const cl_verdict_t_CL_VERDICT_POTENTIALLY_UNWANTED: cl_verdict_t = 3; |
|
| 29 |
+#[doc = " @brief Scan verdicts for cl_scanmap_ex(), cl_scanfile_ex(), and cl_scandesc_ex()."] |
|
| 30 |
+pub type cl_verdict_t = ::std::os::raw::c_uint; |
|
| 21 | 31 |
pub const cl_error_t_CL_CLEAN: cl_error_t = 0; |
| 22 | 32 |
pub const cl_error_t_CL_SUCCESS: cl_error_t = 0; |
| 23 | 33 |
pub const cl_error_t_CL_VIRUS: cl_error_t = 1; |
| ... | ... |
@@ -58,10 +68,11 @@ pub const cl_error_t_CL_EBUSY: cl_error_t = 31; |
| 58 | 58 |
pub const cl_error_t_CL_ESTATE: cl_error_t = 32; |
| 59 | 59 |
#[doc = " may be reported in testmode"] |
| 60 | 60 |
pub const cl_error_t_CL_VERIFIED: cl_error_t = 33; |
| 61 |
-#[doc = " The binary has been deemed trusted"] |
|
| 61 |
+#[doc = " The scan target has been deemed trusted"] |
|
| 62 | 62 |
pub const cl_error_t_CL_ERROR: cl_error_t = 34; |
| 63 | 63 |
#[doc = " Unspecified / generic error"] |
| 64 | 64 |
pub const cl_error_t_CL_ELAST_ERROR: cl_error_t = 35; |
| 65 |
+#[doc = " @brief Return codes used by libclamav functions."] |
|
| 65 | 66 |
pub type cl_error_t = ::std::os::raw::c_uint; |
| 66 | 67 |
#[doc = " scan options"] |
| 67 | 68 |
#[repr(C)] |
| ... | ... |
@@ -651,6 +662,7 @@ pub struct cli_scan_layer {
|
| 651 | 651 |
pub object_id: usize, |
| 652 | 652 |
pub metadata_json: *mut json_object, |
| 653 | 653 |
pub evidence: evidence_t, |
| 654 |
+ pub verdict: cl_verdict_t, |
|
| 654 | 655 |
pub tmpdir: *mut ::std::os::raw::c_char, |
| 655 | 656 |
pub parent: *mut cli_scan_layer, |
| 656 | 657 |
} |
| ... | ... |
@@ -176,7 +176,8 @@ class TC(testcase.TestCase): |
| 176 | 176 |
'Data scanned: 948 B', |
| 177 | 177 |
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa', |
| 178 | 178 |
'File Type: CL_TYPE_ZIP', |
| 179 |
- 'Return code: Virus(es) detected (1)', |
|
| 179 |
+ 'Verdict: Found Strong Indicator', |
|
| 180 |
+ 'Return Code: CL_SUCCESS (0)', |
|
| 180 | 181 |
] |
| 181 | 182 |
|
| 182 | 183 |
command = '{valgrind} {valgrind_args} {example} -d {database} -f {target} --script {script}'.format(
|
| ... | ... |
@@ -187,7 +188,8 @@ class TC(testcase.TestCase): |
| 187 | 187 |
) |
| 188 | 188 |
output = self.execute_command(command) |
| 189 | 189 |
|
| 190 |
- assert output.ec == 1 # virus(es) found |
|
| 190 |
+ # Check for success |
|
| 191 |
+ assert output.ec == 0 |
|
| 191 | 192 |
|
| 192 | 193 |
# Custom logic to verify the output making sure that all expected results are found in the output in order. |
| 193 | 194 |
# |
| ... | ... |
@@ -288,7 +290,7 @@ class TC(testcase.TestCase): |
| 288 | 288 |
'Data scanned: 948 B', |
| 289 | 289 |
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa', |
| 290 | 290 |
'File Type: CL_TYPE_ZIP', |
| 291 |
- 'Return code: No viruses detected (0)', |
|
| 291 |
+ 'Return Code: CL_SUCCESS (0)', |
|
| 292 | 292 |
] |
| 293 | 293 |
|
| 294 | 294 |
command = '{valgrind} {valgrind_args} {example} -d {database} -f {target} --script {script}'.format(
|
| ... | ... |
@@ -299,7 +301,8 @@ class TC(testcase.TestCase): |
| 299 | 299 |
) |
| 300 | 300 |
output = self.execute_command(command) |
| 301 | 301 |
|
| 302 |
- assert output.ec == 0 # no virus(es) found |
|
| 302 |
+ # Check for success |
|
| 303 |
+ assert output.ec == 0 |
|
| 303 | 304 |
|
| 304 | 305 |
# Custom logic to verify the output making sure that all expected results are found in the output in order. |
| 305 | 306 |
# |
| ... | ... |
@@ -336,7 +339,7 @@ class TC(testcase.TestCase): |
| 336 | 336 |
'Data scanned: 0 B', |
| 337 | 337 |
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa', |
| 338 | 338 |
'File Type: CL_TYPE_ZIP', |
| 339 |
- 'Return code: No viruses detected (0)', |
|
| 339 |
+ 'Return Code: CL_SUCCESS (0)', |
|
| 340 | 340 |
] |
| 341 | 341 |
|
| 342 | 342 |
command = '{valgrind} {valgrind_args} {example} -d {database} -f {target} --script {script}'.format(
|
| ... | ... |
@@ -347,7 +350,8 @@ class TC(testcase.TestCase): |
| 347 | 347 |
) |
| 348 | 348 |
output = self.execute_command(command) |
| 349 | 349 |
|
| 350 |
- assert output.ec == 0 # virus(es) found |
|
| 350 |
+ # Check for success |
|
| 351 |
+ assert output.ec == 0 |
|
| 351 | 352 |
|
| 352 | 353 |
# Custom logic to verify the output making sure that all expected results are found in the output in order. |
| 353 | 354 |
# |
| ... | ... |
@@ -398,7 +402,8 @@ class TC(testcase.TestCase): |
| 398 | 398 |
'Data scanned: 0 B', |
| 399 | 399 |
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa', |
| 400 | 400 |
'File Type: CL_TYPE_ZIP', |
| 401 |
- 'Return code: Virus(es) detected (1)', |
|
| 401 |
+ 'Verdict: Found Strong Indicator', |
|
| 402 |
+ 'Return Code: CL_SUCCESS (0)', |
|
| 402 | 403 |
] |
| 403 | 404 |
|
| 404 | 405 |
command = '{valgrind} {valgrind_args} {example} -d {database} -f {target} --script {script}'.format(
|
| ... | ... |
@@ -409,7 +414,8 @@ class TC(testcase.TestCase): |
| 409 | 409 |
) |
| 410 | 410 |
output = self.execute_command(command) |
| 411 | 411 |
|
| 412 |
- assert output.ec == 1 # virus(es) found |
|
| 412 |
+ # Check for success |
|
| 413 |
+ assert output.ec == 0 |
|
| 413 | 414 |
|
| 414 | 415 |
# Custom logic to verify the output making sure that all expected results are found in the output in order. |
| 415 | 416 |
# |
| ... | ... |
@@ -526,7 +532,7 @@ class TC(testcase.TestCase): |
| 526 | 526 |
'Data scanned: 948 B', |
| 527 | 527 |
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa', |
| 528 | 528 |
'File Type: CL_TYPE_ZIP', |
| 529 |
- 'Return code: No viruses detected (0)', |
|
| 529 |
+ 'Return Code: CL_SUCCESS (0)', |
|
| 530 | 530 |
] |
| 531 | 531 |
|
| 532 | 532 |
command = '{valgrind} {valgrind_args} {example} -d {database} -f {target} --script {script}'.format(
|
| ... | ... |
@@ -537,7 +543,8 @@ class TC(testcase.TestCase): |
| 537 | 537 |
) |
| 538 | 538 |
output = self.execute_command(command) |
| 539 | 539 |
|
| 540 |
- assert output.ec == 0 # no virus(es) found |
|
| 540 |
+ # Check for success |
|
| 541 |
+ assert output.ec == 0 |
|
| 541 | 542 |
|
| 542 | 543 |
# Custom logic to verify the output making sure that all expected results are found in the output in order. |
| 543 | 544 |
# |