Skip to content

Commit 8307327

Browse files
committed
libclamav: cl_scan*_ex() functions provide verdict separate from errors
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
1 parent 3281b60 commit 8307327

14 files changed

Lines changed: 573 additions & 274 deletions

File tree

clamscan/manager.c

Lines changed: 109 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ static int print_chain(struct metachain *c, char *str, size_t len)
181181
return i == c->nchains - 1 ? 0 : 1;
182182
}
183183

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)
185185
{
186186
struct clamscan_cb_data *d = context;
187187
struct metachain *c = NULL;
@@ -196,10 +196,10 @@ static cl_error_t post(int fd, int result, const char *virname, void *context)
196196
if (c && c->nchains) {
197197
print_chain(c, str, sizeof(str));
198198

199-
if (c->level == c->lastadd && !virname)
199+
if (c->level == c->lastadd && !alert_name)
200200
free(c->chains[--c->nchains]);
201201

202-
if (virname && !c->lastvir)
202+
if (alert_name && !c->lastvir)
203203
c->lastvir = c->level;
204204
}
205205

@@ -275,7 +275,7 @@ static cl_error_t meta(const char *container_type, unsigned long fsize_container
275275
return CL_CLEAN;
276276
}
277277

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)
279279
{
280280
struct clamscan_cb_data *data = (struct clamscan_cb_data *)context;
281281
const char *filename;
@@ -288,7 +288,7 @@ static void clamscan_virus_found_cb(int fd, const char *virname, void *context)
288288
filename = data->filename;
289289
else
290290
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);
292292
return;
293293
}
294294

@@ -299,7 +299,8 @@ static void scanfile(const char *filename, struct cl_engine *engine, const struc
299299
int included = 0;
300300
unsigned i;
301301
const struct optstruct *opt;
302-
const char *virname = NULL;
302+
cl_verdict_t verdict = CL_VERDICT_NOTHING_FOUND;
303+
const char *alert_name = NULL;
303304
STATBUF sb;
304305
struct metachain chain = {0};
305306
struct clamscan_cb_data data = {0};
@@ -433,43 +434,63 @@ static void scanfile(const char *filename, struct cl_engine *engine, const struc
433434

434435
data.chain = &chain;
435436
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++;
460437

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);
438+
ret = cl_scandesc_ex(
439+
fd,
440+
filename,
441+
&verdict,
442+
&alert_name,
443+
&info.bytes_scanned,
444+
engine, options,
445+
&data,
446+
hash_hint,
447+
hash_out,
448+
hash_alg,
449+
file_type_hint,
450+
file_type_out);
451+
452+
switch (verdict) {
453+
case CL_VERDICT_NOTHING_FOUND: {
454+
if (CL_SUCCESS == ret) {
455+
if (!printinfected && printclean) {
456+
mprintf(LOGG_INFO, "%s: OK\n", filename);
457+
}
458+
info.files++;
459+
} else {
460+
if (!printinfected)
461+
logg(LOGG_INFO, "%s: %s ERROR\n", filename, cl_strerror(ret));
466462

467-
info.files++;
468-
} else {
469-
if (!printinfected)
470-
logg(LOGG_INFO, "%s: %s ERROR\n", filename, cl_strerror(ret));
463+
info.errors++;
464+
}
465+
} break;
471466

472-
info.errors++;
467+
case CL_VERDICT_TRUSTED: {
468+
// TODO: Option to print "TRUSTED" verdict instead of "OK"?
469+
if (!printinfected && printclean) {
470+
mprintf(LOGG_INFO, "%s: OK\n", filename);
471+
}
472+
info.files++;
473+
} break;
474+
475+
case CL_VERDICT_STRONG_INDICATOR:
476+
case CL_VERDICT_POTENTIALLY_UNWANTED: {
477+
if (optget(opts, "archive-verbose")->enabled) {
478+
if (chain.nchains > 1) {
479+
char str[128];
480+
int toolong = print_chain(&chain, str, sizeof(str));
481+
482+
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);
483+
} else if (chain.lastvir) {
484+
logg(LOGG_INFO, "%s!(%llu): %s FOUND\n", filename, (long long unsigned)(chain.lastvir - 1), alert_name);
485+
}
486+
}
487+
info.files++;
488+
info.ifiles++;
489+
490+
if (bell) {
491+
fprintf(stderr, "\007");
492+
}
493+
} break;
473494
}
474495

