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:
Valerie Snyder 2025-07-27 22:47:29 -04:00 committed by Val S.
parent 9d253673f4
commit 6d9b57eeeb
No known key found for this signature in database
GPG key ID: 3A7D293D8274CA1B
14 changed files with 572 additions and 272 deletions

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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,

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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.

View file

@ -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;
}

View file

@ -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;

View file

@ -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,

View file

@ -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) };
}
}

View file

@ -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,
}

View file

@ -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.
#