diff --git a/clambc/bcrun.c b/clambc/bcrun.c index dbf88b133..4d7d55a8c 100644 --- a/clambc/bcrun.c +++ b/clambc/bcrun.c @@ -394,8 +394,8 @@ int main(int argc, char *argv[]) fprintf(stderr, "Out of memory\n"); exit(3); } - ctx->ctx = &cctx; - cctx.engine = engine; + ctx->ctx = &cctx; + cctx.engine = engine; cctx.evidence = evidence_new(); cctx.recursion_stack_size = cctx.engine->max_recursion_level; diff --git a/libclamav/binhex.c b/libclamav/binhex.c index 8c117ed37..a9025da46 100644 --- a/libclamav/binhex.c +++ b/libclamav/binhex.c @@ -123,7 +123,9 @@ int cli_binhex(cli_ctx *ctx) break; } ret = cli_magic_scan_desc(datafd, dname, ctx, NULL, LAYER_ATTRIBUTES_NONE); - if (ret == CL_VIRUS) break; + if (ret != CL_SUCCESS) { + break; + } } if (dec_done) memmove(decoded, &decoded[todo], dec_done); diff --git a/libclamav/bytecode.c b/libclamav/bytecode.c index 31a063384..16e3bab1b 100644 --- a/libclamav/bytecode.c +++ b/libclamav/bytecode.c @@ -2933,14 +2933,15 @@ cl_error_t cli_bytecode_runhook(cli_ctx *cctx, const struct cl_engine *engine, s close(fd); if (!cctx->engine->keeptmp) { - if (tempfile && cli_unlink(tempfile)) + if (tempfile && cli_unlink(tempfile)) { ret = CL_EUNLINK; + } } free(tempfile); - if (ret == CL_VIRUS) { - cli_dbgmsg("Scanning unpacked file by bytecode %u found a virus\n", bc->id); + if (ret != CL_SUCCESS) { + cli_dbgmsg("Scanning unpacked file by bytecode %u found a reason to stop: %s\n", bc->id, cl_strerror(ret)); cli_bytecode_context_clear(ctx); return ret; } diff --git a/libclamav/c++/bytecode2llvm.cpp b/libclamav/c++/bytecode2llvm.cpp index 05b9040ca..a8929c6d5 100644 --- a/libclamav/c++/bytecode2llvm.cpp +++ b/libclamav/c++/bytecode2llvm.cpp @@ -1241,8 +1241,8 @@ class LLVMCodegen unsigned offset = GVoffsetMap[g]; Constant *Idx = ConstantInt::get(Type::getInt32Ty(Context), offset); Value *Idxs[2] = { - ConstantInt::get(Type::getInt32Ty(Context), 0), - Idx}; + ConstantInt::get(Type::getInt32Ty(Context), 0), + Idx}; Value *GEP = Builder.CreateInBoundsGEP(Ctx, ArrayRef(Idxs, Idxs + 2)); Type *Ty = GVtypeMap[g]; Ty = PointerType::getUnqual(PointerType::getUnqual(Ty)); diff --git a/libclamav/gif.c b/libclamav/gif.c index 786401027..8e481013e 100644 --- a/libclamav/gif.c +++ b/libclamav/gif.c @@ -416,8 +416,8 @@ scan_overlay: // Is there an overlay? if (offset < map->len) { cli_dbgmsg("GIF: Found extra data after the end of the GIF data stream: %zu bytes, we'll scan it!\n", map->len - offset); - cl_error_t nested_scan_result = cli_magic_scan_nested_fmap_type(map, offset, map->len - offset, ctx, CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); - status = nested_scan_result != CL_SUCCESS ? nested_scan_result : status; + status = cli_magic_scan_nested_fmap_type(map, offset, map->len - offset, ctx, CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); + goto done; } } diff --git a/libclamav/nsis/nulsft.c b/libclamav/nsis/nulsft.c index 5db3057ad..48ead4544 100644 --- a/libclamav/nsis/nulsft.c +++ b/libclamav/nsis/nulsft.c @@ -545,13 +545,17 @@ int cli_scannulsft(cli_ctx *ctx, off_t offset) free(nsist.dir); return CL_ESEEK; } - if (nsist.fno == 1) + if (nsist.fno == 1) { ret = cli_scan_desc(nsist.ofd, ctx, CL_TYPE_ANY, false, NULL, AC_SCAN_VIR, NULL, NULL, LAYER_ATTRIBUTES_NONE); /// TODO: Extract file names - else + } else { ret = cli_magic_scan_desc(nsist.ofd, nsist.ofn, ctx, NULL, LAYER_ATTRIBUTES_NONE); /// TODO: Extract file names + } close(nsist.ofd); - if (!ctx->engine->keeptmp) - if (cli_unlink(nsist.ofn)) ret = CL_EUNLINK; + if (!ctx->engine->keeptmp) { + if (cli_unlink(nsist.ofn)) { + ret = CL_EUNLINK; + } + } } else if (ret == CL_EMAXSIZE) { ret = nsist.solid ? CL_BREAK : CL_SUCCESS; } @@ -562,8 +566,9 @@ int cli_scannulsft(cli_ctx *ctx, off_t offset) nsis_shutdown(&nsist); - if (!ctx->engine->keeptmp) + if (!ctx->engine->keeptmp) { cli_rmdirs(nsist.dir); + } free(nsist.dir); diff --git a/libclamav/others.c b/libclamav/others.c index 4d2b5e4df..6167bb3b0 100644 --- a/libclamav/others.c +++ b/libclamav/others.c @@ -1114,46 +1114,56 @@ cl_error_t cli_checklimits(const char *who, cli_ctx *ctx, unsigned long need1, u cl_error_t ret = CL_SUCCESS; unsigned long needed; - /* if called without limits, go on, unpack, scan */ - if (!ctx) return ret; + if (!ctx) { + /* if called without limits, go on, unpack, scan */ + goto done; + } needed = (need1 > need2) ? need1 : need2; needed = (needed > need3) ? needed : need3; - /* Enforce timelimit */ - if (CL_ETIMEOUT == (ret = cli_checktimelimit(ctx))) { - /* Abort the scan ... */ - ret = CL_ETIMEOUT; + /* Enforce global time limit, if limit enabled */ + ret = cli_checktimelimit(ctx); + if (CL_SUCCESS != ret) { + // Exceeding the time limit will abort the scan. + // The logic for this and the possible heuristic is done inside the cli_checktimelimit function. + goto done; } - /* Enforce global scan-size limit */ - if (needed && ctx->engine->maxscansize) { - /* if the remaining scansize is too small... */ - if (ctx->engine->maxscansize - ctx->scansize < needed) { - /* Skip this file */ - cli_dbgmsg("%s: scansize exceeded (initial: %lu, consumed: %lu, needed: %lu)\n", who, (unsigned long int)ctx->engine->maxscansize, (unsigned long int)ctx->scansize, needed); - ret = CL_EMAXSIZE; - cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxScanSize"); - } + /* Enforce global scan-size limit, if limit enabled */ + if (needed && (ctx->engine->maxscansize != 0) && (ctx->engine->maxscansize - ctx->scansize < needed)) { + /* The size needed is greater than the remaining scansize ... Skip this file. */ + cli_dbgmsg("%s: scansize exceeded (initial: %lu, consumed: %lu, needed: %lu)\n", who, (unsigned long int)ctx->engine->maxscansize, (unsigned long int)ctx->scansize, needed); + ret = CL_EMAXSIZE; + cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxScanSize"); + goto done; } - /* Enforce per-file file-size limit */ - if (needed && ctx->engine->maxfilesize && ctx->engine->maxfilesize < needed) { - /* Skip this file */ + /* Enforce per-file file-size limit, if limit enabled */ + if (needed && (ctx->engine->maxfilesize != 0) && (ctx->engine->maxfilesize < needed)) { + /* The size needed is greater than that limit ... Skip this file. */ cli_dbgmsg("%s: filesize exceeded (allowed: %lu, needed: %lu)\n", who, (unsigned long int)ctx->engine->maxfilesize, needed); ret = CL_EMAXSIZE; cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFileSize"); + goto done; } - /* Enforce limit on number of embedded files */ - if (ctx->engine->maxfiles && ctx->scannedfiles >= ctx->engine->maxfiles) { - /* Abort the scan ... */ + /* Enforce limit on number of embedded files, if limit enabled */ + if ((ctx->engine->maxfiles != 0) && (ctx->scannedfiles >= ctx->engine->maxfiles)) { + /* This file would exceed the max # of files ... Skip this file. */ cli_dbgmsg("%s: files limit reached (max: %u)\n", who, ctx->engine->maxfiles); ret = CL_EMAXFILES; cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); - ctx->abort_scan = true; + + // We don't need to set the `ctx->abort_scan` flag here. + // We want `cli_magic_scan()` to finish scanning the current file, but not any future files. + // We keep track of the # scanned files with `ctx->scannedfiles`, and that should be sufficient to prevent + // additional files from being scanned. + goto done; } +done: + return ret; } @@ -1191,10 +1201,8 @@ cl_error_t cli_checktimelimit(cli_ctx *ctx) if (ctx->time_limit.tv_sec != 0) { struct timeval now; if (gettimeofday(&now, NULL) == 0) { - if (now.tv_sec > ctx->time_limit.tv_sec) { - ctx->abort_scan = true; - ret = CL_ETIMEOUT; - } else if (now.tv_sec == ctx->time_limit.tv_sec && now.tv_usec > ctx->time_limit.tv_usec) { + if ((now.tv_sec > ctx->time_limit.tv_sec) || + (now.tv_sec == ctx->time_limit.tv_sec && now.tv_usec > ctx->time_limit.tv_usec)) { ctx->abort_scan = true; ret = CL_ETIMEOUT; } @@ -1203,6 +1211,9 @@ cl_error_t cli_checktimelimit(cli_ctx *ctx) if (CL_ETIMEOUT == ret) { cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxScanTime"); + + // abort_scan flag is set so that in cli_magic_scan() we *will* stop scanning, even if we lose the status code. + ctx->abort_scan = true; } done: @@ -1392,6 +1403,8 @@ static cl_error_t append_virus(cli_ctx *ctx, const char *virname, IndicatorType switch (type) { case IndicatorType_Strong: { status = CL_VIRUS; + // abort_scan flag is set so that in cli_magic_scan() we *will* stop scanning, even if we lose the status code. + ctx->abort_scan = true; break; } case IndicatorType_PotentiallyUnwanted: { @@ -1460,7 +1473,7 @@ cl_error_t cli_recursion_stack_push(cli_ctx *ctx, cl_fmap_t *map, cli_file_t typ recursion_level_t *new_container = NULL; // Check the regular limits - if (CL_SUCCESS != (status = cli_checklimits("cli_updatelimits", ctx, map->len, 0, 0))) { + if (CL_SUCCESS != (status = cli_checklimits("cli_recursion_stack_push", ctx, map->len, 0, 0))) { cli_dbgmsg("cli_recursion_stack_push: Some content was skipped. The scan result will not be cached.\n"); emax_reached(ctx); // Disable caching for all recursion layers. goto done; diff --git a/libclamav/others.h b/libclamav/others.h index 8e1f92ab8..e6e06932e 100644 --- a/libclamav/others.h +++ b/libclamav/others.h @@ -204,7 +204,7 @@ typedef struct cli_ctx_tag { uint64_t scansize; struct cl_scan_options *options; unsigned int scannedfiles; - unsigned int corrupted_input; + unsigned int corrupted_input; /* Setting this flag will prevent the PE parser from reporting "broken executable" for unpacked/reconstructed files that may not be 100% to spec. */ recursion_level_t *recursion_stack; /* Array of recursion levels used as a stack. */ uint32_t recursion_stack_size; /* stack size must == engine->max_recursion_level */ uint32_t recursion_level; /* Index into recursion_stack; current fmap recursion level from start of scan. */ diff --git a/libclamav/rtf.c b/libclamav/rtf.c index 682835a2f..21f022fd5 100644 --- a/libclamav/rtf.c +++ b/libclamav/rtf.c @@ -237,19 +237,25 @@ static int rtf_object_begin(struct rtf_state* state, cli_ctx* ctx, const char* t return 0; } -static int decode_and_scan(struct rtf_object_data* data, cli_ctx* ctx) +static cl_error_t decode_and_scan(struct rtf_object_data* data, cli_ctx* ctx) { - int ret = CL_CLEAN; + cl_error_t ret = CL_CLEAN; + + cli_dbgmsg("RTF:Scanning embedded object: %s\n", data->name); + + if (data->fd > 0) { + if (data->bread == 1) { + cli_dbgmsg("Decoding ole object\n"); + + ret = cli_scan_ole10(data->fd, ctx); + } else { + ret = cli_magic_scan_desc(data->fd, data->name, ctx, NULL, LAYER_ATTRIBUTES_NONE); + } - cli_dbgmsg("RTF:Scanning embedded object:%s\n", data->name); - if (data->bread == 1 && data->fd > 0) { - cli_dbgmsg("Decoding ole object\n"); - ret = cli_scan_ole10(data->fd, ctx); - } else if (data->fd > 0) - ret = cli_magic_scan_desc(data->fd, data->name, ctx, NULL, LAYER_ATTRIBUTES_NONE); - if (data->fd > 0) close(data->fd); - data->fd = -1; + data->fd = -1; + } + if (data->name) { if (!ctx->engine->keeptmp) if (cli_unlink(data->name)) ret = CL_EUNLINK; @@ -257,9 +263,7 @@ static int decode_and_scan(struct rtf_object_data* data, cli_ctx* ctx) data->name = NULL; } - if (ret != CL_CLEAN) - return ret; - return 0; + return ret; } static int rtf_object_process(struct rtf_state* state, const unsigned char* input, const size_t len) diff --git a/libclamav/scanners.c b/libclamav/scanners.c index 9d29f1c65..858c25c80 100644 --- a/libclamav/scanners.c +++ b/libclamav/scanners.c @@ -133,8 +133,7 @@ cl_error_t cli_magic_scan_dir(const char *dir, cli_ctx *ctx, uint32_t attributes DIR *dd = NULL; struct dirent *dent; STATBUF statbuf; - char *fname = NULL; - unsigned int viruses_found = 0; + char *fname = NULL; if ((dd = opendir(dir)) != NULL) { while ((dent = readdir(dd))) { @@ -153,24 +152,14 @@ cl_error_t cli_magic_scan_dir(const char *dir, cli_ctx *ctx, uint32_t attributes /* stat the file */ if (LSTAT(fname, &statbuf) != -1) { if (S_ISDIR(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)) { - if (cli_magic_scan_dir(fname, ctx, attributes) == CL_VIRUS) { - if (SCAN_ALLMATCHES) { - viruses_found++; - continue; - } - - status = CL_VIRUS; + status = cli_magic_scan_dir(fname, ctx, attributes); + if (CL_SUCCESS != status) { goto done; } } else { if (S_ISREG(statbuf.st_mode)) { - if (CL_VIRUS == cli_magic_scan_file(fname, ctx, dent->d_name, attributes)) { - if (SCAN_ALLMATCHES) { - viruses_found++; - continue; - } - - status = CL_VIRUS; + status = cli_magic_scan_file(fname, ctx, dent->d_name, attributes); + if (CL_SUCCESS != status) { goto done; } } @@ -195,9 +184,6 @@ done: free(fname); } - if (SCAN_ALLMATCHES && viruses_found) - status = CL_VIRUS; - return status; } @@ -233,8 +219,7 @@ static cl_error_t cli_scanrar_file(const char *filepath, int desc, cli_ctx *ctx) cl_error_t status = CL_EPARSE; cl_unrar_error_t unrar_ret = UNRAR_ERR; - unsigned int file_count = 0; - unsigned int viruses_found = 0; + unsigned int file_count = 0; uint32_t nEncryptedFilesFound = 0; uint32_t nTooLargeFilesFound = 0; @@ -267,7 +252,8 @@ static cl_error_t cli_scanrar_file(const char *filepath, int desc, cli_ctx *ctx) if (UNRAR_OK != (unrar_ret = cli_unrar_open(filepath, &hArchive, &comment, &comment_size, cli_debug_flag))) { if (unrar_ret == UNRAR_ENCRYPTED) { cli_dbgmsg("RAR: Encrypted main header\n"); - status = CL_EUNPACK; + status = CL_SUCCESS; + nEncryptedFilesFound += 1; goto done; } if (unrar_ret == UNRAR_EMEM) { @@ -306,12 +292,7 @@ static cl_error_t cli_scanrar_file(const char *filepath, int desc, cli_ctx *ctx) /* Scan the comment */ status = cli_magic_scan_buff(comment, comment_size, ctx, NULL, LAYER_ATTRIBUTES_NONE); - - if ((status == CL_VIRUS) && SCAN_ALLMATCHES) { - status = CL_CLEAN; - viruses_found++; - } - if ((status == CL_VIRUS) || (status == CL_BREAK)) { + if (status != CL_SUCCESS) { goto done; } } @@ -363,11 +344,9 @@ static cl_error_t cli_scanrar_file(const char *filepath, int desc, cli_ctx *ctx) * Scan the metadata for the file in question since the content was clean, or we're running in all-match. */ status = cli_unrar_scanmetadata(&metadata, ctx, file_count); - if ((status == CL_VIRUS) && SCAN_ALLMATCHES) { - status = CL_CLEAN; - viruses_found++; - } - if ((status == CL_VIRUS) || (status == CL_BREAK)) { + if (status == CL_EUNPACK) { + nEncryptedFilesFound += 1; + } else if (status != CL_SUCCESS) { break; } @@ -464,16 +443,20 @@ static cl_error_t cli_scanrar_file(const char *filepath, int desc, cli_ctx *ctx) status = cli_magic_scan_file(extract_fullpath, ctx, filename_base, LAYER_ATTRIBUTES_NONE); if (status == CL_EOPEN) { cli_dbgmsg("RAR: File not found, Extraction failed!\n"); - status = CL_CLEAN; + + // Don't abort the scan just because one file failed to extract. + status = CL_SUCCESS; } else { /* Delete the tempfile if not --leave-temps */ - if (!ctx->engine->keeptmp) - if (cli_unlink(extract_fullpath)) + if (!ctx->engine->keeptmp) { + if (cli_unlink(extract_fullpath)) { cli_dbgmsg("RAR: Failed to unlink the extracted file: %s\n", extract_fullpath); + } + } - if (status == CL_VIRUS) { - status = CL_VIRUS; - viruses_found++; + if (status != CL_SUCCESS) { + // Bail out if "virus" and also if exceeded scan maximums, etc. + goto done; } } } @@ -486,18 +469,6 @@ static cl_error_t cli_scanrar_file(const char *filepath, int desc, cli_ctx *ctx) } } - if (status == CL_VIRUS) { - if (SCAN_ALLMATCHES) - status = CL_SUCCESS; - else - break; - } - - if (ctx->engine->maxscansize && ctx->scansize >= ctx->engine->maxscansize) { - status = CL_CLEAN; - break; - } - /* * Free up any malloced metadata... */ @@ -510,10 +481,11 @@ static cl_error_t cli_scanrar_file(const char *filepath, int desc, cli_ctx *ctx) filename_base = NULL; } - } while (status == CL_CLEAN); + } while (status == CL_SUCCESS); - if (status == CL_BREAK) - status = CL_CLEAN; + if (status == CL_BREAK) { + status = CL_SUCCESS; + } done: if (NULL != comment) { @@ -549,23 +521,17 @@ done: extract_fullpath = NULL; } - if ((CL_VIRUS != status) && ((CL_EUNPACK == status) || (nEncryptedFilesFound > 0))) { + if ((CL_VIRUS != status) && (nEncryptedFilesFound > 0)) { /* If user requests enabled the Heuristic for encrypted archives... */ if (SCAN_HEURISTIC_ENCRYPTED_ARCHIVE) { if (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Encrypted.RAR")) { status = CL_VIRUS; } } - if (status != CL_VIRUS) { - status = CL_CLEAN; - } } cli_dbgmsg("RAR: Exit code: %d\n", status); - if (SCAN_ALLMATCHES && viruses_found) - status = CL_VIRUS; - return status; } @@ -664,11 +630,10 @@ static cl_error_t cli_egg_scanmetadata(cl_egg_metadata *metadata, cli_ctx *ctx, static cl_error_t cli_scanegg(cli_ctx *ctx) { - cl_error_t status = CL_EPARSE; - cl_error_t egg_ret = CL_EPARSE; + cl_error_t status = CL_SUCCESS; + cl_error_t egg_ret; - unsigned int file_count = 0; - unsigned int viruses_found = 0; + unsigned int file_count = 0; uint32_t nEncryptedFilesFound = 0; uint32_t nTooLargeFilesFound = 0; @@ -683,6 +648,10 @@ static cl_error_t cli_scanegg(cli_ctx *ctx) char *extract_fullpath = NULL; char *comment_fullpath = NULL; + char *extract_filename = NULL; + char *extract_buffer = NULL; + size_t extract_buffer_len = 0; + if (ctx == NULL) { cli_dbgmsg("EGG: Invalid arguments!\n"); return CL_EARG; @@ -699,7 +668,8 @@ static cl_error_t cli_scanegg(cli_ctx *ctx) if (CL_SUCCESS != (egg_ret = cli_egg_open(ctx->fmap, &hArchive, &comments, &nComments))) { if (egg_ret == CL_EUNPACK) { cli_dbgmsg("EGG: Encrypted main header\n"); - status = CL_EUNPACK; + nEncryptedFilesFound += 1; + status = CL_SUCCESS; goto done; } if (egg_ret == CL_EMEM) { @@ -751,12 +721,7 @@ static cl_error_t cli_scanegg(cli_ctx *ctx) * Scan the comment. */ status = cli_magic_scan_buff(comments[i], strlen(comments[i]), ctx, NULL, LAYER_ATTRIBUTES_NONE); - - if ((status == CL_VIRUS) && SCAN_ALLMATCHES) { - status = CL_CLEAN; - viruses_found++; - } - if ((status == CL_VIRUS) || (status == CL_BREAK)) { + if (status != CL_SUCCESS) { goto done; } } @@ -809,13 +774,12 @@ static cl_error_t cli_scanegg(cli_ctx *ctx) * Scan the metadata for the file in question since the content was clean, or we're running in all-match. */ status = cli_egg_scanmetadata(&metadata, ctx, file_count); - if ((status == CL_VIRUS) && SCAN_ALLMATCHES) { - status = CL_CLEAN; - viruses_found++; - } - if ((status == CL_VIRUS) || (status == CL_BREAK)) { + if (status == CL_EUNPACK) { + nEncryptedFilesFound += 1; + } else if (status != CL_SUCCESS) { break; } + /* Check if we've already exceeded the scan limit */ if (cli_checklimits("EGG", ctx, 0, 0, 0)) break; @@ -855,9 +819,6 @@ static cl_error_t cli_scanegg(cli_ctx *ctx) /* * Extract the file... */ - char *extract_filename = NULL; - char *extract_buffer = NULL; - size_t extract_buffer_len = 0; cli_dbgmsg("EGG: Extracting file: %s\n", metadata.filename); @@ -921,9 +882,8 @@ static cl_error_t cli_scanegg(cli_ctx *ctx) */ cli_dbgmsg("EGG: Extraction complete. Scanning now...\n"); status = cli_magic_scan_buff(extract_buffer, extract_buffer_len, ctx, filename_base, LAYER_ATTRIBUTES_NONE); - if (status == CL_VIRUS) { - status = CL_VIRUS; - viruses_found++; + if (status != CL_SUCCESS) { + goto done; } if (NULL != filename_base) { @@ -948,13 +908,6 @@ static cl_error_t cli_scanegg(cli_ctx *ctx) } } - if (status == CL_VIRUS) { - if (SCAN_ALLMATCHES) - status = CL_SUCCESS; - else - break; - } - if (ctx->engine->maxscansize && ctx->scansize >= ctx->engine->maxscansize) { status = CL_CLEAN; break; @@ -970,11 +923,22 @@ static cl_error_t cli_scanegg(cli_ctx *ctx) } while (status == CL_CLEAN); - if (status == CL_BREAK) + if (status == CL_BREAK) { status = CL_CLEAN; + } done: + if (NULL != extract_filename) { + free(extract_filename); + extract_filename = NULL; + } + + if (NULL != extract_buffer) { + free(extract_buffer); + extract_buffer = NULL; + } + if (NULL != comment_fullpath) { free(comment_fullpath); comment_fullpath = NULL; @@ -1000,34 +964,26 @@ done: extract_fullpath = NULL; } - if ((CL_VIRUS != status) && ((CL_EUNPACK == status) || (nEncryptedFilesFound > 0))) { + if ((CL_VIRUS != status) && (nEncryptedFilesFound > 0)) { /* If user requests enabled the Heuristic for encrypted archives... */ if (SCAN_HEURISTIC_ENCRYPTED_ARCHIVE) { if (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Encrypted.EGG")) { status = CL_VIRUS; } } - if (status != CL_VIRUS) { - status = CL_CLEAN; - } } cli_dbgmsg("EGG: Exit code: %d\n", status); - if (SCAN_ALLMATCHES && viruses_found) - status = CL_VIRUS; - return status; } static cl_error_t cli_scanarj(cli_ctx *ctx) { cl_error_t ret = CL_CLEAN; - cl_error_t status; - int file = 0; + int file = 0; arj_metadata_t metadata; - char *dir; - int virus_found = 0; + char *dir = NULL; cli_dbgmsg("in cli_scanarj()\n"); @@ -1053,22 +1009,20 @@ static cl_error_t cli_scanarj(cli_ctx *ctx) } do { - metadata.filename = NULL; - ret = cli_unarj_prepare_file(dir, &metadata); + + ret = cli_unarj_prepare_file(dir, &metadata); if (ret != CL_SUCCESS) { cli_dbgmsg("ARJ: cli_unarj_prepare_file Error: %s\n", cl_strerror(ret)); break; } + file++; - if (cli_matchmeta(ctx, metadata.filename, metadata.comp_size, metadata.orig_size, metadata.encrypted, file, 0, NULL) == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - cli_rmdirs(dir); - free(dir); - return CL_VIRUS; - } - virus_found = 1; - ret = CL_SUCCESS; + + if (CL_VIRUS == cli_matchmeta(ctx, metadata.filename, metadata.comp_size, metadata.orig_size, metadata.encrypted, file, 0, NULL)) { + cli_rmdirs(dir); + free(dir); + return CL_VIRUS; } if ((ret = cli_checklimits("ARJ", ctx, metadata.orig_size, metadata.comp_size, 0)) != CL_CLEAN) { @@ -1077,29 +1031,24 @@ static cl_error_t cli_scanarj(cli_ctx *ctx) free(metadata.filename); continue; } + ret = cli_unarj_extract_file(dir, &metadata); if (ret != CL_SUCCESS) { cli_dbgmsg("ARJ: cli_unarj_extract_file Error: %s\n", cl_strerror(ret)); } + if (metadata.ofd >= 0) { if (lseek(metadata.ofd, 0, SEEK_SET) == -1) { cli_dbgmsg("ARJ: call to lseek() failed\n"); } - status = cli_magic_scan_desc(metadata.ofd, NULL, ctx, metadata.filename, LAYER_ATTRIBUTES_NONE); + + ret = cli_magic_scan_desc(metadata.ofd, NULL, ctx, metadata.filename, LAYER_ATTRIBUTES_NONE); close(metadata.ofd); - if (status == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - ret = CL_VIRUS; - if (metadata.filename) { - free(metadata.filename); - metadata.filename = NULL; - } - break; - } - virus_found = 1; - ret = CL_SUCCESS; + if (ret != CL_SUCCESS) { + break; } } + if (metadata.filename) { free(metadata.filename); metadata.filename = NULL; @@ -1107,19 +1056,23 @@ static cl_error_t cli_scanarj(cli_ctx *ctx) } while (ret == CL_SUCCESS); - if (!ctx->engine->keeptmp) + if (!ctx->engine->keeptmp) { cli_rmdirs(dir); + } + + if (NULL != dir) { + free(dir); + } - free(dir); if (metadata.filename) { free(metadata.filename); } - if (virus_found != 0) - ret = CL_VIRUS; cli_dbgmsg("ARJ: Exit code: %d\n", ret); - if (ret == CL_BREAK) - ret = CL_CLEAN; + + if (ret == CL_BREAK) { + ret = CL_SUCCESS; + } return ret; } @@ -1171,21 +1124,20 @@ static cl_error_t cli_scangzip_with_zib_from_the_80s(cli_ctx *ctx, unsigned char gzclose(gz); - if (CL_VIRUS == (ret = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE))) { + if (CL_SUCCESS != (ret = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE))) { close(fd); if (!ctx->engine->keeptmp) { - if (cli_unlink(tmpname)) { - free(tmpname); - return CL_EUNLINK; - } + (void)cli_unlink(tmpname); } free(tmpname); - return CL_VIRUS; + return ret; } close(fd); - if (!ctx->engine->keeptmp) - if (cli_unlink(tmpname)) + if (!ctx->engine->keeptmp) { + if (cli_unlink(tmpname)) { ret = CL_EUNLINK; + } + } free(tmpname); return ret; } @@ -1272,7 +1224,7 @@ static cl_error_t cli_scangzip(cli_ctx *ctx) inflateEnd(&z); - if (CL_VIRUS == (ret = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE))) { + if (CL_SUCCESS != (ret = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE))) { close(fd); if (!ctx->engine->keeptmp) { if (cli_unlink(tmpname)) { @@ -1281,13 +1233,14 @@ static cl_error_t cli_scangzip(cli_ctx *ctx) } } free(tmpname); - return CL_VIRUS; + return ret; } close(fd); if (!ctx->engine->keeptmp) if (cli_unlink(tmpname)) ret = CL_EUNLINK; free(tmpname); + return ret; } @@ -1377,17 +1330,16 @@ static cl_error_t cli_scanbzip(cli_ctx *ctx) BZ2_bzDecompressEnd(&strm); - if (CL_VIRUS == (ret = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE))) { + if (CL_SUCCESS != (ret = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE))) { close(fd); if (!ctx->engine->keeptmp) { if (cli_unlink(tmpname)) { - ret = CL_EUNLINK; free(tmpname); - return ret; + return CL_EUNLINK; } } free(tmpname); - return CL_VIRUS; + return ret; } close(fd); if (!ctx->engine->keeptmp) @@ -1544,7 +1496,7 @@ static cl_error_t vba_scandata(const unsigned char *data, size_t len, cli_ctx *c bool gmdata_initialized = false; bool tmdata_initialized = false; struct cli_ac_data *mdata[2]; - unsigned int viruses_found = 0; + bool must_pop_stack = false; cl_fmap_t *new_map = NULL; @@ -1562,40 +1514,41 @@ static cl_error_t vba_scandata(const unsigned char *data, size_t len, cli_ctx *c mdata[1] = &gmdata; ret = cli_scan_buff(data, len, 0, ctx, CL_TYPE_MSOLE2, mdata); - if (ret == CL_VIRUS) { - viruses_found++; + if (CL_SUCCESS != ret) { + goto done; } - if (ret == CL_CLEAN || (ret == CL_VIRUS && SCAN_ALLMATCHES)) { - /* - * Evaluate logical & yara rules given the new matches to see if anything alerts. - */ - new_map = fmap_open_memory(data, len, NULL); - if (new_map == NULL) { - cli_dbgmsg("Failed to create fmap for evaluating logical/yara rules after call to cli_scan_buff()\n"); - ret = CL_EMEM; - goto done; - } + /* + * Evaluate logical & yara rules given the new matches to see if anything alerts. + */ + new_map = fmap_open_memory(data, len, NULL); + if (new_map == NULL) { + cli_dbgmsg("Failed to create fmap for evaluating logical/yara rules after call to cli_scan_buff()\n"); + ret = CL_EMEM; + goto done; + } - ret = cli_recursion_stack_push(ctx, new_map, CL_TYPE_MSOLE2, true, LAYER_ATTRIBUTES_NONE); /* Perform exp_eval with child fmap */ - if (CL_SUCCESS != ret) { - cli_dbgmsg("Failed to scan fmap.\n"); - goto done; - } + ret = cli_recursion_stack_push(ctx, new_map, CL_TYPE_MSOLE2, true, LAYER_ATTRIBUTES_NONE); /* Perform exp_eval with child fmap */ + if (CL_SUCCESS != ret) { + cli_dbgmsg("Failed to scan fmap.\n"); + goto done; + } - ret = cli_exp_eval(ctx, target_ac_root, &tmdata, NULL, NULL); - if (ret == CL_VIRUS) { - viruses_found++; - } + must_pop_stack = true; - if (ret == CL_CLEAN || (ret == CL_VIRUS && SCAN_ALLMATCHES)) { - ret = cli_exp_eval(ctx, generic_ac_root, &gmdata, NULL, NULL); - } + ret = cli_exp_eval(ctx, target_ac_root, &tmdata, NULL, NULL); + if (ret == CL_VIRUS) { + goto done; + } + ret = cli_exp_eval(ctx, generic_ac_root, &gmdata, NULL, NULL); + +done: + + if (must_pop_stack) { (void)cli_recursion_stack_pop(ctx); /* Restore the parent fmap */ } -done: if (NULL != new_map) { funmap(new_map); } @@ -1608,9 +1561,6 @@ done: cli_ac_freedata(&gmdata); } - if (ret == CL_CLEAN && viruses_found) { - ret = CL_VIRUS; - } return ret; } @@ -1682,8 +1632,7 @@ static cl_error_t cli_ole2_tempdir_scan_vba_new(const char *dir, cli_ctx *ctx, s char *hash = NULL; char path[PATH_MAX]; char filename[PATH_MAX]; - int tempfd = -1; - int viruses_found = 0; + int tempfd = -1; if (CL_SUCCESS != (ret = uniq_get(U, "dir", 3, &hash, &hashcnt))) { cli_dbgmsg("cli_ole2_tempdir_scan_vba_new: uniq_get('dir') failed with ret code (%d)!\n", ret); @@ -1724,10 +1673,7 @@ static cl_error_t cli_ole2_tempdir_scan_vba_new(const char *dir, cli_ctx *ctx, s if (SCAN_HEURISTIC_MACROS && *has_macros) { ret = cli_append_potentially_unwanted(ctx, "Heuristics.OLE2.ContainsMacros.VBA"); if (ret == CL_VIRUS) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - goto done; - } + goto done; } } @@ -1741,16 +1687,12 @@ static cl_error_t cli_ole2_tempdir_scan_vba_new(const char *dir, cli_ctx *ctx, s } ret = cli_scan_desc(tempfd, ctx, CL_TYPE_SCRIPT, false, NULL, AC_SCAN_VIR, NULL, NULL, LAYER_ATTRIBUTES_NONE); + if (CL_SUCCESS != ret) { + goto done; + } close(tempfd); tempfd = -1; - - if (CL_VIRUS == ret) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - goto done; - } - } } hashcnt--; @@ -1762,8 +1704,6 @@ done: tempfd = -1; } - if (viruses_found > 0) - ret = CL_VIRUS; return ret; } @@ -1846,8 +1786,9 @@ static cl_error_t cli_ole2_tempdir_scan_embedded_ole10(const char *dir, cli_ctx cl_error_t ret; char ole10_filename[1024]; char *hash; - uint32_t hashcnt = 0; - unsigned int viruses_found = 0; + uint32_t hashcnt = 0; + + int fd = -1; /* Check directory for embedded OLE objects */ if (CL_SUCCESS != (ret = uniq_get(U, "_1_ole10native", 14, &hash, &hashcnt))) { @@ -1856,29 +1797,31 @@ static cl_error_t cli_ole2_tempdir_scan_embedded_ole10(const char *dir, cli_ctx goto done; } while (hashcnt) { - int fd = -1; - snprintf(ole10_filename, sizeof(ole10_filename), "%s" PATHSEP "%s_%u", dir, hash, hashcnt); ole10_filename[sizeof(ole10_filename) - 1] = '\0'; fd = open(ole10_filename, O_RDONLY | O_BINARY); - if (fd >= 0) { - ret = cli_scan_ole10(fd, ctx); - close(fd); - if (CL_VIRUS == ret) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - status = ret; - goto done; - } - } + if (fd < 0) { + hashcnt--; + continue; } + + ret = cli_scan_ole10(fd, ctx); + if (CL_SUCCESS != ret) { + status = ret; + goto done; + } + + close(fd); + fd = -1; + hashcnt--; } done: - if (viruses_found > 0) { - status = CL_VIRUS; + + if (fd >= 0) { + close(fd); } return status; @@ -1886,20 +1829,24 @@ done: static cl_error_t cli_ole2_tempdir_scan_vba(const char *dir, cli_ctx *ctx, struct uniq *U, int *has_macros) { - cl_error_t status = CL_CLEAN; + cl_error_t status = CL_SUCCESS; cl_error_t ret; int i, j; size_t data_len; vba_project_t *vba_project; - char *fullname, vbaname[1024]; - unsigned char *data; + char *fullname = NULL; + char vbaname[1024]; + unsigned char *data = NULL; char *hash; - uint32_t hashcnt = 0; - unsigned int viruses_found = 0; + uint32_t hashcnt = 0; - if (CL_SUCCESS != (ret = uniq_get(U, "_vba_project", 12, NULL, &hashcnt))) { - cli_dbgmsg("cli_ole2_tempdir_scan_vba: uniq_get('_vba_project') failed with ret code (%d)!\n", ret); - status = ret; + int fd = -1; + + int proj_contents_fd = -1; + char *proj_contents_fname = NULL; + + if (CL_SUCCESS != (status = uniq_get(U, "_vba_project", 12, NULL, &hashcnt))) { + cli_dbgmsg("cli_ole2_tempdir_scan_vba: uniq_get('_vba_project') failed with ret code (%d)!\n", status); goto done; } while (hashcnt) { @@ -1910,8 +1857,6 @@ static cl_error_t cli_ole2_tempdir_scan_vba(const char *dir, cli_ctx *ctx, struc for (i = 0; i < vba_project->count; i++) { for (j = 1; (unsigned int)j <= vba_project->colls[i]; j++) { - int fd = -1; - snprintf(vbaname, 1024, "%s" PATHSEP "%s_%u", vba_project->dir, vba_project->name[i], j); vbaname[sizeof(vbaname) - 1] = '\0'; @@ -1919,188 +1864,197 @@ static cl_error_t cli_ole2_tempdir_scan_vba(const char *dir, cli_ctx *ctx, struc if (fd == -1) { continue; } + cli_dbgmsg("cli_ole2_tempdir_scan_vba: Decompress VBA project '%s_%u'\n", vba_project->name[i], j); + data = (unsigned char *)cli_vba_inflate(fd, vba_project->offset[i], &data_len); + close(fd); + fd = -1; + *has_macros = *has_macros + 1; - if (!data) { - } else { + + if (NULL != data) { /* cli_dbgmsg("Project content:\n%s", data); */ if (ctx->scanned) *ctx->scanned += data_len / CL_COUNT_PRECISION; if (ctx->engine->keeptmp) { - char *tempfile; - int of; - - if ((ret = cli_gentempfd(ctx->sub_tmpdir, &tempfile, &of)) != CL_SUCCESS) { + if (CL_SUCCESS != (status = cli_gentempfd(ctx->sub_tmpdir, &proj_contents_fname, &proj_contents_fd))) { cli_warnmsg("WARNING: VBA project '%s_%u' cannot be dumped to file\n", vba_project->name[i], j); - status = ret; goto done; } - if (cli_writen(of, data, data_len) != data_len) { + + if (cli_writen(proj_contents_fd, data, data_len) != data_len) { cli_warnmsg("WARNING: VBA project '%s_%u' failed to write to file\n", vba_project->name[i], j); - close(of); - free(tempfile); status = CL_EWRITE; goto done; } - cli_dbgmsg("cli_ole2_tempdir_scan_vba: VBA project '%s_%u' dumped to %s\n", vba_project->name[i], j, tempfile); - free(tempfile); + close(proj_contents_fd); + proj_contents_fd = -1; + + free(proj_contents_fname); + proj_contents_fname = NULL; + + cli_dbgmsg("cli_ole2_tempdir_scan_vba: VBA project '%s_%u' dumped to %s\n", vba_project->name[i], j, proj_contents_fname); } - if (vba_scandata(data, data_len, ctx) == CL_VIRUS) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - free(data); - status = CL_VIRUS; - break; - } + status = vba_scandata(data, data_len, ctx); + if (CL_SUCCESS != status) { + goto done; } + free(data); + data = NULL; } } - - if (status == CL_VIRUS) - break; } cli_free_vba_project(vba_project); vba_project = NULL; - if (status == CL_VIRUS) - break; + hashcnt--; + } + + if (CL_SUCCESS != (status = uniq_get(U, "powerpoint document", 19, &hash, &hashcnt))) { + cli_dbgmsg("cli_ole2_tempdir_scan_vba: uniq_get('powerpoint document') failed with ret code (%d)!\n", status); + goto done; + } + while (hashcnt) { + snprintf(vbaname, 1024, "%s" PATHSEP "%s_%u", dir, hash, hashcnt); + vbaname[sizeof(vbaname) - 1] = '\0'; + + fd = open(vbaname, O_RDONLY | O_BINARY); + if (fd == -1) { + hashcnt--; + continue; + } + + fullname = cli_ppt_vba_read(fd, ctx); + if (NULL != fullname) { + status = cli_magic_scan_dir(fullname, ctx, LAYER_ATTRIBUTES_NONE); + if (CL_SUCCESS != status) { + goto done; + } + + if (!ctx->engine->keeptmp) { + cli_rmdirs(fullname); + } + free(fullname); + fullname = NULL; + } + + close(fd); + fd = -1; hashcnt--; } - if (status == CL_CLEAN || (status == CL_VIRUS && SCAN_ALLMATCHES)) { - if (CL_SUCCESS != (ret = uniq_get(U, "powerpoint document", 19, &hash, &hashcnt))) { - cli_dbgmsg("cli_ole2_tempdir_scan_vba: uniq_get('powerpoint document') failed with ret code (%d)!\n", ret); - status = ret; - goto done; - } - while (hashcnt) { - int fd = -1; - - snprintf(vbaname, 1024, "%s" PATHSEP "%s_%u", dir, hash, hashcnt); - vbaname[sizeof(vbaname) - 1] = '\0'; - - fd = open(vbaname, O_RDONLY | O_BINARY); - if (fd == -1) { - hashcnt--; - continue; - } - if ((fullname = cli_ppt_vba_read(fd, ctx))) { - ret = cli_magic_scan_dir(fullname, ctx, LAYER_ATTRIBUTES_NONE); - - if (!ctx->engine->keeptmp) - cli_rmdirs(fullname); - free(fullname); - - if (ret == CL_VIRUS) { - status = CL_VIRUS; - viruses_found++; - if (!SCAN_ALLMATCHES) { - close(fd); - break; - } - } - } - close(fd); - hashcnt--; - } + if (CL_SUCCESS != (status = uniq_get(U, "worddocument", 12, &hash, &hashcnt))) { + cli_dbgmsg("cli_ole2_tempdir_scan_vba: uniq_get('worddocument') failed with ret code (%d)!\n", status); + goto done; } + while (hashcnt) { + snprintf(vbaname, sizeof(vbaname), "%s" PATHSEP "%s_%u", dir, hash, hashcnt); + vbaname[sizeof(vbaname) - 1] = '\0'; - if (status == CL_CLEAN || (status == CL_VIRUS && SCAN_ALLMATCHES)) { - if (CL_SUCCESS != (ret = uniq_get(U, "worddocument", 12, &hash, &hashcnt))) { - cli_dbgmsg("cli_ole2_tempdir_scan_vba: uniq_get('worddocument') failed with ret code (%d)!\n", ret); - status = ret; - goto done; - } - while (hashcnt) { - int fd = -1; - - snprintf(vbaname, sizeof(vbaname), "%s" PATHSEP "%s_%u", dir, hash, hashcnt); - vbaname[sizeof(vbaname) - 1] = '\0'; - - fd = open(vbaname, O_RDONLY | O_BINARY); - if (fd == -1) { - hashcnt--; - continue; - } - - if (!(vba_project = (vba_project_t *)cli_wm_readdir(fd))) { - close(fd); - hashcnt--; - continue; - } - - for (i = 0; i < vba_project->count; i++) { - cli_dbgmsg("cli_ole2_tempdir_scan_vba: Decompress WM project macro:%d key:%d length:%d\n", i, vba_project->key[i], vba_project->length[i]); - data = (unsigned char *)cli_wm_decrypt_macro(fd, vba_project->offset[i], vba_project->length[i], vba_project->key[i]); - if (!data) { - cli_dbgmsg("cli_ole2_tempdir_scan_vba: WARNING: WM project '%s' macro %d decrypted to NULL\n", vba_project->name[i], i); - } else { - cli_dbgmsg("cli_ole2_tempdir_scan_vba: Project content:\n%s", data); - if (ctx->scanned) - *ctx->scanned += vba_project->length[i] / CL_COUNT_PRECISION; - if (vba_scandata(data, vba_project->length[i], ctx) == CL_VIRUS) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - free(data); - status = CL_VIRUS; - break; - } - } - free(data); - } - } - - close(fd); - cli_free_vba_project(vba_project); - vba_project = NULL; - - if (status == CL_VIRUS && !SCAN_ALLMATCHES) { - break; - } + fd = open(vbaname, O_RDONLY | O_BINARY); + if (fd == -1) { hashcnt--; + continue; } + + if (!(vba_project = (vba_project_t *)cli_wm_readdir(fd))) { + close(fd); + fd = -1; + hashcnt--; + continue; + } + + for (i = 0; i < vba_project->count; i++) { + cli_dbgmsg("cli_ole2_tempdir_scan_vba: Decompress WM project macro:%d key:%d length:%d\n", i, vba_project->key[i], vba_project->length[i]); + + data = (unsigned char *)cli_wm_decrypt_macro(fd, vba_project->offset[i], vba_project->length[i], vba_project->key[i]); + if (!data) { + cli_dbgmsg("cli_ole2_tempdir_scan_vba: WARNING: WM project '%s' macro %d decrypted to NULL\n", vba_project->name[i], i); + } else { + cli_dbgmsg("cli_ole2_tempdir_scan_vba: Project content:\n%s", data); + + if (ctx->scanned) { + *ctx->scanned += vba_project->length[i] / CL_COUNT_PRECISION; + } + + status = vba_scandata(data, vba_project->length[i], ctx); + if (CL_SUCCESS != status) { + goto done; + } + + free(data); + data = NULL; + } + } + + close(fd); + fd = -1; + + cli_free_vba_project(vba_project); + vba_project = NULL; + + hashcnt--; } done: + + if (*has_macros) { #if HAVE_JSON - if (*has_macros && SCAN_COLLECT_METADATA && (ctx->wrkproperty != NULL)) { - cli_jsonbool(ctx->wrkproperty, "HasMacros", 1); - json_object *macro_languages = cli_jsonarray(ctx->wrkproperty, "MacroLanguages"); - if (macro_languages) { - cli_jsonstr(macro_languages, NULL, "VBA"); - } else { - cli_dbgmsg("cli_ole2_tempdir_scan_vba: Failed to add \"VBA\" entry to MacroLanguages JSON array\n"); + if (SCAN_COLLECT_METADATA && (ctx->wrkproperty != NULL)) { + cli_jsonbool(ctx->wrkproperty, "HasMacros", 1); + json_object *macro_languages = cli_jsonarray(ctx->wrkproperty, "MacroLanguages"); + if (macro_languages) { + cli_jsonstr(macro_languages, NULL, "VBA"); + } else { + cli_dbgmsg("cli_ole2_tempdir_scan_vba: Failed to add \"VBA\" entry to MacroLanguages JSON array\n"); + } } - } #endif - if (SCAN_HEURISTIC_MACROS && *has_macros) { - ret = cli_append_potentially_unwanted(ctx, "Heuristics.OLE2.ContainsMacros.VBA"); - if (ret == CL_VIRUS) - viruses_found++; + if (SCAN_HEURISTIC_MACROS) { + ret = cli_append_potentially_unwanted(ctx, "Heuristics.OLE2.ContainsMacros.VBA"); + if (ret == CL_VIRUS) { + status = ret; + } + } } - if (viruses_found > 0) { - status = CL_VIRUS; + if (proj_contents_fd >= 0) { + close(proj_contents_fd); } + if (NULL != proj_contents_fname) { + free(proj_contents_fname); + } + + if (NULL != data) { + free(data); + } + + if (NULL != fullname) { + if (!ctx->engine->keeptmp) { + (void)cli_rmdirs(fullname); + } + + free(fullname); + } + return status; } static cl_error_t cli_ole2_tempdir_scan_for_xlm_and_images(const char *dir, cli_ctx *ctx, struct uniq *U) { - cl_error_t ret = CL_CLEAN; - char *hash = NULL; - uint32_t hashcnt = 0; - unsigned int viruses_found = 0; - char STR_WORKBOOK[] = "workbook"; - char STR_BOOK[] = "book"; + cl_error_t ret = CL_CLEAN; + char *hash = NULL; + uint32_t hashcnt = 0; + char STR_WORKBOOK[] = "workbook"; + char STR_BOOK[] = "book"; if (CL_SUCCESS != (ret = uniq_get(U, STR_WORKBOOK, sizeof(STR_WORKBOOK) - 1, &hash, &hashcnt))) { if (CL_SUCCESS != (ret = uniq_get(U, STR_BOOK, sizeof(STR_BOOK) - 1, &hash, &hashcnt))) { @@ -2110,7 +2064,7 @@ static cl_error_t cli_ole2_tempdir_scan_for_xlm_and_images(const char *dir, cli_ } for (; hashcnt > 0; hashcnt--) { - if ((ret = cli_extract_xlm_macros_and_images(dir, ctx, hash, hashcnt)) != CL_SUCCESS) { + if (CL_SUCCESS != (ret = cli_extract_xlm_macros_and_images(dir, ctx, hash, hashcnt))) { switch (ret) { case CL_VIRUS: case CL_EMEM: @@ -2122,117 +2076,130 @@ static cl_error_t cli_ole2_tempdir_scan_for_xlm_and_images(const char *dir, cli_ } done: - if (SCAN_ALLMATCHES && viruses_found) - return CL_VIRUS; return ret; } static cl_error_t cli_scanhtml(cli_ctx *ctx) { - char *tempname, fullname[1024]; - cl_error_t ret = CL_CLEAN; - int fd; - fmap_t *map = ctx->fmap; - unsigned int viruses_found = 0; - uint64_t curr_len = map->len; + cl_error_t status = CL_SUCCESS; + char *tempname = NULL; + char fullname[1024]; + int fd = -1; + fmap_t *map = ctx->fmap; + uint64_t curr_len = map->len; cli_dbgmsg("in cli_scanhtml()\n"); /* CL_ENGINE_MAX_HTMLNORMALIZE */ if (curr_len > ctx->engine->maxhtmlnormalize) { cli_dbgmsg("cli_scanhtml: exiting (file larger than MaxHTMLNormalize)\n"); - return CL_CLEAN; + status = CL_SUCCESS; + goto done; } - if (!(tempname = cli_gentemp_with_prefix(ctx->sub_tmpdir, "html-tmp"))) - return CL_EMEM; + if (NULL == (tempname = cli_gentemp_with_prefix(ctx->sub_tmpdir, "html-tmp"))) { + status = CL_EMEM; + goto done; + } if (mkdir(tempname, 0700)) { cli_errmsg("cli_scanhtml: Can't create temporary directory %s\n", tempname); - free(tempname); - return CL_ETMPDIR; + status = CL_ETMPDIR; + goto done; } cli_dbgmsg("cli_scanhtml: using tempdir %s\n", tempname); - html_normalise_map(map, tempname, NULL, ctx->dconf); + (void)html_normalise_map(map, tempname, NULL, ctx->dconf); + snprintf(fullname, 1024, "%s" PATHSEP "nocomment.html", tempname); fd = open(fullname, O_RDONLY | O_BINARY); if (fd >= 0) { - if (CL_VIRUS == (ret = cli_scan_desc(fd, ctx, CL_TYPE_HTML, false, NULL, AC_SCAN_VIR, - NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED))) { - viruses_found++; + // nocomment.html file exists, so lets scan it. + + status = cli_scan_desc(fd, ctx, CL_TYPE_HTML, false, NULL, AC_SCAN_VIR, NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED); + if (CL_SUCCESS != status) { + goto done; } close(fd); + fd = -1; } - if (ret == CL_CLEAN || (ret == CL_VIRUS && SCAN_ALLMATCHES)) { - /* CL_ENGINE_MAX_HTMLNOTAGS */ - curr_len = map->len; - if (curr_len > ctx->engine->maxhtmlnotags) { - /* we're not interested in scanning large files in notags form */ - /* TODO: don't even create notags if file is over limit */ - cli_dbgmsg("cli_scanhtml: skipping notags (normalized size over MaxHTMLNoTags)\n"); - } else { - snprintf(fullname, 1024, "%s" PATHSEP "notags.html", tempname); - fd = open(fullname, O_RDONLY | O_BINARY); - if (fd >= 0) { - if (CL_VIRUS == (ret = cli_scan_desc(fd, ctx, CL_TYPE_HTML, false, NULL, AC_SCAN_VIR, - NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED))) { - viruses_found++; - } + /* CL_ENGINE_MAX_HTMLNOTAGS */ + curr_len = map->len; + if (curr_len > ctx->engine->maxhtmlnotags) { + /* we're not interested in scanning large files in notags form */ + /* TODO: don't even create notags if file is over limit */ + cli_dbgmsg("cli_scanhtml: skipping notags (normalized size over MaxHTMLNoTags)\n"); + } else { + snprintf(fullname, 1024, "%s" PATHSEP "notags.html", tempname); - close(fd); - } - } - } - - if (ret == CL_CLEAN || (ret == CL_VIRUS && SCAN_ALLMATCHES)) { - snprintf(fullname, 1024, "%s" PATHSEP "javascript", tempname); fd = open(fullname, O_RDONLY | O_BINARY); if (fd >= 0) { - if (CL_VIRUS == (ret = cli_scan_desc(fd, ctx, CL_TYPE_HTML, false, NULL, AC_SCAN_VIR, - NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED))) { - viruses_found++; + // notags.html file exists, so lets scan it. + + status = cli_scan_desc(fd, ctx, CL_TYPE_HTML, false, NULL, AC_SCAN_VIR, NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED); + if (CL_SUCCESS != status) { + goto done; } - if (ret == CL_CLEAN || (ret == CL_VIRUS && SCAN_ALLMATCHES)) { - if (CL_VIRUS == (ret = cli_scan_desc(fd, ctx, CL_TYPE_TEXT_ASCII, false, NULL, AC_SCAN_VIR, - NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED))) { - viruses_found++; - } - } close(fd); + fd = -1; } } - if (ret == CL_CLEAN || (ret == CL_VIRUS && SCAN_ALLMATCHES)) { - snprintf(fullname, 1024, "%s" PATHSEP "rfc2397", tempname); - ret = cli_magic_scan_dir(fullname, ctx, LAYER_ATTRIBUTES_NORMALIZED); - if (CL_EOPEN == ret) { - /* If the directory doesn't exist, that's fine */ - ret = CL_CLEAN; + snprintf(fullname, 1024, "%s" PATHSEP "javascript", tempname); + fd = open(fullname, O_RDONLY | O_BINARY); + if (fd >= 0) { + // javascript file exists, so lets scan it (twice, as different types). + + status = cli_scan_desc(fd, ctx, CL_TYPE_HTML, false, NULL, AC_SCAN_VIR, NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED); + if (CL_SUCCESS != status) { + goto done; } + + status = cli_scan_desc(fd, ctx, CL_TYPE_TEXT_ASCII, false, NULL, AC_SCAN_VIR, NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED); + if (CL_SUCCESS != status) { + goto done; + } + + close(fd); + fd = -1; } - if (!ctx->engine->keeptmp) - cli_rmdirs(tempname); + snprintf(fullname, 1024, "%s" PATHSEP "rfc2397", tempname); - free(tempname); - if (SCAN_ALLMATCHES && viruses_found) - return CL_VIRUS; - return ret; + status = cli_magic_scan_dir(fullname, ctx, LAYER_ATTRIBUTES_NORMALIZED); + if (CL_EOPEN == status) { + /* If the directory doesn't exist, that's fine */ + status = CL_SUCCESS; + } else { + goto done; + } + +done: + if (fd >= 0) { + close(fd); + } + if (NULL != tempname) { + if (!ctx->engine->keeptmp) { + cli_rmdirs(tempname); + } + free(tempname); + } + + return status; } static cl_error_t cli_scanscript(cli_ctx *ctx) { + cl_error_t ret = CL_SUCCESS; const unsigned char *buff; unsigned char *normalized = NULL; struct text_norm_state state; char *tmpname = NULL; int ofd = -1; - cl_error_t ret; struct cli_matcher *target_ac_root; uint32_t maxpatlen, offset = 0; struct cli_matcher *generic_ac_root; @@ -2242,8 +2209,7 @@ static cl_error_t cli_scanscript(cli_ctx *ctx) struct cli_ac_data *mdata[2]; cl_fmap_t *new_map = NULL; fmap_t *map; - size_t at = 0; - unsigned int viruses_found = 0; + size_t at = 0; uint64_t curr_len; struct cli_target_info info; @@ -2333,11 +2299,13 @@ static cl_error_t cli_scanscript(cli_ctx *ctx) /* scan map */ ret = cli_scan_fmap(ctx, CL_TYPE_TEXT_ASCII, false, NULL, AC_SCAN_VIR, NULL, NULL); - if (ret == CL_VIRUS) { - viruses_found++; - } (void)cli_recursion_stack_pop(ctx); /* Restore the parent fmap */ + + if (CL_SUCCESS != ret) { + goto done; + } + } else { /* Since the above is moderately costly all in all, * do the old stuff if there's no relative offsets. */ @@ -2362,17 +2330,15 @@ static cl_error_t cli_scanscript(cli_ctx *ctx) /* we can continue to scan in memory */ } /* when we flush the buffer also scan */ - if (cli_scan_buff(state.out, state.out_pos, offset, ctx, CL_TYPE_TEXT_ASCII, mdata) == CL_VIRUS) { - if (SCAN_ALLMATCHES) - viruses_found++; - else { - ret = CL_VIRUS; - break; - } + ret = cli_scan_buff(state.out, state.out_pos, offset, ctx, CL_TYPE_TEXT_ASCII, mdata); + if (CL_SUCCESS != ret) { + goto done; } + if (ctx->scanned) *ctx->scanned += state.out_pos / CL_COUNT_PRECISION; offset += state.out_pos; + /* carry over maxpatlen from previous buffer */ if (state.out_pos > maxpatlen) memmove(state.out, state.out + state.out_pos - maxpatlen, maxpatlen); @@ -2387,12 +2353,14 @@ static cl_error_t cli_scanscript(cli_ctx *ctx) } } - if (ret != CL_VIRUS || SCAN_ALLMATCHES) { - if ((ret = cli_exp_eval(ctx, target_ac_root, &tmdata, NULL, NULL)) == CL_VIRUS) - viruses_found++; - if (ret != CL_VIRUS || SCAN_ALLMATCHES) - if ((ret = cli_exp_eval(ctx, generic_ac_root, &gmdata, NULL, NULL)) == CL_VIRUS) - viruses_found++; + ret = cli_exp_eval(ctx, target_ac_root, &tmdata, NULL, NULL); + if (CL_SUCCESS != ret) { + goto done; + } + + ret = cli_exp_eval(ctx, generic_ac_root, &gmdata, NULL, NULL); + if (CL_SUCCESS != ret) { + goto done; } done: @@ -2414,16 +2382,16 @@ done: cli_ac_freedata(&gmdata); } - if (ofd != -1) + if (ofd != -1) { close(ofd); - if (tmpname != NULL) { - if (!ctx->engine->keeptmp) - cli_unlink(tmpname); - free(tmpname); } - if (viruses_found) - return CL_VIRUS; + if (tmpname != NULL) { + if (!ctx->engine->keeptmp) { + (void)cli_unlink(tmpname); + } + free(tmpname); + } return ret; } @@ -2491,7 +2459,9 @@ static cl_error_t cli_scanhtml_utf16(cli_ctx *ctx) (void)cli_recursion_stack_pop(ctx); /* Restore the parent fmap */ - status = CL_SUCCESS; + if (CL_SUCCESS != status) { + goto done; + } done: if (NULL != new_map) { @@ -2528,7 +2498,6 @@ static cl_error_t cli_ole2_scan_tempdir( { cl_error_t status = CL_CLEAN; DIR *dd = NULL; - int viruses_found = 0; int has_macros = 0; struct dirent *dent; @@ -2543,39 +2512,18 @@ static cl_error_t cli_ole2_scan_tempdir( } status = cli_ole2_tempdir_scan_embedded_ole10(dir, ctx, files); - if (CL_VIRUS == status) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - goto done; - } - } else if (CL_SUCCESS != status) { - /* Some error occured */ - cli_dbgmsg("An error occured while scanning ole2 extracted VBA files: %s\n", cl_strerror(status)); + if (CL_SUCCESS != status) { goto done; } if (has_vba) { status = cli_ole2_tempdir_scan_vba(dir, ctx, files, &has_macros); - if (CL_VIRUS == status) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - goto done; - } - } else if (CL_SUCCESS != status) { - /* Some error occured */ - cli_dbgmsg("An error occured while scanning ole2 extracted VBA files: %s\n", cl_strerror(status)); + if (CL_SUCCESS != status) { goto done; } status = cli_ole2_tempdir_scan_vba_new(dir, ctx, files, &has_macros); - if (CL_VIRUS == status) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - goto done; - } - } else if (CL_SUCCESS != status) { - /* Some error occured */ - cli_dbgmsg("An error occured while scanning ole2 extracted VBA files: %s\n", cl_strerror(status)); + if (CL_SUCCESS != status) { goto done; } } @@ -2583,11 +2531,8 @@ static cl_error_t cli_ole2_scan_tempdir( if (has_xlm) { if (SCAN_HEURISTIC_MACROS) { status = cli_append_potentially_unwanted(ctx, "Heuristics.OLE2.ContainsMacros.XLM"); - if (status == CL_VIRUS) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - goto done; - } + if (CL_SUCCESS != status) { + goto done; } } } @@ -2596,28 +2541,14 @@ static cl_error_t cli_ole2_scan_tempdir( /* TODO: Consider moving image extraction to handler_enum and * removing the has_image and found_image stuff. */ status = cli_ole2_tempdir_scan_for_xlm_and_images(dir, ctx, files); - if (CL_VIRUS == status) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - goto done; - } - } else if (CL_SUCCESS != status) { - /* Some error occured */ - cli_dbgmsg("An error occured while scanning ole2 extracted VBA files: %s\n", cl_strerror(status)); + if (CL_SUCCESS != status) { goto done; } } if (has_xlm || has_vba) { status = cli_magic_scan_dir(dir, ctx, LAYER_ATTRIBUTES_NONE); - if (CL_VIRUS == status) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - goto done; - } - } else if (CL_SUCCESS != status) { - /* Some error occured */ - cli_dbgmsg("An error occured while scanning ole2 extracted VBA files: %s\n", cl_strerror(status)); + if (CL_SUCCESS != status) { goto done; } } @@ -2652,15 +2583,7 @@ static cl_error_t cli_ole2_scan_tempdir( has_vba, has_xlm, has_image); - if (CL_VIRUS == status) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - status = CL_VIRUS; - goto done; - } - } else if (CL_SUCCESS != status) { - /* Some error occured */ - cli_dbgmsg("An error occured while scanning ole2 extracted VBA files: %s\n", cl_strerror(status)); + if (CL_SUCCESS != status) { goto done; } } @@ -2684,9 +2607,6 @@ done: free(subdirectory); } - if (viruses_found > 0) { - status = CL_VIRUS; - } return status; } @@ -2698,7 +2618,6 @@ static cl_error_t cli_scanole2(cli_ctx *ctx) int has_vba = 0; int has_xlm = 0; int has_image = 0; - int viruses_found = 0; cli_dbgmsg("in cli_scanole2()\n"); @@ -2717,16 +2636,9 @@ static cl_error_t cli_scanole2(cli_ctx *ctx) } ret = cli_ole2_extract(dir, ctx, &files, &has_vba, &has_xlm, &has_image); - if (ret != CL_CLEAN && ret != CL_VIRUS) { - cli_dbgmsg("OLE2: %s\n", cl_strerror(ret)); + if (CL_SUCCESS != ret) { goto done; } - if (CL_VIRUS == ret) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - goto done; - } - } if (files) { /* @@ -2760,10 +2672,6 @@ done: free(dir); } - if (viruses_found > 0) { - ret = CL_VIRUS; - } - return ret; } @@ -2879,10 +2787,13 @@ static cl_error_t cli_scancryptff(cli_ctx *ctx) close(ndesc); - if (ctx->engine->keeptmp) + if (ctx->engine->keeptmp) { cli_dbgmsg("CryptFF: Decompressed data saved in %s\n", tempfile); - else if (cli_unlink(tempfile)) - ret = CL_EUNLINK; + } else { + if (CL_SUCCESS != cli_unlink(tempfile)) { + ret = CL_EUNLINK; + } + } free(tempfile); return ret; @@ -2965,44 +2876,45 @@ static cl_error_t cli_scanuuencoded(cli_ctx *ctx) static cl_error_t cli_scanmail(cli_ctx *ctx) { - char *dir; + char *dir = NULL; cl_error_t ret; - unsigned int viruses_found = 0; cli_dbgmsg("Starting cli_scanmail()\n"); /* generate the temporary directory */ - if (!(dir = cli_gentemp_with_prefix(ctx->sub_tmpdir, "mail-tmp"))) - return CL_EMEM; + if (NULL == (dir = cli_gentemp_with_prefix(ctx->sub_tmpdir, "mail-tmp"))) { + ret = CL_EMEM; + goto done; + } if (mkdir(dir, 0700)) { cli_dbgmsg("Mail: Can't create temporary directory %s\n", dir); - free(dir); - return CL_ETMPDIR; + ret = CL_ETMPDIR; + goto done; } /* * Extract the attachments into the temporary directory */ - if ((ret = cli_mbox(dir, ctx))) { - if (ret == CL_VIRUS && SCAN_ALLMATCHES) - viruses_found++; - else { - if (!ctx->engine->keeptmp) - cli_rmdirs(dir); - free(dir); - return ret; - } + ret = cli_mbox(dir, ctx); + if (CL_SUCCESS != ret) { + goto done; } ret = cli_magic_scan_dir(dir, ctx, LAYER_ATTRIBUTES_NONE); + if (CL_SUCCESS != ret) { + goto done; + } - if (!ctx->engine->keeptmp) - cli_rmdirs(dir); +done: + if (NULL != dir) { + if (!ctx->engine->keeptmp) { + cli_rmdirs(dir); + } + + free(dir); + } - free(dir); - if (viruses_found) - return CL_VIRUS; return ret; } @@ -3012,12 +2924,11 @@ static cl_error_t cli_scan_structured(cli_ctx *ctx) size_t result = 0; unsigned int cc_count = 0; unsigned int ssn_count = 0; - int done = 0; + bool done = false; fmap_t *map; size_t pos = 0; int (*ccfunc)(const unsigned char *buffer, size_t length, int cc_only); int (*ssnfunc)(const unsigned char *buffer, size_t length); - unsigned int viruses_found = 0; if (ctx == NULL) return CL_ENULLARG; @@ -3059,38 +2970,28 @@ static cl_error_t cli_scan_structured(cli_ctx *ctx) pos += result; if ((cc_count += ccfunc((const unsigned char *)buf, result, (ctx->options->heuristic & CL_SCAN_HEURISTIC_STRUCTURED_CC) ? 1 : 0)) >= ctx->engine->min_cc_count) { - done = 1; + done = true; } if (ssnfunc && ((ssn_count += ssnfunc((const unsigned char *)buf, result)) >= ctx->engine->min_ssn_count)) { - done = 1; + done = true; } } if (cc_count != 0 && cc_count >= ctx->engine->min_cc_count) { cli_dbgmsg("cli_scan_structured: %u credit card numbers detected\n", cc_count); if (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Structured.CreditCardNumber")) { - if (SCAN_ALLMATCHES) { - viruses_found++; - } else { - return CL_VIRUS; - } + return CL_VIRUS; } } if (ssn_count != 0 && ssn_count >= ctx->engine->min_ssn_count) { cli_dbgmsg("cli_scan_structured: %u social security numbers detected\n", ssn_count); if (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Structured.SSN")) { - if (SCAN_ALLMATCHES) { - viruses_found++; - } else { - return CL_VIRUS; - } + return CL_VIRUS; } } - if (viruses_found) - return CL_VIRUS; return CL_CLEAN; } @@ -3153,11 +3054,12 @@ static cl_error_t cli_scanembpe(cli_ctx *ctx, off_t offset) } } + // Setting ctx->corrupted_input will prevent the PE parser from reporting "broken executable" for unpacked/reconstructed files that may not be 100% to spec. corrupted_input = ctx->corrupted_input; ctx->corrupted_input = 1; ret = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE); ctx->corrupted_input = corrupted_input; - if (ret == CL_VIRUS) { + if (ret != CL_SUCCESS) { close(fd); if (!ctx->engine->keeptmp) { if (cli_unlink(tmpname)) { @@ -3166,7 +3068,7 @@ static cl_error_t cli_scanembpe(cli_ctx *ctx, off_t offset) } } free(tmpname); - return CL_VIRUS; + return ret; } close(fd); @@ -3178,7 +3080,6 @@ static cl_error_t cli_scanembpe(cli_ctx *ctx, off_t offset) } free(tmpname); - /* intentionally ignore possible errors from cli_magic_scan_desc */ return CL_CLEAN; } @@ -3366,6 +3267,8 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi struct cli_exe_info peinfo; unsigned int acmode = AC_SCAN_VIR, break_loop = 0; + cli_file_t found_type; + #if HAVE_JSON struct json_object *parent_property = NULL; #else @@ -3395,13 +3298,14 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi ret = cli_scan_fmap(ctx, type == CL_TYPE_TEXT_ASCII ? CL_TYPE_ANY : type, false, &ftoffset, acmode, NULL, refhash); perf_stop(ctx, PERFT_RAW); - // I think this (CL_TYPENO business) causes embedded file extraction to stop when a - // signature has matched in cli_scan_fmap, which wouldn't be what - // we want if allmatch is specified. - // - // TODO: find a way to return type matches separately from malware matches + // In allmatch-mode, ret will never be CL_VIRUS, so ret may be used exlusively for file type detection and for terminal errors. + // When not in allmatch-mode, it's more important to return right away if ret is CL_VIRUS, so we don't care if file type matches were found. if (ret >= CL_TYPENO) { + // Matched 1+ file type signatures. Handle them. + found_type = (cli_file_t)ret; + perf_nested_start(ctx, PERFT_RAWTYPENO, PERFT_SCAN); + fpt = ftoffset; while (fpt) { @@ -3409,6 +3313,9 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi bool type_has_been_handled = true; #if HAVE_JSON + /* + * Add embedded file to metadata JSON. + */ if (SCAN_COLLECT_METADATA && ctx->wrkproperty) { json_object *arrobj; @@ -3589,6 +3496,10 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi type_has_been_handled = false; } + if ((CL_EMEM == nret) || ctx->abort_scan) { + break; + } + /* * Next, check for actual embedded files. */ @@ -3932,11 +3843,10 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi if (NULL != new_map) { free_duplicate_fmap(new_map); } - } - } + } // end check for embedded files + } // end if (fpt->offset > 0) - if ((nret == CL_VIRUS && !SCAN_ALLMATCHES) || - (nret == CL_EMEM) || + if ((nret == CL_EMEM) || (ctx->abort_scan) || (break_loop)) { break; @@ -3950,17 +3860,17 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi parent_property = NULL; } #endif - } + } // end while (fpt) loop - if (nret != CL_VIRUS) { + if (!((nret == CL_EMEM) || (ctx->abort_scan))) { /* * Now run the other file type parsers that may rely on file type * recognition to determine the actual file type. */ - switch (ret) { + switch (found_type) { case CL_TYPE_HTML: - /* bb#11196 - autoit script file misclassified as HTML */ if (cli_recursion_stack_get_type(ctx, -2) == CL_TYPE_AUTOIT) { + /* bb#11196 - autoit script file misclassified as HTML */ ret = CL_TYPE_TEXT_ASCII; } else if (SCAN_PARSE_HTML && (type == CL_TYPE_TEXT_ASCII || @@ -3987,7 +3897,7 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi perf_nested_stop(ctx, PERFT_RAWTYPENO, PERFT_SCAN); ret = nret; - } + } // end if (ret >= CL_TYPENO) #if HAVE_JSON if (NULL != parent_property) { @@ -4128,12 +4038,11 @@ 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"); - status = CL_BREAK; + status = CL_VERIFIED; break; case CL_VIRUS: cli_dbgmsg("dispatch_prescan_callback: file blocked by callback\n"); - cli_append_virus(ctx, "Detected.By.Callback"); - status = CL_VIRUS; + status = cli_append_virus(ctx, "Detected.By.Callback"); break; case CL_CLEAN: break; @@ -4197,18 +4106,118 @@ done: return status; } +/** + * @brief A unified list of reasons why a scan result inside the magic_scan function + * should goto done instead of continuing to parse/scan this layer. + * + * These are not reasons why the scan should abort entirely. For that, just check ctx->abort_scan. + * + * @param ctx The scan context. + * @param result_in The result to compare. + * @param result_out The result that magic_scan should return. + * @return true We found a reason to goto done. + * @return false The scan must go on. + */ +static inline bool result_should_goto_done(cli_ctx *ctx, cl_error_t result_in, cl_error_t *result_out) +{ + bool halt_scan = false; + + if (NULL == ctx || NULL == result_out) { + cli_dbgmsg("Invalid arguments for file scan result check.\n"); + halt_scan = true; + goto done; + } + + if (NULL != ctx && ctx->abort_scan) { + // this covers CL_ETIMEOUT and CL_VIRUS at a minimum. + halt_scan = true; + *result_out = result_in; + goto done; + } + + switch (result_in) { + /* + * Reasons to halt the scan and report the error up to the caller/user. + */ + + // A virus result means we should halt the scan. + // We do not return CL_VIRUS in allmatch-mode until the very end. + case CL_VIRUS: + + // Each of these error codes considered terminal and will halt the scan. + case CL_EUNLINK: + case CL_ESTAT: + case CL_ESEEK: + case CL_EWRITE: + case CL_EDUP: + case CL_ETMPFILE: + case CL_ETMPDIR: + case CL_EMEM: + cli_dbgmsg("Descriptor[%d]: halting after file scan because: %s\n", fmap_fd(ctx->fmap), cl_strerror(result_in)); + halt_scan = true; + *result_out = result_in; + break; + + /* + * Reasons to halt the scan but report a successful scan. + */ + + // Exceeding the time limit should definitly halt the scan. + // But unless the user enabled alert-exceeds-max, we don't want to complain about it. + case CL_ETIMEOUT: + + // If the file was determined to be trusted, then we can stop scanning this layer. (Ex: EXE with a valid Authenticode sig.) + // Convert CL_VERIFIED to CL_SUCCESS because we don't want to propagate the CL_VERIFIED return code up to the caller. + // If we didn't, a trusted file could cause a larger archive containing non-trustworthy files to be trusted. + case CL_VERIFIED: + cli_dbgmsg("Descriptor[%d]: halting after file scan because: %s\n", fmap_fd(ctx->fmap), cl_strerror(result_in)); + halt_scan = true; + *result_out = CL_SUCCESS; + break; + + /* + * All other results must not halt the scan. + */ + + // Nothing to do. + case CL_SUCCESS: + + // Unless ctx->abort_scan was set, all these "MAX" conditions should finish scanning as much as is allowed. + // That is, the can may still be blocked from recursing into the next layer, or scanning new files or large files. + case CL_EMAXREC: + case CL_EMAXSIZE: + case CL_EMAXFILES: + + // The following are explicitly listed here so you think twice before putting them in the scan-halt list, above. + // Malformed/truncated files could report as any of these three, and that's fine. + // See commit 087e7fc3fa923e5d6a6fd2efe8df852a36256b5b for additional details. + case CL_EFORMAT: + case CL_EPARSE: + case CL_EREAD: + case CL_EUNPACK: + + default: + cli_dbgmsg("Descriptor[%d]: Continuing after file scan resulted with: %s\n", + fmap_fd(ctx->fmap), cl_strerror(result_in)); + *result_out = CL_SUCCESS; + } + +done: + return halt_scan; +} + cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) { cl_error_t ret = CL_CLEAN; - cl_error_t res; - cl_error_t cb_retcode; + cl_error_t cache_check_result; + cl_error_t verdict_at_this_level; cli_file_t dettype = 0; uint8_t typercg = 1; size_t hashed_size = 0; unsigned char *hash = NULL; bitset_t *old_hook_lsig_matches = NULL; const char *filetype; - int cache_clean = 0; + #if HAVE_JSON struct json_object *parent_property = NULL; #else @@ -4231,7 +4240,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) } if (ctx->fmap->len <= 5) { - cli_dbgmsg("cli_magic_scandesc: File is too too small (%zu bytes), ignoring.\n", ctx->fmap->len); + cli_dbgmsg("cli_magic_scan: File is too too small (%zu bytes), ignoring.\n", ctx->fmap->len); ret = CL_CLEAN; goto early_ret; } @@ -4392,13 +4401,11 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) } #endif + /* + * Run the pre_scan callback. + */ ret = dispatch_prescan_callback(ctx->engine->cb_pre_cache, ctx, filetype); - if (CL_CLEAN != ret) { - if (ret == CL_VIRUS) { - ret = cli_check_fp(ctx, NULL); - } else { - ret = CL_CLEAN; - } + if (CL_VERIFIED == ret || CL_VIRUS == ret) { goto done; } @@ -4407,6 +4414,10 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) */ if (CL_SUCCESS != fmap_get_hash(ctx->fmap, &hash, CLI_HASH_MD5)) { cli_dbgmsg("cli_magic_scan: Failed to get a hash for the current fmap.\n"); + + // It may be that the file was truncated between the time we started the scan and the time we got the hash. + // Not a reason to print an error message. + ret = CL_SUCCESS; goto done; } hashed_size = ctx->fmap->len; @@ -4425,19 +4436,20 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) * Check if we've already scanned this file before. */ perf_start(ctx, PERFT_CACHE); - - res = clean_cache_check(hash, hashed_size, ctx); + cache_check_result = clean_cache_check(hash, hashed_size, ctx); + perf_stop(ctx, PERFT_CACHE); #if HAVE_JSON - if (SCAN_COLLECT_METADATA /* ctx.options->general & CL_SCAN_GENERAL_COLLECT_METADATA && ctx->wrkproperty != NULL */) { - char hashstr[33]; - snprintf(hashstr, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + if (SCAN_COLLECT_METADATA) { + char hashstr[CLI_HASHLEN_MD5 * 2 + 1]; + snprintf(hashstr, CLI_HASHLEN_MD5 * 2 + 1, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]); ret = cli_jsonstr(ctx->wrkproperty, "FileMD5", hashstr); - if (ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_CACHE) - memset(hash, 0, 16); + if (ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_CACHE) { + memset(hash, 0, CLI_HASHLEN_MD5); + } if (ret != CL_SUCCESS) { cli_dbgmsg("cli_magic_scan: returning %d %s (no post, no cache)\n", ret, __AT__); goto early_ret; @@ -4445,9 +4457,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) } #endif - perf_stop(ctx, PERFT_CACHE); - - if (res != CL_VIRUS) { + if (cache_check_result != CL_VIRUS) { cli_dbgmsg("cli_magic_scan: returning %d %s (no post, no cache)\n", ret, __AT__); goto early_ret; } @@ -4455,35 +4465,25 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) old_hook_lsig_matches = ctx->hook_lsig_matches; ctx->hook_lsig_matches = NULL; + /* + * Run the pre_scan callback. + */ + ret = dispatch_prescan_callback(ctx->engine->cb_pre_scan, ctx, filetype); + if (CL_VERIFIED == ret || CL_VIRUS == ret) { + goto done; + } + + // If none of the scan options are enabled, then we can skip parsing and just do a raw pattern match. + // For this check, we don't care if the CL_SCAN_GENERAL_ALLMATCHES option is enabled, hence the `~`. if (!((ctx->options->general & ~CL_SCAN_GENERAL_ALLMATCHES) || (ctx->options->parse) || (ctx->options->heuristic) || (ctx->options->mail) || (ctx->options->dev))) { /* * Scanning in raw mode (stdin, etc.) */ - ret = dispatch_prescan_callback(ctx->engine->cb_pre_scan, ctx, filetype); - if (CL_CLEAN != ret) { - if (ret == CL_VIRUS) { - ret = cli_check_fp(ctx, NULL); - } else if (ret == CL_BREAK) { - ret = CL_CLEAN; - } - goto done; - } - ret = cli_scan_fmap(ctx, CL_TYPE_ANY, false, NULL, AC_SCAN_VIR, NULL, hash); // It doesn't matter what was returned, always go to the end after this. Raw mode! No parsing files! goto done; } - ret = dispatch_prescan_callback(ctx->engine->cb_pre_scan, ctx, filetype); - if (CL_CLEAN != ret) { - if (ret == CL_VIRUS) { - ret = cli_check_fp(ctx, NULL); - } else if (ret == CL_BREAK) { - ret = CL_CLEAN; - } - goto done; - } - #ifdef HAVE__INTERNAL__SHA_COLLECT if (!ctx->sha_collect && type == CL_TYPE_MSEXE) ctx->sha_collect = 1; @@ -4492,7 +4492,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) // We already saved the hook_lsig_matches (above) // The ctx one is NULL at present. ctx->hook_lsig_matches = cli_bitset_init(); - if (!ctx->hook_lsig_matches) { + if (NULL == ctx->hook_lsig_matches) { ret = CL_EMEM; goto done; } @@ -4503,8 +4503,10 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) * before extracting with a file type parser. */ ret = scanraw(ctx, type, 0, &dettype, (ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_CACHE) ? NULL : hash); - if (ret == CL_EMEM || ret == CL_VIRUS) { - ret = cli_check_fp(ctx, NULL); + + // Evaluate the result from the scan to see if it end the scan of this layer early, + // and to decid if we should propagate an error or not. + if (result_should_goto_done(ctx, ret, &ret)) { goto done; } } @@ -4875,79 +4877,35 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) } perf_nested_stop(ctx, PERFT_CONTAINER, PERFT_SCAN); + // Evaluate the result from the parsers to see if it end the scan of this layer early, + // and to decid if we should propagate an error or not. + if (result_should_goto_done(ctx, ret, &ret)) { + goto done; + } + /* * Perform the raw scan, which may include file type recognition signatures. */ - if ((ret == CL_VIRUS && !SCAN_ALLMATCHES) || - (ctx->abort_scan)) { - goto done; - } /* Disable type recognition for the raw scan for zip files larger than maxziptypercg */ if (type == CL_TYPE_ZIP && SCAN_PARSE_ARCHIVE && (DCONF_ARCH & ARCH_CONF_ZIP)) { /* CL_ENGINE_MAX_ZIPTYPERCG */ uint64_t curr_len = ctx->fmap->len; if (curr_len > ctx->engine->maxziptypercg) { - cli_dbgmsg("cli_magic_scan_desc: Not checking for embedded PEs (zip file > MaxZipTypeRcg)\n"); + cli_dbgmsg("cli_magic_scan: Not checking for embedded PEs (zip file > MaxZipTypeRcg)\n"); typercg = 0; } } /* CL_TYPE_HTML: raw HTML files are not scanned, unless safety measure activated via DCONF */ if (type != CL_TYPE_IGNORED && (type != CL_TYPE_HTML || !(SCAN_PARSE_HTML) || !(DCONF_DOC & DOC_CONF_HTML_SKIPRAW)) && !ctx->engine->sdb) { - res = scanraw(ctx, type, typercg, &dettype, (ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_CACHE) ? NULL : hash); - if (res != CL_CLEAN) { - switch (res) { - /* List of scan halts, runtime errors only! */ - case CL_EUNLINK: - case CL_ESTAT: - case CL_ESEEK: - case CL_EWRITE: - case CL_EDUP: - case CL_ETMPFILE: - case CL_ETMPDIR: - case CL_EMEM: - cli_dbgmsg("Descriptor[%d]: scanraw error %s\n", fmap_fd(ctx->fmap), cl_strerror(res)); - ret = res; - goto done; - /* CL_VIRUS = malware found, check FP and report. - * Likewise, if the file was determined to be trusted, then we - * can also finish with the scan. (Ex: EXE with a valid - * Authenticode sig.) */ - case CL_VERIFIED: - // For now just conver CL_VERIFIED to CL_CLEAN, since - // CL_VERIFIED isn't used elsewhere - res = CL_CLEAN; - // Fall through - case CL_VIRUS: - ret = res; - if (SCAN_ALLMATCHES) - break; - goto done; - /* All other "MAX" conditions should still fully scan the current file */ - case CL_ETIMEOUT: - case CL_EMAXREC: - case CL_EMAXSIZE: - case CL_EMAXFILES: - ret = res; - cli_dbgmsg("Descriptor[%d]: Continuing after scanraw reached %s\n", - fmap_fd(ctx->fmap), cl_strerror(res)); - break; - /* Other errors must not block further scans below - * This specifically includes CL_EFORMAT & CL_EREAD & CL_EUNPACK - * Malformed/truncated files could report as any of these three. - */ - default: - ret = res; - cli_dbgmsg("Descriptor[%d]: Continuing after scanraw error %s\n", - fmap_fd(ctx->fmap), cl_strerror(res)); - } - } - } + ret = scanraw(ctx, type, typercg, &dettype, (ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_CACHE) ? NULL : hash); - /* Make sure we bail out if required. */ - if (ctx->abort_scan) { - goto done; + // Evaluate the result from the scan to see if it end the scan of this layer early, + // and to decid if we should propagate an error or not. + if (result_should_goto_done(ctx, ret, &ret)) { + goto done; + } } /* @@ -4961,83 +4919,62 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) case CL_TYPE_TEXT_UTF16LE: case CL_TYPE_TEXT_UTF8: perf_nested_start(ctx, PERFT_SCRIPT, PERFT_SCAN); - if ((DCONF_DOC & DOC_CONF_SCRIPT) && dettype != CL_TYPE_HTML && (ret != CL_VIRUS || SCAN_ALLMATCHES) && SCAN_PARSE_HTML) + if ((dettype != CL_TYPE_HTML) && + SCAN_PARSE_HTML && (DCONF_DOC & DOC_CONF_SCRIPT) && (ret != CL_VIRUS)) { ret = cli_scanscript(ctx); - if (SCAN_PARSE_MAIL && (DCONF_MAIL & MAIL_CONF_MBOX) && ret != CL_VIRUS && (cli_recursion_stack_get_type(ctx, -1) == CL_TYPE_MAIL || dettype == CL_TYPE_MAIL)) { + } + if (((dettype == CL_TYPE_MAIL) || (cli_recursion_stack_get_type(ctx, -1) == CL_TYPE_MAIL)) && + SCAN_PARSE_MAIL && (DCONF_MAIL & MAIL_CONF_MBOX) && (ret != CL_VIRUS)) { ret = cli_scan_fmap(ctx, CL_TYPE_MAIL, false, NULL, AC_SCAN_VIR, NULL, NULL); } perf_nested_stop(ctx, PERFT_SCRIPT, PERFT_SCAN); break; + /* Due to performance reasons all executables were first scanned * in raw mode. Now we will try to unpack them */ case CL_TYPE_MSEXE: perf_nested_start(ctx, PERFT_PE, PERFT_SCAN); if (SCAN_PARSE_PE && ctx->dconf->pe) { + // Setting ctx->corrupted_input will prevent the PE parser from reporting "broken executable" for unpacked/reconstructed files that may not be 100% to spec. + // In here we're just carrying the corrupted_input flag from parent to child, in case the parent's flag was set. unsigned int corrupted_input = ctx->corrupted_input; ret = cli_scanpe(ctx); ctx->corrupted_input = corrupted_input; } perf_nested_stop(ctx, PERFT_PE, PERFT_SCAN); break; + case CL_TYPE_ELF: perf_nested_start(ctx, PERFT_ELF, PERFT_SCAN); ret = cli_unpackelf(ctx); perf_nested_stop(ctx, PERFT_ELF, PERFT_SCAN); break; + case CL_TYPE_MACHO: case CL_TYPE_MACHO_UNIBIN: perf_nested_start(ctx, PERFT_MACHO, PERFT_SCAN); ret = cli_unpackmacho(ctx); perf_nested_stop(ctx, PERFT_MACHO, PERFT_SCAN); break; + case CL_TYPE_BINARY_DATA: ret = cli_scan_fmap(ctx, CL_TYPE_OTHER, false, NULL, AC_SCAN_VIR, NULL, NULL); break; + case CL_TYPE_PDF: /* FIXMELIMITS: pdf should be an archive! */ if (SCAN_PARSE_PDF && (DCONF_DOC & DOC_CONF_PDF)) ret = cli_scanpdf(ctx, 0); break; + default: break; } done: - switch (ret) { - /* - * Limits exceeded - */ - // Exceeding these maximums means we have to stop scanning: - case CL_ETIMEOUT: - case CL_EMAXFILES: - ctx->abort_scan = true; - cli_dbgmsg("Descriptor[%d]: %s\n", fmap_fd(ctx->fmap), cl_strerror(ret)); - ret = CL_CLEAN; - break; - // Exceeding these maximums means we had to skip an embedded file: - case CL_EMAXREC: - case CL_EMAXSIZE: - cli_dbgmsg("Descriptor[%d]: %s\n", fmap_fd(ctx->fmap), cl_strerror(ret)); - ret = CL_CLEAN; - break; - - /* - * Malformed file cases - */ - case CL_EFORMAT: - case CL_EREAD: - case CL_EUNPACK: - cli_dbgmsg("Descriptor[%d]: %s\n", fmap_fd(ctx->fmap), cl_strerror(ret)); - ret = CL_CLEAN; - break; - - case CL_CLEAN: - cache_clean = 1; - break; - - default: - break; - } + // Filter the result from the parsers so we don't propagate non-fatal errors. + // And to convert CL_VERIFIED -> CL_CLEAN + (void)result_should_goto_done(ctx, ret, &ret); if (old_hook_lsig_matches) { /* We need to restore the old hook_lsig_matches */ @@ -5049,54 +4986,70 @@ done: ctx->wrkproperty = (struct json_object *)(parent_property); #endif - if ((ret == CL_SUCCESS) && - (evidence_num_alerts(ctx->evidence) > 0)) { - cb_retcode = CL_VIRUS; + /* + * Determine if there was an alert for this layer (or its children). + */ + if ((evidence_num_alerts(ctx->evidence) > 0)) { + // TODO: Bug here. + // If there was a PUA match in a previous file in a zip, all subsequent files will + // think they have a match. + // In allmatch mode, this affects strong sigs too, not just PUA sigs. + // The only way to solve this is to keep track of the # of alerts for each layer, + // including only children layers and propagating the evidence up to the parent layer + // only at the end, after the cache_add. + verdict_at_this_level = CL_VIRUS; } else { - cb_retcode = ret; + verdict_at_this_level = ret; } - cli_dbgmsg("cli_magic_scan_desc: returning %d %s\n", ret, __AT__); + /* + * Run the post-scan callback (if one exists) and provide the verdict for this layer. + */ + cli_dbgmsg("cli_magic_scan: returning %d %s\n", ret, __AT__); if (ctx->engine->cb_post_scan) { - cl_error_t callbacK_ret; + cl_error_t callback_ret; const char *virusname = NULL; - if (cb_retcode == CL_VIRUS) + // Get the last signature that matched (if any). + if (verdict_at_this_level == CL_VIRUS) { virusname = cli_get_last_virus(ctx); + } perf_start(ctx, PERFT_POSTCB); - callbacK_ret = ctx->engine->cb_post_scan(fmap_fd(ctx->fmap), cb_retcode, virusname, ctx->cb_ctx); + callback_ret = ctx->engine->cb_post_scan(fmap_fd(ctx->fmap), verdict_at_this_level, virusname, ctx->cb_ctx); perf_stop(ctx, PERFT_POSTCB); - switch (callbacK_ret) { + switch (callback_ret) { case CL_BREAK: - cli_dbgmsg("cli_magic_scan_desc: file allowed by post_scan callback\n"); + cli_dbgmsg("cli_magic_scan: file allowed by post_scan callback\n"); ret = CL_CLEAN; break; case CL_VIRUS: - cli_dbgmsg("cli_magic_scan_desc: file blocked by post_scan callback\n"); - cli_append_virus(ctx, "Detected.By.Callback"); - if (ret != CL_VIRUS) { - ret = cli_check_fp(ctx, NULL); + cli_dbgmsg("cli_magic_scan: file blocked by post_scan callback\n"); + callback_ret = cli_append_virus(ctx, "Detected.By.Callback"); + if (callback_ret == CL_VIRUS) { + ret = CL_VIRUS; } break; case CL_CLEAN: break; default: - cli_warnmsg("cli_magic_scan_desc: ignoring bad return code from post_scan callback\n"); + ret = CL_CLEAN; + cli_warnmsg("cli_magic_scan: ignoring bad return code from post_scan callback\n"); } } - if (cb_retcode == CL_CLEAN && cache_clean) { + /* + * If the verdict for this layer is "clean", we can cache it. + */ + if (verdict_at_this_level == CL_CLEAN) { + // clean_cache_add() will check the fmap->dont_cache_flag, + // so this may not actually cache if we exceeded limits earlier. perf_start(ctx, PERFT_CACHE); clean_cache_add(hash, hashed_size, ctx); perf_stop(ctx, PERFT_CACHE); } - if (ret == CL_VIRUS && SCAN_ALLMATCHES) { - ret = CL_CLEAN; - } - early_ret: if ((ctx->engine->keeptmp) && (NULL != old_temp_path)) { @@ -5138,7 +5091,7 @@ cl_error_t cli_magic_scan_desc_type(int desc, const char *filepath, cli_ctx *ctx cli_dbgmsg("in cli_magic_scan_desc_type (recursion_level: %u/%u)\n", ctx->recursion_level, ctx->engine->max_recursion_level); if (FSTAT(desc, &sb) == -1) { - cli_errmsg("cli_magic_scan: Can't fstat descriptor %d\n", desc); + cli_errmsg("cli_magic_scan_desc_type: Can't fstat descriptor %d\n", desc); status = CL_ESTAT; cli_dbgmsg("cli_magic_scan_desc_type: returning %d %s (no post, no cache)\n", status, __AT__); @@ -5372,11 +5325,14 @@ cl_error_t cli_magic_scan_buff(const void *buffer, size_t length, cli_ctx *ctx, */ static cl_error_t scan_common(cl_fmap_t *map, const char *filepath, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, struct cl_scan_options *scanoptions, void *context) { - cl_error_t status; + cl_error_t status = CL_SUCCESS; + cl_error_t ret; cl_error_t verdict = CL_CLEAN; cli_ctx ctx = {0}; + bool logg_initalized = false; + char *target_basename = NULL; char *new_temp_prefix = NULL; size_t new_temp_prefix_len; @@ -5506,6 +5462,7 @@ static cl_error_t scan_common(cl_fmap_t *map, const char *filepath, const char * } cli_logg_setup(&ctx); + logg_initalized = true; /* We have a limit of around 2GB (INT_MAX - 2). Enforce it here. */ /* TODO: Large file support is large-ly untested. Remove this restriction @@ -5524,13 +5481,15 @@ static cl_error_t scan_common(cl_fmap_t *map, const char *filepath, const char * status = cli_magic_scan(&ctx, CL_TYPE_ANY); - // Set the output pointer to the "latest" alert signature name. - *virname = cli_get_last_virus_str(&ctx); - + // If any alerts occurred, set the output pointer to the "latest" alert signature name. if (0 < evidence_num_alerts(ctx.evidence)) { - verdict = CL_VIRUS; + *virname = cli_get_last_virus_str(&ctx); + verdict = CL_VIRUS; } + /* + * Report PUA alerts here. + */ num_potentially_unwanted_indicators = evidence_num_indicators_type( ctx.evidence, IndicatorType_PotentiallyUnwanted); @@ -5599,62 +5558,72 @@ static cl_error_t scan_common(cl_fmap_t *map, const char *filepath, const char * if (NULL == jstring) { cli_errmsg("scan_common: no memory for json serialization.\n"); status = CL_EMEM; - } else { - int ret = CL_SUCCESS; + goto done; + } + + cli_dbgmsg("%s\n", jstring); + + if (status != CL_VIRUS) { + /* + * Run bytecode preclass hook. + */ struct cli_matcher *iroot = ctx.engine->root[13]; - cli_dbgmsg("%s\n", jstring); - if ((status != CL_VIRUS) || (ctx.options->general & CL_SCAN_GENERAL_ALLMATCHES)) { - /* run bytecode preclass hook; generate fmap if needed for running hook */ - struct cli_bc_ctx *bc_ctx = cli_bytecode_context_alloc(); - if (!bc_ctx) { - cli_errmsg("scan_common: can't allocate memory for bc_ctx\n"); - status = CL_EMEM; - } else { - cli_bytecode_context_setctx(bc_ctx, &ctx); - status = cli_bytecode_runhook(&ctx, ctx.engine, bc_ctx, BC_PRECLASS, map); - cli_bytecode_context_destroy(bc_ctx); - } + struct cli_bc_ctx *bc_ctx = cli_bytecode_context_alloc(); + if (!bc_ctx) { + cli_errmsg("scan_common: can't allocate memory for bc_ctx\n"); + status = CL_EMEM; + } else { + cli_bytecode_context_setctx(bc_ctx, &ctx); + status = cli_bytecode_runhook(&ctx, ctx.engine, bc_ctx, BC_PRECLASS, map); + cli_bytecode_context_destroy(bc_ctx); + } - /* backwards compatibility: scan the json string unless a virus was detected */ - if (status != CL_VIRUS && (iroot->ac_lsigs || iroot->ac_patterns + /* backwards compatibility: scan the json string unless a virus was detected */ + if (status != CL_VIRUS && (iroot->ac_lsigs || iroot->ac_patterns #ifdef HAVE_PCRE - || iroot->pcre_metas + || iroot->pcre_metas #endif // HAVE_PCRE - )) { - cli_dbgmsg("scan_common: running deprecated preclass bytecodes for target type 13\n"); - ctx.options->general &= ~CL_SCAN_GENERAL_COLLECT_METADATA; - status = cli_magic_scan_buff(jstring, strlen(jstring), &ctx, NULL, LAYER_ATTRIBUTES_NONE); - } - } - - /* Invoke file props callback */ - if (ctx.engine->cb_file_props != NULL) { - ret = ctx.engine->cb_file_props(jstring, status, ctx.cb_ctx); - if (ret != CL_SUCCESS) - status = ret; - } - - /* keeptmp file processing for file properties json string */ - if (ctx.engine->keeptmp) { - int fd = -1; - char *tmpname = NULL; - - if ((ret = cli_newfilepathfd(ctx.sub_tmpdir, "metadata.json", &tmpname, &fd)) != CL_SUCCESS) { - cli_dbgmsg("scan_common: Can't create json properties file, ret = %i.\n", ret); - } else { - if (cli_writen(fd, jstring, strlen(jstring)) == (size_t)-1) - cli_dbgmsg("scan_common: cli_writen error writing json properties file.\n"); - else - cli_dbgmsg("json written to: %s\n", tmpname); - } - if (fd != -1) - close(fd); - if (NULL != tmpname) - free(tmpname); + )) { + cli_dbgmsg("scan_common: running deprecated preclass bytecodes for target type 13\n"); + ctx.options->general &= ~CL_SCAN_GENERAL_COLLECT_METADATA; + status = cli_magic_scan_buff(jstring, strlen(jstring), &ctx, NULL, LAYER_ATTRIBUTES_NONE); + } + } + + /* + * Invoke file props callback. + */ + if (ctx.engine->cb_file_props != NULL) { + ret = ctx.engine->cb_file_props(jstring, status, ctx.cb_ctx); + if (ret != CL_SUCCESS) { + status = ret; + } + } + + /* + * Write the file properties metadata JSON to metadata.json if keeptmp is enabled. + */ + if (ctx.engine->keeptmp) { + int fd = -1; + char *tmpname = NULL; + + if ((ret = cli_newfilepathfd(ctx.sub_tmpdir, "metadata.json", &tmpname, &fd)) != CL_SUCCESS) { + cli_dbgmsg("scan_common: Can't create json properties file, ret = %i.\n", ret); + } else { + if ((size_t)-1 == cli_writen(fd, jstring, strlen(jstring))) { + cli_dbgmsg("scan_common: cli_writen error writing json properties file.\n"); + } else { + cli_dbgmsg("json written to: %s\n", tmpname); + } + } + if (fd != -1) { + close(fd); + } + if (NULL != tmpname) { + free(tmpname); } } - cli_json_delobj(ctx.properties); /* frees all json memory */ } #endif // HAVE_JSON @@ -5664,9 +5633,15 @@ static cl_error_t scan_common(cl_fmap_t *map, const char *filepath, const char * status = verdict; } - cli_logg_unsetup(); - done: + if (logg_initalized) { + cli_logg_unsetup(); + } + + if (NULL != ctx.properties) { + cli_json_delobj(ctx.properties); + } + if (NULL != ctx.sub_tmpdir) { if (!ctx.engine->keeptmp) { (void)cli_rmdirs(ctx.sub_tmpdir); diff --git a/libclamav/sis.c b/libclamav/sis.c index 11a9c46b0..650a9ae4d 100644 --- a/libclamav/sis.c +++ b/libclamav/sis.c @@ -517,8 +517,8 @@ static cl_error_t real_scansis(cli_ctx *ctx, const char *tmpd) FREE(decomp); - if (CL_VIRUS == cli_magic_scan_desc(fd, ofn, ctx, original_filepath, LAYER_ATTRIBUTES_NONE)) { - status = CL_VIRUS; + status = cli_magic_scan_desc(fd, ofn, ctx, original_filepath, LAYER_ATTRIBUTES_NONE); + if (CL_SUCCESS != status) { goto done; } @@ -713,6 +713,8 @@ static inline void seeknext(struct SISTREAM *s) static cl_error_t real_scansis9x(cli_ctx *ctx, const char *tmpd) { + cl_error_t ret; + struct SISTREAM stream; struct SISTREAM *s = &stream; uint32_t field, optst[] = {T_CONTROLLERCHECKSUM, T_DATACHECKSUM, T_COMPRESSED}; @@ -840,9 +842,10 @@ static cl_error_t real_scansis9x(cli_ctx *ctx, const char *tmpd) break; } free(dst); - if (cli_magic_scan_desc(fd, tempf, ctx, NULL, LAYER_ATTRIBUTES_NONE) == CL_VIRUS) { + ret = cli_magic_scan_desc(fd, tempf, ctx, NULL, LAYER_ATTRIBUTES_NONE); + if (CL_SUCCESS != ret) { close(fd); - return CL_VIRUS; + return ret; } close(fd); break; diff --git a/libclamav/swf.c b/libclamav/swf.c index 404092cda..b6bd133dc 100644 --- a/libclamav/swf.c +++ b/libclamav/swf.c @@ -291,7 +291,7 @@ static cl_error_t scanzws(cli_ctx *ctx, struct swf_file_hdr *hdr) ret = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE); close(fd); - if (!(ctx->engine->keeptmp)) { + if (!ctx->engine->keeptmp) { if (cli_unlink(tmpname)) { free(tmpname); return CL_EUNLINK; diff --git a/libclamav/xdp.c b/libclamav/xdp.c index 6536d9ce6..f0e2fdde2 100644 --- a/libclamav/xdp.c +++ b/libclamav/xdp.c @@ -161,7 +161,7 @@ cl_error_t cli_scanxdp(cli_ctx *ctx) rc = cli_magic_scan_buff(decoded, decodedlen, ctx, NULL, LAYER_ATTRIBUTES_NONE); free(decoded); - if (rc != CL_SUCCESS || rc == CL_BREAK) { + if (rc != CL_SUCCESS) { xmlFree((void *)value); break; } diff --git a/libfreshclam/libfreshclam_internal.c b/libfreshclam/libfreshclam_internal.c index bc687b5e2..8af13feb3 100644 --- a/libfreshclam/libfreshclam_internal.c +++ b/libfreshclam/libfreshclam_internal.c @@ -2120,7 +2120,7 @@ static fc_error_t check_for_new_database_version( &remotever, &remotename); switch (ret) { - case FC_SUCCESS: { + case FC_SUCCESS: if (0 == localver) { logg(LOGG_INFO, "%s database available for download (remote version: %d)\n", database, remotever); @@ -2131,8 +2131,8 @@ static fc_error_t check_for_new_database_version( break; } /* fall-through */ - } - case FC_UPTODATE: { + + case FC_UPTODATE: if (NULL == local_database) { logg(LOGG_ERROR, "check_for_new_database_version: server claims we're up-to-date, but we don't have a local database!\n"); status = FC_EFAILEDGET; @@ -2149,18 +2149,17 @@ static fc_error_t check_for_new_database_version( We know it will be the same as the local version though. */ remotever = localver; break; - } - case FC_EFORBIDDEN: { + + case FC_EFORBIDDEN: /* We tried to look up the version using HTTP and were actively blocked. */ logg(LOGG_ERROR, "check_for_new_database_version: Blocked from using server %s.\n", server); status = FC_EFORBIDDEN; goto done; - } - default: { + + default: logg(LOGG_ERROR, "check_for_new_database_version: Failed to find %s database using server %s.\n", database, server); status = FC_EFAILEDGET; goto done; - } } *remoteVersion = remotever; diff --git a/unit_tests/check_clamav.c b/unit_tests/check_clamav.c index 74cc8e5b1..045591ed6 100644 --- a/unit_tests/check_clamav.c +++ b/unit_tests/check_clamav.c @@ -194,14 +194,14 @@ END_TEST static int get_test_file(int i, char *file, unsigned fsize, unsigned long *size); static struct cl_engine *g_engine; -/* int cl_scandesc(int desc, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, struct cl_scan_options* options) */ +/* cl_error_t cl_scandesc(int desc, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, struct cl_scan_options* options) */ START_TEST(test_cl_scandesc) { const char *virname = NULL; char file[256]; unsigned long size; unsigned long int scanned = 0; - int ret; + cl_error_t ret; struct cl_scan_options options; memset(&options, 0, sizeof(struct cl_scan_options)); diff --git a/unit_tests/check_str.c b/unit_tests/check_str.c index 611b19820..501039c44 100644 --- a/unit_tests/check_str.c +++ b/unit_tests/check_str.c @@ -73,7 +73,7 @@ START_TEST(test_unescape_hex) free(str); str = cli_unescape("%00"); - ck_assert_msg(str && !strcmp(str, "\x1"), "cli_unescape %00"); + ck_assert_msg(str && !strcmp(str, "\x1"), "cli_unescape 00"); free(str); } END_TEST