475496
if (NULL != hash) {
@@ -630,9 +651,10 @@ static int scanstdin(const struct cl_engine *engine, const struct optstruct *opt
630651
{
631652
cl_error_t ret;
632653

633-
size_t fsize = 0;
634-
const char *virname = NULL;
635-
const char *tmpdir = NULL;
654+
size_t fsize = 0;
655+
cl_verdict_t verdict = CL_VERDICT_NOTHING_FOUND;
656+
const char *alert_name = NULL;
657+
const char *tmpdir = NULL;
636658
char *filename, buff[FILEBUFF];
637659
size_t bread;
638660
FILE *fs;
@@ -702,30 +724,51 @@ static int scanstdin(const struct cl_engine *engine, const struct optstruct *opt
702724

703725
data.filename = "stdin";
704726
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));
727727

728-
info.errors++;
728+
ret = cl_scanfile_ex(
729+
filename,
730+
&verdict,
731+
&alert_name,
732+
&info.bytes_scanned,
733+
engine,
734+
options,
735+
&data,
736+
hash_hint,
737+
hash_out,
738+
hash_alg,
739+
file_type_hint,
740+
file_type_out);
741+
742+
switch (verdict) {
743+
case CL_VERDICT_NOTHING_FOUND: {
744+
if (CL_SUCCESS == ret) {
745+
if (!printinfected) {
746+
mprintf(LOGG_INFO, "stdin: OK\n");
747+
}
748+
info.files++;
749+
} else {
750+
if (!printinfected) {
751+
logg(LOGG_INFO, "stdin: %s ERROR\n", cl_strerror(ret));
752+
}
753+
info.errors++;
754+
}
755+
} break;
756+
757+
case CL_VERDICT_TRUSTED: {
758+
// TODO: Option to print "TRUSTED" verdict instead of "OK"?
759+
if (!printinfected) {
760+
mprintf(LOGG_INFO, "stdin: OK\n");
761+
}
762+
} break;
763+
764+
case CL_VERDICT_STRONG_INDICATOR:
765+
case CL_VERDICT_POTENTIALLY_UNWANTED: {
766+
info.ifiles++;
767+
768+
if (bell) {
769+
fprintf(stderr, "\007");
770+
}
771+
} break;
729772
}
730773

731774
if (NULL != hash) {

common/scanmem.c

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -533,8 +533,10 @@ int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *inf
533533
{
534534
int fd;
535535
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;
538540

539541
logg(LOGG_DEBUG, "Scanning %s\n", filename);
540542

@@ -565,7 +567,8 @@ int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *inf
565567
ret = cl_scandesc_ex(
566568
fd,
567569
filename,
568-
&virname,
570+
&verdict,
571+
&alert_name,
569572
&info->bytes_scanned,
570573
info->engine,
571574
info->options,
@@ -576,11 +579,22 @@ int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *inf
576579
NULL,
577580
NULL,
578581
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);
582+
583+
switch (verdict) {
584+
case CL_VERDICT_NOTHING_FOUND: {
585+
logg(LOGG_INFO, "%s: OK \n", filename);
586+
ret = CL_CLEAN;
587+
} break;
588+
case CL_VERDICT_TRUSTED: {
589+
// TODO: Option to print "TRUSTED" verdict instead of "OK"?
590+
logg(LOGG_INFO, "%s: OK \n", filename);
591+
ret = CL_CLEAN;
592+
} break;
593+
case CL_VERDICT_STRONG_INDICATOR:
594+
case CL_VERDICT_POTENTIALLY_UNWANTED: {
595+
logg(LOGG_INFO, "%s: %s FOUND\n", filename, alert_name);
596+
info->ifiles++;
597+
} break;
584598
}
585599
}
586600

0 commit comments

Comments
 (0)