mirror of
https://github.com/Cisco-Talos/clamav.git
synced 2025-10-19 10:23:17 +00:00
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
This commit is contained in:
parent
9d253673f4
commit
6d9b57eeeb
14 changed files with 572 additions and 272 deletions
|
@ -181,7 +181,7 @@ static int print_chain(struct metachain *c, char *str, size_t len)
|
|||
return i == c->nchains - 1 ? 0 : 1;
|
||||
}
|
||||
|
||||
static cl_error_t post(int fd, int result, const char *virname, void *context)
|
||||
static cl_error_t post(int fd, int result, const char *alert_name, void *context)
|
||||
{
|
||||
struct clamscan_cb_data *d = context;
|
||||
struct metachain *c = NULL;
|
||||
|
@ -196,10 +196,10 @@ static cl_error_t post(int fd, int result, const char *virname, void *context)
|
|||
if (c && c->nchains) {
|
||||
print_chain(c, str, sizeof(str));
|
||||
|
||||
if (c->level == c->lastadd && !virname)
|
||||
if (c->level == c->lastadd && !alert_name)
|
||||
free(c->chains[--c->nchains]);
|
||||
|
||||
if (virname && !c->lastvir)
|
||||
if (alert_name && !c->lastvir)
|
||||
c->lastvir = c->level;
|
||||
}
|
||||
|
||||
|
@ -275,7 +275,7 @@ static cl_error_t meta(const char *container_type, unsigned long fsize_container
|
|||
return CL_CLEAN;
|
||||
}
|
||||
|
||||
static void clamscan_virus_found_cb(int fd, const char *virname, void *context)
|
||||
static void clamscan_virus_found_cb(int fd, const char *alert_name, void *context)
|
||||
{
|
||||
struct clamscan_cb_data *data = (struct clamscan_cb_data *)context;
|
||||
const char *filename;
|
||||
|
@ -288,7 +288,7 @@ static void clamscan_virus_found_cb(int fd, const char *virname, void *context)
|
|||
filename = data->filename;
|
||||
else
|
||||
filename = "(filename not set)";
|
||||
logg(LOGG_INFO, "%s: %s FOUND\n", filename, virname);
|
||||
logg(LOGG_INFO, "%s: %s FOUND\n", filename, alert_name);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -299,7 +299,8 @@ static void scanfile(const char *filename, struct cl_engine *engine, const struc
|
|||
int included = 0;
|
||||
unsigned i;
|
||||
const struct optstruct *opt;
|
||||
const char *virname = NULL;
|
||||
cl_verdict_t verdict = CL_VERDICT_NOTHING_FOUND;
|
||||
const char *alert_name = NULL;
|
||||
STATBUF sb;
|
||||
struct metachain chain = {0};
|
||||
struct clamscan_cb_data data = {0};
|
||||
|
@ -433,43 +434,63 @@ static void scanfile(const char *filename, struct cl_engine *engine, const struc
|
|||
|
||||
data.chain = &chain;
|
||||
data.filename = filename;
|
||||
if (CL_VIRUS == (ret = cl_scandesc_ex(
|
||||
fd,
|
||||
filename,
|
||||
&virname,
|
||||
&info.bytes_scanned,
|
||||
engine, options,
|
||||
&data,
|
||||
hash_hint,
|
||||
hash_out,
|
||||
hash_alg,
|
||||
file_type_hint,
|
||||
file_type_out))) {
|
||||
if (optget(opts, "archive-verbose")->enabled) {
|
||||
if (chain.nchains > 1) {
|
||||
char str[128];
|
||||
int toolong = print_chain(&chain, str, sizeof(str));
|
||||
|
||||
logg(LOGG_INFO, "%s%s!(%llu)%s: %s FOUND\n", str, toolong ? "..." : "", (long long unsigned)(chain.lastvir - 1), chain.chains[chain.nchains - 1], virname);
|
||||
} else if (chain.lastvir) {
|
||||
logg(LOGG_INFO, "%s!(%llu): %s FOUND\n", filename, (long long unsigned)(chain.lastvir - 1), virname);
|
||||
ret = cl_scandesc_ex(
|
||||
fd,
|
||||
filename,
|
||||
&verdict,
|
||||
&alert_name,
|
||||
&info.bytes_scanned,
|
||||
engine, options,
|
||||
&data,
|
||||
hash_hint,
|
||||
hash_out,
|
||||
hash_alg,
|
||||
file_type_hint,
|
||||
file_type_out);
|
||||
|
||||
switch (verdict) {
|
||||
case CL_VERDICT_NOTHING_FOUND: {
|
||||
if (CL_SUCCESS == ret) {
|
||||
if (!printinfected && printclean) {
|
||||
mprintf(LOGG_INFO, "%s: OK\n", filename);
|
||||
}
|
||||
info.files++;
|
||||
} else {
|
||||
if (!printinfected)
|
||||
logg(LOGG_INFO, "%s: %s ERROR\n", filename, cl_strerror(ret));
|
||||
|
||||
info.errors++;
|
||||
}
|
||||
}
|
||||
info.files++;
|
||||
info.ifiles++;
|
||||
} break;
|
||||
|
||||
if (bell)
|
||||
fprintf(stderr, "\007");
|
||||
} else if (ret == CL_CLEAN) {
|
||||
if (!printinfected && printclean)
|
||||
mprintf(LOGG_INFO, "%s: OK\n", filename);
|
||||
case CL_VERDICT_TRUSTED: {
|
||||
// TODO: Option to print "TRUSTED" verdict instead of "OK"?
|
||||
if (!printinfected && printclean) {
|
||||
mprintf(LOGG_INFO, "%s: OK\n", filename);
|
||||
}
|
||||
info.files++;
|
||||
} break;
|
||||
|
||||
info.files++;
|
||||
} else {
|
||||
if (!printinfected)
|
||||
logg(LOGG_INFO, "%s: %s ERROR\n", filename, cl_strerror(ret));
|
||||
case CL_VERDICT_STRONG_INDICATOR:
|
||||
case CL_VERDICT_POTENTIALLY_UNWANTED: {
|
||||
if (optget(opts, "archive-verbose")->enabled) {
|
||||
if (chain.nchains > 1) {
|
||||
char str[128];
|
||||
int toolong = print_chain(&chain, str, sizeof(str));
|
||||
|
||||
info.errors++;
|
||||
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);
|
||||
} else if (chain.lastvir) {
|
||||
logg(LOGG_INFO, "%s!(%llu): %s FOUND\n", filename, (long long unsigned)(chain.lastvir - 1), alert_name);
|
||||
}
|
||||
}
|
||||
info.files++;
|
||||
info.ifiles++;
|
||||
|
||||
if (bell) {
|
||||
fprintf(stderr, "\007");
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
if (NULL != hash) {
|
||||
|
@ -488,7 +509,7 @@ done:
|
|||
/*
|
||||
* Run the action callback if the file was infected.
|
||||
*/
|
||||
if (ret == CL_VIRUS && action) {
|
||||
if (((verdict == CL_VERDICT_STRONG_INDICATOR) || (verdict == CL_VERDICT_POTENTIALLY_UNWANTED)) && action) {
|
||||
action(filename);
|
||||
}
|
||||
|
||||
|
@ -630,9 +651,10 @@ static int scanstdin(const struct cl_engine *engine, const struct optstruct *opt
|
|||
{
|
||||
cl_error_t ret;
|
||||
|
||||
size_t fsize = 0;
|
||||
const char *virname = NULL;
|
||||
const char *tmpdir = NULL;
|
||||
size_t fsize = 0;
|
||||
cl_verdict_t verdict = CL_VERDICT_NOTHING_FOUND;
|
||||
const char *alert_name = NULL;
|
||||
const char *tmpdir = NULL;
|
||||
char *filename, buff[FILEBUFF];
|
||||
size_t bread;
|
||||
FILE *fs;
|
||||
|
@ -702,30 +724,51 @@ static int scanstdin(const struct cl_engine *engine, const struct optstruct *opt
|
|||
|
||||
data.filename = "stdin";
|
||||
data.chain = NULL;
|
||||
if (CL_VIRUS == (ret = cl_scanfile_ex(
|
||||
filename,
|
||||
&virname,
|
||||
&info.bytes_scanned,
|
||||
engine,
|
||||
options,
|
||||
&data,
|
||||
hash_hint,
|
||||
hash_out,
|
||||
hash_alg,
|
||||
file_type_hint,
|
||||
file_type_out))) {
|
||||
info.ifiles++;
|
||||
|
||||
if (bell)
|
||||
fprintf(stderr, "\007");
|
||||
} else if (ret == CL_CLEAN) {
|
||||
if (!printinfected)
|
||||
mprintf(LOGG_INFO, "stdin: OK\n");
|
||||
} else {
|
||||
if (!printinfected)
|
||||
logg(LOGG_INFO, "stdin: %s ERROR\n", cl_strerror(ret));
|
||||
ret = cl_scanfile_ex(
|
||||
filename,
|
||||
&verdict,
|
||||
&alert_name,
|
||||
&info.bytes_scanned,
|
||||
engine,
|
||||
options,
|
||||
&data,
|
||||
hash_hint,
|
||||
hash_out,
|
||||
hash_alg,
|
||||
file_type_hint,
|
||||
file_type_out);
|
||||
|
||||
info.errors++;
|
||||
switch (verdict) {
|
||||
case CL_VERDICT_NOTHING_FOUND: {
|
||||
if (CL_SUCCESS == ret) {
|
||||
if (!printinfected) {
|
||||
mprintf(LOGG_INFO, "stdin: OK\n");
|
||||
}
|
||||
info.files++;
|
||||
} else {
|
||||
if (!printinfected) {
|
||||
logg(LOGG_INFO, "stdin: %s ERROR\n", cl_strerror(ret));
|
||||
}
|
||||
info.errors++;
|
||||
}
|
||||
} break;
|
||||
|
||||
case CL_VERDICT_TRUSTED: {
|
||||
// TODO: Option to print "TRUSTED" verdict instead of "OK"?
|
||||
if (!printinfected) {
|
||||
mprintf(LOGG_INFO, "stdin: OK\n");
|
||||
}
|
||||
} break;
|
||||
|
||||
case CL_VERDICT_STRONG_INDICATOR:
|
||||
case CL_VERDICT_POTENTIALLY_UNWANTED: {
|
||||
info.ifiles++;
|
||||
|
||||
if (bell) {
|
||||
fprintf(stderr, "\007");
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
if (NULL != hash) {
|
||||
|
|
|
@ -533,8 +533,10 @@ int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *inf
|
|||
{
|
||||
int fd;
|
||||
int scantype;
|
||||
int ret = CL_CLEAN;
|
||||
const char *virname = NULL;
|
||||
int ret = CL_CLEAN;
|
||||
|
||||
cl_verdict_t verdict = CL_VERDICT_NOTHING_FOUND;
|
||||
const char *alert_name = NULL;
|
||||
|
||||
logg(LOGG_DEBUG, "Scanning %s\n", filename);
|
||||
|
||||
|
@ -565,7 +567,8 @@ int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *inf
|
|||
ret = cl_scandesc_ex(
|
||||
fd,
|
||||
filename,
|
||||
&virname,
|
||||
&verdict,
|
||||
&alert_name,
|
||||
&info->bytes_scanned,
|
||||
info->engine,
|
||||
info->options,
|
||||
|
@ -576,11 +579,23 @@ int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *inf
|
|||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
if (ret == CL_VIRUS) {
|
||||
logg(LOGG_INFO, "%s: %s FOUND\n", filename, virname);
|
||||
info->ifiles++;
|
||||
} else if (scan_data->printclean) {
|
||||
logg(LOGG_INFO, "%s: OK \n", filename);
|
||||
|
||||
switch (verdict) {
|
||||
case CL_VERDICT_NOTHING_FOUND: {
|
||||
logg(LOGG_INFO, "%s: OK \n", filename);
|
||||
ret = CL_CLEAN;
|
||||
} break;
|
||||
case CL_VERDICT_TRUSTED: {
|
||||
// TODO: Option to print "TRUSTED" verdict instead of "OK"?
|
||||
logg(LOGG_INFO, "%s: OK \n", filename);
|
||||
ret = CL_CLEAN;
|
||||
} break;
|
||||
case CL_VERDICT_STRONG_INDICATOR:
|
||||
case CL_VERDICT_POTENTIALLY_UNWANTED: {
|
||||
logg(LOGG_INFO, "%s: %s FOUND\n", filename, alert_name);
|
||||
info->ifiles++;
|
||||
ret = CL_VIRUS;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1008,41 +1008,76 @@ done:
|
|||
const char *cl_error_t_to_string(cl_error_t clerror)
|
||||
{
|
||||
switch (clerror) {
|
||||
case CL_SUCCESS: return "CL_SUCCESS";
|
||||
case CL_VIRUS: return "CL_VIRUS";
|
||||
case CL_ENULLARG: return "CL_ENULLARG";
|
||||
case CL_EARG: return "CL_EARG";
|
||||
case CL_EMALFDB: return "CL_EMALFDB";
|
||||
case CL_ECVD: return "CL_ECVD";
|
||||
case CL_EVERIFY: return "CL_EVERIFY";
|
||||
case CL_EUNPACK: return "CL_EUNPACK";
|
||||
case CL_EPARSE: return "CL_EPARSE";
|
||||
case CL_EOPEN: return "CL_EOPEN";
|
||||
case CL_ECREAT: return "CL_ECREAT";
|
||||
case CL_EUNLINK: return "CL_EUNLINK";
|
||||
case CL_ESTAT: return "CL_ESTAT";
|
||||
case CL_EREAD: return "CL_EREAD";
|
||||
case CL_ESEEK: return "CL_ESEEK";
|
||||
case CL_EWRITE: return "CL_EWRITE";
|
||||
case CL_EDUP: return "CL_EDUP";
|
||||
case CL_EACCES: return "CL_EACCES";
|
||||
case CL_ETMPFILE: return "CL_ETMPFILE";
|
||||
case CL_ETMPDIR: return "CL_ETMPDIR";
|
||||
case CL_EMAP: return "CL_EMAP";
|
||||
case CL_EMEM: return "CL_EMEM";
|
||||
case CL_ETIMEOUT: return "CL_ETIMEOUT";
|
||||
case CL_EMAXREC: return "CL_EMAXREC";
|
||||
case CL_EMAXSIZE: return "CL_EMAXSIZE";
|
||||
case CL_EMAXFILES: return "CL_EMAXFILES";
|
||||
case CL_EFORMAT: return "CL_EFORMAT";
|
||||
case CL_EBYTECODE: return "CL_EBYTECODE";
|
||||
case CL_EBYTECODE_TESTFAIL: return "CL_EBYTECODE_TESTFAIL";
|
||||
case CL_ELOCK: return "CL_ELOCK";
|
||||
case CL_EBUSY: return "CL_EBUSY";
|
||||
case CL_ESTATE: return "CL_ESTATE";
|
||||
case CL_ERROR: return "CL_ERROR";
|
||||
case CL_VERIFIED: return "CL_VERIFIED";
|
||||
case CL_BREAK: return "CL_BREAK";
|
||||
case CL_SUCCESS:
|
||||
return "CL_SUCCESS";
|
||||
case CL_VIRUS:
|
||||
return "CL_VIRUS";
|
||||
case CL_ENULLARG:
|
||||
return "CL_ENULLARG";
|
||||
case CL_EARG:
|
||||
return "CL_EARG";
|
||||
case CL_EMALFDB:
|
||||
return "CL_EMALFDB";
|
||||
case CL_ECVD:
|
||||
return "CL_ECVD";
|
||||
case CL_EVERIFY:
|
||||
return "CL_EVERIFY";
|
||||
case CL_EUNPACK:
|
||||
return "CL_EUNPACK";
|
||||
case CL_EPARSE:
|
||||
return "CL_EPARSE";
|
||||
case CL_EOPEN:
|
||||
return "CL_EOPEN";
|
||||
case CL_ECREAT:
|
||||
return "CL_ECREAT";
|
||||
case CL_EUNLINK:
|
||||
return "CL_EUNLINK";
|
||||
case CL_ESTAT:
|
||||
return "CL_ESTAT";
|
||||
case CL_EREAD:
|
||||
return "CL_EREAD";
|
||||
case CL_ESEEK:
|
||||
return "CL_ESEEK";
|
||||
case CL_EWRITE:
|
||||
return "CL_EWRITE";
|
||||
case CL_EDUP:
|
||||
return "CL_EDUP";
|
||||
case CL_EACCES:
|
||||
return "CL_EACCES";
|
||||
case CL_ETMPFILE:
|
||||
return "CL_ETMPFILE";
|
||||
case CL_ETMPDIR:
|
||||
return "CL_ETMPDIR";
|
||||
case CL_EMAP:
|
||||
return "CL_EMAP";
|
||||
case CL_EMEM:
|
||||
return "CL_EMEM";
|
||||
case CL_ETIMEOUT:
|
||||
return "CL_ETIMEOUT";
|
||||
case CL_EMAXREC:
|
||||
return "CL_EMAXREC";
|
||||
case CL_EMAXSIZE:
|
||||
return "CL_EMAXSIZE";
|
||||
case CL_EMAXFILES:
|
||||
return "CL_EMAXFILES";
|
||||
case CL_EFORMAT:
|
||||
return "CL_EFORMAT";
|
||||
case CL_EBYTECODE:
|
||||
return "CL_EBYTECODE";
|
||||
case CL_EBYTECODE_TESTFAIL:
|
||||
return "CL_EBYTECODE_TESTFAIL";
|
||||
case CL_ELOCK:
|
||||
return "CL_ELOCK";
|
||||
case CL_EBUSY:
|
||||
return "CL_EBUSY";
|
||||
case CL_ESTATE:
|
||||
return "CL_ESTATE";
|
||||
case CL_ERROR:
|
||||
return "CL_ERROR";
|
||||
case CL_VERIFIED:
|
||||
return "CL_VERIFIED";
|
||||
case CL_BREAK:
|
||||
return "CL_BREAK";
|
||||
default:
|
||||
return "Unknown error code";
|
||||
}
|
||||
|
@ -1195,7 +1230,10 @@ int main(int argc, char **argv)
|
|||
script_context_t *script_context = NULL;
|
||||
|
||||
unsigned long int size = 0;
|
||||
const char *virname;
|
||||
|
||||
cl_verdict_t verdict = CL_VERDICT_NOTHING_FOUND;
|
||||
const char *alert_name;
|
||||
|
||||
struct cl_engine *engine = NULL;
|
||||
struct cl_scan_options options;
|
||||
unsigned int signo = 0;
|
||||
|
@ -1336,27 +1374,21 @@ int main(int argc, char **argv)
|
|||
* Run the scan.
|
||||
* Note that the callbacks will be called during this function.
|
||||
*/
|
||||
if (CL_VIRUS == (ret = cl_scandesc_ex(
|
||||
target_fd,
|
||||
filename,
|
||||
&virname,
|
||||
&size,
|
||||
engine,
|
||||
&options,
|
||||
script_context, // context,
|
||||
hash_hint, // hash_hint,
|
||||
&hash_out, // hash_out,
|
||||
hash_alg, // hash_alg,
|
||||
file_type_hint, // file_type_hint,
|
||||
&file_type_out // file_type_out
|
||||
))) {
|
||||
printf("Virus detected: %s\n", virname);
|
||||
} else {
|
||||
if (ret != CL_SUCCESS) {
|
||||
printf("Error: %s\n", cl_strerror(ret));
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
ret = cl_scandesc_ex(
|
||||
target_fd,
|
||||
filename,
|
||||
&verdict,
|
||||
&alert_name,
|
||||
&size,
|
||||
engine,
|
||||
&options,
|
||||
script_context, // context,
|
||||
hash_hint, // hash_hint,
|
||||
&hash_out, // hash_out,
|
||||
hash_alg, // hash_alg,
|
||||
file_type_hint, // file_type_hint,
|
||||
&file_type_out // file_type_out
|
||||
);
|
||||
|
||||
/* Calculate size of scanned data */
|
||||
printf("\n");
|
||||
|
@ -1373,7 +1405,24 @@ int main(int argc, char **argv)
|
|||
} else {
|
||||
printf("No file type provided for this file.\n");
|
||||
}
|
||||
printf("Return code: %s (%d)\n", cl_strerror(ret), ret);
|
||||
switch (verdict) {
|
||||
case CL_VERDICT_NOTHING_FOUND: {
|
||||
printf("Verdict: Nothing found.\n");
|
||||
} break;
|
||||
|
||||
case CL_VERDICT_TRUSTED: {
|
||||
printf("Verdict: Trusted.\n");
|
||||
} break;
|
||||
|
||||
case CL_VERDICT_STRONG_INDICATOR: {
|
||||
printf("Verdict: Found Strong Indicator: %s\n", alert_name);
|
||||
} break;
|
||||
|
||||
case CL_VERDICT_POTENTIALLY_UNWANTED: {
|
||||
printf("Verdict: Found Potentially Unwanted Indicator: %s\n", alert_name);
|
||||
} break;
|
||||
}
|
||||
printf("Return Code: %s (%d)\n", cl_error_t_to_string(ret), ret);
|
||||
|
||||
status = ret == CL_VIRUS ? 1 : 0;
|
||||
|
||||
|
|
|
@ -2440,5 +2440,9 @@ cl_error_t asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset
|
|||
}
|
||||
|
||||
cli_dbgmsg("asn1_check_mscat: file with valid authenticode signature, trusted\n");
|
||||
|
||||
// Remove any evidence for this layer and set the verdict to trusted.
|
||||
(void)cli_trust_this_layer(ctx);
|
||||
|
||||
return CL_VERIFIED;
|
||||
}
|
||||
|
|
|
@ -81,7 +81,19 @@ extern "C" {
|
|||
|
||||
#define CL_COUNT_PRECISION 4096
|
||||
|
||||
/* return codes */
|
||||
/**
|
||||
* @brief Scan verdicts for cl_scanmap_ex(), cl_scanfile_ex(), and cl_scandesc_ex().
|
||||
*/
|
||||
typedef enum cl_verdict_t {
|
||||
CL_VERDICT_NOTHING_FOUND = 0, /**< No alerting signatures matched. */
|
||||
CL_VERDICT_TRUSTED, /**< The scan target has been deemed trusted (e.g. by FP signature or Authenticode). */
|
||||
CL_VERDICT_STRONG_INDICATOR, /**< One or more strong indicator signatures matched. */
|
||||
CL_VERDICT_POTENTIALLY_UNWANTED, /**< One or more potentially unwanted signatures matched. */
|
||||
} cl_verdict_t;
|
||||
|
||||
/**
|
||||
* @brief Return codes used by libclamav functions.
|
||||
*/
|
||||
typedef enum cl_error_t {
|
||||
/* libclamav specific */
|
||||
CL_CLEAN = 0,
|
||||
|
@ -123,7 +135,7 @@ typedef enum cl_error_t {
|
|||
CL_EBUSY,
|
||||
CL_ESTATE,
|
||||
|
||||
CL_VERIFIED, /** The binary has been deemed trusted */
|
||||
CL_VERIFIED, /** The scan target has been deemed trusted */
|
||||
CL_ERROR, /** Unspecified / generic error */
|
||||
|
||||
/* no error codes below this line please */
|
||||
|
@ -1630,9 +1642,12 @@ extern cl_error_t cl_scandesc_callback(
|
|||
* This variant also upgrades the `scanned` output parameter to a 64-bit integer.
|
||||
*
|
||||
* @param desc File descriptor of an open file. The caller must provide this or the map.
|
||||
* @param filename (optional) Filepath of the open file descriptor or file map.
|
||||
* @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.
|
||||
* @param[out] scanned The (exact) number of bytes scanned.
|
||||
* @param filename (Optional) Filepath of the open file descriptor or file map.
|
||||
* @param[out] verdict_out A pointer to a cl_verdict_t that will be set to the scan verdict.
|
||||
* You should check the verdict even if the function returns an error.
|
||||
* @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.
|
||||
* @param[out] scanned_out The (exact) number of bytes scanned.
|
||||
* @param engine The scanning engine.
|
||||
* @param scanoptions Scanning options.
|
||||
* @param[in,out] context (Optional) An application-defined context struct, opaque to libclamav.
|
||||
|
@ -1653,15 +1668,16 @@ extern cl_error_t cl_scandesc_callback(
|
|||
* of the top layer as determined by ClamAV.
|
||||
* Will take the form of the standard ClamAV file type format. E.g. "CL_TYPE_PE".
|
||||
* See also: https://docs.clamav.net/appendix/FileTypes.html#file-types
|
||||
* @return cl_error_t CL_CLEAN if no signature matched.
|
||||
* CL_VIRUS if a signature matched.
|
||||
* Another CL_E* error code if an error occurred.
|
||||
* @return cl_error_t CL_SUCCESS if no error occured.
|
||||
* Otherwise a CL_E* error code.
|
||||
* Does NOT return CL_VIRUS for a signature match. Check the `verdict_out` parameter instead.
|
||||
*/
|
||||
extern cl_error_t cl_scandesc_ex(
|
||||
int desc,
|
||||
const char *filename,
|
||||
const char **virname,
|
||||
uint64_t *scanned,
|
||||
cl_verdict_t *verdict_out,
|
||||
const char **last_alert_out,
|
||||
uint64_t *scanned_out,
|
||||
const struct cl_engine *engine,
|
||||
struct cl_scan_options *scanoptions,
|
||||
void *context,
|
||||
|
@ -1727,8 +1743,11 @@ extern cl_error_t cl_scanfile_callback(
|
|||
* This variant also upgrades the `scanned` output parameter to a 64-bit integer.
|
||||
*
|
||||
* @param filename Filepath of the file to be scanned.
|
||||
* @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.
|
||||
* @param[out] scanned The exact number of bytes scanned.
|
||||
* @param[out] verdict_out A pointer to a cl_verdict_t that will be set to the scan verdict.
|
||||
* You should check the verdict even if the function returns an error.
|
||||
* @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.
|
||||
* @param[out] scanned_out The (exact) number of bytes scanned.
|
||||
* @param engine The scanning engine.
|
||||
* @param scanoptions Scanning options.
|
||||
* @param[in,out] context (Optional) An application-defined context struct, opaque to libclamav.
|
||||
|
@ -1749,14 +1768,15 @@ extern cl_error_t cl_scanfile_callback(
|
|||
* of the top layer as determined by ClamAV.
|
||||
* Will take the form of the standard ClamAV file type format. E.g. "CL_TYPE_PE".
|
||||
* See also: https://docs.clamav.net/appendix/FileTypes.html#file-types
|
||||
* @return cl_error_t CL_CLEAN if no signature matched.
|
||||
* CL_VIRUS if a signature matched.
|
||||
* Another CL_E* error code if an error occurred.
|
||||
* @return cl_error_t CL_SUCCESS if no error occured.
|
||||
* Otherwise a CL_E* error code.
|
||||
* Does NOT return CL_VIRUS for a signature match. Check the `verdict_out` parameter instead.
|
||||
*/
|
||||
extern cl_error_t cl_scanfile_ex(
|
||||
const char *filename,
|
||||
const char **virname,
|
||||
uint64_t *scanned,
|
||||
cl_verdict_t *verdict_out,
|
||||
const char **last_alert_out,
|
||||
uint64_t *scanned_out,
|
||||
const struct cl_engine *engine,
|
||||
struct cl_scan_options *scanoptions,
|
||||
void *context,
|
||||
|
@ -1819,13 +1839,15 @@ extern cl_error_t cl_scanmap_callback(
|
|||
* This variant also upgrades the `scanned` output parameter to a 64-bit integer.
|
||||
*
|
||||
* @param map Buffer to be scanned, in form of a cl_fmap_t.
|
||||
* @param filename Name of data origin. Does not need to be an actual
|
||||
* file on disk. May be NULL if a name is not available.
|
||||
* @param[out] virname Pointer to receive the signature match name name if a
|
||||
* signature matched.
|
||||
* @param[out] scanned The exact number of bytes scanned.
|
||||
* @param filename (Optional) Name of data origin. Does not need to be an actual file on disk.
|
||||
* May be NULL if a name is not available.
|
||||
* @param[out] verdict_out A pointer to a cl_verdict_t that will be set to the scan verdict.
|
||||
* You should check the verdict even if the function returns an error.
|
||||
* @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.
|
||||
* @param[out] scanned_out The (exact) number of bytes scanned.
|
||||
* @param engine The scanning engine.
|
||||
* @param scanoptions The scanning options struct.
|
||||
* @param scanoptions Scanning options.
|
||||
* @param[in,out] context (Optional) An application-defined context struct, opaque to libclamav.
|
||||
* May be used within your callback functions.
|
||||
* @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(
|
|||
* of the top layer as determined by ClamAV.
|
||||
* Will take the form of the standard ClamAV file type format. E.g. "CL_TYPE_PE".
|
||||
* See also: https://docs.clamav.net/appendix/FileTypes.html#file-types
|
||||
* @return cl_error_t CL_CLEAN if no signature matched.
|
||||
* CL_VIRUS if a signature matched.
|
||||
* Another CL_E* error code if an error occurred.
|
||||
* @return cl_error_t CL_SUCCESS if no error occured.
|
||||
* Otherwise a CL_E* error code.
|
||||
* Does NOT return CL_VIRUS for a signature match. Check the `verdict_out` parameter instead.
|
||||
*/
|
||||
extern cl_error_t cl_scanmap_ex(
|
||||
cl_fmap_t *map,
|
||||
const char *filename,
|
||||
const char **virname,
|
||||
uint64_t *scanned,
|
||||
cl_verdict_t *verdict_out,
|
||||
const char **last_alert_out,
|
||||
uint64_t *scanned_out,
|
||||
const struct cl_engine *engine,
|
||||
struct cl_scan_options *scanoptions,
|
||||
void *context,
|
||||
|
|
|
@ -668,12 +668,20 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname)
|
|||
|
||||
if (cli_hm_scan(hash, map->len, &virname, ctx->engine->hm_fp, hash_type) == CL_VIRUS) {
|
||||
cli_dbgmsg("cli_check_fp: Found false positive detection for %s (fp sig: %s)\n", cli_hash_name(hash_type), virname);
|
||||
status = CL_CLEAN;
|
||||
|
||||
// Remove any evidence and set the verdict to trusted for the layer where the FP hash matched, and for all contained layers.
|
||||
(void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level);
|
||||
|
||||
status = CL_VERIFIED;
|
||||
goto done;
|
||||
}
|
||||
if (cli_hm_scan_wild(hash, &virname, ctx->engine->hm_fp, hash_type) == CL_VIRUS) {
|
||||
cli_dbgmsg("cli_check_fp: Found false positive detection for %s (fp sig: %s)\n", cli_hash_name(hash_type), virname);
|
||||
status = CL_CLEAN;
|
||||
|
||||
// Remove any evidence and set the verdict to trusted for the layer where the FP hash matched, and for all contained layers.
|
||||
(void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level);
|
||||
|
||||
status = CL_VERIFIED;
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
@ -682,7 +690,11 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname)
|
|||
* (associated with the .CAB file type) */
|
||||
if (cli_hm_scan(hash, 1, &virname, ctx->engine->hm_fp, hash_type) == CL_VIRUS) {
|
||||
cli_dbgmsg("cli_check_fp: Found .CAB false positive detection for %s via catalog file\n", cli_hash_name(hash_type));
|
||||
status = CL_CLEAN;
|
||||
|
||||
// Remove any evidence and set the verdict to trusted for the layer where the FP hash matched, and for all contained layers.
|
||||
(void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level);
|
||||
|
||||
status = CL_VERIFIED;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1521,11 +1521,13 @@ static cl_error_t append_virus(cli_ctx *ctx, const char *virname, IndicatorType
|
|||
ctx->this_layer_evidence = ctx->recursion_stack[ctx->recursion_level].evidence;
|
||||
|
||||
if ((ctx->fmap != NULL) &&
|
||||
(ctx->recursion_stack != NULL) &&
|
||||
(CL_VIRUS != cli_check_fp(ctx, virname))) {
|
||||
// FP signature found for one of the layers. Ignore indicator.
|
||||
status = CL_SUCCESS;
|
||||
goto done;
|
||||
(ctx->recursion_stack != NULL)) {
|
||||
|
||||
status = cli_check_fp(ctx, virname);
|
||||
if (CL_VERIFIED == status) {
|
||||
// FP signature found for one of the layers. Ignore indicator.
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
add_successful = evidence_add_indicator(
|
||||
|
@ -2621,10 +2623,8 @@ cl_error_t cli_dispatch_scan_callback(cli_ctx *ctx, cl_scan_callback_t location)
|
|||
// So we need to remove any alerts for this layer and return CL_VERIFIED (will stop scanning this layer).
|
||||
cli_dbgmsg("dispatch_scan_callback: Layer verified clean by callback\n");
|
||||
|
||||
evidence_free(ctx->recursion_stack[ctx->recursion_level].evidence);
|
||||
ctx->recursion_stack[ctx->recursion_level].evidence = NULL;
|
||||
ctx->this_layer_evidence = NULL;
|
||||
|
||||
// Remove any evidence for this layer and set the verdict to trusted.
|
||||
(void)cli_trust_this_layer(ctx);
|
||||
status = CL_VERIFIED;
|
||||
} break;
|
||||
|
||||
|
@ -2726,3 +2726,55 @@ uint8_t cli_set_debug_flag(uint8_t debug_flag)
|
|||
|
||||
return was;
|
||||
}
|
||||
|
||||
cl_error_t cli_trust_this_layer(cli_ctx *ctx)
|
||||
{
|
||||
cl_error_t status = CL_ERROR;
|
||||
|
||||
if (!ctx) {
|
||||
cli_errmsg("cli_trust_this_layer: invalid context\n");
|
||||
status = CL_ENULLARG;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (NULL != ctx->recursion_stack[ctx->recursion_level].evidence) {
|
||||
evidence_free(ctx->recursion_stack[ctx->recursion_level].evidence);
|
||||
ctx->recursion_stack[ctx->recursion_level].evidence = NULL;
|
||||
ctx->this_layer_evidence = NULL;
|
||||
}
|
||||
|
||||
ctx->recursion_stack[ctx->recursion_level].verdict = CL_VERDICT_TRUSTED;
|
||||
|
||||
status = CL_SUCCESS;
|
||||
|
||||
done:
|
||||
return status;
|
||||
}
|
||||
|
||||
cl_error_t cli_trust_layers(cli_ctx *ctx, uint32_t start_layer, uint32_t end_layer)
|
||||
{
|
||||
cl_error_t status = CL_ERROR;
|
||||
size_t i;
|
||||
|
||||
if (!ctx) {
|
||||
cli_errmsg("cli_trust_layers: invalid context\n");
|
||||
status = CL_ENULLARG;
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (i = start_layer; i <= end_layer; i++) {
|
||||
|
||||
if (NULL != ctx->recursion_stack[i].evidence) {
|
||||
evidence_free(ctx->recursion_stack[i].evidence);
|
||||
ctx->recursion_stack[i].evidence = NULL;
|
||||
ctx->this_layer_evidence = NULL;
|
||||
}
|
||||
|
||||
ctx->recursion_stack[i].verdict = CL_VERDICT_TRUSTED;
|
||||
}
|
||||
|
||||
status = CL_SUCCESS;
|
||||
|
||||
done:
|
||||
return status;
|
||||
}
|
||||
|
|
|
@ -1323,6 +1323,24 @@ uint8_t cli_get_debug_flag(void);
|
|||
*/
|
||||
uint8_t cli_set_debug_flag(uint8_t debug_flag);
|
||||
|
||||
/**
|
||||
* @brief Trust the current layer by removing any evidence and setting the verdict to trusted.
|
||||
*
|
||||
* @param ctx The scan context.
|
||||
* @return cl_error_t CL_SUCCESS on success, or an error code.
|
||||
*/
|
||||
cl_error_t cli_trust_this_layer(cli_ctx *ctx);
|
||||
|
||||
/**
|
||||
* @brief Trust a range of layers by removing any evidence and setting the verdict to trusted.
|
||||
*
|
||||
* @param ctx The scan context.
|
||||
* @param start_layer The layer to start trusting from (inclusive).
|
||||
* @param end_layer The layer to stop trusting at (inclusive).
|
||||
* @return cl_error_t CL_SUCCESS on success, or an error code.
|
||||
*/
|
||||
cl_error_t cli_trust_layers(cli_ctx *ctx, uint32_t start_layer, uint32_t end_layer);
|
||||
|
||||
#ifndef CLI_SAFER_STRDUP_OR_GOTO_DONE
|
||||
/**
|
||||
* @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)
|
|||
|
||||
if (cli_hm_scan(authsha, 2, NULL, ctx->engine->hm_fp, hashtype) == CL_VIRUS) {
|
||||
cli_dbgmsg("cli_check_auth_header: PE file trusted by catalog file (%s)\n", hashctx_name);
|
||||
|
||||
// Remove any evidence for this layer and set the verdict to trusted.
|
||||
(void)cli_trust_this_layer(ctx);
|
||||
|
||||
ret = CL_VERIFIED;
|
||||
goto finish;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ typedef struct cli_scan_layer {
|
|||
size_t object_id; /* Unique ID for this object. */
|
||||
json_object *metadata_json; /* JSON object for this recursion level, e.g. for JSON metadata. */
|
||||
evidence_t evidence; /* Store signature matches for this layer and its children. */
|
||||
cl_verdict_t verdict; /* Verdict for this layer, e.g. CL_VERDICT_STRONG_INDICATOR, CL_VERDICT_NOTHING_FOUND, CL_VERDICT_TRUSTED. */
|
||||
char *tmpdir; /* The directory to store tmp files created when processing this layer. */
|
||||
struct cli_scan_layer *parent; /* Pointer to the parent layer, if any. */
|
||||
} cli_scan_layer_t;
|
||||
|
|
|
@ -4380,6 +4380,10 @@ static cl_error_t dispatch_prescan_callback(clcb_pre_scan cb, cli_ctx *ctx, cons
|
|||
switch (status) {
|
||||
case CL_BREAK:
|
||||
cli_dbgmsg("dispatch_prescan_callback: file allowed by callback\n");
|
||||
|
||||
// Remove any evidence for this layer and set the verdict to trusted.
|
||||
(void)cli_trust_this_layer(ctx);
|
||||
|
||||
status = CL_VERIFIED;
|
||||
break;
|
||||
case CL_VIRUS:
|
||||
|
@ -4548,9 +4552,9 @@ done:
|
|||
|
||||
cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type)
|
||||
{
|
||||
cl_error_t ret = CL_CLEAN;
|
||||
cl_error_t cache_check_result = CL_VIRUS;
|
||||
cl_error_t verdict_at_this_level = CL_CLEAN;
|
||||
cl_error_t ret = CL_CLEAN;
|
||||
cl_error_t cache_check_result = CL_VIRUS;
|
||||
cl_verdict_t verdict_at_this_level = CL_VERDICT_NOTHING_FOUND;
|
||||
|
||||
bool cache_enabled = true;
|
||||
cli_file_t dettype = 0;
|
||||
|
@ -5351,15 +5355,6 @@ done:
|
|||
// And to convert CL_VERIFIED -> CL_CLEAN
|
||||
(void)result_should_goto_done(ctx, ret, &ret);
|
||||
|
||||
/*
|
||||
* Determine if there was an alert for this layer (or its children).
|
||||
*/
|
||||
if (0 < evidence_num_alerts(ctx->this_layer_evidence)) {
|
||||
verdict_at_this_level = CL_VIRUS;
|
||||
} else {
|
||||
verdict_at_this_level = ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Run the deprecated post-scan callback (if one exists) and provide the verdict for this layer.
|
||||
*/
|
||||
|
@ -5369,7 +5364,7 @@ done:
|
|||
const char *virusname = NULL;
|
||||
|
||||
// Get the last signature that matched (if any).
|
||||
if (verdict_at_this_level == CL_VIRUS) {
|
||||
if (0 < evidence_num_alerts(ctx->this_layer_evidence)) {
|
||||
virusname = cli_get_last_virus(ctx);
|
||||
}
|
||||
|
||||
|
@ -5397,6 +5392,25 @@ done:
|
|||
}
|
||||
}
|
||||
|
||||
if (CL_VERDICT_TRUSTED == ctx->recursion_stack[ctx->recursion_level].verdict) {
|
||||
/* Remove any alerts for this layer. */
|
||||
if (NULL != ctx->recursion_stack[ctx->recursion_level].evidence) {
|
||||
evidence_free(ctx->recursion_stack[ctx->recursion_level].evidence);
|
||||
ctx->recursion_stack[ctx->recursion_level].evidence = NULL;
|
||||
ctx->this_layer_evidence = NULL;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Update the verdict for this layer based on the scan results.
|
||||
* If the verdict is CL_VERDICT_TRUSTED, then we don't change it.
|
||||
*/
|
||||
if (0 < evidence_num_indicators_type(ctx->this_layer_evidence, IndicatorType_Strong)) {
|
||||
ctx->recursion_stack[ctx->recursion_level].verdict = CL_VERDICT_STRONG_INDICATOR;
|
||||
} else if (0 < evidence_num_indicators_type(ctx->this_layer_evidence, IndicatorType_PotentiallyUnwanted)) {
|
||||
ctx->recursion_stack[ctx->recursion_level].verdict = CL_VERDICT_POTENTIALLY_UNWANTED;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the verdict for this layer is "clean", we can cache it.
|
||||
*
|
||||
|
@ -5404,10 +5418,15 @@ done:
|
|||
* so this may not actually cache if we exceeded limits earlier.
|
||||
* It will also check if caching is disabled.
|
||||
*/
|
||||
if (verdict_at_this_level == CL_CLEAN) {
|
||||
perf_start(ctx, PERFT_CACHE);
|
||||
clean_cache_add(ctx);
|
||||
perf_stop(ctx, PERFT_CACHE);
|
||||
if ((CL_VERDICT_TRUSTED == ctx->recursion_stack[ctx->recursion_level].verdict) ||
|
||||
(CL_VERDICT_NOTHING_FOUND == ctx->recursion_stack[ctx->recursion_level].verdict)) {
|
||||
// Also verify we have no weak indicators before adding to the clean cache.
|
||||
// Weak indicators may be used in the future to match a strong indicator.
|
||||
if (evidence_num_indicators_type(ctx->this_layer_evidence, IndicatorType_Weak) == 0) {
|
||||
perf_start(ctx, PERFT_CACHE);
|
||||
clean_cache_add(ctx);
|
||||
perf_stop(ctx, PERFT_CACHE);
|
||||
}
|
||||
}
|
||||
|
||||
early_ret:
|
||||
|
@ -5646,38 +5665,41 @@ cl_error_t cli_magic_scan_buff(const void *buffer, size_t length, cli_ctx *ctx,
|
|||
/**
|
||||
* @brief The main function to initiate a scan of an fmap.
|
||||
*
|
||||
* @param map File map.
|
||||
* @param filepath (optional, recommended) filepath of the open file descriptor or file map.
|
||||
* @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.
|
||||
* @param[out] scanned (Optional) The number of bytes scanned.
|
||||
* @param engine The scanning engine.
|
||||
* @param scanoptions Scanning options.
|
||||
* @param[in,out] context (Optional) An application-defined context struct, opaque to libclamav.
|
||||
* May be used within your callback functions.
|
||||
* @param hash_hint (Optional) A NULL terminated string of the file hash so that
|
||||
* libclamav does not need to calculate it.
|
||||
* @param[out] hash_out (Optional) A NULL terminated string of the file hash.
|
||||
* The caller is responsible for freeing this string.
|
||||
* @param hash_alg The hashing algorithm used for either `hash_hint` or `hash_out`.
|
||||
* Supported algorithms are "md5", "sha1", "sha2-256".
|
||||
* Required only if you provide a `hash_hint` or want to receive a `hash_out`.
|
||||
* @param file_type_hint (Optional) A NULL terminated string of the file type hint.
|
||||
* E.g. "pe", "elf", "zip", etc.
|
||||
* You may also use ClamAV type names such as "CL_TYPE_PE".
|
||||
* ClamAV will ignore the hint if it is not familiar with the specified type.
|
||||
* @param file_type_out (Optional) A NULL terminated string of the file type
|
||||
* of the top layer as determined by ClamAV.
|
||||
* Will take the form of the standard ClamAV file type format. E.g. "CL_TYPE_PE".
|
||||
* The caller is responsible for freeing this string.
|
||||
* @return cl_error_t CL_CLEAN if no signature matched.
|
||||
* CL_VIRUS if a signature matched.
|
||||
* Another CL_E* error code if an error occurred.
|
||||
* @param map File map.
|
||||
* @param filepath (optional, recommended) filepath of the open file descriptor or file map.
|
||||
* @param[out] verdict_out A pointer to a cl_verdict_t that will be set to the scan verdict.
|
||||
* You should check the verdict even if the function returns an error.
|
||||
* @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.
|
||||
* @param[out] scanned_out (Optional) The number of bytes scanned.
|
||||
* @param engine The scanning engine.
|
||||
* @param scanoptions Scanning options.
|
||||
* @param[in,out] context (Optional) An application-defined context struct, opaque to libclamav.
|
||||
* May be used within your callback functions.
|
||||
* @param hash_hint (Optional) A NULL terminated string of the file hash so that
|
||||
* libclamav does not need to calculate it.
|
||||
* @param[out] hash_out (Optional) A NULL terminated string of the file hash.
|
||||
* The caller is responsible for freeing this string.
|
||||
* @param hash_alg The hashing algorithm used for either `hash_hint` or `hash_out`.
|
||||
* Supported algorithms are "md5", "sha1", "sha2-256".
|
||||
* Required only if you provide a `hash_hint` or want to receive a `hash_out`.
|
||||
* @param file_type_hint (Optional) A NULL terminated string of the file type hint.
|
||||
* E.g. "pe", "elf", "zip", etc.
|
||||
* You may also use ClamAV type names such as "CL_TYPE_PE".
|
||||
* ClamAV will ignore the hint if it is not familiar with the specified type.
|
||||
* @param file_type_out (Optional) A NULL terminated string of the file type
|
||||
* of the top layer as determined by ClamAV.
|
||||
* Will take the form of the standard ClamAV file type format. E.g. "CL_TYPE_PE".
|
||||
* The caller is responsible for freeing this string.
|
||||
* @return cl_error_t CL_SUCCESS if no error occured.
|
||||
* Otherwise a CL_E* error code.
|
||||
* Does NOT return CL_VIRUS for a signature match. Check the `verdict_out` parameter instead.
|
||||
*/
|
||||
static cl_error_t scan_common(
|
||||
cl_fmap_t *map,
|
||||
const char *filepath,
|
||||
const char **virname,
|
||||
uint64_t *scanned,
|
||||
cl_verdict_t *verdict_out,
|
||||
const char **last_alert_out,
|
||||
uint64_t *scanned_out,
|
||||
const struct cl_engine *engine,
|
||||
struct cl_scan_options *scanoptions,
|
||||
void *context,
|
||||
|
@ -5689,7 +5711,6 @@ static cl_error_t scan_common(
|
|||
{
|
||||
cl_error_t status = CL_SUCCESS;
|
||||
cl_error_t ret;
|
||||
cl_error_t verdict = CL_CLEAN;
|
||||
|
||||
cli_ctx ctx = {0};
|
||||
|
||||
|
@ -5710,12 +5731,13 @@ static cl_error_t scan_common(
|
|||
// The type of the file being scanned.
|
||||
cli_file_t file_type = CL_TYPE_ANY;
|
||||
|
||||
if (NULL == map || NULL == scanoptions || NULL == virname) {
|
||||
if (NULL == map || NULL == scanoptions || NULL == verdict_out || NULL == last_alert_out || NULL == engine) {
|
||||
return CL_ENULLARG;
|
||||
}
|
||||
|
||||
/* Initialize output variables */
|
||||
*virname = NULL;
|
||||
*verdict_out = CL_VERDICT_NOTHING_FOUND;
|
||||
*last_alert_out = NULL;
|
||||
|
||||
// If the caller provided a file type hint, we make a best effort to use it.
|
||||
if (file_type_hint) {
|
||||
|
@ -5776,7 +5798,7 @@ static cl_error_t scan_common(
|
|||
}
|
||||
|
||||
ctx.engine = engine;
|
||||
ctx.scanned = scanned;
|
||||
ctx.scanned = scanned_out;
|
||||
CLI_MALLOC_OR_GOTO_DONE(ctx.options, sizeof(struct cl_scan_options), status = CL_EMEM);
|
||||
|
||||
memcpy(ctx.options, scanoptions, sizeof(struct cl_scan_options));
|
||||
|
@ -6043,8 +6065,8 @@ static cl_error_t scan_common(
|
|||
|
||||
// If any alerts occurred, set the output pointer to the "latest" alert signature name.
|
||||
if (0 < evidence_num_alerts(ctx.this_layer_evidence)) {
|
||||
*virname = cli_get_last_virus_str(&ctx);
|
||||
verdict = CL_VIRUS;
|
||||
*last_alert_out = cli_get_last_virus_str(&ctx);
|
||||
*verdict_out = CL_VERDICT_STRONG_INDICATOR;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -6152,12 +6174,6 @@ static cl_error_t scan_common(
|
|||
}
|
||||
}
|
||||
|
||||
if (verdict != CL_CLEAN) {
|
||||
// Reporting "VIRUS" is more important than reporting and error,
|
||||
// because... unfortunately we can only do one with the current API.
|
||||
status = verdict;
|
||||
}
|
||||
|
||||
done:
|
||||
|
||||
if (logg_initialized) {
|
||||
|
@ -6222,10 +6238,12 @@ cl_error_t cl_scandesc(
|
|||
{
|
||||
cl_error_t status;
|
||||
uint64_t scanned_out;
|
||||
cl_verdict_t verdict_out = CL_VERDICT_NOTHING_FOUND;
|
||||
|
||||
status = cl_scandesc_ex(
|
||||
desc,
|
||||
filename,
|
||||
&verdict_out,
|
||||
virname,
|
||||
&scanned_out,
|
||||
engine,
|
||||
|
@ -6247,6 +6265,12 @@ cl_error_t cl_scandesc(
|
|||
}
|
||||
}
|
||||
|
||||
if (verdict_out == CL_VERDICT_STRONG_INDICATOR || verdict_out == CL_VERDICT_POTENTIALLY_UNWANTED) {
|
||||
// Reporting "CL_VIRUS" is more important than reporting an error,
|
||||
// because... unfortunately we can only do one with this API.
|
||||
status = CL_VIRUS;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
@ -6260,13 +6284,15 @@ cl_error_t cl_scandesc_callback(
|
|||
void *context)
|
||||
{
|
||||
cl_error_t status;
|
||||
uint64_t scanned_out;
|
||||
uint64_t scanned_bytes;
|
||||
cl_verdict_t verdict_out = CL_VERDICT_NOTHING_FOUND;
|
||||
|
||||
status = cl_scandesc_ex(
|
||||
desc,
|
||||
filename,
|
||||
&verdict_out,
|
||||
virname,
|
||||
&scanned_out,
|
||||
&scanned_bytes,
|
||||
engine,
|
||||
scanoptions,
|
||||
context,
|
||||
|
@ -6278,22 +6304,29 @@ cl_error_t cl_scandesc_callback(
|
|||
|
||||
if (NULL != scanned) {
|
||||
if ((SIZEOF_LONG == 4) &&
|
||||
(scanned_out / CL_COUNT_PRECISION > UINT32_MAX)) {
|
||||
cli_warnmsg("cl_scanfile_callback: scanned_out exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
||||
(scanned_bytes / CL_COUNT_PRECISION > UINT32_MAX)) {
|
||||
cli_warnmsg("cl_scanfile_callback: scanned_bytes exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
||||
*scanned = UINT32_MAX;
|
||||
} else {
|
||||
*scanned = (unsigned long int)(scanned_out / CL_COUNT_PRECISION);
|
||||
*scanned = (unsigned long int)(scanned_bytes / CL_COUNT_PRECISION);
|
||||
}
|
||||
}
|
||||
|
||||
if (verdict_out == CL_VERDICT_STRONG_INDICATOR || verdict_out == CL_VERDICT_POTENTIALLY_UNWANTED) {
|
||||
// Reporting "CL_VIRUS" is more important than reporting an error,
|
||||
// because... unfortunately we can only do one with this API.
|
||||
status = CL_VIRUS;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
cl_error_t cl_scandesc_ex(
|
||||
int desc,
|
||||
const char *filename,
|
||||
const char **virname,
|
||||
uint64_t *scanned,
|
||||
cl_verdict_t *verdict_out,
|
||||
const char **last_alert_out,
|
||||
uint64_t *scanned_out,
|
||||
const struct cl_engine *engine,
|
||||
struct cl_scan_options *scanoptions,
|
||||
void *context,
|
||||
|
@ -6323,8 +6356,8 @@ cl_error_t cl_scandesc_ex(
|
|||
if (scanoptions->heuristic & CL_SCAN_HEURISTIC_EXCEEDS_MAX) {
|
||||
if (engine->cb_virus_found) {
|
||||
engine->cb_virus_found(desc, "Heuristics.Limits.Exceeded.MaxFileSize", context);
|
||||
if (virname) {
|
||||
*virname = "Heuristics.Limits.Exceeded.MaxFileSize";
|
||||
if (last_alert_out) {
|
||||
*last_alert_out = "Heuristics.Limits.Exceeded.MaxFileSize";
|
||||
}
|
||||
}
|
||||
status = CL_VIRUS;
|
||||
|
@ -6347,8 +6380,9 @@ cl_error_t cl_scandesc_ex(
|
|||
status = scan_common(
|
||||
map,
|
||||
filename,
|
||||
virname,
|
||||
scanned,
|
||||
verdict_out,
|
||||
last_alert_out,
|
||||
scanned_out,
|
||||
engine,
|
||||
scanoptions,
|
||||
context,
|
||||
|
@ -6379,13 +6413,15 @@ cl_error_t cl_scanmap_callback(
|
|||
void *context)
|
||||
{
|
||||
cl_error_t status;
|
||||
uint64_t scanned_out;
|
||||
uint64_t scanned_bytes;
|
||||
cl_verdict_t verdict_out = CL_VERDICT_NOTHING_FOUND;
|
||||
|
||||
status = cl_scanmap_ex(
|
||||
map,
|
||||
filename,
|
||||
&verdict_out,
|
||||
virname,
|
||||
&scanned_out,
|
||||
&scanned_bytes,
|
||||
engine,
|
||||
scanoptions,
|
||||
context,
|
||||
|
@ -6397,22 +6433,29 @@ cl_error_t cl_scanmap_callback(
|
|||
|
||||
if (NULL != scanned) {
|
||||
if ((SIZEOF_LONG == 4) &&
|
||||
(scanned_out / CL_COUNT_PRECISION > UINT32_MAX)) {
|
||||
cli_warnmsg("cl_scanfile_callback: scanned_out exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
||||
(scanned_bytes / CL_COUNT_PRECISION > UINT32_MAX)) {
|
||||
cli_warnmsg("cl_scanfile_callback: scanned_bytes exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
||||
*scanned = UINT32_MAX;
|
||||
} else {
|
||||
*scanned = (unsigned long int)(scanned_out / CL_COUNT_PRECISION);
|
||||
*scanned = (unsigned long int)(scanned_bytes / CL_COUNT_PRECISION);
|
||||
}
|
||||
}
|
||||
|
||||
if (verdict_out == CL_VERDICT_STRONG_INDICATOR || verdict_out == CL_VERDICT_POTENTIALLY_UNWANTED) {
|
||||
// Reporting "CL_VIRUS" is more important than reporting an error,
|
||||
// because... unfortunately we can only do one with this API.
|
||||
status = CL_VIRUS;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
cl_error_t cl_scanmap_ex(
|
||||
cl_fmap_t *map,
|
||||
const char *filename,
|
||||
const char **virname,
|
||||
uint64_t *scanned,
|
||||
cl_verdict_t *verdict_out,
|
||||
const char **last_alert_out,
|
||||
uint64_t *scanned_out,
|
||||
const struct cl_engine *engine,
|
||||
struct cl_scan_options *scanoptions,
|
||||
void *context,
|
||||
|
@ -6427,8 +6470,8 @@ cl_error_t cl_scanmap_ex(
|
|||
if (scanoptions->heuristic & CL_SCAN_HEURISTIC_EXCEEDS_MAX) {
|
||||
if (engine->cb_virus_found) {
|
||||
engine->cb_virus_found(fmap_fd(map), "Heuristics.Limits.Exceeded.MaxFileSize", context);
|
||||
if (virname) {
|
||||
*virname = "Heuristics.Limits.Exceeded.MaxFileSize";
|
||||
if (last_alert_out) {
|
||||
*last_alert_out = "Heuristics.Limits.Exceeded.MaxFileSize";
|
||||
}
|
||||
}
|
||||
return CL_VIRUS;
|
||||
|
@ -6444,8 +6487,9 @@ cl_error_t cl_scanmap_ex(
|
|||
return scan_common(
|
||||
map,
|
||||
filename,
|
||||
virname,
|
||||
scanned,
|
||||
verdict_out,
|
||||
last_alert_out,
|
||||
scanned_out,
|
||||
engine,
|
||||
scanoptions,
|
||||
context,
|
||||
|
@ -6485,12 +6529,14 @@ cl_error_t cl_scanfile(
|
|||
struct cl_scan_options *scanoptions)
|
||||
{
|
||||
cl_error_t status;
|
||||
uint64_t scanned_out;
|
||||
uint64_t scanned_bytes;
|
||||
cl_verdict_t verdict_out = CL_VERDICT_NOTHING_FOUND;
|
||||
|
||||
status = cl_scanfile_ex(
|
||||
filename,
|
||||
&verdict_out,
|
||||
virname,
|
||||
&scanned_out,
|
||||
&scanned_bytes,
|
||||
engine,
|
||||
scanoptions,
|
||||
NULL,
|
||||
|
@ -6501,14 +6547,20 @@ cl_error_t cl_scanfile(
|
|||
NULL);
|
||||
|
||||
if (NULL != scanned) {
|
||||
if (SIZEOF_LONG == 4 && scanned_out > UINT32_MAX) {
|
||||
cli_warnmsg("cl_scanfile_callback: scanned_out exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
||||
if (SIZEOF_LONG == 4 && scanned_bytes > UINT32_MAX) {
|
||||
cli_warnmsg("cl_scanfile_callback: scanned_bytes exceeds UINT32_MAX, setting to UINT32_MAX\n");
|
||||
*scanned = UINT32_MAX;
|
||||
} else {
|
||||
*scanned = (unsigned long int)scanned_out;
|
||||
*scanned = (unsigned long int)scanned_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
if (verdict_out == CL_VERDICT_STRONG_INDICATOR || verdict_out == CL_VERDICT_POTENTIALLY_UNWANTED) {
|
||||
// Reporting "CL_VIRUS" is more important than reporting an error,
|
||||
// because... unfortunately we can only do one with this API.
|
||||
status = CL_VIRUS;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
@ -6522,9 +6574,11 @@ cl_error_t cl_scanfile_callback(
|
|||
{
|
||||
cl_error_t status;
|
||||
uint64_t scanned_out;
|
||||
cl_verdict_t verdict_out = CL_VERDICT_NOTHING_FOUND;
|
||||
|
||||
status = cl_scanfile_ex(
|
||||
filename,
|
||||
&verdict_out,
|
||||
virname,
|
||||
&scanned_out,
|
||||
engine,
|
||||
|
@ -6545,13 +6599,20 @@ cl_error_t cl_scanfile_callback(
|
|||
}
|
||||
}
|
||||
|
||||
if (verdict_out == CL_VERDICT_STRONG_INDICATOR || verdict_out == CL_VERDICT_POTENTIALLY_UNWANTED) {
|
||||
// Reporting "CL_VIRUS" is more important than reporting an error,
|
||||
// because... unfortunately we can only do one with this API.
|
||||
status = CL_VIRUS;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
cl_error_t cl_scanfile_ex(
|
||||
const char *filename,
|
||||
const char **virname,
|
||||
uint64_t *scanned,
|
||||
cl_verdict_t *verdict_out,
|
||||
const char **last_alert_out,
|
||||
uint64_t *scanned_out,
|
||||
const struct cl_engine *engine,
|
||||
struct cl_scan_options *scanoptions,
|
||||
void *context,
|
||||
|
@ -6582,8 +6643,9 @@ cl_error_t cl_scanfile_ex(
|
|||
ret = cl_scandesc_ex(
|
||||
fd,
|
||||
filename,
|
||||
virname,
|
||||
scanned,
|
||||
verdict_out,
|
||||
last_alert_out,
|
||||
scanned_out,
|
||||
engine,
|
||||
scanoptions,
|
||||
context,
|
||||
|
|
|
@ -154,9 +154,7 @@ pub unsafe extern "C" fn _evidence_add_child_evidence(
|
|||
/// Free the evidence
|
||||
#[no_mangle]
|
||||
pub extern "C" fn evidence_free(evidence: sys::evidence_t) {
|
||||
if evidence.is_null() {
|
||||
warn!("Attempted to free a NULL evidence pointer. Please report this at: https://github.com/Cisco-Talos/clamav/issues");
|
||||
} else {
|
||||
if !evidence.is_null() {
|
||||
let _ = unsafe { Box::from_raw(evidence as *mut Evidence) };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,16 @@ pub struct bignum_st {
|
|||
_unused: [u8; 0],
|
||||
}
|
||||
pub type BIGNUM = bignum_st;
|
||||
#[doc = "< No alerting signatures matched."]
|
||||
pub const cl_verdict_t_CL_VERDICT_NOTHING_FOUND: cl_verdict_t = 0;
|
||||
#[doc = "< The scan target has been deemed trusted (e.g. by FP signature or Authenticode)."]
|
||||
pub const cl_verdict_t_CL_VERDICT_TRUSTED: cl_verdict_t = 1;
|
||||
#[doc = "< One or more strong indicator signatures matched."]
|
||||
pub const cl_verdict_t_CL_VERDICT_STRONG_INDICATOR: cl_verdict_t = 2;
|
||||
#[doc = "< One or more potentially unwanted signatures matched."]
|
||||
pub const cl_verdict_t_CL_VERDICT_POTENTIALLY_UNWANTED: cl_verdict_t = 3;
|
||||
#[doc = " @brief Scan verdicts for cl_scanmap_ex(), cl_scanfile_ex(), and cl_scandesc_ex()."]
|
||||
pub type cl_verdict_t = ::std::os::raw::c_uint;
|
||||
pub const cl_error_t_CL_CLEAN: cl_error_t = 0;
|
||||
pub const cl_error_t_CL_SUCCESS: cl_error_t = 0;
|
||||
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;
|
|||
pub const cl_error_t_CL_ESTATE: cl_error_t = 32;
|
||||
#[doc = " may be reported in testmode"]
|
||||
pub const cl_error_t_CL_VERIFIED: cl_error_t = 33;
|
||||
#[doc = " The binary has been deemed trusted"]
|
||||
#[doc = " The scan target has been deemed trusted"]
|
||||
pub const cl_error_t_CL_ERROR: cl_error_t = 34;
|
||||
#[doc = " Unspecified / generic error"]
|
||||
pub const cl_error_t_CL_ELAST_ERROR: cl_error_t = 35;
|
||||
#[doc = " @brief Return codes used by libclamav functions."]
|
||||
pub type cl_error_t = ::std::os::raw::c_uint;
|
||||
#[doc = " scan options"]
|
||||
#[repr(C)]
|
||||
|
@ -651,6 +662,7 @@ pub struct cli_scan_layer {
|
|||
pub object_id: usize,
|
||||
pub metadata_json: *mut json_object,
|
||||
pub evidence: evidence_t,
|
||||
pub verdict: cl_verdict_t,
|
||||
pub tmpdir: *mut ::std::os::raw::c_char,
|
||||
pub parent: *mut cli_scan_layer,
|
||||
}
|
||||
|
|
|
@ -176,7 +176,8 @@ class TC(testcase.TestCase):
|
|||
'Data scanned: 948 B',
|
||||
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa',
|
||||
'File Type: CL_TYPE_ZIP',
|
||||
'Return code: Virus(es) detected (1)',
|
||||
'Verdict: Found Strong Indicator',
|
||||
'Return Code: CL_SUCCESS (0)',
|
||||
]
|
||||
|
||||
command = '{valgrind} {valgrind_args} {example} -d {database} -f {target} --script {script}'.format(
|
||||
|
@ -187,7 +188,8 @@ class TC(testcase.TestCase):
|
|||
)
|
||||
output = self.execute_command(command)
|
||||
|
||||
assert output.ec == 1 # virus(es) found
|
||||
# Check for success
|
||||
assert output.ec == 0
|
||||
|
||||
# Custom logic to verify the output making sure that all expected results are found in the output in order.
|
||||
#
|
||||
|
@ -288,7 +290,7 @@ class TC(testcase.TestCase):
|
|||
'Data scanned: 948 B',
|
||||
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa',
|
||||
'File Type: CL_TYPE_ZIP',
|
||||
'Return code: No viruses detected (0)',
|
||||
'Return Code: CL_SUCCESS (0)',
|
||||
]
|
||||
|
||||
command = '{valgrind} {valgrind_args} {example} -d {database} -f {target} --script {script}'.format(
|
||||
|
@ -299,7 +301,8 @@ class TC(testcase.TestCase):
|
|||
)
|
||||
output = self.execute_command(command)
|
||||
|
||||
assert output.ec == 0 # no virus(es) found
|
||||
# Check for success
|
||||
assert output.ec == 0
|
||||
|
||||
# Custom logic to verify the output making sure that all expected results are found in the output in order.
|
||||
#
|
||||
|
@ -336,7 +339,7 @@ class TC(testcase.TestCase):
|
|||
'Data scanned: 0 B',
|
||||
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa',
|
||||
'File Type: CL_TYPE_ZIP',
|
||||
'Return code: No viruses detected (0)',
|
||||
'Return Code: CL_SUCCESS (0)',
|
||||
]
|
||||
|
||||
command = '{valgrind} {valgrind_args} {example} -d {database} -f {target} --script {script}'.format(
|
||||
|
@ -347,7 +350,8 @@ class TC(testcase.TestCase):
|
|||
)
|
||||
output = self.execute_command(command)
|
||||
|
||||
assert output.ec == 0 # virus(es) found
|
||||
# Check for success
|
||||
assert output.ec == 0
|
||||
|
||||
# Custom logic to verify the output making sure that all expected results are found in the output in order.
|
||||
#
|
||||
|
@ -398,7 +402,8 @@ class TC(testcase.TestCase):
|
|||
'Data scanned: 0 B',
|
||||
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa',
|
||||
'File Type: CL_TYPE_ZIP',
|
||||
'Return code: Virus(es) detected (1)',
|
||||
'Verdict: Found Strong Indicator',
|
||||
'Return Code: CL_SUCCESS (0)',
|
||||
]
|
||||
|
||||
command = '{valgrind} {valgrind_args} {example} -d {database} -f {target} --script {script}'.format(
|
||||
|
@ -409,7 +414,8 @@ class TC(testcase.TestCase):
|
|||
)
|
||||
output = self.execute_command(command)
|
||||
|
||||
assert output.ec == 1 # virus(es) found
|
||||
# Check for success
|
||||
assert output.ec == 0
|
||||
|
||||
# Custom logic to verify the output making sure that all expected results are found in the output in order.
|
||||
#
|
||||
|
@ -526,7 +532,7 @@ class TC(testcase.TestCase):
|
|||
'Data scanned: 948 B',
|
||||
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa',
|
||||
'File Type: CL_TYPE_ZIP',
|
||||
'Return code: No viruses detected (0)',
|
||||
'Return Code: CL_SUCCESS (0)',
|
||||
]
|
||||
|
||||
command = '{valgrind} {valgrind_args} {example} -d {database} -f {target} --script {script}'.format(
|
||||
|
@ -537,7 +543,8 @@ class TC(testcase.TestCase):
|
|||
)
|
||||
output = self.execute_command(command)
|
||||
|
||||
assert output.ec == 0 # no virus(es) found
|
||||
# Check for success
|
||||
assert output.ec == 0
|
||||
|
||||
# Custom logic to verify the output making sure that all expected results are found in the output in order.
|
||||
#
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue