diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b1774e671..08c31690af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -383,8 +383,6 @@ elseif(APPLE) ) if(${MARKDOWN_MODULE_EXIT_CODE} EQUAL 0) # The markdown module is installed, we can do the conversion. - set(PythonTest_COMMAND "${Python3_EXECUTABLE};-m;markdown;-v") - execute_process( COMMAND echo "" OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/prefix.html) @@ -894,6 +892,7 @@ else() extract_valid_cxx_flags(WARNCXXFLAGS -Wall -Wformat-security + -Wno-comment # Disabled because LLVM's CFG.h has a warning about a multiline comment because of ascii-art of a graph with a `\` in it. ) endif() diff --git a/clambc/bcrun.c b/clambc/bcrun.c index 669df0a93c..4d7d55a8c9 100644 --- a/clambc/bcrun.c +++ b/clambc/bcrun.c @@ -34,6 +34,7 @@ #include "others.h" #include "bytecode.h" #include "bytecode_priv.h" +#include "clamav_rust.h" // common #include "optparser.h" @@ -393,8 +394,9 @@ 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; cctx.recursion_stack = cli_calloc(sizeof(recursion_level_t), cctx.recursion_stack_size); @@ -478,6 +480,7 @@ int main(int argc, char *argv[]) funmap(map); cl_engine_free(engine); free(cctx.recursion_stack); + evidence_free(cctx.evidence); } cli_bytecode_destroy(bc); cli_bytecode_done(&bcs); diff --git a/common/cert_util.c b/common/cert_util.c index 78f8d657ca..567863abbe 100644 --- a/common/cert_util.c +++ b/common/cert_util.c @@ -85,7 +85,7 @@ static cl_error_t _x509_to_pem(X509 *cert, ret = CL_SUCCESS; done: - return (0); + return ret; } /** @@ -125,7 +125,7 @@ static cl_error_t _x509_to_pem_append(X509 *ca_cert, current_len = *total_buf_len; - if (_x509_to_pem(ca_cert, &pem_data, &pem_data_len) != 0) { + if (CL_SUCCESS != _x509_to_pem(ca_cert, &pem_data, &pem_data_len)) { mprintf(LOGG_ERROR, "Failed to convert x509 certificate to PEM\n"); goto done; } diff --git a/libclamav/7z_iface.c b/libclamav/7z_iface.c index d82134f785..4812c318bf 100644 --- a/libclamav/7z_iface.c +++ b/libclamav/7z_iface.c @@ -90,7 +90,6 @@ int cli_7unz(cli_ctx *ctx, size_t offset) int namelen = UTFBUFSZ; cl_error_t found = CL_CLEAN; Int64 begin_of_archive = offset; - UInt32 viruses_found = 0; /* Replacement for FileInStream_CreateVTable(&archiveStream); */ @@ -111,7 +110,7 @@ int cli_7unz(cli_ctx *ctx, size_t offset) res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp); if (res == SZ_ERROR_ENCRYPTED && SCAN_HEURISTIC_ENCRYPTED_ARCHIVE) { cli_dbgmsg("cli_7unz: Encrypted header found in archive.\n"); - found = cli_append_virus(ctx, "Heuristics.Encrypted.7Zip"); + found = cli_append_potentially_unwanted(ctx, "Heuristics.Encrypted.7Zip"); } else if (res == SZ_OK) { UInt32 i, blockIndex = 0xFFFFFFFF; Byte *outBuffer = 0; @@ -127,12 +126,14 @@ int cli_7unz(cli_ctx *ctx, size_t offset) size_t j; int newnamelen, fd; + // abort if we would exceed max files or max scan time. if ((found = cli_checklimits("7unz", ctx, 0, 0, 0))) break; if (f->IsDir) continue; + // skip this file if we would exceed max file size or max scan size. (we already checked for the max files and max scan time) if (cli_checklimits("7unz", ctx, f->Size, 0, 0)) continue; @@ -164,21 +165,15 @@ int cli_7unz(cli_ctx *ctx, size_t offset) encrypted = 1; if (SCAN_HEURISTIC_ENCRYPTED_ARCHIVE) { cli_dbgmsg("cli_7unz: Encrypted files found in archive.\n"); - found = cli_append_virus(ctx, "Heuristics.Encrypted.7Zip"); - if (found != CL_CLEAN) { - if (found == CL_VIRUS) { - if (SCAN_ALLMATCHES) - viruses_found++; - } else - break; + found = cli_append_potentially_unwanted(ctx, "Heuristics.Encrypted.7Zip"); + if (found != CL_SUCCESS) { + break; } } } - if (cli_matchmeta(ctx, name, 0, f->Size, encrypted, i, f->CrcDefined ? f->Crc : 0, NULL)) { + if (CL_VIRUS == cli_matchmeta(ctx, name, 0, f->Size, encrypted, i, f->CrcDefined ? f->Crc : 0, NULL)) { found = CL_VIRUS; - viruses_found++; - if (!SCAN_ALLMATCHES) - break; + break; } if (res != SZ_OK) cli_dbgmsg("cli_unz: extraction failed with %d\n", res); @@ -189,18 +184,19 @@ int cli_7unz(cli_ctx *ctx, size_t offset) break; cli_dbgmsg("cli_7unz: Saving to %s\n", tmp_name); - if (cli_writen(fd, outBuffer + offset, outSizeProcessed) != outSizeProcessed) + if (cli_writen(fd, outBuffer + offset, outSizeProcessed) != outSizeProcessed) { found = CL_EWRITE; - else if (CL_VIRUS == (found = cli_magic_scan_desc(fd, tmp_name, ctx, name, LAYER_ATTRIBUTES_NONE))) - viruses_found++; + } + + found = cli_magic_scan_desc(fd, tmp_name, ctx, name, LAYER_ATTRIBUTES_NONE); + close(fd); if (!ctx->engine->keeptmp && cli_unlink(tmp_name)) found = CL_EUNLINK; free(tmp_name); - if (found != CL_CLEAN) - if (!(SCAN_ALLMATCHES && found == CL_VIRUS)) - break; + if (found != CL_SUCCESS) + break; } } IAlloc_Free(&allocImp, outBuffer); @@ -222,7 +218,5 @@ int cli_7unz(cli_ctx *ctx, size_t offset) else cli_dbgmsg("cli_7unz: error %d\n", res); - if (SCAN_ALLMATCHES && viruses_found) - return CL_VIRUS; return found; } diff --git a/libclamav/CMakeLists.txt b/libclamav/CMakeLists.txt index 4655aa4a9f..7dfc26c95e 100644 --- a/libclamav/CMakeLists.txt +++ b/libclamav/CMakeLists.txt @@ -319,7 +319,7 @@ set(LIBCLAMAV_SOURCES matcher-ac.c matcher-ac.h matcher-bm.c matcher-bm.h matcher-byte-comp.c matcher-byte-comp.h - matcher-hash.c matcher-hash.h + matcher-hash.c matcher-hash.h matcher-hash-types.h matcher-pcre.c matcher-pcre.h matcher.c matcher.h regex_pcre.c regex_pcre.h diff --git a/libclamav/apm.c b/libclamav/apm.c index 9297fc3c76..327aca7bc4 100644 --- a/libclamav/apm.c +++ b/libclamav/apm.c @@ -45,13 +45,14 @@ #define apm_parsemsg(...) ; #endif -static int apm_partition_intersection(cli_ctx *ctx, struct apm_partition_info *aptable, size_t sectorsize, int old_school); +static cl_error_t apm_partition_intersection(cli_ctx *ctx, struct apm_partition_info *aptable, size_t sectorsize, bool old_school); -int cli_scanapm(cli_ctx *ctx) +cl_error_t cli_scanapm(cli_ctx *ctx) { + cl_error_t status = CL_SUCCESS; struct apm_driver_desc_map ddm; struct apm_partition_info aptable, apentry; - int ret = CL_CLEAN, detection = CL_CLEAN, old_school = 0; + bool old_school = false; size_t sectorsize, maplen, partsize; size_t pos = 0, partoff = 0; unsigned i; @@ -59,13 +60,15 @@ int cli_scanapm(cli_ctx *ctx) if (!ctx || !ctx->fmap) { cli_errmsg("cli_scanapm: Invalid context\n"); - return CL_ENULLARG; + status = CL_ENULLARG; + goto done; } /* read driver description map at sector 0 */ if (fmap_readn(ctx->fmap, &ddm, pos, sizeof(ddm)) != sizeof(ddm)) { cli_dbgmsg("cli_scanapm: Invalid Apple driver description map\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* convert driver description map big-endian to host */ @@ -76,7 +79,8 @@ int cli_scanapm(cli_ctx *ctx) /* check DDM signature */ if (ddm.signature != DDM_SIGNATURE) { cli_dbgmsg("cli_scanapm: Apple driver description map signature mismatch\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* sector size is determined by the ddm */ @@ -87,20 +91,22 @@ int cli_scanapm(cli_ctx *ctx) if ((ddm.blockSize * ddm.blockCount) != maplen) { cli_dbgmsg("cli_scanapm: File described %u size does not match %lu actual size\n", (ddm.blockSize * ddm.blockCount), (unsigned long)maplen); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* check for old-school partition map */ if (sectorsize == 2048) { if (fmap_readn(ctx->fmap, &aptable, APM_FALLBACK_SECTOR_SIZE, sizeof(aptable)) != sizeof(aptable)) { cli_dbgmsg("cli_scanapm: Invalid Apple partition entry\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } aptable.signature = be16_to_host(aptable.signature); if (aptable.signature == APM_SIGNATURE) { sectorsize = APM_FALLBACK_SECTOR_SIZE; - old_school = 1; + old_school = true; } } @@ -109,7 +115,8 @@ int cli_scanapm(cli_ctx *ctx) if (fmap_readn(ctx->fmap, &aptable, pos, sizeof(aptable)) != sizeof(aptable)) { cli_dbgmsg("cli_scanapm: Invalid Apple partition table\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* convert partition table big endian to host */ @@ -121,7 +128,8 @@ int cli_scanapm(cli_ctx *ctx) /* check the partition entry signature */ if (aptable.signature != APM_SIGNATURE) { cli_dbgmsg("cli_scanapm: Apple partition table signature mismatch\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* check if partition table partition */ @@ -129,17 +137,15 @@ int cli_scanapm(cli_ctx *ctx) strncmp((char *)aptable.type, "Apple_partition_map", 32) && strncmp((char *)aptable.type, "Apple_patition_map", 32)) { cli_dbgmsg("cli_scanapm: Initial Apple Partition Map partition is not detected\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* check that the partition table fits in the space specified - HEURISTICS */ if (SCAN_HEURISTIC_PARTITION_INTXN && (ctx->dconf->other & OTHER_CONF_PRTNINTXN)) { - ret = apm_partition_intersection(ctx, &aptable, sectorsize, old_school); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = apm_partition_intersection(ctx, &aptable, sectorsize, old_school); + if (status != CL_SUCCESS) { + goto done; } } @@ -167,7 +173,8 @@ int cli_scanapm(cli_ctx *ctx) pos = i * sectorsize; if (fmap_readn(ctx->fmap, &apentry, pos, sizeof(apentry)) != sizeof(apentry)) { cli_dbgmsg("cli_scanapm: Invalid Apple partition entry\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* convert partition entry big endian to host */ @@ -180,7 +187,8 @@ int cli_scanapm(cli_ctx *ctx) /* check the partition entry signature */ if (aptable.signature != APM_SIGNATURE) { cli_dbgmsg("cli_scanapm: Apple partition entry signature mismatch\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* check if a out-of-order partition map */ @@ -223,13 +231,9 @@ int cli_scanapm(cli_ctx *ctx) apentry.pBlockStart, apentry.pBlockCount, partoff, partsize); /* send the partition to cli_magic_scan_nested_fmap_type */ - ret = cli_magic_scan_nested_fmap_type(ctx->fmap, partoff, partsize, ctx, CL_TYPE_PART_ANY, - (const char *)apentry.name, LAYER_ATTRIBUTES_NONE); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = cli_magic_scan_nested_fmap_type(ctx->fmap, partoff, partsize, ctx, CL_TYPE_PART_ANY, (const char *)apentry.name, LAYER_ATTRIBUTES_NONE); + if (status != CL_SUCCESS) { + goto done; } } @@ -237,18 +241,20 @@ int cli_scanapm(cli_ctx *ctx) cli_dbgmsg("cli_scanapm: max partitions reached\n"); } - return detection; +done: + + return status; } -static int apm_partition_intersection(cli_ctx *ctx, struct apm_partition_info *aptable, size_t sectorsize, int old_school) +static cl_error_t apm_partition_intersection(cli_ctx *ctx, struct apm_partition_info *aptable, size_t sectorsize, bool old_school) { + cl_error_t status = CL_SUCCESS; + cl_error_t ret; partition_intersection_list_t prtncheck; struct apm_partition_info apentry; unsigned i, pitxn; - int ret = CL_CLEAN, tmp = CL_CLEAN; size_t pos; uint32_t max_prtns = 0; - int virus_found = 0; partition_intersection_list_init(&prtncheck); @@ -265,7 +271,8 @@ static int apm_partition_intersection(cli_ctx *ctx, struct apm_partition_info *a if (fmap_readn(ctx->fmap, &apentry, pos, sizeof(apentry)) != sizeof(apentry)) { cli_dbgmsg("cli_scanapm: Invalid Apple partition entry\n"); partition_intersection_list_free(&prtncheck); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* convert necessary info big endian to host */ @@ -284,33 +291,31 @@ static int apm_partition_intersection(cli_ctx *ctx, struct apm_partition_info *a } } - tmp = partition_intersection_list_check(&prtncheck, &pitxn, apentry.pBlockStart, apentry.pBlockCount); - if (tmp != CL_CLEAN) { - if (tmp == CL_VIRUS) { + ret = partition_intersection_list_check(&prtncheck, &pitxn, apentry.pBlockStart, apentry.pBlockCount); + if (ret != CL_CLEAN) { + if (ret == CL_VIRUS) { apm_parsemsg("Name: %s\n", (char *)aptable.name); apm_parsemsg("Type: %s\n", (char *)aptable.type); cli_dbgmsg("cli_scanapm: detected intersection with partitions " "[%u, %u]\n", pitxn, i); - ret = cli_append_virus(ctx, PRTN_INTXN_DETECTION); - if (ret == CL_VIRUS) - virus_found = 1; - if (SCAN_ALLMATCHES || ret == CL_CLEAN) - tmp = 0; - else - goto leave; + status = cli_append_potentially_unwanted(ctx, "Heuristics.APMPartitionIntersection"); + if (status != CL_SUCCESS) { + goto done; + } } else { - ret = tmp; - goto leave; + status = ret; + goto done; } } + + /* increment the offsets to next partition entry */ pos += sectorsize; } -leave: +done: partition_intersection_list_free(&prtncheck); - if (virus_found) - return CL_VIRUS; - return ret; + + return status; } diff --git a/libclamav/apm.h b/libclamav/apm.h index 113eef3081..248df275bf 100644 --- a/libclamav/apm.h +++ b/libclamav/apm.h @@ -25,7 +25,7 @@ #include "clamav-config.h" #endif -#include "clamav-types.h" +#include "clamav.h" #include "others.h" #define APM_FALLBACK_SECTOR_SIZE 512 @@ -112,6 +112,6 @@ struct apm_partition_info { #pragma pack #endif -int cli_scanapm(cli_ctx *ctx); +cl_error_t cli_scanapm(cli_ctx *ctx); #endif diff --git a/libclamav/asn1.c b/libclamav/asn1.c index d49a0229c0..1eec3b0ed0 100644 --- a/libclamav/asn1.c +++ b/libclamav/asn1.c @@ -1620,7 +1620,7 @@ static cl_error_t asn1_parse_mscat(struct cl_engine *engine, fmap_t *map, size_t cli_dbgmsg("asn1_parse_mscat: Found Authenticode certificate blocked by %s\n", crt->name ? crt->name : "(unnamed CRB rule)"); if (NULL != ctx) { ret = cli_append_virus(ctx, crt->name ? crt->name : "(unnamed CRB rule)"); - if ((ret == CL_VIRUS) && !SCAN_ALLMATCHES) { + if (ret == CL_VIRUS) { crtmgr_free(&newcerts); goto finish; } @@ -1664,10 +1664,6 @@ static cl_error_t asn1_parse_mscat(struct cl_engine *engine, fmap_t *map, size_t break; } - /* In the SCAN_ALLMATCHES case, we'd get here with - * ret == CL_VIRUS if a match occurred but we wanted - * to keep looping to look for other matches. In that - * case, bail here. */ if (CL_VIRUS == ret) { crtmgr_free(&newcerts); break; @@ -2213,7 +2209,7 @@ int asn1_load_mscat(fmap_t *map, struct cl_engine *engine) struct cli_asn1 tagval1, tagval2, tagval3; int hashed_obj_type; cli_crt_hashtype hashtype; - enum CLI_HASH_TYPE hm_hashtype; + cli_hash_type_t hm_hashtype; unsigned int hashsize; if (asn1_expect_objtype(map, tag.content, &tag.size, &tagval1, ASN1_TYPE_SEQUENCE)) diff --git a/libclamav/autoit.c b/libclamav/autoit.c index ad34f0e196..4c1b77e581 100644 --- a/libclamav/autoit.c +++ b/libclamav/autoit.c @@ -638,39 +638,49 @@ static uint32_t getbits(struct UNP *UNP, uint32_t size) autoit3 EA05 handler *********************/ -static int ea05(cli_ctx *ctx, const uint8_t *base, char *tmpd) +static cl_error_t ea05(cli_ctx *ctx, const uint8_t *base, char *tmpd) { - uint8_t b[300], comp; + cl_error_t status = CL_SUCCESS; + cl_error_t ret; + uint8_t b[300]; + uint8_t comp; uint32_t s, m4sum = 0; - int i, ret, det = 0; - unsigned int files = 0; - char tempfile[1024]; - struct UNP UNP; - fmap_t *map = ctx->fmap; - - if (!fmap_need_ptr_once(map, base, 16)) - return CL_CLEAN; + int i; + unsigned int files = 0; + char tempfile[1024] = {0}; + int tempfd = -1; + struct UNP UNP = {0}; + fmap_t *map = ctx->fmap; + + if (!fmap_need_ptr_once(map, base, 16)) { + goto done; + } for (i = 0; i < 16; i++) m4sum += *base++; - while ((ret = cli_checklimits("autoit", ctx, 0, 0, 0)) == CL_CLEAN) { - if (!fmap_need_ptr_once(map, base, 8)) - return (det ? CL_VIRUS : CL_CLEAN); + // While we have not exceeded the max files limit or the max time limit... + while (CL_SUCCESS == (status = cli_checklimits("autoit", ctx, 0, 0, 0))) { + if (!fmap_need_ptr_once(map, base, 8)) { + goto done; + } /* MT_decrypt(buf,4,0x16fa); waste of time */ if ((uint32_t)cli_readint32(base) != 0xceb06dff) { cli_dbgmsg("autoit: no FILE magic found, extraction complete\n"); - return (det ? CL_VIRUS : CL_CLEAN); + goto done; } s = cli_readint32(base + 4) ^ 0x29bc; - if ((int32_t)s < 0) - return (det ? CL_VIRUS : CL_CLEAN); /* the original code wouldn't seek back here */ + if ((int32_t)s < 0) { + /* the original code wouldn't seek back here */ + goto done; + } base += 8; if (cli_debug_flag && s < sizeof(b)) { - if (!fmap_need_ptr_once(map, base, s)) - return (det ? CL_VIRUS : CL_CLEAN); + if (!fmap_need_ptr_once(map, base, s)) { + goto done; + } memcpy(b, base, s); MT_decrypt(b, s, s + 0xa25e); b[s] = '\0'; @@ -678,15 +688,20 @@ static int ea05(cli_ctx *ctx, const uint8_t *base, char *tmpd) } base += s; - if (!fmap_need_ptr_once(map, base, 4)) - return (det ? CL_VIRUS : CL_CLEAN); + if (!fmap_need_ptr_once(map, base, 4)) { + goto done; + } s = cli_readint32(base) ^ 0x29ac; - if ((int32_t)s < 0) - return (det ? CL_VIRUS : CL_CLEAN); /* the original code wouldn't seek back here */ + if ((int32_t)s < 0) { + /* the original code wouldn't seek back here */ + goto done; + } base += 4; if (cli_debug_flag && s < sizeof(b)) { - if (!fmap_need_ptr_once(map, base, s)) - return (det ? CL_VIRUS : CL_CLEAN); + if (!fmap_need_ptr_once(map, base, s)) { + goto done; + } + memcpy(b, base, s); MT_decrypt(b, s, s + 0xf25e); b[s] = '\0'; @@ -694,13 +709,15 @@ static int ea05(cli_ctx *ctx, const uint8_t *base, char *tmpd) } base += s; - if (!fmap_need_ptr_once(map, base, 13)) - return (det ? CL_VIRUS : CL_CLEAN); + if (!fmap_need_ptr_once(map, base, 13)) { + goto done; + } + comp = *base; UNP.csize = cli_readint32(base + 1) ^ 0x45aa; if ((int32_t)UNP.csize < 0) { cli_dbgmsg("autoit: bad file size - giving up\n"); - return (det ? CL_VIRUS : CL_CLEAN); + goto done; } if (!UNP.csize) { @@ -724,36 +741,42 @@ static int ea05(cli_ctx *ctx, const uint8_t *base, char *tmpd) continue; } - if (!(UNP.inputbuf = cli_malloc(UNP.csize))) - return CL_EMEM; + if (!(UNP.inputbuf = cli_malloc(UNP.csize))) { + status = CL_EMEM; + goto done; + } if (!fmap_need_ptr_once(map, base, UNP.csize)) { cli_dbgmsg("autoit: failed to read compressed stream. broken/truncated file?\n"); - free(UNP.inputbuf); - return (det ? CL_VIRUS : CL_CLEAN); + goto done; } + memcpy(UNP.inputbuf, base, UNP.csize); base += UNP.csize; MT_decrypt(UNP.inputbuf, UNP.csize, 0x22af + m4sum); if (comp == 1) { + /* + * File is compressed. Decompress! + */ cli_dbgmsg("autoit: file is compressed\n"); if (cli_readint32(UNP.inputbuf) != 0x35304145) { cli_dbgmsg("autoit: bad magic or unsupported version\n"); - free(UNP.inputbuf); continue; } - if (!(UNP.usize = be32_to_host(*(uint32_t *)(UNP.inputbuf + 4)))) + if (!(UNP.usize = be32_to_host(*(uint32_t *)(UNP.inputbuf + 4)))) { UNP.usize = UNP.csize; /* only a specifically crafted or badly corrupted sample should land here */ + } + if (cli_checklimits("autoit", ctx, UNP.usize, 0, 0) != CL_CLEAN) { - free(UNP.inputbuf); continue; } if (!(UNP.outputbuf = cli_malloc(UNP.usize))) { - free(UNP.inputbuf); - return CL_EMEM; + status = CL_EMEM; + goto done; } + cli_dbgmsg("autoit: uncompressed size again: %x\n", UNP.usize); UNP.cur_output = 0; @@ -806,6 +829,8 @@ static int ea05(cli_ctx *ctx, const uint8_t *base, char *tmpd) } free(UNP.inputbuf); + UNP.inputbuf = NULL; + /* Sometimes the autoit exe is in turn packed/lamed with a runtime compressor and similar shit. * However, since the autoit script doesn't compress a second time very well, chances are we're * still able to match the headers and unpack something (see sample 0811129) @@ -818,6 +843,9 @@ static int ea05(cli_ctx *ctx, const uint8_t *base, char *tmpd) UNP.usize = UNP.cur_output; } } else { + /* + * File is NOT compressed. + */ cli_dbgmsg("autoit: file is not compressed\n"); UNP.outputbuf = UNP.inputbuf; UNP.usize = UNP.csize; @@ -836,41 +864,62 @@ static int ea05(cli_ctx *ctx, const uint8_t *base, char *tmpd) snprintf(tempfile, 1023, "%s" PATHSEP "autoit.%.3u", tmpd, files); tempfile[1023] = '\0'; - if ((i = open(tempfile, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) < 0) { + + tempfd = open(tempfile, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR); + if (tempfd < 0) { cli_dbgmsg("autoit: Can't create file %s\n", tempfile); - free(UNP.outputbuf); - return CL_ECREAT; + status = CL_ECREAT; + goto done; } - if (cli_writen(i, UNP.outputbuf, UNP.usize) != UNP.usize) { + + if (cli_writen(tempfd, UNP.outputbuf, UNP.usize) != UNP.usize) { cli_dbgmsg("autoit: cannot write %d bytes\n", UNP.usize); - close(i); - free(UNP.outputbuf); - return CL_EWRITE; + status = CL_EWRITE; + goto done; } + free(UNP.outputbuf); - if (ctx->engine->keeptmp) + UNP.outputbuf = NULL; + + if (ctx->engine->keeptmp) { cli_dbgmsg("autoit: file extracted to %s\n", tempfile); - else + } else { cli_dbgmsg("autoit: file successfully extracted\n"); - if (lseek(i, 0, SEEK_SET) == -1) { + } + + if (lseek(tempfd, 0, SEEK_SET) == -1) { cli_dbgmsg("autoit: call to lseek() has failed\n"); - close(i); - return CL_ESEEK; + status = CL_ESEEK; + goto done; } - if (CL_VIRUS == cli_magic_scan_desc(i, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE)) { - if (!SCAN_ALLMATCHES) { - close(i); - if (!ctx->engine->keeptmp) - if (cli_unlink(tempfile)) return CL_EUNLINK; - return CL_VIRUS; - } - det = 1; + + ret = cli_magic_scan_desc(tempfd, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE); + if (CL_SUCCESS != ret) { + status = ret; + goto done; + } + + close(tempfd); + tempfd = -1; + if (!ctx->engine->keeptmp) { + (void)cli_unlink(tempfile); } - close(i); - if (!ctx->engine->keeptmp) - if (cli_unlink(tempfile)) return CL_EUNLINK; } - return (det ? CL_VIRUS : ret); + +done: + if (NULL != UNP.inputbuf) { + free(UNP.inputbuf); + } + if (NULL != UNP.outputbuf) { + free(UNP.outputbuf); + } + if (tempfd >= 0) { + close(tempfd); + if (!ctx->engine->keeptmp) { + (void)cli_unlink(tempfile); + } + } + return status; } /********************* @@ -964,9 +1013,10 @@ static void LAME_decrypt(uint8_t *cypher, uint32_t size, uint16_t seed) static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) { - uint8_t b[600], comp, script, *buf; + cl_error_t ret; + uint8_t b[600], comp, *buf; uint32_t s; - int i, ret, det = 0; + int i; unsigned int files = 0; char tempfile[1024]; const char prefixes[] = {'\0', '\0', '@', '$', '\0', '.', '"', '\0'}; @@ -981,60 +1031,81 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) /* buf+=0x10; */ base += 16; /* for now we just skip the garbage */ - while ((ret = cli_checklimits("cli_autoit", ctx, 0, 0, 0)) == CL_CLEAN) { - if (!fmap_need_ptr_once(map, base, 8)) - return (det ? CL_VIRUS : CL_CLEAN); + while (CL_SUCCESS == (ret = cli_checklimits("cli_autoit", ctx, 0, 0, 0))) { + bool script = false; + + if (!fmap_need_ptr_once(map, base, 8)) { + return CL_SUCCESS; + } + /* LAME_decrypt(buf, 4, 0x18ee); waste of time */ if (cli_readint32(base) != 0x52ca436b) { cli_dbgmsg("autoit: no FILE magic found, giving up (got 0x%08x)\n", cli_readint32(base)); - return (det ? CL_VIRUS : CL_CLEAN); + return CL_SUCCESS; } - script = 0; - s = cli_readint32(base + 4) ^ 0xadbc; - if ((int32_t)(s * 2) < 0) - return (det ? CL_VIRUS : CL_CLEAN); /* the original code wouldn't seek back here */ + if ((int32_t)(s * 2) < 0) { + return CL_SUCCESS; /* the original code wouldn't seek back here */ + } + base += 8; + if (s < sizeof(b) / 2) { - if (!fmap_need_ptr_once(map, base, s * 2)) - return (det ? CL_VIRUS : CL_CLEAN); + if (!fmap_need_ptr_once(map, base, s * 2)) { + return CL_SUCCESS; + } + memcpy(b, base, s * 2); LAME_decrypt(b, s * 2, s + 0xb33f); u2a(b, s * 2); cli_dbgmsg("autoit: magic string '%s'\n", b); - if (s == 19 && !memcmp(">>>AUTOIT SCRIPT<<<", b, 19)) - script = 1; + + if (s == 19 && !memcmp(">>>AUTOIT SCRIPT<<<", b, 19)) { + script = true; + } } else { cli_dbgmsg("autoit: magic string too long to print\n"); } + base += s * 2; - if (!fmap_need_ptr_once(map, base, 4)) - return (det ? CL_VIRUS : CL_CLEAN); + if (!fmap_need_ptr_once(map, base, 4)) { + return CL_SUCCESS; + } + s = cli_readint32(base) ^ 0xf820; - if ((int32_t)(s * 2) < 0) - return (det ? CL_VIRUS : CL_CLEAN); /* the original code wouldn't seek back here */ + if ((int32_t)(s * 2) < 0) { + return CL_SUCCESS; /* the original code wouldn't seek back here */ + } + base += 4; + if (cli_debug_flag && s < sizeof(b) / 2) { - if (!fmap_need_ptr_once(map, base, s * 2)) - return (det ? CL_VIRUS : CL_CLEAN); + if (!fmap_need_ptr_once(map, base, s * 2)) { + return CL_SUCCESS; + } + memcpy(b, base, s * 2); LAME_decrypt(b, s * 2, s + 0xf479); b[s * 2] = '\0'; b[s * 2 + 1] = '\0'; u2a(b, s * 2); + cli_dbgmsg("autoit: original filename '%s'\n", b); } + base += s * 2; - if (!fmap_need_ptr_once(map, base, 13)) - return (det ? CL_VIRUS : CL_CLEAN); + if (!fmap_need_ptr_once(map, base, 13)) { + return CL_SUCCESS; + } + comp = *base; UNP.csize = cli_readint32(base + 1) ^ 0x87bc; if ((int32_t)UNP.csize < 0) { cli_dbgmsg("autoit: bad file size - giving up\n"); - return (det ? CL_VIRUS : CL_CLEAN); + return CL_SUCCESS; } if (!UNP.csize) { @@ -1042,6 +1113,7 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) base += 13 + 16; continue; } + cli_dbgmsg("autoit: compressed size: %x\n", UNP.csize); cli_dbgmsg("autoit: advertised uncompressed size %x\n", cli_readint32(base + 5) ^ 0x87bc); cli_dbgmsg("autoit: ref chksum: %x\n", cli_readint32(base + 9) ^ 0xa685); @@ -1060,35 +1132,44 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) files++; - if (!(UNP.inputbuf = cli_malloc(UNP.csize))) + if (!(UNP.inputbuf = cli_malloc(UNP.csize))) { return CL_EMEM; + } + if (!fmap_need_ptr_once(map, base, UNP.csize)) { cli_dbgmsg("autoit: failed to read compressed stream. broken/truncated file?\n"); free(UNP.inputbuf); - return (det ? CL_VIRUS : CL_CLEAN); + return CL_SUCCESS; } + memcpy(UNP.inputbuf, base, UNP.csize); base += UNP.csize; + LAME_decrypt(UNP.inputbuf, UNP.csize, 0x2477 /* + m4sum (broken by design) */); if (comp == 1) { cli_dbgmsg("autoit: file is compressed\n"); + if (cli_readint32(UNP.inputbuf) != 0x36304145) { cli_dbgmsg("autoit: bad magic or unsupported version\n"); free(UNP.inputbuf); continue; } - if (!(UNP.usize = be32_to_host(*(uint32_t *)(UNP.inputbuf + 4)))) + if (!(UNP.usize = be32_to_host(*(uint32_t *)(UNP.inputbuf + 4)))) { UNP.usize = UNP.csize; /* only a specifically crafted or badly corrupted sample should land here */ + } + if (cli_checklimits("autoit", ctx, UNP.usize, 0, 0) != CL_CLEAN) { free(UNP.inputbuf); continue; } + if (!(UNP.outputbuf = cli_malloc(UNP.usize))) { free(UNP.inputbuf); return CL_EMEM; } + cli_dbgmsg("autoit: uncompressed size again: %x\n", UNP.usize); UNP.cur_output = 0; @@ -1131,6 +1212,7 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) UNP.error = 1; break; } + while (bs--) { UNP.outputbuf[UNP.cur_output] = UNP.outputbuf[UNP.cur_output - bb]; UNP.cur_output++; @@ -1166,10 +1248,12 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) free(UNP.outputbuf); return CL_EMEM; } + UNP.cur_output = 0; UNP.cur_input = 4; UNP.bits_avail = cli_readint32((char *)UNP.outputbuf); UNP.error = 0; + cli_dbgmsg("autoit: script has got %u lines\n", UNP.bits_avail); while (!UNP.error && UNP.bits_avail && UNP.cur_input < UNP.usize) { @@ -1184,13 +1268,16 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) cli_dbgmsg("autoit: too few bytes present - expected enough for a keyword ID\n"); break; } + keyword_id = cli_readint32((char *)&UNP.outputbuf[UNP.cur_input]); if (keyword_id >= (sizeof(autoit_keywords) / sizeof(autoit_keywords[0]))) { UNP.error = 1; cli_dbgmsg("autoit: unknown AutoIT keyword ID: 0x%x\n", keyword_id); break; } + UNP.cur_input += 4; + keyword_len = strlen(autoit_keywords[keyword_id]); if (UNP.cur_output + keyword_len + 2 >= UNP.csize) { uint8_t *newout; @@ -1201,11 +1288,13 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) } buf = newout; } + if (cli_debug_flag) { if (0 == memcmp(autoit_keywords[keyword_id], "UNKNOWN", MIN(strlen("UNKNOWN"), keyword_len))) { cli_dbgmsg("autoit: encountered use of unknown keyword ID: %s\n", autoit_keywords[keyword_id]); } } + snprintf((char *)&buf[UNP.cur_output], keyword_len + 2, "%s ", autoit_keywords[keyword_id]); UNP.cur_output += keyword_len + 1; break; @@ -1218,13 +1307,16 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) cli_dbgmsg("autoit: too few bytes present - expected enough for a function ID\n"); break; } + function_id = cli_readint32((char *)&UNP.outputbuf[UNP.cur_input]); if (function_id >= (sizeof(autoit_functions) / sizeof(autoit_functions[0]))) { UNP.error = 1; cli_dbgmsg("autoit: unknown AutoIT function ID: 0x%x\n", function_id); break; } + UNP.cur_input += 4; + function_len = strlen(autoit_functions[function_id]); if (UNP.cur_output + function_len + 2 >= UNP.csize) { uint8_t *newout; @@ -1235,11 +1327,13 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) } buf = newout; } + if (cli_debug_flag) { if (0 == memcmp(autoit_functions[function_id], "UNKNOWN", MIN(strlen("UNKNOWN"), function_len))) { cli_dbgmsg("autoit: encountered use of unknown function ID: %s\n", autoit_functions[function_id]); } } + snprintf((char *)&buf[UNP.cur_output], function_len + 2, "%s ", autoit_functions[function_id]); UNP.cur_output += function_len + 1; break; @@ -1250,6 +1344,7 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) cli_dbgmsg("autoit: not enough space for an int\n"); break; } + if (UNP.cur_output + 12 >= UNP.csize) { uint8_t *newout; UNP.csize += 512; @@ -1259,6 +1354,7 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) } buf = newout; } + snprintf((char *)&buf[UNP.cur_output], 12, "0x%08x ", cli_readint32((char *)&UNP.outputbuf[UNP.cur_input])); UNP.cur_output += 11; UNP.cur_input += 4; @@ -1272,6 +1368,7 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) cli_dbgmsg("autoit: not enough space for an int64\n"); break; } + if (UNP.cur_output + 20 >= UNP.csize) { uint8_t *newout; UNP.csize += 512; @@ -1281,6 +1378,7 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) } buf = newout; } + val = (uint64_t)cli_readint32((char *)&UNP.outputbuf[UNP.cur_input + 4]); val <<= 32; val += (uint64_t)cli_readint32((char *)&UNP.outputbuf[UNP.cur_input]); @@ -1296,6 +1394,7 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) cli_dbgmsg("autoit: not enough space for a double\n"); break; } + if (UNP.cur_output + 40 >= UNP.csize) { uint8_t *newout; UNP.csize += 512; @@ -1305,16 +1404,19 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) } buf = newout; } - if (fpu_words == FPU_ENDIAN_LITTLE) + + if (fpu_words == FPU_ENDIAN_LITTLE) { snprintf((char *)&buf[UNP.cur_output], 39, "%g ", *(double *)&UNP.outputbuf[UNP.cur_input]); - else + } else do { double x; uint8_t *j = (uint8_t *)&x; unsigned int i; - for (i = 0; i < 8; i++) + for (i = 0; i < 8; i++) { j[7 - i] = UNP.outputbuf[UNP.cur_input + i]; + } + snprintf((char *)&buf[UNP.cur_output], 39, "%g ", x); /* FIXME: check */ } while (0); buf[UNP.cur_output + 38] = ' '; @@ -1339,6 +1441,7 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) cli_dbgmsg("autoit: not enough space for size\n"); break; } + chars = cli_readint32((char *)&UNP.outputbuf[UNP.cur_input]); dchars = chars * 2; UNP.cur_input += 4; @@ -1348,6 +1451,7 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) cli_dbgmsg("autoit: size too big - needed %d, total %d, avail %d\n", dchars, UNP.usize, UNP.usize - UNP.cur_input); break; } + if (UNP.cur_output + chars + 3 >= UNP.csize) { uint8_t *newout; UNP.csize += chars + 512; @@ -1358,8 +1462,9 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) buf = newout; } - if (prefixes[op - 0x30]) + if (prefixes[op - 0x30]) { buf[UNP.cur_output++] = prefixes[op - 0x30]; + } if (chars) { for (i = 0; i < dchars; i += 2) { @@ -1371,11 +1476,14 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) UNP.cur_output += chars; UNP.cur_input += dchars; } - if (op == 0x36) + + if (op == 0x36) { // TODO: Mask possible double quotes inside the string: >Say:"Hi "< ==> >"Say:""Hi"" "< buf[UNP.cur_output++] = '"'; - if (op != 0x34) + } + if (op != 0x34) { buf[UNP.cur_output++] = ' '; + } } break; case 0x40: /* , */ @@ -1442,8 +1550,9 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) } } - if (UNP.error) + if (UNP.error) { cli_dbgmsg("autoit: decompilation aborted - partial script may exist\n"); + } free(UNP.outputbuf); } else { @@ -1464,40 +1573,51 @@ static int ea06(cli_ctx *ctx, const uint8_t *base, char *tmpd) free(buf); return CL_EWRITE; } + free(buf); - if (ctx->engine->keeptmp) + + if (ctx->engine->keeptmp) { cli_dbgmsg("autoit: %s extracted to %s\n", (script) ? "script" : "file", tempfile); - else + } else { cli_dbgmsg("autoit: %s successfully extracted\n", (script) ? "script" : "file"); + } + if (lseek(i, 0, SEEK_SET) == -1) { cli_dbgmsg("autoit: call to lseek() has failed\n"); close(i); return CL_ESEEK; } - if (CL_VIRUS == cli_magic_scan_desc(i, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE)) { - if (!SCAN_ALLMATCHES) { - close(i); - if (!ctx->engine->keeptmp) - if (cli_unlink(tempfile)) return CL_EUNLINK; - return CL_VIRUS; + + ret = cli_magic_scan_desc(i, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE); + if (CL_SUCCESS != ret) { + close(i); + if (!ctx->engine->keeptmp) { + if (cli_unlink(tempfile)) { + return CL_EUNLINK; + } } - det = 1; + return CL_VIRUS; } + close(i); - if (!ctx->engine->keeptmp) - if (cli_unlink(tempfile)) return CL_EUNLINK; + + if (!ctx->engine->keeptmp) { + if (cli_unlink(tempfile)) { + return CL_EUNLINK; + } + } } - return (det ? CL_VIRUS : ret); + return ret; } /********************* autoit3 wrapper *********************/ -int cli_scanautoit(cli_ctx *ctx, off_t offset) +cl_error_t cli_scanautoit(cli_ctx *ctx, off_t offset) { + cl_error_t status = CL_SUCCESS; const uint8_t *version; - int r; char *tmpd; fmap_t *map = ctx->fmap; @@ -1518,7 +1638,7 @@ int cli_scanautoit(cli_ctx *ctx, off_t offset) switch (*version) { case 0x35: - r = ea05(ctx, version + 1, tmpd); + status = ea05(ctx, version + 1, tmpd); break; case 0x36: if (fpu_words == FPU_ENDIAN_INITME) @@ -1526,19 +1646,19 @@ int cli_scanautoit(cli_ctx *ctx, off_t offset) if (fpu_words == FPU_ENDIAN_UNKNOWN) { cli_dbgmsg("autoit: EA06 support not available" "(cannot extract ea06 doubles, unknown floating double representation).\n"); - r = CL_CLEAN; + status = CL_CLEAN; } else - r = ea06(ctx, version + 1, tmpd); + status = ea06(ctx, version + 1, tmpd); break; default: /* NOT REACHED */ cli_dbgmsg("autoit: unknown method\n"); - r = CL_CLEAN; + status = CL_CLEAN; } if (!ctx->engine->keeptmp) cli_rmdirs(tmpd); free(tmpd); - return r; + return status; } diff --git a/libclamav/autoit.h b/libclamav/autoit.h index c29d609702..cdffe005ae 100644 --- a/libclamav/autoit.h +++ b/libclamav/autoit.h @@ -23,5 +23,5 @@ #define __AUTOIT_H #include "others.h" -int cli_scanautoit(cli_ctx *ctx, off_t offset); +cl_error_t cli_scanautoit(cli_ctx *ctx, off_t offset); #endif diff --git a/libclamav/binhex.c b/libclamav/binhex.c index 8c117ed373..a9025da468 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/blob.c b/libclamav/blob.c index b1d3ca05bb..f45d9fc4c2 100644 --- a/libclamav/blob.c +++ b/libclamav/blob.c @@ -43,6 +43,7 @@ #include #endif +#include "clamav.h" #include "others.h" #include "mbox.h" #include "matcher.h" @@ -597,7 +598,6 @@ int fileblobAddData(fileblob *fb, const unsigned char *data, size_t len) fb->bytes_scanned += (unsigned long)len; if ((len > 5) && cli_updatelimits(ctx, len) == CL_CLEAN && (cli_scan_buff(data, (unsigned int)len, 0, ctx->virname, ctx->engine, CL_TYPE_BINARY_DATA, NULL) == CL_VIRUS)) { - cli_dbgmsg("fileblobAddData: found %s\n", cli_get_last_virus_str(ctx->virname)); fb->isInfected = 1; } } @@ -632,12 +632,10 @@ void fileblobSetCTX(fileblob *fb, cli_ctx *ctx) * CL_CLEAN means unknown * CL_VIRUS means infected */ -int fileblobScan(const fileblob *fb) +cl_error_t fileblobScan(const fileblob *fb) { - int rc; - cli_ctx *ctx = fb->ctx; + cl_error_t rc; STATBUF sb; - int virus_found = 0; if (fb->isInfected) return CL_VIRUS; @@ -655,18 +653,17 @@ int fileblobScan(const fileblob *fb) fflush(fb->fp); lseek(fb->fd, 0, SEEK_SET); FSTAT(fb->fd, &sb); - if (cli_matchmeta(fb->ctx, fb->b.name, sb.st_size, sb.st_size, 0, 0, 0, NULL) == CL_VIRUS) { - if (!SCAN_ALLMATCHES) - return CL_VIRUS; - virus_found = 1; + + rc = cli_matchmeta(fb->ctx, fb->b.name, sb.st_size, sb.st_size, 0, 0, 0, NULL); + if (rc != CL_SUCCESS) { + return rc; } rc = cli_magic_scan_desc(fb->fd, fb->fullname, fb->ctx, fb->b.name, LAYER_ATTRIBUTES_NONE); - if (rc == CL_VIRUS || virus_found != 0) { - cli_dbgmsg("%s is infected\n", fb->fullname); - return CL_VIRUS; + if (rc != CL_SUCCESS) { + return rc; } - cli_dbgmsg("%s is clean\n", fb->fullname); + return CL_BREAK; } @@ -688,8 +685,9 @@ void sanitiseName(char *name) { char c; while ((c = *name)) { - if (c != '.' && c != '_' && (c > 'z' || c < '0' || (c > '9' && c < 'A') || (c > 'Z' && c < 'a'))) + if (c != '.' && c != '_' && (c > 'z' || c < '0' || (c > '9' && c < 'A') || (c > 'Z' && c < 'a'))) { *name = '_'; + } name++; } } diff --git a/libclamav/blob.h b/libclamav/blob.h index eccdbe191c..d3b9338aad 100644 --- a/libclamav/blob.h +++ b/libclamav/blob.h @@ -22,6 +22,8 @@ #ifndef __BLOB_H #define __BLOB_H +#include "clamav.h" + /* * Resizable chunk of memory */ @@ -74,7 +76,7 @@ void fileblobPartialSet(fileblob *fb, const char *fullname, const char *arg); const char *fileblobGetFilename(const fileblob *fb); void fileblobSetCTX(fileblob *fb, cli_ctx *ctx); int fileblobAddData(fileblob *fb, const unsigned char *data, size_t len); -int fileblobScan(const fileblob *fb); +cl_error_t fileblobScan(const fileblob *fb); int fileblobInfected(const fileblob *fb); void sanitiseName(char *name); diff --git a/libclamav/bytecode.c b/libclamav/bytecode.c index 446bace96e..dd865fa15a 100644 --- a/libclamav/bytecode.c +++ b/libclamav/bytecode.c @@ -102,37 +102,14 @@ static void context_safe(struct cli_bc_ctx *ctx) ctx->hooks.pedata = &nopedata; } -static int cli_bytecode_context_reset(struct cli_bc_ctx *ctx); -struct cli_bc_ctx *cli_bytecode_context_alloc(void) -{ - struct cli_bc_ctx *ctx = cli_calloc(1, sizeof(*ctx)); - if (!ctx) { - cli_errmsg("Out of memory allocating cli_bytecode_context_reset\n"); - return NULL; - } - ctx->bytecode_timeout = 60000; - cli_bytecode_context_reset(ctx); - return ctx; -} - -void cli_bytecode_context_destroy(struct cli_bc_ctx *ctx) -{ - cli_bytecode_context_clear(ctx); - free(ctx); -} - -int cli_bytecode_context_getresult_file(struct cli_bc_ctx *ctx, char **tempfilename) -{ - int fd; - *tempfilename = ctx->tempfile; - fd = ctx->outfd; - ctx->tempfile = NULL; - ctx->outfd = 0; - return fd; -} - -/* resets bytecode state, so you can run another bytecode with same ctx */ -static int cli_bytecode_context_reset(struct cli_bc_ctx *ctx) +/** + * @brief Reset bytecode state, so you can run another bytecode with same ctx. + * + * IMPORTANT: This function does not clear/reset all fields in the context! + * + * @param ctx + */ +static void bytecode_context_reset(struct cli_bc_ctx *ctx) { unsigned i; @@ -145,46 +122,53 @@ static int cli_bytecode_context_reset(struct cli_bc_ctx *ctx) free(ctx->operands); ctx->operands = NULL; - if (ctx->outfd) { + if (-1 != ctx->outfd) { + close(ctx->outfd); + ctx->outfd = -1; + cli_ctx *cctx = ctx->ctx; - if (ctx->outfd) - close(ctx->outfd); if (ctx->tempfile && (!cctx || !cctx->engine->keeptmp)) { cli_unlink(ctx->tempfile); } free(ctx->tempfile); ctx->tempfile = NULL; - ctx->outfd = 0; } + if (ctx->jsnormdir) { char fullname[1025]; cli_ctx *cctx = ctx->ctx; - int fd, ret = CL_CLEAN; + int fd; + cl_error_t ret = CL_CLEAN; if (!ctx->found) { snprintf(fullname, 1024, "%s" PATHSEP "javascript", ctx->jsnormdir); fd = open(fullname, O_RDONLY | O_BINARY); if (fd >= 0) { - ret = cli_scan_desc(fd, cctx, CL_TYPE_HTML, 0, NULL, AC_SCAN_VIR, + ret = cli_scan_desc(fd, cctx, CL_TYPE_HTML, false, NULL, AC_SCAN_VIR, NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED); if (ret == CL_CLEAN) { if (lseek(fd, 0, SEEK_SET) == -1) cli_dbgmsg("cli_bytecode: call to lseek() has failed\n"); else { - ret = cli_scan_desc(fd, cctx, CL_TYPE_TEXT_ASCII, 0, NULL, AC_SCAN_VIR, + ret = cli_scan_desc(fd, cctx, CL_TYPE_TEXT_ASCII, false, NULL, AC_SCAN_VIR, NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED); } } close(fd); } } + if (!cctx || !cctx->engine->keeptmp) { cli_rmdirs(ctx->jsnormdir); } + free(ctx->jsnormdir); - if (ret != CL_CLEAN) + + if (ret != CL_SUCCESS) { ctx->found = 1; + } } + ctx->numParams = 0; ctx->funcid = 0; /* don't touch fmap, file_size, and hooks, sections, ctx, timeout, pdf* */ @@ -254,14 +238,46 @@ static int cli_bytecode_context_reset(struct cli_bc_ctx *ctx) #endif ctx->containertype = CL_TYPE_ANY; - return CL_SUCCESS; } -int cli_bytecode_context_clear(struct cli_bc_ctx *ctx) -{ - cli_bytecode_context_reset(ctx); +static inline void bytecode_context_initialize(struct cli_bc_ctx *ctx) { memset(ctx, 0, sizeof(*ctx)); - return CL_SUCCESS; + + ctx->bytecode_timeout = 60000; + + // 0 (aka stdin) is not a valid fd for `outfd`. + // If encountered, we should initialize it to -1 instead. + ctx->outfd = -1; +} + +struct cli_bc_ctx *cli_bytecode_context_alloc(void) +{ + struct cli_bc_ctx *ctx = cli_malloc(sizeof(*ctx)); + if (!ctx) { + cli_errmsg("Failed to allocate bytecode context\n"); + return NULL; + } + + bytecode_context_initialize(ctx); + bytecode_context_reset(ctx); + + return ctx; +} + +void cli_bytecode_context_destroy(struct cli_bc_ctx *ctx) +{ + bytecode_context_reset(ctx); + free(ctx); +} + +int cli_bytecode_context_getresult_file(struct cli_bc_ctx *ctx, char **tempfilename) +{ + int fd; + *tempfilename = ctx->tempfile; + fd = ctx->outfd; + ctx->tempfile = NULL; + ctx->outfd = -1; + return fd; } static unsigned typesize(const struct cli_bc *bc, uint16_t type) @@ -311,7 +327,7 @@ static unsigned typealign(const struct cli_bc *bc, uint16_t type) return bc->types[type - 65].align; } -int cli_bytecode_context_setfuncid(struct cli_bc_ctx *ctx, const struct cli_bc *bc, unsigned funcid) +cl_error_t cli_bytecode_context_setfuncid(struct cli_bc_ctx *ctx, const struct cli_bc *bc, unsigned funcid) { unsigned i, s = 0; const struct cli_bc_func *func; @@ -351,12 +367,12 @@ int cli_bytecode_context_setfuncid(struct cli_bc_ctx *ctx, const struct cli_bc * return CL_SUCCESS; } -static inline int type_isint(uint16_t type) +static inline bool type_isint(uint16_t type) { return type > 0 && type <= 64; } -int cli_bytecode_context_setparam_int(struct cli_bc_ctx *ctx, unsigned i, uint64_t c) +cl_error_t cli_bytecode_context_setparam_int(struct cli_bc_ctx *ctx, unsigned i, uint64_t c) { if (i >= ctx->numParams) { cli_errmsg("bytecode: param index out of bounds: %u\n", i); @@ -383,7 +399,7 @@ int cli_bytecode_context_setparam_int(struct cli_bc_ctx *ctx, unsigned i, uint64 return CL_SUCCESS; } -int cli_bytecode_context_setparam_ptr(struct cli_bc_ctx *ctx, unsigned i, void *data, unsigned datalen) +cl_error_t cli_bytecode_context_setparam_ptr(struct cli_bc_ctx *ctx, unsigned i, void *data, unsigned datalen) { UNUSEDPARAM(ctx); UNUSEDPARAM(i); @@ -393,7 +409,7 @@ int cli_bytecode_context_setparam_ptr(struct cli_bc_ctx *ctx, unsigned i, void * return CL_EARG; } -static inline uint64_t readNumber(const unsigned char *p, unsigned *off, unsigned len, char *ok) +static inline uint64_t readNumber(const unsigned char *p, unsigned *off, unsigned len, bool *ok) { uint64_t n = 0; unsigned i, newoff, lim, p0 = p[*off], shift = 0; @@ -401,13 +417,13 @@ static inline uint64_t readNumber(const unsigned char *p, unsigned *off, unsigne lim = p0 - 0x60; if (lim > 0x10) { cli_errmsg("Invalid number type: %c\n", p0); - *ok = 0; + *ok = false; return 0; } newoff = *off + lim + 1; if (newoff > len) { cli_errmsg("End of line encountered while reading number\n"); - *ok = 0; + *ok = false; return 0; } @@ -420,7 +436,7 @@ static inline uint64_t readNumber(const unsigned char *p, unsigned *off, unsigne uint64_t v = p[i]; if (UNLIKELY((v & 0xf0) != 0x60)) { cli_errmsg("Invalid number part: %c\n", (char)v); - *ok = 0; + *ok = false; return 0; } v &= 0xf; @@ -433,44 +449,44 @@ static inline uint64_t readNumber(const unsigned char *p, unsigned *off, unsigne } static inline funcid_t readFuncID(struct cli_bc *bc, unsigned char *p, - unsigned *off, unsigned len, char *ok) + unsigned *off, unsigned len, bool *ok) { funcid_t id = readNumber(p, off, len, ok) - 1; if (*ok && id >= bc->num_func) { cli_errmsg("Called function out of range: %u >= %u\n", id, bc->num_func); - *ok = 0; + *ok = false; return ~0; } return id; } static inline funcid_t readAPIFuncID(struct cli_bc *bc, unsigned char *p, - unsigned *off, unsigned len, char *ok) + unsigned *off, unsigned len, bool *ok) { funcid_t id = readNumber(p, off, len, ok) - 1; if (*ok && !cli_bitset_test(bc->uses_apis, id)) { cli_errmsg("Called undeclared API function: %u\n", id); - *ok = 0; + *ok = false; return ~0; } return id; } static inline unsigned readFixedNumber(const unsigned char *p, unsigned *off, - unsigned len, char *ok, unsigned width) + unsigned len, bool *ok, unsigned width) { unsigned i, n = 0, shift = 0; unsigned newoff = *off + width; if (newoff > len) { cli_errmsg("Newline encountered while reading number\n"); - *ok = 0; + *ok = false; return 0; } for (i = *off; i < newoff; i++) { unsigned v = p[i]; if (UNLIKELY((v & 0xf0) != 0x60)) { cli_errmsg("Invalid number part: %c\n", v); - *ok = 0; + *ok = false; return 0; } v &= 0xf; @@ -483,7 +499,7 @@ static inline unsigned readFixedNumber(const unsigned char *p, unsigned *off, } static inline operand_t readOperand(struct cli_bc_func *func, unsigned char *p, - unsigned *off, unsigned len, char *ok) + unsigned *off, unsigned len, bool *ok) { uint64_t v; if ((p[*off] & 0xf0) == 0x40 || p[*off] == 0x50) { @@ -493,7 +509,7 @@ static inline operand_t readOperand(struct cli_bc_func *func, unsigned char *p, /* TODO: unique constants */ func->constants = cli_realloc2(func->constants, (func->numConstants + 1) * sizeof(*func->constants)); if (!func->constants) { - *ok = 0; + *ok = false; return MAX_OP; } v = readNumber(p, off, len, ok); @@ -522,19 +538,19 @@ static inline operand_t readOperand(struct cli_bc_func *func, unsigned char *p, return MAX_OP; if (v >= func->numValues) { cli_errmsg("Operand index exceeds bounds: %u >= %u!\n", (unsigned)v, (unsigned)func->numValues); - *ok = 0; + *ok = false; return MAX_OP; } return v; } -static inline char *readData(const unsigned char *p, unsigned *off, unsigned len, char *ok, unsigned *datalen) +static inline char *readData(const unsigned char *p, unsigned *off, unsigned len, bool *ok, unsigned *datalen) { unsigned char *dat, *q; unsigned l, newoff, i; if (p[*off] != '|') { cli_errmsg("Data start marker missing: %c\n", p[*off]); - *ok = 0; + *ok = false; return NULL; } (*off)++; @@ -546,13 +562,13 @@ static inline char *readData(const unsigned char *p, unsigned *off, unsigned len newoff = *off + 2 * l; if (newoff > len) { cli_errmsg("Line ended while reading data\n"); - *ok = 0; + *ok = false; return 0; } dat = cli_malloc(l); if (!dat) { cli_errmsg("Cannot allocate memory for data\n"); - *ok = 0; + *ok = false; return NULL; } q = dat; @@ -561,7 +577,7 @@ static inline char *readData(const unsigned char *p, unsigned *off, unsigned len const unsigned char v1 = p[i + 1]; if (UNLIKELY((v0 & 0xf0) != 0x60 || (v1 & 0xf0) != 0x60)) { cli_errmsg("Invalid data part: %c%c\n", v0, v1); - *ok = 0; + *ok = false; free(dat); return 0; } @@ -572,7 +588,7 @@ static inline char *readData(const unsigned char *p, unsigned *off, unsigned len return (char *)dat; } -static inline char *readString(const unsigned char *p, unsigned *off, unsigned len, char *ok) +static inline char *readString(const unsigned char *p, unsigned *off, unsigned len, bool *ok) { unsigned stringlen = 0; char *str = readData(p, off, len, ok, &stringlen); @@ -580,17 +596,17 @@ static inline char *readString(const unsigned char *p, unsigned *off, unsigned l str[stringlen - 1] = '\0'; cli_errmsg("bytecode: string missing \\0 terminator: %s\n", str); free(str); - *ok = 0; + *ok = false; return NULL; } return str; } -static int parseHeader(struct cli_bc *bc, unsigned char *buffer, unsigned *linelength) +static cl_error_t parseHeader(struct cli_bc *bc, unsigned char *buffer, unsigned *linelength) { uint64_t magic1; unsigned magic2; - char ok = 1; + bool ok = true; unsigned offset, len, flevel; char *pos; @@ -676,15 +692,16 @@ static int parseHeader(struct cli_bc *bc, unsigned char *buffer, unsigned *linel return CL_SUCCESS; } -static int parseLSig(struct cli_bc *bc, char *buffer) +static cl_error_t parseLSig(struct cli_bc *bc, char *buffer) { - const char *prefix; - char *vnames, *vend = strchr(buffer, ';'); + // const char *prefix; + // char *vnames; + char *vend = strchr(buffer, ';'); if (vend) { bc->lsig = cli_strdup(buffer); *vend++ = '\0'; - prefix = buffer; - vnames = strchr(vend, '{'); + // prefix = buffer; + // vnames = strchr(vend, '{'); } else { /* Not a logical signature, but we still have a virusname */ bc->hook_name = cli_strdup(buffer); @@ -695,14 +712,14 @@ static int parseLSig(struct cli_bc *bc, char *buffer) } static uint16_t readTypeID(struct cli_bc *bc, unsigned char *buffer, - unsigned *offset, unsigned len, char *ok) + unsigned *offset, unsigned len, bool *ok) { uint64_t t = readNumber(buffer, offset, len, ok); if (!ok) return ~0; if (t >= bc->num_types + bc->start_tid) { cli_errmsg("Invalid type id: %llu\n", (unsigned long long)t); - *ok = 0; + *ok = false; return ~0; } return t; @@ -710,20 +727,20 @@ static uint16_t readTypeID(struct cli_bc *bc, unsigned char *buffer, static void parseType(struct cli_bc *bc, struct cli_bc_type *ty, unsigned char *buffer, unsigned *off, unsigned len, - char *ok) + bool *ok) { unsigned j; ty->numElements = readNumber(buffer, off, len, ok); if (!*ok) { cli_errmsg("Error parsing type\n"); - *ok = 0; + *ok = false; return; } ty->containedTypes = cli_malloc(sizeof(*ty->containedTypes) * ty->numElements); if (!ty->containedTypes) { cli_errmsg("Out of memory allocating %u types\n", ty->numElements); - *ok = 0; + *ok = false; return; } for (j = 0; j < ty->numElements; j++) { @@ -745,10 +762,10 @@ static void add_static_types(struct cli_bc *bc) } } -static int parseTypes(struct cli_bc *bc, unsigned char *buffer) +static cl_error_t parseTypes(struct cli_bc *bc, unsigned char *buffer) { unsigned i, offset = 1, len = strlen((const char *)buffer); - char ok = 1; + bool ok = true; if (buffer[0] != 'T') { cli_errmsg("Invalid function types header: %c\n", buffer[0]); @@ -842,7 +859,7 @@ static int parseTypes(struct cli_bc *bc, unsigned char *buffer) /* checks whether the type described by tid is the same as the one described by * apitid. */ -static int types_equal(const struct cli_bc *bc, uint16_t *apity2ty, uint16_t tid, uint16_t apitid) +static bool types_equal(const struct cli_bc *bc, uint16_t *apity2ty, uint16_t tid, uint16_t apitid) { unsigned i; const struct cli_bc_type *ty = &bc->types[tid - 65]; @@ -851,37 +868,38 @@ static int types_equal(const struct cli_bc *bc, uint16_t *apity2ty, uint16_t tid * Since we need to check equality of recursive types, we assume types are * equal while checking equality of contained types, unless proven * otherwise. */ - if (apity2ty[apitid] == tid + 1) - return 1; + if (apity2ty[apitid] == tid + 1) { + return true; + } apity2ty[apitid] = tid + 1; if (ty->kind != apity->kind) { cli_dbgmsg("bytecode: type kind mismatch: %u != %u\n", ty->kind, apity->kind); - return 0; + return false; } if (ty->numElements != apity->numElements) { cli_dbgmsg("bytecode: type numElements mismatch: %u != %u\n", ty->numElements, apity->numElements); - return 0; + return false; } for (i = 0; i < ty->numElements; i++) { if (apity->containedTypes[i] < BC_START_TID) { if (ty->containedTypes[i] != apity->containedTypes[i]) { cli_dbgmsg("bytecode: contained type mismatch: %u != %u\n", ty->containedTypes[i], apity->containedTypes[i]); - return 0; + return false; } } else if (!types_equal(bc, apity2ty, ty->containedTypes[i], apity->containedTypes[i] - BC_START_TID)) - return 0; + return false; if (ty->kind == DArrayType) break; /* validated the contained type already */ } - return 1; + return true; } -static int parseApis(struct cli_bc *bc, unsigned char *buffer) +static cl_error_t parseApis(struct cli_bc *bc, unsigned char *buffer) { unsigned i, offset = 1, len = strlen((const char *)buffer), maxapi, calls; - char ok = 1; + bool ok = true; uint16_t *apity2ty; /*map of api type to current bytecode type ID */ if (buffer[0] != 'E') { @@ -921,17 +939,17 @@ static int parseApis(struct cli_bc *bc, unsigned char *buffer) /* validate APIcall prototype */ if (id > maxapi) { cli_errmsg("bytecode: API id %u out of range, max %u\n", id, maxapi); - ok = 0; + ok = false; } /* API ids start from 1 */ id--; if (ok && name && strcmp(cli_apicalls[id].name, name)) { cli_errmsg("bytecode: API %u name mismatch: %s expected %s\n", id, name, cli_apicalls[id].name); - ok = 0; + ok = false; } if (ok && !types_equal(bc, apity2ty, tid, cli_apicalls[id].type)) { cli_errmsg("bytecode: API %u prototype doesn't match\n", id); - ok = 0; + ok = false; } /* don't need the name anymore */ free(name); @@ -948,7 +966,7 @@ static int parseApis(struct cli_bc *bc, unsigned char *buffer) return CL_SUCCESS; } -static uint16_t type_components(struct cli_bc *bc, uint16_t id, char *ok) +static uint16_t type_components(struct cli_bc *bc, uint16_t id, bool *ok) { unsigned i, sum = 0; const struct cli_bc_type *ty; @@ -960,7 +978,7 @@ static uint16_t type_components(struct cli_bc *bc, uint16_t id, char *ok) case DFunctionType: cli_errmsg("bytecode: function type not accepted for constant: %u\n", id); /* don't accept functions as constant initializers */ - *ok = 0; + *ok = false; return 0; case DPointerType: return 2; @@ -973,14 +991,14 @@ static uint16_t type_components(struct cli_bc *bc, uint16_t id, char *ok) case DArrayType: return type_components(bc, ty->containedTypes[0], ok) * ty->numElements; default: - *ok = 0; + *ok = false; return 0; } } static void readConstant(struct cli_bc *bc, unsigned i, unsigned comp, unsigned char *buffer, unsigned *offset, - unsigned len, char *ok) + unsigned len, bool *ok) { unsigned j = 0; if (*ok && buffer[*offset] == 0x40 && @@ -993,7 +1011,7 @@ static void readConstant(struct cli_bc *bc, unsigned i, unsigned comp, while (*ok && buffer[*offset] != 0x60) { if (j >= comp) { cli_errmsg("bytecode: constant has too many subcomponents, expected %u\n", comp); - *ok = 0; + *ok = false; return; } buffer[*offset] |= 0x20; @@ -1001,17 +1019,17 @@ static void readConstant(struct cli_bc *bc, unsigned i, unsigned comp, } if (*ok && j != comp) { cli_errmsg("bytecode: constant has too few subcomponents: %u < %u\n", j, comp); - *ok = 0; + *ok = false; } (*offset)++; } /* parse constant globals with constant initializers */ -static int parseGlobals(struct cli_bc *bc, unsigned char *buffer) +static cl_error_t parseGlobals(struct cli_bc *bc, unsigned char *buffer) { unsigned i, offset = 1, len = strlen((const char *)buffer), numglobals; unsigned maxglobal; - char ok = 1; + bool ok = true; if (buffer[0] != 'G') { cli_errmsg("bytecode: Invalid globals header: %c\n", buffer[0]); @@ -1057,11 +1075,11 @@ static int parseGlobals(struct cli_bc *bc, unsigned char *buffer) return CL_SUCCESS; } -static int parseMD(struct cli_bc *bc, unsigned char *buffer) +static cl_error_t parseMD(struct cli_bc *bc, unsigned char *buffer) { unsigned offset = 1, len = strlen((const char *)buffer); unsigned numMD, i, b; - char ok = 1; + bool ok = true; if (buffer[0] != 'D') return CL_EMALFDB; numMD = readNumber(buffer, &offset, len, &ok); @@ -1108,9 +1126,9 @@ static int parseMD(struct cli_bc *bc, unsigned char *buffer) return CL_SUCCESS; } -static int parseFunctionHeader(struct cli_bc *bc, unsigned fn, unsigned char *buffer) +static cl_error_t parseFunctionHeader(struct cli_bc *bc, unsigned fn, unsigned char *buffer) { - char ok = 1; + bool ok = true; unsigned offset, len, all_locals = 0, i; struct cli_bc_func *func; @@ -1189,12 +1207,12 @@ static int parseFunctionHeader(struct cli_bc *bc, unsigned fn, unsigned char *bu return CL_SUCCESS; } -static bbid_t readBBID(struct cli_bc_func *func, const unsigned char *buffer, unsigned *off, unsigned len, char *ok) +static bbid_t readBBID(struct cli_bc_func *func, const unsigned char *buffer, unsigned *off, unsigned len, bool *ok) { unsigned id = readNumber(buffer, off, len, ok); if (!id || id >= func->numBB) { cli_errmsg("Basic block ID out of range: %u\n", id); - *ok = 0; + *ok = false; } if (!*ok) return ~0; @@ -1215,9 +1233,9 @@ static int16_t get_optype(const struct cli_bc_func *bcfunc, operand_t op) return bcfunc->types[op] & 0x7fff; } -static int parseBB(struct cli_bc *bc, unsigned func, unsigned bb, unsigned char *buffer) +static cl_error_t parseBB(struct cli_bc *bc, unsigned func, unsigned bb, unsigned char *buffer) { - char ok = 1; + bool ok = true; unsigned offset, len, i, last = 0; struct cli_bc_bb *BB; struct cli_bc_func *bcfunc = &bc->funcs[func]; @@ -1368,7 +1386,7 @@ static int parseBB(struct cli_bc *bc, unsigned func, unsigned bb, unsigned char break; default: cli_errmsg("Opcode %u with too many operands: %u?\n", inst.opcode, numOp); - ok = 0; + ok = false; break; } } @@ -1574,14 +1592,15 @@ void cli_sigperf_events_destroy() cli_events_free(g_sigevents); } -int cli_bytecode_load(struct cli_bc *bc, FILE *f, struct cli_dbio *dbio, int trust, int sigperf) +cl_error_t cli_bytecode_load(struct cli_bc *bc, FILE *f, struct cli_dbio *dbio, int trust, int sigperf) { unsigned row = 0, current_func = 0, bb = 0; char *buffer; unsigned linelength = 0; char firstbuf[FILEBUFF]; enum parse_state state; - int rc, end = 0; + cl_error_t rc; + int end = 0; memset(bc, 0, sizeof(*bc)); cli_dbgmsg("Loading %s bytecode\n", trust ? "trusted" : "untrusted"); @@ -1776,15 +1795,15 @@ static int register_events(cli_events_t *ev) return 0; } -int cli_bytecode_run(const struct cli_all_bc *bcs, const struct cli_bc *bc, struct cli_bc_ctx *ctx) +cl_error_t cli_bytecode_run(const struct cli_all_bc *bcs, const struct cli_bc *bc, struct cli_bc_ctx *ctx) { - int ret = CL_SUCCESS; + cl_error_t ret = CL_SUCCESS; struct cli_bc_inst inst; struct cli_bc_func func; cli_events_t *jit_ev = NULL, *interp_ev = NULL; - int test_mode = 0; - cli_ctx *cctx = (cli_ctx *)ctx->ctx; + bool test_mode = 0; + cli_ctx *cctx = (cli_ctx *)ctx->ctx; if (!ctx || !ctx->bc || !ctx->func) return CL_ENULLARG; @@ -1792,7 +1811,7 @@ int cli_bytecode_run(const struct cli_all_bc *bcs, const struct cli_bc *bc, stru return CL_ENULLARG; if (cctx && cctx->engine->bytecode_mode == CL_BYTECODE_MODE_TEST) - test_mode = 1; + test_mode = true; if (bc->state == bc_loaded) { cli_errmsg("bytecode has to be prepared either for interpreter or JIT!\n"); @@ -1851,7 +1870,7 @@ int cli_bytecode_run(const struct cli_all_bc *bcs, const struct cli_bc *bc, stru cli_event_string(interp_ev, BCEV_VIRUSNAME, ctx->virname); /* need to be called here to catch any extracted but not yet scanned files */ - if (ctx->outfd && (ret != CL_VIRUS || cctx->options->general & CL_SCAN_GENERAL_ALLMATCHES)) + if (ctx->outfd && (ret != CL_VIRUS)) cli_bcapi_extract_new(ctx, -1); } if (bc->state == bc_jit || test_mode) { @@ -1870,7 +1889,7 @@ int cli_bytecode_run(const struct cli_all_bc *bcs, const struct cli_bc *bc, stru cli_event_string(jit_ev, BCEV_VIRUSNAME, ctx->virname); /* need to be called here to catch any extracted but not yet scanned files */ - if (ctx->outfd && (ret != CL_VIRUS || cctx->options->general & CL_SCAN_GENERAL_ALLMATCHES)) + if (ctx->outfd && (ret != CL_VIRUS)) cli_bcapi_extract_new(ctx, -1); } cli_event_time_stop(g_sigevents, bc->sigtime_id); @@ -1881,18 +1900,18 @@ int cli_bytecode_run(const struct cli_all_bc *bcs, const struct cli_bc *bc, stru unsigned interp_errors = cli_event_errors(interp_ev); unsigned jit_errors = cli_event_errors(jit_ev); unsigned interp_warns = 0, jit_warns = 0; - int ok = 1; + bool ok = true; enum bc_events evid; if (interp_errors || jit_errors) { cli_infomsg(cctx, "bytecode %d encountered %u JIT and %u interpreter errors\n", bc->id, interp_errors, jit_errors); - ok = 0; + ok = false; } if (!ctx->no_diff && cli_event_diff_all(interp_ev, jit_ev, NULL)) { cli_infomsg(cctx, "bytecode %d execution different with JIT and interpreter, see --debug for details\n", bc->id); - ok = 0; + ok = false; } for (evid = BCEV_API_WARN_BEGIN + 1; evid < BCEV_API_WARN_END; evid++) { union ev_val v; @@ -1906,7 +1925,7 @@ int cli_bytecode_run(const struct cli_all_bc *bcs, const struct cli_bc *bc, stru if (interp_warns || jit_warns) { cli_infomsg(cctx, "bytecode %d encountered %u JIT and %u interpreter warnings\n", bc->id, interp_warns, jit_warns); - ok = 0; + ok = false; } /*cli_event_debug(jit_ev, BCEV_EXEC_TIME); cli_event_debug(interp_ev, BCEV_EXEC_TIME); @@ -2082,12 +2101,12 @@ static int calc_gepz(struct cli_bc *bc, struct cli_bc_func *func, uint16_t tid, return 1; } -static int cli_bytecode_prepare_interpreter(struct cli_bc *bc) +static cl_error_t cli_bytecode_prepare_interpreter(struct cli_bc *bc) { unsigned i, j, k; uint64_t *gmap; unsigned bcglobalid = cli_apicall_maxglobal - _FIRST_GLOBAL + 2; - int ret = CL_SUCCESS; + cl_error_t ret = CL_SUCCESS; bc->numGlobalBytes = 0; gmap = cli_malloc(bc->num_globals * sizeof(*gmap)); if (!gmap) { @@ -2355,7 +2374,7 @@ static int cli_bytecode_prepare_interpreter(struct cli_bc *bc) return ret; } -static int add_selfcheck(struct cli_all_bc *bcs) +static cl_error_t add_selfcheck(struct cli_all_bc *bcs) { struct cli_bc_func *func; struct cli_bc_inst *inst; @@ -2451,14 +2470,14 @@ static int add_selfcheck(struct cli_all_bc *bcs) inst->interp_op = inst->opcode * 5; bc->state = bc_loaded; - return 0; + return CL_SUCCESS; } -static int run_selfcheck(struct cli_all_bc *bcs) +static cl_error_t run_selfcheck(struct cli_all_bc *bcs) { struct cli_bc_ctx *ctx; struct cli_bc *bc = &bcs->all_bcs[bcs->count - 1]; - int rc; + cl_error_t rc; if (bc->state != bc_jit && bc->state != bc_interp) { cli_errmsg("Failed to prepare selfcheck bytecode\n"); return CL_EBYTECODE; @@ -2483,10 +2502,10 @@ static int run_selfcheck(struct cli_all_bc *bcs) return rc; } -static int selfcheck(int jit, struct cli_bcengine *engine) +static cl_error_t selfcheck(bool jit, struct cli_bcengine *engine) { struct cli_all_bc bcs; - int rc; + cl_error_t rc; memset(&bcs, 0, sizeof(bcs)); bcs.all_bcs = NULL; @@ -2547,7 +2566,7 @@ static int set_mode(struct cl_engine *engine, enum bytecode_mode mode) /* runs the first bytecode of the specified kind, or the builtin one if no * bytecode of that kind is loaded */ -static int run_builtin_or_loaded(struct cli_all_bc *bcs, uint8_t kind, const char *builtin_cbc, struct cli_bc_ctx *ctx, const char *desc) +static cl_error_t run_builtin_or_loaded(struct cli_all_bc *bcs, uint8_t kind, const char *builtin_cbc, struct cli_bc_ctx *ctx, const char *desc) { unsigned i, builtin = 0, rc = 0; struct cli_bc *bc = NULL; @@ -2613,10 +2632,10 @@ static int run_builtin_or_loaded(struct cli_all_bc *bcs, uint8_t kind, const cha return rc; } -int cli_bytecode_prepare2(struct cl_engine *engine, struct cli_all_bc *bcs, unsigned dconfmask) +cl_error_t cli_bytecode_prepare2(struct cl_engine *engine, struct cli_all_bc *bcs, unsigned dconfmask) { - unsigned i, interp = 0, jitok = 0, jitcount = 0; - int rc; + unsigned i, interp = 0, jitcount = 0; + cl_error_t rc; struct cli_bc_ctx *ctx; if (!bcs->count) { @@ -2668,13 +2687,15 @@ int cli_bytecode_prepare2(struct cl_engine *engine, struct cli_all_bc *bcs, unsi cli_warnmsg("Bytecode: BC_STARTUP failed to run, disabling ALL bytecodes! Please report to https://github.com/Cisco-Talos/clamav/issues\n"); ctx->bytecode_disable_status = 2; } else { + uint64_t context_result; + cli_dbgmsg("Bytecode: disable status is %d\n", ctx->bytecode_disable_status); - rc = cli_bytecode_context_getresult_int(ctx); + context_result = cli_bytecode_context_getresult_int(ctx); /* check magic number, don't use 0 here because it is too easy for a * buggy bytecode to return 0 */ - if ((unsigned int)rc != (unsigned int)0xda7aba5e) { - cli_warnmsg("Bytecode: selftest failed with code %08x. Please report to https://github.com/Cisco-Talos/clamav/issues\n", - rc); + if (context_result != (uint64_t)0xda7aba5e) { + cli_warnmsg("Bytecode: selftest failed with code " STDx64 ". Please report to https://github.com/Cisco-Talos/clamav/issues\n", + context_result); if (engine->bytecode_mode == CL_BYTECODE_MODE_TEST) return CL_EBYTECODE_TESTFAIL; } @@ -2695,10 +2716,9 @@ int cli_bytecode_prepare2(struct cl_engine *engine, struct cli_all_bc *bcs, unsi if (engine->bytecode_mode != CL_BYTECODE_MODE_INTERPRETER && engine->bytecode_mode != CL_BYTECODE_MODE_OFF) { - selfcheck(1, bcs->engine); + selfcheck(true, bcs->engine); rc = cli_bytecode_prepare_jit(bcs); if (rc == CL_SUCCESS) { - jitok = 1; cli_dbgmsg("Bytecode: %u bytecode prepared with JIT\n", bcs->count); if (engine->bytecode_mode != CL_BYTECODE_MODE_TEST) return CL_SUCCESS; @@ -2753,9 +2773,9 @@ int cli_bytecode_prepare2(struct cl_engine *engine, struct cli_all_bc *bcs, unsi return CL_SUCCESS; } -int cli_bytecode_init(struct cli_all_bc *allbc) +cl_error_t cli_bytecode_init(struct cli_all_bc *allbc) { - int ret; + cl_error_t ret; memset(allbc, 0, sizeof(*allbc)); ret = cli_bytecode_init_jit(allbc, 0 /*XXX*/); cli_dbgmsg("Bytecode initialized in %s mode\n", @@ -2764,33 +2784,40 @@ int cli_bytecode_init(struct cli_all_bc *allbc) return ret; } -int cli_bytecode_done(struct cli_all_bc *allbc) +cl_error_t cli_bytecode_done(struct cli_all_bc *allbc) { return cli_bytecode_done_jit(allbc, 0); } -int cli_bytecode_context_setfile(struct cli_bc_ctx *ctx, fmap_t *map) +cl_error_t cli_bytecode_context_setfile(struct cli_bc_ctx *ctx, fmap_t *map) { ctx->fmap = map; ctx->file_size = map->len; ctx->hooks.filesize = &ctx->file_size; - return 0; + return CL_SUCCESS; } -int cli_bytecode_runlsig(cli_ctx *cctx, struct cli_target_info *tinfo, - const struct cli_all_bc *bcs, unsigned bc_idx, - const uint32_t *lsigcnt, - const uint32_t *lsigsuboff, fmap_t *map) +cl_error_t cli_bytecode_runlsig(cli_ctx *cctx, struct cli_target_info *tinfo, + const struct cli_all_bc *bcs, unsigned bc_idx, + const uint32_t *lsigcnt, + const uint32_t *lsigsuboff, fmap_t *map) { - int ret; + cl_error_t ret; struct cli_bc_ctx ctx; const struct cli_bc *bc = &bcs->all_bcs[bc_idx - 1]; struct cli_pe_hook_data pehookdata; + const char *bc_name = NULL; if (bc_idx == 0) return CL_ENULLARG; - memset(&ctx, 0, sizeof(ctx)); + if (NULL != bc->lsig) { + bc_name = bc->lsig; + } else if (NULL != bc->hook_name) { + bc_name = bc->hook_name; + } + + bytecode_context_initialize(&ctx); cli_bytecode_context_setfuncid(&ctx, bc, 0); ctx.hooks.match_counts = lsigcnt; ctx.hooks.match_offsets = lsigsuboff; @@ -2815,44 +2842,44 @@ int cli_bytecode_runlsig(cli_ctx *cctx, struct cli_target_info *tinfo, /* save match counts */ memcpy(&ctx.lsigcnt, lsigcnt, 64 * 4); memcpy(&ctx.lsigoff, lsigsuboff, 64 * 4); - cli_bytecode_context_clear(&ctx); + bytecode_context_reset(&ctx); return CL_SUCCESS; } - cli_dbgmsg("Running bytecode for logical signature match\n"); + cli_dbgmsg("Running bytecode '%s' (id: %u) for logical signature match.\n", bc_name, bc->id); ret = cli_bytecode_run(bcs, bc, &ctx); if (ret != CL_SUCCESS) { - cli_warnmsg("Bytecode %u failed to run: %s\n", bc->id, cl_strerror(ret)); - cli_bytecode_context_clear(&ctx); + cli_warnmsg("Bytecode '%s' (id: %u) failed to run: %s\n", bc_name, bc->id, cl_strerror(ret)); + bytecode_context_reset(&ctx); + + if (cli_checktimelimit(cctx) != CL_SUCCESS) { + cli_dbgmsg("Exceeded scan timeout during bytecode run (max: %u)\n", cctx->engine->maxscantime); + return CL_ETIMEOUT; + } + return CL_SUCCESS; } if (ctx.virname) { - if (cctx->num_viruses == 0) { - int rc; - cli_dbgmsg("Bytecode found virus: %s\n", ctx.virname); - if (!strncmp(ctx.virname, "BC.Heuristics", 13)) - rc = cli_append_possibly_unwanted(cctx, ctx.virname); - else - rc = cli_append_virus(cctx, ctx.virname); - cli_bytecode_context_clear(&ctx); - return rc; - } else { - cli_bytecode_context_clear(&ctx); - return CL_VIRUS; - } + cl_error_t rc; + cli_dbgmsg("Bytecode found virus: %s\n", ctx.virname); + + rc = cli_append_virus(cctx, ctx.virname); + + bytecode_context_reset(&ctx); + return rc; } ret = cli_bytecode_context_getresult_int(&ctx); - cli_dbgmsg("Bytecode %u returned code: %u\n", bc->id, ret); - cli_bytecode_context_clear(&ctx); + cli_dbgmsg("Bytecode '%s' (id: %u) returned code: %u\n", bc_name, bc->id, ret); + bytecode_context_reset(&ctx); return CL_SUCCESS; } -int cli_bytecode_runhook(cli_ctx *cctx, const struct cl_engine *engine, struct cli_bc_ctx *ctx, - unsigned id, fmap_t *map) +cl_error_t cli_bytecode_runhook(cli_ctx *cctx, const struct cl_engine *engine, struct cli_bc_ctx *ctx, + unsigned id, fmap_t *map) { const unsigned *hooks = engine->hooks[id - _BC_START_HOOKS]; unsigned i, hooks_cnt = engine->hooks_cnt[id - _BC_START_HOOKS]; - int ret; + cl_error_t ret; unsigned executed = 0, breakflag = 0, errorflag = 0; if (!cctx) @@ -2881,12 +2908,17 @@ int cli_bytecode_runhook(cli_ctx *cctx, const struct cl_engine *engine, struct c } if (ctx->virname) { cli_dbgmsg("Bytecode runhook found virus: %s\n", ctx->virname); - cli_append_virus(cctx, ctx->virname); - if (!(cctx->options->general & CL_SCAN_GENERAL_ALLMATCHES)) { - cli_bytecode_context_clear(ctx); + + if (!strncmp(ctx->virname, "BC.Heuristics", 13)) { + ret = cli_append_potentially_unwanted(cctx, ctx->virname); + } else { + ret = cli_append_virus(cctx, ctx->virname); + } + if (ret == CL_VIRUS) { + bytecode_context_reset(ctx); return CL_VIRUS; } - cli_bytecode_context_reset(ctx); + bytecode_context_reset(ctx); continue; } ret = cli_bytecode_context_getresult_int(ctx); @@ -2898,58 +2930,79 @@ int cli_bytecode_runhook(cli_ctx *cctx, const struct cl_engine *engine, struct c } if (!ret) { char *tempfile; + int fd = cli_bytecode_context_getresult_file(ctx, &tempfile); if (fd && fd != -1) { - if (cctx->engine->keeptmp) + if (cctx->engine->keeptmp) { cli_dbgmsg("Bytecode %u unpacked file saved in %s\n", bc->id, tempfile); - else + } else { cli_dbgmsg("Bytecode %u unpacked file\n", bc->id); + } + lseek(fd, 0, SEEK_SET); cli_dbgmsg("***** Scanning unpacked file ******\n"); ret = cli_magic_scan_desc(fd, tempfile, cctx, NULL, LAYER_ATTRIBUTES_NONE); - if (!cctx->engine->keeptmp) - if (ftruncate(fd, 0) == -1) + if (!cctx->engine->keeptmp) { + if (ftruncate(fd, 0) == -1) { cli_dbgmsg("ftruncate failed on %d\n", fd); + } + } + close(fd); + if (!cctx->engine->keeptmp) { - if (tempfile && cli_unlink(tempfile)) + if (tempfile && cli_unlink(tempfile)) { ret = CL_EUNLINK; + } } + free(tempfile); - if (ret != CL_CLEAN) { - if (ret == CL_VIRUS) { - cli_dbgmsg("Scanning unpacked file by bytecode %u found a virus\n", bc->id); - if (cctx->options->general & CL_SCAN_GENERAL_ALLMATCHES) { - cli_bytecode_context_reset(ctx); - continue; - } - cli_bytecode_context_clear(ctx); - return ret; - } + + if (ret != CL_SUCCESS) { + cli_dbgmsg("Scanning unpacked file by bytecode %u found a reason to stop: %s\n", bc->id, cl_strerror(ret)); + bytecode_context_reset(ctx); + return ret; } - cli_bytecode_context_reset(ctx); + + bytecode_context_reset(ctx); continue; } } - cli_bytecode_context_reset(ctx); + bytecode_context_reset(ctx); } if (executed) cli_dbgmsg("Bytecode: executed %u bytecodes for this hook\n", executed); else cli_dbgmsg("Bytecode: no logical signature matched, no bytecode executed\n"); + if (errorflag && cctx->engine->bytecode_mode == CL_BYTECODE_MODE_TEST) return CL_EBYTECODE_TESTFAIL; + return breakflag ? CL_BREAK : CL_CLEAN; } -int cli_bytecode_context_setpe(struct cli_bc_ctx *ctx, const struct cli_pe_hook_data *data, const struct cli_exe_section *sections) +cl_error_t cli_bytecode_context_setpe(struct cli_bc_ctx *ctx, const struct cli_pe_hook_data *data, const struct cli_exe_section *sections) { ctx->sections = sections; ctx->hooks.pedata = data; - return 0; + return CL_SUCCESS; +} + +cl_error_t cli_bytecode_context_setpdf(struct cli_bc_ctx *ctx, unsigned phase, + unsigned nobjs, + struct pdf_obj **objs, uint32_t *pdf_flags, + uint32_t pdfsize, uint32_t pdfstartoff) +{ + ctx->pdf_nobjs = nobjs; + ctx->pdf_objs = objs; + ctx->pdf_flags = pdf_flags; + ctx->pdf_size = pdfsize; + ctx->pdf_startoff = pdfstartoff; + ctx->pdf_phase = phase; + return CL_SUCCESS; } void cli_bytecode_context_setctx(struct cli_bc_ctx *ctx, void *cctx) @@ -2964,7 +3017,7 @@ void cli_bytecode_describe(const struct cli_bc *bc) int cols; unsigned i; time_t stamp; - int had; + bool had; if (!bc) { printf("(null bytecode)\n"); @@ -3071,7 +3124,7 @@ void cli_bytecode_describe(const struct cli_bc *bc) printf("\tnumber of debug nodes: %u\n", bc->dbgnode_cnt); printf("\tbytecode APIs used:"); cols = 0; /* remaining */ - had = 0; + had = false; for (i = 0; i < cli_apicall_maxapi; i++) { if (cli_bitset_test(bc->uses_apis, i)) { unsigned len = strlen(cli_apicalls[i].name); @@ -3082,7 +3135,7 @@ void cli_bytecode_describe(const struct cli_bc *bc) cols = 72; } printf(" %s", cli_apicalls[i].name); - had = 1; + had = true; cols -= len; } } @@ -3405,7 +3458,7 @@ void cli_byteinst_describe(const struct cli_bc_inst *inst, unsigned *bbnum) case OP_BC_CALL_DIRECT: printf("%d = call F.%d (", inst->dest, inst->u.ops.funcid); for (j = 0; j < inst->u.ops.numOps; ++j) { - if (j == inst->u.ops.numOps - 1) { + if (j == (size_t)(inst->u.ops.numOps - 1)) { printf("%d", inst->u.ops.ops[j]); } else { printf("%d, ", inst->u.ops.ops[j]); diff --git a/libclamav/bytecode.h b/libclamav/bytecode.h index 85017f2007..57520c7c94 100644 --- a/libclamav/bytecode.h +++ b/libclamav/bytecode.h @@ -90,13 +90,13 @@ struct pdf_obj; struct cli_bc_ctx *cli_bytecode_context_alloc(void); /* FIXME: we can't include others.h because others.h includes us...*/ void cli_bytecode_context_setctx(struct cli_bc_ctx *ctx, void *cctx); -int cli_bytecode_context_setfuncid(struct cli_bc_ctx *ctx, const struct cli_bc *bc, unsigned funcid); -int cli_bytecode_context_setparam_int(struct cli_bc_ctx *ctx, unsigned i, uint64_t c); -int cli_bytecode_context_setparam_ptr(struct cli_bc_ctx *ctx, unsigned i, void *data, unsigned datalen); -int cli_bytecode_context_setfile(struct cli_bc_ctx *ctx, fmap_t *map); -int cli_bytecode_context_setpe(struct cli_bc_ctx *ctx, const struct cli_pe_hook_data *data, const struct cli_exe_section *sections); -int cli_bytecode_context_setpdf(struct cli_bc_ctx *ctx, unsigned phase, unsigned nobjs, struct pdf_obj **objs, uint32_t *pdf_flags, uint32_t pdfsize, uint32_t pdfstartoff); -int cli_bytecode_context_clear(struct cli_bc_ctx *ctx); +cl_error_t cli_bytecode_context_setfuncid(struct cli_bc_ctx *ctx, const struct cli_bc *bc, unsigned funcid); +cl_error_t cli_bytecode_context_setparam_int(struct cli_bc_ctx *ctx, unsigned i, uint64_t c); +cl_error_t cli_bytecode_context_setparam_ptr(struct cli_bc_ctx *ctx, unsigned i, void *data, unsigned datalen); +cl_error_t cli_bytecode_context_setfile(struct cli_bc_ctx *ctx, fmap_t *map); +cl_error_t cli_bytecode_context_setpe(struct cli_bc_ctx *ctx, const struct cli_pe_hook_data *data, const struct cli_exe_section *sections); +cl_error_t cli_bytecode_context_setpdf(struct cli_bc_ctx *ctx, unsigned phase, unsigned nobjs, struct pdf_obj **objs, uint32_t *pdf_flags, uint32_t pdfsize, uint32_t pdfstartoff); + /* returns file descriptor, sets tempfile. Caller takes ownership, and is * responsible for freeing/unlinking */ int cli_bytecode_context_getresult_file(struct cli_bc_ctx *ctx, char **tempfilename); @@ -106,16 +106,16 @@ void cli_bytecode_context_destroy(struct cli_bc_ctx *ctx); #ifdef __cplusplus extern "C" { #endif -extern LIBCLAMAV_EXPORT int have_clamjit(); +extern LIBCLAMAV_EXPORT bool have_clamjit(); #ifdef __cplusplus } #endif -int cli_bytecode_init(struct cli_all_bc *allbc); -int cli_bytecode_load(struct cli_bc *bc, FILE *f, struct cli_dbio *dbio, int security, int sigperf); -int cli_bytecode_prepare2(struct cl_engine *engine, struct cli_all_bc *allbc, unsigned dconfmask); -int cli_bytecode_run(const struct cli_all_bc *bcs, const struct cli_bc *bc, struct cli_bc_ctx *ctx); +cl_error_t cli_bytecode_init(struct cli_all_bc *allbc); +cl_error_t cli_bytecode_load(struct cli_bc *bc, FILE *f, struct cli_dbio *dbio, int security, int sigperf); +cl_error_t cli_bytecode_prepare2(struct cl_engine *engine, struct cli_all_bc *allbc, unsigned dconfmask); +cl_error_t cli_bytecode_run(const struct cli_all_bc *bcs, const struct cli_bc *bc, struct cli_bc_ctx *ctx); void cli_bytecode_destroy(struct cli_bc *bc); -int cli_bytecode_done(struct cli_all_bc *allbc); +cl_error_t cli_bytecode_done(struct cli_all_bc *allbc); /* Bytecode IR descriptions */ void cli_bytecode_describe(const struct cli_bc *bc); @@ -128,14 +128,14 @@ void cli_bytefunc_describe(const struct cli_bc *bc, unsigned funcid); struct cli_exe_info; struct cli_ctx_tag; struct cli_target_info; -int cli_bytecode_runlsig(struct cli_ctx_tag *ctx, struct cli_target_info *info, const struct cli_all_bc *bcs, unsigned bc_idx, const uint32_t *lsigcnt, const uint32_t *lsigsuboff, fmap_t *map); -int cli_bytecode_runhook(struct cli_ctx_tag *cctx, const struct cl_engine *engine, struct cli_bc_ctx *ctx, unsigned id, fmap_t *map); +cl_error_t cli_bytecode_runlsig(struct cli_ctx_tag *ctx, struct cli_target_info *info, const struct cli_all_bc *bcs, unsigned bc_idx, const uint32_t *lsigcnt, const uint32_t *lsigsuboff, fmap_t *map); +cl_error_t cli_bytecode_runhook(struct cli_ctx_tag *cctx, const struct cl_engine *engine, struct cli_bc_ctx *ctx, unsigned id, fmap_t *map); #ifdef __cplusplus extern "C" { #endif -int bytecode_init(void); +cl_error_t bytecode_init(void); /* Bytecode internal debug API */ void cli_bytecode_debug(int argc, char **argv); void cli_bytecode_printversion(void); diff --git a/libclamav/bytecode_api.c b/libclamav/bytecode_api.c index f59ff11dc9..be249d31fe 100644 --- a/libclamav/bytecode_api.c +++ b/libclamav/bytecode_api.c @@ -220,8 +220,8 @@ int32_t cli_bcapi_write(struct cli_bc_ctx *ctx, uint8_t *data, int32_t len) API_MISUSE(); return -1; } - if (!ctx->outfd) { - ctx->tempfile = cli_gentemp(cctx ? cctx->engine->tmpdir : NULL); + if (-1 == ctx->outfd) { + ctx->tempfile = cli_gentemp_with_prefix(cctx ? cctx->sub_tmpdir : NULL, "bcapi_write"); if (!ctx->tempfile) { cli_dbgmsg("Bytecode API: Unable to allocate memory for tempfile\n"); cli_event_error_oom(EV, 0); @@ -229,7 +229,6 @@ int32_t cli_bcapi_write(struct cli_bc_ctx *ctx, uint8_t *data, int32_t len) } ctx->outfd = open(ctx->tempfile, O_RDWR | O_CREAT | O_EXCL | O_TRUNC | O_BINARY, 0600); if (ctx->outfd == -1) { - ctx->outfd = 0; cli_warnmsg("Bytecode API: Can't create file %s: %s\n", ctx->tempfile, cli_strerror(errno, err, sizeof(err))); cli_event_error_str(EV, "cli_bcapi_write: Can't create temporary file"); free(ctx->tempfile); @@ -558,13 +557,14 @@ int32_t cli_bcapi_extract_new(struct cli_bc_ctx *ctx, int32_t id) } if ((cctx && cctx->engine->keeptmp) || (ftruncate(ctx->outfd, 0) == -1)) { - close(ctx->outfd); - if (!(cctx && cctx->engine->keeptmp) && ctx->tempfile) + ctx->outfd = -1; + + if (!(cctx && cctx->engine->keeptmp) && ctx->tempfile) { cli_unlink(ctx->tempfile); + } free(ctx->tempfile); ctx->tempfile = NULL; - ctx->outfd = 0; } cli_dbgmsg("bytecode: extracting new file with id %u\n", id); return res; @@ -626,7 +626,7 @@ int32_t cli_bcapi_hashset_add(struct cli_bc_ctx *ctx, int32_t id, uint32_t key) struct cli_hashset *s = get_hashset(ctx, id); if (!s) return -1; - return cli_hashset_addkey(s, key); + return cli_hashset_addkey(s, key) == CL_SUCCESS ? 0 : -1; } int32_t cli_bcapi_hashset_remove(struct cli_bc_ctx *ctx, int32_t id, uint32_t key) @@ -634,7 +634,7 @@ int32_t cli_bcapi_hashset_remove(struct cli_bc_ctx *ctx, int32_t id, uint32_t ke struct cli_hashset *s = get_hashset(ctx, id); if (!s) return -1; - return cli_hashset_removekey(s, key); + return cli_hashset_removekey(s, key) == CL_SUCCESS ? 0 : -1; } int32_t cli_bcapi_hashset_contains(struct cli_bc_ctx *ctx, int32_t id, uint32_t key) @@ -1402,7 +1402,7 @@ int32_t cli_bcapi_map_new(struct cli_bc_ctx *ctx, int32_t keysize, int32_t value ctx->maps = s; ctx->nmaps = n; s = &s[n - 1]; - cli_map_init(s, keysize, valuesize, 16); + (void)cli_map_init(s, keysize, valuesize, 16); return n - 1; } @@ -1415,10 +1415,26 @@ static struct cli_map *get_hashtab(struct cli_bc_ctx *ctx, int32_t id) int32_t cli_bcapi_map_addkey(struct cli_bc_ctx *ctx, const uint8_t *key, int32_t keysize, int32_t id) { + cl_error_t ret; struct cli_map *s = get_hashtab(ctx, id); if (!s) return -1; - return cli_map_addkey(s, key, keysize); + + ret = cli_map_addkey(s, key, keysize); + switch (ret) { + case CL_SUCCESS: { + // key didn't exist and was added + return 1; + } + case CL_ECREAT: { + // already added + return 0; + } + default: { + // error occurred + return -1; + } + } } int32_t cli_bcapi_map_setvalue(struct cli_bc_ctx *ctx, const uint8_t *value, int32_t valuesize, int32_t id) @@ -1426,23 +1442,55 @@ int32_t cli_bcapi_map_setvalue(struct cli_bc_ctx *ctx, const uint8_t *value, int struct cli_map *s = get_hashtab(ctx, id); if (!s) return -1; - return cli_map_setvalue(s, value, valuesize); + return cli_map_setvalue(s, value, valuesize) == CL_SUCCESS ? 0 : -1; } int32_t cli_bcapi_map_remove(struct cli_bc_ctx *ctx, const uint8_t *key, int32_t keysize, int32_t id) { + cl_error_t ret; struct cli_map *s = get_hashtab(ctx, id); if (!s) return -1; - return cli_map_removekey(s, key, keysize); + + ret = cli_map_removekey(s, key, keysize); + switch (ret) { + case CL_SUCCESS: { + // found and removed + return 1; + } + case CL_EUNLINK: { + // not found + return 0; + } + default: { + // error occurred + return -1; + } + } } int32_t cli_bcapi_map_find(struct cli_bc_ctx *ctx, const uint8_t *key, int32_t keysize, int32_t id) { + cl_error_t ret; struct cli_map *s = get_hashtab(ctx, id); if (!s) return -1; - return cli_map_find(s, key, keysize); + + ret = cli_map_find(s, key, keysize); + switch (ret) { + case CL_SUCCESS: { + // found + return 1; + } + case CL_EACCES: { + // not found + return 0; + } + default: { + // error occurred + return -1; + } + } } int32_t cli_bcapi_map_getvaluesize(struct cli_bc_ctx *ctx, int32_t id) @@ -1460,7 +1508,7 @@ uint8_t *cli_bcapi_map_getvalue(struct cli_bc_ctx *ctx, int32_t id, int32_t valu return NULL; if (cli_map_getvalue_size(s) != valuesize) return NULL; - return cli_map_getvalue(s); + return (uint8_t *)cli_map_getvalue(s); } int32_t cli_bcapi_map_done(struct cli_bc_ctx *ctx, int32_t id) @@ -1851,20 +1899,6 @@ uint32_t cli_bcapi_check_platform(struct cli_bc_ctx *ctx, uint32_t a, uint32_t b return ret; } -int cli_bytecode_context_setpdf(struct cli_bc_ctx *ctx, unsigned phase, - unsigned nobjs, - struct pdf_obj **objs, uint32_t *pdf_flags, - uint32_t pdfsize, uint32_t pdfstartoff) -{ - ctx->pdf_nobjs = nobjs; - ctx->pdf_objs = objs; - ctx->pdf_flags = pdf_flags; - ctx->pdf_size = pdfsize; - ctx->pdf_startoff = pdfstartoff; - ctx->pdf_phase = phase; - return 0; -} - int32_t cli_bcapi_pdf_get_obj_num(struct cli_bc_ctx *ctx) { if (!ctx->pdf_phase) diff --git a/libclamav/bytecode_detect.c b/libclamav/bytecode_detect.c index 867280d8dd..503bce629d 100644 --- a/libclamav/bytecode_detect.c +++ b/libclamav/bytecode_detect.c @@ -41,7 +41,7 @@ #define CHECK_ARCH(a) \ if (!strcmp(TARGET_ARCH_TYPE, #a)) env->arch = arch_##a -extern int have_clamjit(); +extern bool have_clamjit(); static void cli_print_environment(struct cli_environment *env) { diff --git a/libclamav/bytecode_nojit.c b/libclamav/bytecode_nojit.c index 0e836c919f..733b00636f 100644 --- a/libclamav/bytecode_nojit.c +++ b/libclamav/bytecode_nojit.c @@ -29,7 +29,7 @@ #include "clamav.h" #include "others.h" -int cli_bytecode_prepare_jit(struct cli_all_bc *bcs) +cl_error_t cli_bytecode_prepare_jit(struct cli_all_bc *bcs) { unsigned i; for (i = 0; i < bcs->count; i++) { @@ -45,7 +45,7 @@ int cli_bytecode_prepare_jit(struct cli_all_bc *bcs) return CL_EBYTECODE; } -int cli_vm_execute_jit(const struct cli_all_bc *bcs, struct cli_bc_ctx *ctx, const struct cli_bc_func *func) +cl_error_t cli_vm_execute_jit(const struct cli_all_bc *bcs, struct cli_bc_ctx *ctx, const struct cli_bc_func *func) { UNUSEDPARAM(bcs); UNUSEDPARAM(ctx); @@ -53,14 +53,14 @@ int cli_vm_execute_jit(const struct cli_all_bc *bcs, struct cli_bc_ctx *ctx, con return CL_EBYTECODE; } -int cli_bytecode_init_jit(struct cli_all_bc *allbc, unsigned dconfmask) +cl_error_t cli_bytecode_init_jit(struct cli_all_bc *allbc, unsigned dconfmask) { UNUSEDPARAM(allbc); UNUSEDPARAM(dconfmask); return CL_SUCCESS; } -int cli_bytecode_done_jit(struct cli_all_bc *allbc, int partial) +cl_error_t cli_bytecode_done_jit(struct cli_all_bc *allbc, int partial) { UNUSEDPARAM(allbc); UNUSEDPARAM(partial); @@ -74,9 +74,9 @@ void cli_bytecode_debug(int argc, char **argv) UNUSEDPARAM(argv); } -int bytecode_init(void) +cl_error_t bytecode_init(void) { - return 0; + return CL_SUCCESS; } void cli_bytecode_debug_printsrc(const struct cli_bc_ctx *ctx) @@ -88,9 +88,9 @@ void cli_bytecode_printversion(void) { printf("LLVM is not compiled or not linked\n"); } -int have_clamjit() +bool have_clamjit() { - return 0; + return false; } void cli_printcxxver() diff --git a/libclamav/bytecode_priv.h b/libclamav/bytecode_priv.h index c683403b77..6144ea97da 100644 --- a/libclamav/bytecode_priv.h +++ b/libclamav/bytecode_priv.h @@ -242,16 +242,16 @@ struct cli_bc_ctx { #endif }; struct cli_all_bc; -int cli_vm_execute(const struct cli_bc *bc, struct cli_bc_ctx *ctx, const struct cli_bc_func *func, const struct cli_bc_inst *inst); +cl_error_t cli_vm_execute(const struct cli_bc *bc, struct cli_bc_ctx *ctx, const struct cli_bc_func *func, const struct cli_bc_inst *inst); #ifdef __cplusplus extern "C" { #endif -int cli_vm_execute_jit(const struct cli_all_bc *bcs, struct cli_bc_ctx *ctx, const struct cli_bc_func *func); -int cli_bytecode_prepare_jit(struct cli_all_bc *bc); -int cli_bytecode_init_jit(struct cli_all_bc *bc, unsigned dconfmask); -int cli_bytecode_done_jit(struct cli_all_bc *bc, int partial); +cl_error_t cli_vm_execute_jit(const struct cli_all_bc *bcs, struct cli_bc_ctx *ctx, const struct cli_bc_func *func); +cl_error_t cli_bytecode_prepare_jit(struct cli_all_bc *bc); +cl_error_t cli_bytecode_init_jit(struct cli_all_bc *bc, unsigned dconfmask); +cl_error_t cli_bytecode_done_jit(struct cli_all_bc *bc, int partial); #ifdef __cplusplus } diff --git a/libclamav/bytecode_vm.c b/libclamav/bytecode_vm.c index 74953c852b..adc594f459 100644 --- a/libclamav/bytecode_vm.c +++ b/libclamav/bytecode_vm.c @@ -685,7 +685,7 @@ static struct { {(void *)cli_bcapi_get_pe_section, sizeof(struct cli_exe_section)}, }; -int cli_vm_execute(const struct cli_bc *bc, struct cli_bc_ctx *ctx, const struct cli_bc_func *func, const struct cli_bc_inst *inst) +cl_error_t cli_vm_execute(const struct cli_bc *bc, struct cli_bc_ctx *ctx, const struct cli_bc_func *func, const struct cli_bc_inst *inst) { size_t i; uint32_t j; @@ -1162,7 +1162,7 @@ int cli_vm_execute(const struct cli_bc *bc, struct cli_bc_ctx *ctx, const struct READ64(ptr, inst->u.three[1]); off += (ptr & 0x00000000ffffffffULL); iptr = (ptr & 0xffffffff00000000ULL) + (uint64_t)(off); - WRITE64(inst->dest, ptr + off); + WRITE64(inst->dest, iptr); } break; } @@ -1275,8 +1275,8 @@ int cli_vm_execute(const struct cli_bc *bc, struct cli_bc_ctx *ctx, const struct } else { READ64(ptr, inst->u.three[1]); off *= inst->u.three[0]; - off += (ptr & 0x00000000ffffffff); - iptr = (ptr & 0xffffffff00000000) + (uint64_t)(off); + off += (ptr & 0x00000000ffffffffULL); + iptr = (ptr & 0xffffffff00000000ULL) + (uint64_t)(off); WRITE64(inst->dest, iptr); } break; diff --git a/libclamav/c++/bytecode2llvm.cpp b/libclamav/c++/bytecode2llvm.cpp index 8bd76cf45f..a8929c6d54 100644 --- a/libclamav/c++/bytecode2llvm.cpp +++ b/libclamav/c++/bytecode2llvm.cpp @@ -214,9 +214,9 @@ void cli_warnmsg(const char *str, ...); #endif #ifdef __GNUC__ -inline void cli_dbgmsg(const char *str, ...) __attribute__((format(printf, 1, 2))); +void cli_dbgmsg_no_inline(const char *str, ...) __attribute__((format(printf, 1, 2))); #else -inline void cli_dbgmsg(const char *str, ...); +void cli_dbgmsg_no_inline(const char *str, ...); #endif } @@ -398,9 +398,9 @@ class NotifyListener : public JITEventListener { if (!cli_debug_flag) return; - cli_dbgmsg("[Bytecode JIT]; emitted %s %s of %zd bytes\n", - Obj.getFileFormatName().str().c_str(), - Obj.getFileName().str().c_str(), Obj.getData().size()); + cli_dbgmsg_no_inline("[Bytecode JIT]; emitted %s %s of %zd bytes\n", + Obj.getFileFormatName().str().c_str(), + Obj.getFileName().str().c_str(), Obj.getData().size()); } }; @@ -878,7 +878,7 @@ class LLVMCodegen V->print(ostr); Ty->print(ostr); // M->dump(); - cli_dbgmsg("[Bytecode JIT]: operand %d: %s\n", operand, ostr.str().c_str()); + cli_dbgmsg_no_inline("[Bytecode JIT]: operand %d: %s\n", operand, ostr.str().c_str()); } llvm_report_error("(libclamav) Type mismatch converting operand"); } @@ -1051,7 +1051,7 @@ class LLVMCodegen Value *V = createGEP(Base, ETy, ARef); if (!V) { if (cli_debug_flag) { - cli_dbgmsg("[Bytecode JIT] @%d\n", dest); + cli_dbgmsg_no_inline("[Bytecode JIT] @%d\n", dest); } return false; } @@ -1261,7 +1261,7 @@ class LLVMCodegen raw_string_ostream ostr(str); ostr << i << ":" << g << ":" << bc->globals[i][0] << "\n"; Ty->print(ostr); - cli_dbgmsg("[Bytecode JIT]: %s\n", ostr.str().c_str()); + cli_dbgmsg_no_inline("[Bytecode JIT]: %s\n", ostr.str().c_str()); } llvm_report_error("(libclamav) unable to create fake global"); } @@ -1688,7 +1688,7 @@ class LLVMCodegen std::string str; raw_string_ostream ostr(str); F->print(ostr); - cli_dbgmsg("[Bytecode JIT]: %s\n", ostr.str().c_str()); + cli_dbgmsg_no_inline("[Bytecode JIT]: %s\n", ostr.str().c_str()); } } } @@ -1920,7 +1920,7 @@ static void *bytecode_watchdog(void *arg) char err[128]; pthread_mutex_lock(&watchdog_mutex); if (cli_debug_flag) - cli_dbgmsg("bytecode watchdog is running\n"); + cli_dbgmsg_no_inline("bytecode watchdog is running\n"); do { struct watchdog_item *item; gettimeofday(&tv, NULL); @@ -1966,7 +1966,7 @@ static void *bytecode_watchdog(void *arg) } while (1); watchdog_running = 0; if (cli_debug_flag) - cli_dbgmsg("bytecode watchdog quiting\n"); + cli_dbgmsg_no_inline("bytecode watchdog quiting\n"); pthread_mutex_unlock(&watchdog_mutex); return NULL; } @@ -2038,7 +2038,7 @@ static int watchdog_arm(struct watchdog_item *item, int ms, volatile uint8_t *ti return rc; } -static int bytecode_execute(intptr_t code, struct cli_bc_ctx *ctx) +static cl_error_t bytecode_execute(intptr_t code, struct cli_bc_ctx *ctx) { ScopedExceptionHandler handler; // execute; @@ -2047,17 +2047,17 @@ static int bytecode_execute(intptr_t code, struct cli_bc_ctx *ctx) // setup exception handler to longjmp back here uint32_t result = ((uint32_t(*)(struct cli_bc_ctx *))(intptr_t)code)(ctx); *(uint32_t *)ctx->values = result; - return 0; + return CL_SUCCESS; } HANDLER_END(handler); cli_warnmsg("[%s]: JITed code intercepted runtime error!\n", MODULE); return CL_EBYTECODE; } -int cli_vm_execute_jit(const struct cli_all_bc *bcs, struct cli_bc_ctx *ctx, - const struct cli_bc_func *func) +cl_error_t cli_vm_execute_jit(const struct cli_all_bc *bcs, struct cli_bc_ctx *ctx, + const struct cli_bc_func *func) { - int ret; + cl_error_t ret; struct timeval tv0, tv1; struct watchdog_item witem; // no locks needed here, since LLVM automatically acquires a JIT lock @@ -2091,7 +2091,7 @@ int cli_vm_execute_jit(const struct cli_all_bc *bcs, struct cli_bc_ctx *ctx, tv1.tv_sec -= tv0.tv_sec; tv1.tv_usec -= tv0.tv_usec; diff = tv1.tv_sec * 1000000 + tv1.tv_usec; - cli_dbgmsg("bytecode finished in %ld us\n", diff); + cli_dbgmsg_no_inline("bytecode finished in %ld us\n", diff); } return ctx->timeout ? CL_ETIMEOUT : ret; } // namespace @@ -2117,7 +2117,7 @@ static void addFPasses(legacy::FunctionPassManager &FPM, bool trusted, Module *M FPM.add(createDeadCodeEliminationPass()); } -int cli_bytecode_prepare_jit(struct cli_all_bc *bcs) +cl_error_t cli_bytecode_prepare_jit(struct cli_all_bc *bcs) { if (!bcs->engine) return CL_EBYTECODE; @@ -2313,14 +2313,14 @@ int cli_bytecode_prepare_jit(struct cli_all_bc *bcs) cli_errmsg("[Bytecode JIT]: Unexpected unknown exception occurred\n"); return CL_EBYTECODE; } - return 0; + return CL_SUCCESS; } HANDLER_END(handler); cli_errmsg("[Bytecode JIT] *** FATAL error encountered during bytecode generation\n"); return CL_EBYTECODE; } -int bytecode_init(void) +cl_error_t bytecode_init(void) { if (!LLVMIsMultithreaded()) { cli_warnmsg("[%s] bytecode_init: LLVM is compiled without multithreading support\n", MODULE); @@ -2352,11 +2352,11 @@ int bytecode_init(void) "should be built for i686, not i386!\n"; cli_warnmsg("[%s] %s", MODULE, warnmsg); } - return 0; + return CL_SUCCESS; } // Called once when loading a new set of BC files -int cli_bytecode_init_jit(struct cli_all_bc *bcs, unsigned dconfmask) +cl_error_t cli_bytecode_init_jit(struct cli_all_bc *bcs, unsigned dconfmask) { LLVMApiScopedLock scopedLock; bcs->engine = new (std::nothrow) cli_bcengine; @@ -2364,10 +2364,10 @@ int cli_bytecode_init_jit(struct cli_all_bc *bcs, unsigned dconfmask) return CL_EMEM; bcs->engine->EE = 0; bcs->engine->Listener = 0; - return 0; + return CL_SUCCESS; } -int cli_bytecode_done_jit(struct cli_all_bc *bcs, int partial) +cl_error_t cli_bytecode_done_jit(struct cli_all_bc *bcs, int partial) { LLVMApiScopedLock scopedLock; if (bcs->engine) { @@ -2384,7 +2384,7 @@ int cli_bytecode_done_jit(struct cli_all_bc *bcs, int partial) bcs->engine = 0; } } - return 0; + return CL_SUCCESS; } void cli_bytecode_debug(int argc, char **argv) @@ -2460,9 +2460,9 @@ void cli_bytecode_debug_printsrc(const struct cli_bc_ctx *ctx) assert(ctx->line < lines->linev.size()); } -int have_clamjit() +bool have_clamjit() { - return 1; + return true; } void cli_bytecode_printversion() diff --git a/libclamav/cache.c b/libclamav/cache.c index aa9b821a52..9c5c66b10f 100644 --- a/libclamav/cache.c +++ b/libclamav/cache.c @@ -33,6 +33,8 @@ #include "cache.h" #include "fmap.h" +#include "clamav_rust.h" + /* The number of root trees and the chooser function Each tree is protected by a mutex against concurrent access */ /* #define TREES 1 */ @@ -73,6 +75,13 @@ struct cache_set { /* a tree */ struct node *last; }; +struct CACHE { + struct cache_set cacheset; +#ifdef CL_THREAD_SAFE + pthread_mutex_t mutex; +#endif +}; + /* Allocates all the nodes and sets up the replacement chain */ static int cacheset_init(struct cache_set *cs, mpool_t *mempool) { @@ -519,40 +528,62 @@ static inline void cacheset_remove(struct cache_set *cs, unsigned char *md5, siz printchain("remove (after)", cs); } -/* COMMON STUFF --------------------------------------------------------------------- */ +/* Looks up an hash in the proper tree */ +static int cache_lookup_hash(unsigned char *md5, size_t len, struct CACHE *cache, uint32_t recursion_level) +{ + unsigned int key = 0; + int ret = CL_VIRUS; + struct CACHE *c; + + if (!md5) { + cli_dbgmsg("cache_lookup: No hash available. Nothing to look up.\n"); + return ret; + } + + key = getkey(md5); + + c = &cache[key]; -struct CACHE { - struct cache_set cacheset; #ifdef CL_THREAD_SAFE - pthread_mutex_t mutex; + if (pthread_mutex_lock(&c->mutex)) { + cli_errmsg("cache_lookup_hash: cache_lookup_hash: mutex lock fail\n"); + return ret; + } #endif -}; -/* Allocates the trees for the engine cache */ -int cli_cache_init(struct cl_engine *engine) + ret = (cacheset_lookup(&c->cacheset, md5, len, recursion_level)) ? CL_CLEAN : CL_VIRUS; + +#ifdef CL_THREAD_SAFE + pthread_mutex_unlock(&c->mutex); +#endif + + return ret; +} + +int clean_cache_init(struct cl_engine *engine) { struct CACHE *cache; unsigned int i, j; if (!engine) { - cli_errmsg("cli_cache_init: mpool malloc fail\n"); + cli_errmsg("clean_cache_init: mpool malloc fail\n"); return 1; } if (engine->engine_options & ENGINE_OPTIONS_DISABLE_CACHE) { - cli_dbgmsg("cli_cache_init: Caching disabled.\n"); + cli_dbgmsg("clean_cache_init: Caching disabled.\n"); return 0; } if (!(cache = MPOOL_MALLOC(engine->mempool, sizeof(struct CACHE) * TREES))) { - cli_errmsg("cli_cache_init: mpool malloc fail\n"); + cli_errmsg("clean_cache_init: mpool malloc fail\n"); return 1; } for (i = 0; i < TREES; i++) { #ifdef CL_THREAD_SAFE if (pthread_mutex_init(&cache[i].mutex, NULL)) { - cli_errmsg("cli_cache_init: mutex init fail\n"); + cli_errmsg("clean_cache_init: mutex init fail\n"); for (j = 0; j < i; j++) cacheset_destroy(&cache[j].cacheset, engine->mempool); for (j = 0; j < i; j++) pthread_mutex_destroy(&cache[j].mutex); MPOOL_FREE(engine->mempool, cache); @@ -572,8 +603,7 @@ int cli_cache_init(struct cl_engine *engine) return 0; } -/* Frees the engine cache */ -void cli_cache_destroy(struct cl_engine *engine) +void clean_cache_destroy(struct cl_engine *engine) { struct CACHE *cache; unsigned int i; @@ -594,40 +624,7 @@ void cli_cache_destroy(struct cl_engine *engine) MPOOL_FREE(engine->mempool, cache); } -/* Looks up an hash in the proper tree */ -static int cache_lookup_hash(unsigned char *md5, size_t len, struct CACHE *cache, uint32_t recursion_level) -{ - unsigned int key = 0; - int ret = CL_VIRUS; - struct CACHE *c; - - if (!md5) { - cli_dbgmsg("cache_lookup: No hash available. Nothing to look up.\n"); - return ret; - } - - key = getkey(md5); - - c = &cache[key]; -#ifdef CL_THREAD_SAFE - if (pthread_mutex_lock(&c->mutex)) { - cli_errmsg("cache_lookup_hash: cache_lookup_hash: mutex lock fail\n"); - return ret; - } -#endif - - /* cli_warnmsg("cache_lookup_hash: key is %u\n", key); */ - - ret = (cacheset_lookup(&c->cacheset, md5, len, recursion_level)) ? CL_CLEAN : CL_VIRUS; -#ifdef CL_THREAD_SAFE - pthread_mutex_unlock(&c->mutex); - // if(ret == CL_CLEAN) cli_warnmsg("cached\n"); -#endif - return ret; -} - -/* Adds an hash to the cache */ -void cache_add(unsigned char *md5, size_t size, cli_ctx *ctx) +void clean_cache_add(unsigned char *md5, size_t size, cli_ctx *ctx) { const char *errmsg = NULL; @@ -639,24 +636,40 @@ void cache_add(unsigned char *md5, size_t size, cli_ctx *ctx) return; if (ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_CACHE) { - cli_dbgmsg("cache_add: Caching disabled. Not adding sample to cache.\n"); + cli_dbgmsg("clean_cache_add: Caching disabled. Not adding sample to cache.\n"); return; } if (!md5) { - cli_dbgmsg("cache_add: No hash available. Nothing to add to cache.\n"); + cli_dbgmsg("clean_cache_add: No hash available. Nothing to add to cache.\n"); return; } - key = getkey(md5); - level = (ctx->fmap && ctx->fmap->dont_cache_flag) ? ctx->recursion_level : 0; - if (ctx->found_possibly_unwanted && (level || 0 == ctx->recursion_level)) + if (SCAN_COLLECT_METADATA) { + // Don't cache when using the "collect metadata" feature. + // We don't cache the JSON, so we can't reproduce it when the cache is positive. + cli_dbgmsg("clean_cache_add: collect metadata feature enabled, skipping cache\n"); return; - if (SCAN_ALLMATCHES && (ctx->num_viruses > 0)) { - cli_dbgmsg("cache_add: alert found within same topfile, skipping cache\n"); + } + + if (ctx->fmap && ctx->fmap->dont_cache_flag == true) { + cli_dbgmsg("clean_cache_add: caching disabled for this layer, skipping cache\n"); return; } - c = &ctx->engine->cache[key]; + + if (0 < evidence_num_alerts(ctx->evidence)) { + // TODO: The dont cache flag should take care of preventing caching of files with embedded files that alert. + // Consider removing this check to allow caching of other actually clean files found within archives. + // It would be a (very) minor optimization. + cli_dbgmsg("clean_cache_add: alert found within same topfile, skipping cache\n"); + return; + } + + level = (ctx->fmap && ctx->fmap->dont_cache_flag) ? ctx->recursion_level : 0; + + key = getkey(md5); + c = &ctx->engine->cache[key]; + #ifdef CL_THREAD_SAFE if (pthread_mutex_lock(&c->mutex)) { cli_errmsg("cli_add: mutex lock fail\n"); @@ -664,8 +677,6 @@ void cache_add(unsigned char *md5, size_t size, cli_ctx *ctx) } #endif - /* cli_warnmsg("cache_add: key is %u\n", key); */ - errmsg = cacheset_add(&c->cacheset, md5, size, level); #ifdef CL_THREAD_SAFE @@ -674,12 +685,13 @@ void cache_add(unsigned char *md5, size_t size, cli_ctx *ctx) if (errmsg != NULL) { cli_errmsg("%s\n", errmsg); } - cli_dbgmsg("cache_add: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x (level %u)\n", md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7], md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14], md5[15], level); + + cli_dbgmsg("clean_cache_add: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x (level %u)\n", md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7], md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14], md5[15], level); + return; } -/* Removes a hash from the cache */ -void cache_remove(unsigned char *md5, size_t size, const struct cl_engine *engine) +void clean_cache_remove(unsigned char *md5, size_t size, const struct cl_engine *engine) { unsigned int key = 0; struct CACHE *c; @@ -688,12 +700,12 @@ void cache_remove(unsigned char *md5, size_t size, const struct cl_engine *engin return; if (engine->engine_options & ENGINE_OPTIONS_DISABLE_CACHE) { - cli_dbgmsg("cache_remove: Caching disabled.\n"); + cli_dbgmsg("clean_cache_remove: Caching disabled.\n"); return; } if (!md5) { - cli_dbgmsg("cache_remove: No hash available. Nothing to remove from cache.\n"); + cli_dbgmsg("clean_cache_remove: No hash available. Nothing to remove from cache.\n"); return; } @@ -712,28 +724,30 @@ void cache_remove(unsigned char *md5, size_t size, const struct cl_engine *engin #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&c->mutex); #endif - cli_dbgmsg("cache_remove: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7], md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14], md5[15]); + cli_dbgmsg("clean_cache_remove: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7], md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14], md5[15]); return; } -/* Hashes a file onto the provided buffer and looks it up the cache. - Returns CL_VIRUS if found, CL_CLEAN if not FIXME or a recoverable error, - and returns CL_EREAD if unrecoverable */ -cl_error_t cache_check(unsigned char *hash, cli_ctx *ctx) +cl_error_t clean_cache_check(unsigned char *md5, size_t size, cli_ctx *ctx) { - fmap_t *map; int ret; if (!ctx || !ctx->engine || !ctx->engine->cache) return CL_VIRUS; + if (SCAN_COLLECT_METADATA) { + // Don't cache when using the "collect metadata" feature. + // We don't cache the JSON, so we can't reproduce it when the cache is positive. + cli_dbgmsg("clean_cache_check: collect metadata feature enabled, skipping cache\n"); + return CL_VIRUS; + } + if (ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_CACHE) { - cli_dbgmsg("cache_check: Caching disabled. Returning CL_VIRUS.\n"); + cli_dbgmsg("clean_cache_check: Caching disabled. Returning CL_VIRUS.\n"); return CL_VIRUS; } - map = ctx->fmap; - ret = cache_lookup_hash(hash, map->len, ctx->engine->cache, ctx->recursion_level); - cli_dbgmsg("cache_check: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x is %s\n", 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 == CL_VIRUS) ? "negative" : "positive"); + ret = cache_lookup_hash(md5, size, ctx->engine->cache, ctx->recursion_level); + cli_dbgmsg("clean_cache_check: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x is %s\n", md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7], md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14], md5[15], (ret == CL_VIRUS) ? "negative" : "positive"); return ret; } diff --git a/libclamav/cache.h b/libclamav/cache.h index d0d45d3613..fd0f86fd62 100644 --- a/libclamav/cache.h +++ b/libclamav/cache.h @@ -25,11 +25,47 @@ #include "clamav.h" #include "others.h" -void cache_add(unsigned char *md5, size_t size, cli_ctx *ctx); -/* Removes a hash from the cache */ -void cache_remove(unsigned char *md5, size_t size, const struct cl_engine *engine); -cl_error_t cache_check(unsigned char *hash, cli_ctx *ctx); -int cli_cache_init(struct cl_engine *engine); -void cli_cache_destroy(struct cl_engine *engine); +/** + * @brief Add a hash to the cache of clean files. + * + * @param md5 The file to add. + * @param size The size of the file. + * @param ctx The scanning context. + */ +void clean_cache_add(unsigned char *md5, size_t size, cli_ctx *ctx); + +/** + * @brief Removes a hash from the clean cache + * + * @param md5 The file to remove. + * @param size The size of the file. + * @param ctx The scanning context. + */ +void clean_cache_remove(unsigned char *md5, size_t size, const struct cl_engine *engine); + +/** + * @brief Hashes a file onto the provided buffer and looks it up the clean cache. + * + * @param hash Hash to check + * @param ctx + * @return CL_VIRUS if found, CL_CLEAN if not FIXME or a recoverable error. + @return CL_EREAD if unrecoverable. + */ +cl_error_t clean_cache_check(unsigned char *md5, size_t size, cli_ctx *ctx); + +/** + * @brief Allocates the trees for the clean cache. + * + * @param engine + * @return int + */ +int clean_cache_init(struct cl_engine *engine); + +/** + * @brief Frees the clean cache + * + * @param engine + */ +void clean_cache_destroy(struct cl_engine *engine); #endif diff --git a/libclamav/clamav.h b/libclamav/clamav.h index aa9439e536..62f64fc833 100644 --- a/libclamav/clamav.h +++ b/libclamav/clamav.h @@ -1200,7 +1200,7 @@ extern const char *cl_retver(void); /* ---------------------------------------------------------------------------- * Others. */ -extern const char *cl_strerror(int clerror); +extern const char *cl_strerror(cl_error_t clerror); /* ---------------------------------------------------------------------------- * Custom data scanning. diff --git a/libclamav/cpio.c b/libclamav/cpio.c index a691144854..57c28abb20 100644 --- a/libclamav/cpio.c +++ b/libclamav/cpio.c @@ -95,24 +95,24 @@ static void sanitname(char *name) } } -int cli_scancpio_old(cli_ctx *ctx) +cl_error_t cli_scancpio_old(cli_ctx *ctx) { + cl_error_t status = CL_SUCCESS; struct cpio_hdr_old hdr_old; char *fmap_name = NULL; char name[513]; unsigned int file = 0, trailer = 0; uint32_t filesize, namesize, hdr_namesize; - int ret = CL_CLEAN, conv; - size_t pos = 0; - int virus_found = 0; + int conv; + size_t pos = 0; memset(name, 0, sizeof(name)); while (fmap_readn(ctx->fmap, &hdr_old, pos, sizeof(hdr_old)) == sizeof(hdr_old)) { pos += sizeof(hdr_old); if (!hdr_old.magic && trailer) { - ret = CL_SUCCESS; - goto leave; + status = CL_SUCCESS; + goto done; } if (hdr_old.magic == 070707) { @@ -121,8 +121,8 @@ int cli_scancpio_old(cli_ctx *ctx) conv = 1; } else { cli_dbgmsg("cli_scancpio_old: Invalid magic number\n"); - ret = CL_EFORMAT; - goto leave; + status = CL_EFORMAT; + goto done; } cli_dbgmsg("CPIO: -- File %u --\n", ++file); @@ -132,21 +132,25 @@ int cli_scancpio_old(cli_ctx *ctx) namesize = MIN(sizeof(name), hdr_namesize); if (fmap_readn(ctx->fmap, &name, pos, namesize) != namesize) { cli_dbgmsg("cli_scancpio_old: Can't read file name\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } pos += namesize; name[namesize - 1] = 0; sanitname(name); cli_dbgmsg("CPIO: Name: %s\n", name); - if (!strcmp(name, "TRAILER!!!")) + if (!strcmp(name, "TRAILER!!!")) { trailer = 1; + } if (namesize < hdr_namesize) { - if (hdr_namesize % 2) + if (hdr_namesize % 2) { hdr_namesize++; + } pos += hdr_namesize - namesize; - } else if (hdr_namesize % 2) + } else if (hdr_namesize % 2) { pos++; + } fmap_name = name; } @@ -155,61 +159,53 @@ int cli_scancpio_old(cli_ctx *ctx) if (!filesize) continue; - if (cli_matchmeta(ctx, name, filesize, filesize, 0, file, 0, NULL) == CL_VIRUS) { - if (!SCAN_ALLMATCHES) - return CL_VIRUS; - virus_found = 1; + status = cli_matchmeta(ctx, name, filesize, filesize, 0, file, 0, NULL); + if (status != CL_SUCCESS) { + goto done; } if ((EC16(hdr_old.mode, conv) & 0170000) != 0100000) { cli_dbgmsg("CPIO: Not a regular file, skipping\n"); } else { - ret = cli_checklimits("cli_scancpio_old", ctx, filesize, 0, 0); - if (ret == CL_EMAXFILES) { - goto leave; - } else if (ret == CL_SUCCESS) { - ret = cli_magic_scan_nested_fmap_type(ctx->fmap, pos, filesize, ctx, CL_TYPE_ANY, - fmap_name, LAYER_ATTRIBUTES_NONE); - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) - return ret; - virus_found = 1; - } + status = cli_magic_scan_nested_fmap_type(ctx->fmap, pos, filesize, ctx, CL_TYPE_ANY, fmap_name, LAYER_ATTRIBUTES_NONE); + if (status != CL_SUCCESS) { + goto done; } } - if (filesize % 2) + if (filesize % 2) { filesize++; + } pos += filesize; } -leave: - if (virus_found != 0) - return CL_VIRUS; - return ret; +done: + + return status; } -int cli_scancpio_odc(cli_ctx *ctx) +cl_error_t cli_scancpio_odc(cli_ctx *ctx) { + cl_error_t status = CL_SUCCESS; struct cpio_hdr_odc hdr_odc; char name[513] = {0}, buff[12] = {0}; unsigned int file = 0, trailer = 0; uint32_t filesize = 0, namesize = 0, hdr_namesize = 0; - int ret = CL_CLEAN; - size_t pos = 0; - int virus_found = 0; + size_t pos = 0; memset(&hdr_odc, 0, sizeof(hdr_odc)); while (fmap_readn(ctx->fmap, &hdr_odc, pos, sizeof(hdr_odc)) == sizeof(hdr_odc)) { pos += sizeof(hdr_odc); - if (!hdr_odc.magic[0] && trailer) - goto leave; + if (!hdr_odc.magic[0] && trailer) { + status = CL_SUCCESS; + goto done; + } if (strncmp(hdr_odc.magic, "070707", 6)) { cli_dbgmsg("cli_scancpio_odc: Invalid magic string\n"); - ret = CL_EFORMAT; - goto leave; + status = CL_EFORMAT; + goto done; } cli_dbgmsg("CPIO: -- File %u --\n", ++file); @@ -218,87 +214,81 @@ int cli_scancpio_odc(cli_ctx *ctx) buff[6] = 0; if (sscanf(buff, "%o", &hdr_namesize) != 1) { cli_dbgmsg("cli_scancpio_odc: Can't convert name size\n"); - ret = CL_EFORMAT; - goto leave; + status = CL_EFORMAT; + goto done; } if (hdr_namesize) { namesize = MIN(sizeof(name), hdr_namesize); if (fmap_readn(ctx->fmap, &name, pos, namesize) != namesize) { cli_dbgmsg("cli_scancpio_odc: Can't read file name\n"); - ret = CL_EFORMAT; - goto leave; + status = CL_EFORMAT; + goto done; } pos += namesize; name[namesize - 1] = 0; sanitname(name); cli_dbgmsg("CPIO: Name: %s\n", name); - if (!strcmp(name, "TRAILER!!!")) + if (!strcmp(name, "TRAILER!!!")) { trailer = 1; + } - if (namesize < hdr_namesize) + if (namesize < hdr_namesize) { pos += hdr_namesize - namesize; + } } strncpy(buff, hdr_odc.filesize, 11); buff[11] = 0; if (sscanf(buff, "%o", &filesize) != 1) { cli_dbgmsg("cli_scancpio_odc: Can't convert file size\n"); - ret = CL_EFORMAT; - goto leave; + status = CL_EFORMAT; + goto done; } cli_dbgmsg("CPIO: Filesize: %u\n", filesize); - if (!filesize) + if (!filesize) { continue; + } - if (cli_matchmeta(ctx, name, filesize, filesize, 0, file, 0, NULL) == CL_VIRUS) { - if (!SCAN_ALLMATCHES) - return CL_VIRUS; - virus_found = 1; + status = cli_matchmeta(ctx, name, filesize, filesize, 0, file, 0, NULL); + if (status == CL_VIRUS) { + goto done; } - ret = cli_checklimits("cli_scancpio_odc", ctx, filesize, 0, 0); - if (ret == CL_EMAXFILES) { - goto leave; - } else if (ret == CL_SUCCESS) { - ret = cli_magic_scan_nested_fmap_type(ctx->fmap, pos, filesize, ctx, CL_TYPE_ANY, - name, LAYER_ATTRIBUTES_NONE); - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) - return ret; - virus_found = 1; - } + status = cli_magic_scan_nested_fmap_type(ctx->fmap, pos, filesize, ctx, CL_TYPE_ANY, name, LAYER_ATTRIBUTES_NONE); + if (status != CL_SUCCESS) { + goto done; } pos += filesize; } -leave: - if (virus_found != 0) - return CL_VIRUS; - return ret; +done: + + return status; } -int cli_scancpio_newc(cli_ctx *ctx, int crc) +cl_error_t cli_scancpio_newc(cli_ctx *ctx, int crc) { + cl_error_t status = CL_SUCCESS; struct cpio_hdr_newc hdr_newc; char name[513], buff[9]; unsigned int file = 0, trailer = 0; uint32_t filesize, namesize, hdr_namesize, pad; - int ret = CL_CLEAN; - size_t pos = 0; - int virus_found = 0; + size_t pos = 0; memset(name, 0, 513); while (fmap_readn(ctx->fmap, &hdr_newc, pos, sizeof(hdr_newc)) == sizeof(hdr_newc)) { pos += sizeof(hdr_newc); - if (!hdr_newc.magic[0] && trailer) - goto leave; + if (!hdr_newc.magic[0] && trailer) { + status = CL_SUCCESS; + goto done; + } if ((!crc && strncmp(hdr_newc.magic, "070701", 6)) || (crc && strncmp(hdr_newc.magic, "070702", 6))) { cli_dbgmsg("cli_scancpio_newc: Invalid magic string\n"); - ret = CL_EFORMAT; - goto leave; + status = CL_EFORMAT; + goto done; } cli_dbgmsg("CPIO: -- File %u --\n", ++file); @@ -307,70 +297,65 @@ int cli_scancpio_newc(cli_ctx *ctx, int crc) buff[8] = 0; if (sscanf(buff, "%x", &hdr_namesize) != 1) { cli_dbgmsg("cli_scancpio_newc: Can't convert name size\n"); - ret = CL_EFORMAT; - goto leave; + status = CL_EFORMAT; + goto done; } if (hdr_namesize) { namesize = MIN(sizeof(name), hdr_namesize); if (fmap_readn(ctx->fmap, &name, pos, namesize) != namesize) { cli_dbgmsg("cli_scancpio_newc: Can't read file name\n"); - ret = CL_EFORMAT; - goto leave; + status = CL_EFORMAT; + goto done; } pos += namesize; name[namesize - 1] = 0; sanitname(name); cli_dbgmsg("CPIO: Name: %s\n", name); - if (!strcmp(name, "TRAILER!!!")) + if (!strcmp(name, "TRAILER!!!")) { trailer = 1; + } pad = (4 - (sizeof(hdr_newc) + hdr_namesize) % 4) % 4; if (namesize < hdr_namesize) { - if (pad) + if (pad) { hdr_namesize += pad; + } pos += hdr_namesize - namesize; - } else if (pad) + } else if (pad) { pos += pad; + } } strncpy(buff, hdr_newc.filesize, 8); buff[8] = 0; if (sscanf(buff, "%x", &filesize) != 1) { cli_dbgmsg("cli_scancpio_newc: Can't convert file size\n"); - ret = CL_EFORMAT; - goto leave; + status = CL_EFORMAT; + goto done; } cli_dbgmsg("CPIO: Filesize: %u\n", filesize); - if (!filesize) + if (!filesize) { continue; + } - if (cli_matchmeta(ctx, name, filesize, filesize, 0, file, 0, NULL) == CL_VIRUS) { - if (!SCAN_ALLMATCHES) - return CL_VIRUS; - virus_found = 1; + status = cli_matchmeta(ctx, name, filesize, filesize, 0, file, 0, NULL); + if (status == CL_VIRUS) { + goto done; } - ret = cli_checklimits("cli_scancpio_newc", ctx, filesize, 0, 0); - if (ret == CL_EMAXFILES) { - goto leave; - } else if (ret == CL_SUCCESS) { - ret = cli_magic_scan_nested_fmap_type(ctx->fmap, pos, filesize, ctx, CL_TYPE_ANY, - name, LAYER_ATTRIBUTES_NONE); - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) - return ret; - virus_found = 1; - } + status = cli_magic_scan_nested_fmap_type(ctx->fmap, pos, filesize, ctx, CL_TYPE_ANY, name, LAYER_ATTRIBUTES_NONE); + if (status != CL_SUCCESS) { + goto done; } - if ((pad = filesize % 4)) + if ((pad = filesize % 4)) { filesize += (4 - pad); + } pos += filesize; } -leave: - if (virus_found != 0) - return CL_VIRUS; - return ret; +done: + + return status; } diff --git a/libclamav/cpio.h b/libclamav/cpio.h index c211e34521..0e77a7b3f8 100644 --- a/libclamav/cpio.h +++ b/libclamav/cpio.h @@ -22,10 +22,11 @@ #ifndef __CPIO_H #define __CPIO_H +#include "clamav.h" #include "others.h" -int cli_scancpio_old(cli_ctx *ctx); -int cli_scancpio_odc(cli_ctx *ctx); -int cli_scancpio_newc(cli_ctx *ctx, int crc); +cl_error_t cli_scancpio_old(cli_ctx *ctx); +cl_error_t cli_scancpio_odc(cli_ctx *ctx); +cl_error_t cli_scancpio_newc(cli_ctx *ctx, int crc); #endif diff --git a/libclamav/elf.c b/libclamav/elf.c index c5b428a671..0f86bbfdd8 100644 --- a/libclamav/elf.c +++ b/libclamav/elf.c @@ -99,8 +99,8 @@ static uint64_t cli_rawaddr64(uint64_t vaddr, struct elf_program_hdr64 *ph, uint } /* Return converted endian-fixed header, or error code */ -static int cli_elf_fileheader(cli_ctx *ctx, fmap_t *map, union elf_file_hdr *file_hdr, - uint8_t *do_convert, uint8_t *is64) +static cl_error_t cli_elf_fileheader(cli_ctx *ctx, fmap_t *map, union elf_file_hdr *file_hdr, + uint8_t *do_convert, uint8_t *is64) { uint8_t format64, conv; @@ -127,7 +127,7 @@ static int cli_elf_fileheader(cli_ctx *ctx, fmap_t *map, union elf_file_hdr *fil break; default: cli_dbgmsg("ELF: Unknown ELF class (%u)\n", file_hdr->hdr64.e_ident[4]); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_BREAK; @@ -220,7 +220,7 @@ static int cli_elf_ph32(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, cli_dbgmsg("ELF: Number of program headers: %d\n", phnum); if (phnum > 128) { cli_dbgmsg("ELF: Suspicious number of program headers\n"); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_EFORMAT; @@ -232,7 +232,7 @@ static int cli_elf_ph32(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, /* Sanity check */ if (phentsize != sizeof(struct elf_program_hdr32)) { cli_dbgmsg("ELF: phentsize != sizeof(struct elf_program_hdr32)\n"); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_EFORMAT; @@ -266,7 +266,7 @@ static int cli_elf_ph32(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, cli_dbgmsg("ELF: Possibly broken ELF file\n"); } free(program_hdr); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_BREAK; @@ -287,7 +287,7 @@ static int cli_elf_ph32(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, free(program_hdr); if (err) { cli_dbgmsg("ELF: Can't calculate file offset of entry point\n"); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_EFORMAT; @@ -306,8 +306,8 @@ static int cli_elf_ph32(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, } /* Read 64-bit program headers */ -static int cli_elf_ph64(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, - struct elf_file_hdr64 *file_hdr, uint8_t conv) +static cl_error_t cli_elf_ph64(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, + struct elf_file_hdr64 *file_hdr, uint8_t conv) { struct elf_program_hdr64 *program_hdr = NULL; uint16_t phnum, phentsize; @@ -320,7 +320,7 @@ static int cli_elf_ph64(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, cli_dbgmsg("ELF: Number of program headers: %d\n", phnum); if (phnum > 128) { cli_dbgmsg("ELF: Suspicious number of program headers\n"); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_EFORMAT; @@ -332,7 +332,7 @@ static int cli_elf_ph64(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, /* Sanity check */ if (phentsize != sizeof(struct elf_program_hdr64)) { cli_dbgmsg("ELF: phentsize != sizeof(struct elf_program_hdr64)\n"); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_EFORMAT; @@ -366,7 +366,7 @@ static int cli_elf_ph64(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, cli_dbgmsg("ELF: Possibly broken ELF file\n"); } free(program_hdr); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_BREAK; @@ -387,7 +387,7 @@ static int cli_elf_ph64(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, free(program_hdr); if (err) { cli_dbgmsg("ELF: Can't calculate file offset of entry point\n"); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_EFORMAT; @@ -430,7 +430,7 @@ static int cli_elf_sh32(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, /* Sanity check */ if (shentsize != sizeof(struct elf_section_hdr32)) { cli_dbgmsg("ELF: shentsize != sizeof(struct elf_section_hdr32)\n"); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_EFORMAT; @@ -473,7 +473,7 @@ static int cli_elf_sh32(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, cli_dbgmsg("ELF: Possibly broken ELF file\n"); } free(section_hdr); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_BREAK; @@ -529,7 +529,7 @@ static int cli_elf_sh64(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, /* Sanity check */ if (shentsize != sizeof(struct elf_section_hdr64)) { cli_dbgmsg("ELF: shentsize != sizeof(struct elf_section_hdr64)\n"); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_EFORMAT; @@ -572,7 +572,7 @@ static int cli_elf_sh64(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, cli_dbgmsg("ELF: Possibly broken ELF file\n"); } free(section_hdr); - if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable"))) { + if (ctx && SCAN_HEURISTIC_BROKEN && (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"))) { return CL_VIRUS; } return CL_BREAK; @@ -672,11 +672,11 @@ static void cli_elf_sectionlog(uint32_t sh_type, uint32_t sh_flags) } /* Scan function for ELF */ -int cli_scanelf(cli_ctx *ctx) +cl_error_t cli_scanelf(cli_ctx *ctx) { union elf_file_hdr file_hdr; fmap_t *map = ctx->fmap; - int ret; + cl_error_t ret; uint8_t conv = 0, is64 = 0; cli_dbgmsg("in cli_scanelf\n"); @@ -791,11 +791,11 @@ int cli_scanelf(cli_ctx *ctx) /* ELF header parsing only * Returns 0 on success, -1 on error */ -int cli_elfheader(cli_ctx *ctx, struct cli_exe_info *elfinfo) +cl_error_t cli_elfheader(cli_ctx *ctx, struct cli_exe_info *elfinfo) { union elf_file_hdr file_hdr; uint8_t conv = 0, is64 = 0; - int ret; + cl_error_t ret = CL_SUCCESS; cli_dbgmsg("in cli_elfheader\n"); @@ -806,8 +806,8 @@ int cli_elfheader(cli_ctx *ctx, struct cli_exe_info *elfinfo) } ret = cli_elf_fileheader(NULL, ctx->fmap, &file_hdr, &conv, &is64); - if (ret != CL_CLEAN) { - return -1; + if (ret != CL_SUCCESS) { + goto done; } /* Program headers and Entry */ @@ -816,8 +816,8 @@ int cli_elfheader(cli_ctx *ctx, struct cli_exe_info *elfinfo) } else { ret = cli_elf_ph32(NULL, ctx->fmap, elfinfo, &(file_hdr.hdr32.hdr), conv); } - if (ret != CL_CLEAN) { - return -1; + if (ret != CL_SUCCESS) { + goto done; } /* Section Headers */ @@ -826,65 +826,66 @@ int cli_elfheader(cli_ctx *ctx, struct cli_exe_info *elfinfo) } else { ret = cli_elf_sh32(NULL, ctx->fmap, elfinfo, &(file_hdr.hdr32.hdr), conv); } - if (ret != CL_CLEAN) { - return -1; + if (ret != CL_SUCCESS) { + goto done; } - return 0; +done: + + return ret; } /* * ELF file unpacking. */ -int cli_unpackelf(cli_ctx *ctx) +cl_error_t cli_unpackelf(cli_ctx *ctx) { - char *tempfile; - int ndesc; + cl_error_t ret = CL_SUCCESS; + char *tempfile = NULL; + int ndesc = -1; struct cli_bc_ctx *bc_ctx; - int ret; - fmap_t *map = ctx->fmap; /* Bytecode BC_ELF_UNPACKER hook */ bc_ctx = cli_bytecode_context_alloc(); if (!bc_ctx) { cli_errmsg("cli_scanelf: can't allocate memory for bc_ctx\n"); - return CL_EMEM; + ret = CL_EMEM; + goto done; } cli_bytecode_context_setctx(bc_ctx, ctx); cli_dbgmsg("Running bytecode hook\n"); - ret = cli_bytecode_runhook(ctx, ctx->engine, bc_ctx, BC_ELF_UNPACKER, map); + ret = cli_bytecode_runhook(ctx, ctx->engine, bc_ctx, BC_ELF_UNPACKER, ctx->fmap); cli_dbgmsg("Finished running bytecode hook\n"); - switch (ret) { - case CL_VIRUS: - cli_bytecode_context_destroy(bc_ctx); - return CL_VIRUS; - case CL_SUCCESS: - ndesc = cli_bytecode_context_getresult_file(bc_ctx, &tempfile); - cli_bytecode_context_destroy(bc_ctx); - if (ndesc != -1 && tempfile) { - if (ctx->engine->keeptmp) - cli_dbgmsg("cli_scanelf: Unpacked and rebuilt executable saved in %s\n", tempfile); - else - cli_dbgmsg("cli_scanelf: Unpacked and rebuilt executable\n"); - lseek(ndesc, 0, SEEK_SET); - cli_dbgmsg("***** Scanning rebuilt ELF file *****\n"); - if (CL_VIRUS == cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE)) { - close(ndesc); - CLI_TMPUNLK(); - free(tempfile); - return CL_VIRUS; - } - close(ndesc); - CLI_TMPUNLK(); - free(tempfile); - return CL_CLEAN; - } - break; - default: - cli_bytecode_context_destroy(bc_ctx); + if (CL_SUCCESS == ret) { + // check for unpacked/rebuilt executable + ndesc = cli_bytecode_context_getresult_file(bc_ctx, &tempfile); + if (ndesc != -1 && tempfile) { + cli_dbgmsg("cli_scanelf: Unpacked and rebuilt ELF executable saved in %s\n", tempfile); + + lseek(ndesc, 0, SEEK_SET); + + cli_dbgmsg("***** Scanning rebuilt ELF file *****\n"); + ret = cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE); + } } - return CL_CLEAN; +done: + // cli_bytecode_context_getresult_file() gives up ownership of temp file, so we must clean it up. + if (-1 != ndesc) { + close(ndesc); + } + if (NULL != tempfile) { + if (!ctx->engine->keeptmp) { + (void)cli_unlink(tempfile); + } + free(tempfile); + } + + if (NULL != bc_ctx) { + cli_bytecode_context_destroy(bc_ctx); + } + + return ret; } diff --git a/libclamav/elf.h b/libclamav/elf.h index 93eb46008b..6cb8267e09 100644 --- a/libclamav/elf.h +++ b/libclamav/elf.h @@ -143,10 +143,10 @@ struct elf_section_hdr64 { /* Exposed functions */ -int cli_scanelf(cli_ctx *ctx); +cl_error_t cli_scanelf(cli_ctx *ctx); -int cli_elfheader(cli_ctx *ctx, struct cli_exe_info *elfinfo); +cl_error_t cli_elfheader(cli_ctx *ctx, struct cli_exe_info *elfinfo); -int cli_unpackelf(cli_ctx *ctx); +cl_error_t cli_unpackelf(cli_ctx *ctx); #endif diff --git a/libclamav/filetypes.c b/libclamav/filetypes.c index c218cdeee0..c3dbd2c0d1 100644 --- a/libclamav/filetypes.c +++ b/libclamav/filetypes.c @@ -277,7 +277,8 @@ cli_file_t cli_determine_fmap_type(fmap_t *map, const struct cl_engine *engine, unsigned char buffer[MAGIC_BUFFER_SIZE]; const unsigned char *buff; unsigned char *decoded; - int bread, sret; + int bread; + cli_file_t scan_ret; cli_file_t ret = CL_TYPE_BINARY_DATA; struct cli_matcher *root; struct cli_ac_data mdata; @@ -299,12 +300,10 @@ cli_file_t cli_determine_fmap_type(fmap_t *map, const struct cl_engine *engine, buff = fmap_need_off_once(map, 0, bread); if (buff) { - sret = cli_memcpy(buffer, buff, bread); - if (sret) { + if (CL_SUCCESS != cli_memcpy(buffer, buff, bread)) { cli_errmsg("cli_determine_fmap_type: fileread error!\n"); return CL_TYPE_ERROR; } - sret = 0; } else { return CL_TYPE_ERROR; } @@ -413,21 +412,28 @@ cli_file_t cli_determine_fmap_type(fmap_t *map, const struct cl_engine *engine, if (cli_ac_initdata(&mdata, root->ac_partsigs, root->ac_lsigs, root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN)) return ret; - sret = cli_ac_scanbuff(buff, bread, NULL, NULL, NULL, engine->root[0], &mdata, 0, ret, NULL, AC_SCAN_FT, NULL); + scan_ret = (cli_file_t)cli_ac_scanbuff(buff, bread, NULL, NULL, NULL, engine->root[0], &mdata, 0, ret, NULL, AC_SCAN_FT, NULL); cli_ac_freedata(&mdata); - if (sret >= CL_TYPENO) { - ret = sret; + if (scan_ret >= CL_TYPENO && + /* Omit SFX archive types selected. We'll detect these in scanraw() */ + ((scan_ret != CL_TYPE_ZIPSFX) && + (scan_ret != CL_TYPE_ARJSFX) && + (scan_ret != CL_TYPE_RARSFX) && + (scan_ret != CL_TYPE_EGGSFX) && + (scan_ret != CL_TYPE_CABSFX) && + (scan_ret != CL_TYPE_7ZSFX))) { + ret = scan_ret; } else { if (cli_ac_initdata(&mdata, root->ac_partsigs, root->ac_lsigs, root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN)) return ret; decoded = (unsigned char *)cli_utf16toascii((char *)buff, bread); if (decoded) { - sret = cli_ac_scanbuff(decoded, bread / 2, NULL, NULL, NULL, engine->root[0], &mdata, 0, CL_TYPE_TEXT_ASCII, NULL, AC_SCAN_FT, NULL); + scan_ret = (cli_file_t)cli_ac_scanbuff(decoded, bread / 2, NULL, NULL, NULL, engine->root[0], &mdata, 0, CL_TYPE_TEXT_ASCII, NULL, AC_SCAN_FT, NULL); free(decoded); - if (sret == CL_TYPE_HTML) + if (scan_ret == CL_TYPE_HTML) ret = CL_TYPE_HTML_UTF16; } cli_ac_freedata(&mdata); @@ -460,8 +466,8 @@ cli_file_t cli_determine_fmap_type(fmap_t *map, const struct cl_engine *engine, return ret; if (out_area.length > 0) { - sret = cli_ac_scanbuff(decodedbuff, out_area.length, NULL, NULL, NULL, engine->root[0], &mdata, 0, 0, NULL, AC_SCAN_FT, NULL); /* FIXME: can we use CL_TYPE_TEXT_ASCII instead of 0? */ - if (sret == CL_TYPE_HTML) { + scan_ret = (cli_file_t)cli_ac_scanbuff(decodedbuff, out_area.length, NULL, NULL, NULL, engine->root[0], &mdata, 0, 0, NULL, AC_SCAN_FT, NULL); /* FIXME: can we use CL_TYPE_TEXT_ASCII instead of 0? */ + if (scan_ret == CL_TYPE_HTML) { cli_dbgmsg("cli_determine_fmap_type: detected HTML signature in Unicode file\n"); /* htmlnorm is able to handle any unicode now, since it skips null chars */ ret = CL_TYPE_HTML; diff --git a/libclamav/fmap.c b/libclamav/fmap.c index 3b5acbb853..e824a4894b 100644 --- a/libclamav/fmap.c +++ b/libclamav/fmap.c @@ -285,9 +285,11 @@ fmap_t *fmap_duplicate(cl_fmap_t *map, size_t offset, size_t length, const char } /* This also means the hash will be different. - * Clear the have_maphash flag. + * Clear the have_ flags. * It will be calculated when next it is needed. */ - duplicate_map->have_maphash = false; + duplicate_map->have_md5 = false; + duplicate_map->have_sha1 = false; + duplicate_map->have_sha256 = false; } if (NULL != name) { @@ -422,14 +424,16 @@ extern cl_fmap_t *cl_fmap_open_handle(void *handle, size_t offset, size_t len, m->pages = pages; m->pgsz = pgsz; m->paged = 0; - m->dont_cache_flag = 0; + m->dont_cache_flag = false; m->unmap = unmap_handle; m->need = handle_need; m->need_offstr = handle_need_offstr; m->gets = handle_gets; m->unneed_off = handle_unneed_off; m->handle_is_fd = 1; - m->have_maphash = false; + m->have_md5 = false; + m->have_sha1 = false; + m->have_sha256 = false; status = CL_SUCCESS; @@ -1082,7 +1086,45 @@ extern void cl_fmap_close(cl_fmap_t *map) funmap(map); } -cl_error_t fmap_get_MD5(fmap_t *map, unsigned char **hash) +cl_error_t fmap_set_hash(fmap_t *map, unsigned char *hash, cli_hash_type_t type) +{ + cl_error_t status = CL_SUCCESS; + + if (NULL == map) { + cli_errmsg("fmap_set_hash: Attempted to set hash for NULL fmap\n"); + status = CL_EARG; + goto done; + } + if (NULL == hash) { + cli_errmsg("fmap_set_hash: Attempted to set hash to NULL\n"); + status = CL_EARG; + goto done; + } + + switch (type) { + case CLI_HASH_MD5: + memcpy(map->md5, hash, CLI_HASHLEN_MD5); + map->have_md5 = true; + break; + case CLI_HASH_SHA1: + memcpy(map->sha1, hash, CLI_HASHLEN_SHA1); + map->have_sha1 = true; + break; + case CLI_HASH_SHA256: + memcpy(map->sha256, hash, CLI_HASHLEN_SHA256); + map->have_sha256 = true; + break; + default: + cli_errmsg("fmap_set_hash: Unsupported hash type %u\n", type); + status = CL_EARG; + goto done; + } + +done: + return status; +} + +cl_error_t fmap_get_hash(fmap_t *map, unsigned char **hash, cli_hash_type_t type) { cl_error_t status = CL_ERROR; size_t todo, at = 0; @@ -1090,41 +1132,109 @@ cl_error_t fmap_get_MD5(fmap_t *map, unsigned char **hash) todo = map->len; - if (!map->have_maphash) { - /* Need to calculate the hash */ - hashctx = cl_hash_init("md5"); - if (!(hashctx)) { - cli_errmsg("fmap_get_MD5: error initializing new md5 hash!\n"); + switch (type) { + case CLI_HASH_MD5: + if (map->have_md5) { + goto complete; + } + break; + case CLI_HASH_SHA1: + if (map->have_sha1) { + goto complete; + } + break; + case CLI_HASH_SHA256: + if (map->have_sha256) { + goto complete; + } + break; + default: + cli_errmsg("fmap_get_hash: Unsupported hash type %u\n", type); + status = CL_EARG; goto done; - } + } - while (todo) { - const void *buf; - size_t readme = todo < 1024 * 1024 * 10 ? todo : 1024 * 1024 * 10; + /* + * Need to calculate the hash. + */ - if (!(buf = fmap_need_off_once(map, at, readme))) { - cli_errmsg("fmap_get_MD5: error reading while generating hash!\n"); - status = CL_EREAD; - goto done; - } + switch (type) { + case CLI_HASH_MD5: + hashctx = cl_hash_init("md5"); + break; + case CLI_HASH_SHA1: + hashctx = cl_hash_init("sha1"); + break; + case CLI_HASH_SHA256: + hashctx = cl_hash_init("sha256"); + break; + default: + cli_errmsg("fmap_get_hash: Unsupported hash type %u\n", type); + status = CL_EARG; + goto done; + } + if (!(hashctx)) { + cli_errmsg("fmap_get_hash: error initializing new md5 hash!\n"); + goto done; + } - todo -= readme; - at += readme; + while (todo) { + const void *buf; + size_t readme = todo < 1024 * 1024 * 10 ? todo : 1024 * 1024 * 10; - if (cl_update_hash(hashctx, (void *)buf, readme)) { - cli_errmsg("fmap_get_MD5: error calculating hash!\n"); - status = CL_EREAD; - goto done; - } + if (!(buf = fmap_need_off_once(map, at, readme))) { + cli_errmsg("fmap_get_hash: error reading while generating hash!\n"); + status = CL_EREAD; + goto done; } - cl_finish_hash(hashctx, map->maphash); - hashctx = NULL; + todo -= readme; + at += readme; - map->have_maphash = true; + if (cl_update_hash(hashctx, (void *)buf, readme)) { + cli_errmsg("fmap_get_hash: error calculating hash!\n"); + status = CL_EREAD; + goto done; + } } - *hash = map->maphash; + switch (type) { + case CLI_HASH_MD5: + cl_finish_hash(hashctx, map->md5); + map->have_md5 = true; + break; + case CLI_HASH_SHA1: + cl_finish_hash(hashctx, map->sha1); + map->have_sha1 = true; + break; + case CLI_HASH_SHA256: + cl_finish_hash(hashctx, map->sha256); + map->have_sha256 = true; + break; + default: + cli_errmsg("fmap_get_hash: Unsupported hash type %u\n", type); + status = CL_EARG; + goto done; + } + hashctx = NULL; + +complete: + + switch (type) { + case CLI_HASH_MD5: + *hash = map->md5; + break; + case CLI_HASH_SHA1: + *hash = map->sha1; + break; + case CLI_HASH_SHA256: + *hash = map->sha256; + break; + default: + cli_errmsg("fmap_get_hash: Unsupported hash type %u\n", type); + status = CL_EARG; + goto done; + } status = CL_SUCCESS; diff --git a/libclamav/fmap.h b/libclamav/fmap.h index 481510aa1a..4d1e374cd6 100644 --- a/libclamav/fmap.h +++ b/libclamav/fmap.h @@ -37,6 +37,8 @@ #include "clamav.h" +#include "matcher-hash-types.h" + struct cl_fmap; typedef cl_fmap_t fmap_t; @@ -54,14 +56,14 @@ struct cl_fmap { uint64_t pgsz; uint64_t paged; uint16_t aging; - uint16_t dont_cache_flag; /** indicates if we should not cache scan results for this fmap. Used if limits exceeded */ - uint16_t handle_is_fd; /** non-zero if map->handle is an fd. */ - size_t offset; /** file offset representing start of original fmap, if the fmap created reading from a file starting at offset other than 0 */ - size_t nested_offset; /** offset from start of original fmap (data) for nested scan. 0 for orig fmap. */ - size_t real_len; /** len from start of original fmap (data) to end of current (possibly nested) map. */ - /* real_len == nested_offset + len. - real_len is needed for nested maps because we only reference the original mapping data. - We convert caller's fmap offsets & lengths to real data offsets using nested_offset & real_len. */ + bool dont_cache_flag; /** indicates if we should not cache scan results for this fmap. Used if limits exceeded */ + uint16_t handle_is_fd; /** non-zero if map->handle is an fd. */ + size_t offset; /** file offset representing start of original fmap, if the fmap created reading from a file starting at offset other than 0 */ + size_t nested_offset; /** offset from start of original fmap (data) for nested scan. 0 for orig fmap. */ + size_t real_len; /** len from start of original fmap (data) to end of current (possibly nested) map. */ + /* real_len == nested_offset + len. + real_len is needed for nested maps because we only reference the original mapping data. + We convert caller's fmap offsets & lengths to real data offsets using nested_offset & real_len. */ /* external */ size_t len; /** length of data from nested_offset, accessible via current fmap */ @@ -84,8 +86,12 @@ struct cl_fmap { HANDLE fh; HANDLE mh; #endif - bool have_maphash; - unsigned char maphash[16]; + bool have_md5; + unsigned char md5[CLI_HASHLEN_MD5]; + bool have_sha1; + unsigned char sha1[CLI_HASHLEN_SHA1]; + bool have_sha256; + unsigned char sha256[CLI_HASHLEN_SHA256]; uint64_t *bitmap; char *name; }; @@ -426,14 +432,25 @@ cl_error_t fmap_dump_to_file(fmap_t *map, const char *filepath, const char *tmpd int fmap_fd(fmap_t *m); /** - * @brief Get a pointer to the fmap hash. + * @brief Get a pointer to the fmap hash. * * Will calculate the hash if not already previously calculated. * * @param map The map in question. * @param[out] hash A pointer to the hash. + * @param type The type of hash to calculate. * @return cl_error_t CL_SUCCESS if was able to get the hash, else some error. */ -cl_error_t fmap_get_MD5(fmap_t *map, unsigned char **hash); +cl_error_t fmap_get_hash(fmap_t *map, unsigned char **hash, cli_hash_type_t type); + +/** + * @brief Set the hash for the fmap that was previously calculated. + * + * @param map The map in question. + * @param hash The hash to set. + * @param type The type of hash to calculate. + * @return cl_error_t CL_SUCCESS if was able to set the hash, else some error. + */ +cl_error_t fmap_set_hash(fmap_t *map, unsigned char *hash, cli_hash_type_t type); #endif diff --git a/libclamav/gif.c b/libclamav/gif.c index 21c75a7edb..8e481013e6 100644 --- a/libclamav/gif.c +++ b/libclamav/gif.c @@ -163,6 +163,7 @@ struct gif_image_descriptor { cl_error_t cli_parsegif(cli_ctx *ctx) { cl_error_t status = CL_SUCCESS; + bool parse_error = false; fmap_t *map = NULL; size_t offset = 0; @@ -210,8 +211,8 @@ cl_error_t cli_parsegif(cli_ctx *ctx) */ if (fmap_readn(map, &screen_desc, offset, sizeof(screen_desc)) != sizeof(screen_desc)) { cli_errmsg("GIF: Can't read logical screen description, file truncated?\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedScreenDescriptor"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedScreenDescriptor"); + parse_error = true; goto scan_overlay; } offset += sizeof(screen_desc); @@ -226,8 +227,8 @@ cl_error_t cli_parsegif(cli_ctx *ctx) if (offset + (size_t)global_color_table_size > map->len) { cli_errmsg("GIF: EOF in the middle of the global color table, file truncated?\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedGlobalColorTable"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedGlobalColorTable"); + parse_error = true; goto scan_overlay; } offset += global_color_table_size; @@ -248,9 +249,9 @@ cl_error_t cli_parsegif(cli_ctx *ctx) cli_dbgmsg("GIF: Missing GIF trailer, slightly (but acceptably) malformed.\n"); } else { cli_errmsg("GIF: Can't read block label, EOF before image data. File truncated?\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.MissingImageData"); + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.GIF.MissingImageData"); } - status = CL_EPARSE; + parse_error = true; goto scan_overlay; } offset += sizeof(block_label); @@ -270,8 +271,8 @@ cl_error_t cli_parsegif(cli_ctx *ctx) if (fmap_readn(map, &extension_label, offset, sizeof(extension_label)) != sizeof(extension_label)) { cli_errmsg("GIF: Failed to read the extension block label, file truncated?\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtension"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtension"); + parse_error = true; goto scan_overlay; } offset += sizeof(extension_label); @@ -304,8 +305,8 @@ cl_error_t cli_parsegif(cli_ctx *ctx) uint8_t extension_block_size = 0; if (fmap_readn(map, &extension_block_size, offset, sizeof(extension_block_size)) != sizeof(extension_block_size)) { cli_errmsg("GIF: EOF while attempting to read the block size for an extension, file truncated?\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtension"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtension"); + parse_error = true; goto scan_overlay; } else { offset += sizeof(extension_block_size); @@ -319,8 +320,8 @@ cl_error_t cli_parsegif(cli_ctx *ctx) if (offset + (size_t)extension_block_size > map->len) { cli_errmsg("GIF: EOF in the middle of a graphic control extension sub-block, file truncated?\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtensionSubBlock"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtensionSubBlock"); + parse_error = true; goto scan_overlay; } offset += extension_block_size; @@ -335,8 +336,8 @@ cl_error_t cli_parsegif(cli_ctx *ctx) cli_dbgmsg("GIF: Found an image descriptor.\n"); if (fmap_readn(map, &image_desc, offset, sizeof(image_desc)) != sizeof(image_desc)) { cli_errmsg("GIF: Can't read image descriptor, file truncated?\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDescriptor"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDescriptor"); + parse_error = true; goto scan_overlay; } else { offset += sizeof(image_desc); @@ -368,8 +369,8 @@ cl_error_t cli_parsegif(cli_ctx *ctx) uint8_t image_data_block_size = 0; if (fmap_readn(map, &image_data_block_size, offset, sizeof(image_data_block_size)) != sizeof(image_data_block_size)) { cli_errmsg("GIF: EOF while attempting to read the block size for an image data block, file truncated?\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDataBlock"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDataBlock"); + parse_error = true; goto scan_overlay; } else { offset += sizeof(image_data_block_size); @@ -383,8 +384,8 @@ cl_error_t cli_parsegif(cli_ctx *ctx) if (offset + (size_t)image_data_block_size > map->len) { cli_errmsg("GIF: EOF in the middle of an image data sub-block, file truncated?\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDataBlock"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDataBlock"); + parse_error = true; goto scan_overlay; } offset += image_data_block_size; @@ -395,32 +396,29 @@ cl_error_t cli_parsegif(cli_ctx *ctx) default: { // An unknown code: break. cli_errmsg("GIF: Found an unfamiliar block label: 0x%x\n", block_label); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.UnknownBlockLabel"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.GIF.UnknownBlockLabel"); + parse_error = true; goto scan_overlay; } } } scan_overlay: - if (status == CL_EPARSE) { - /* We added with cli_append_possibly_unwanted so it will alert at the end if nothing else matches. */ - status = CL_CLEAN; - // Some recovery (I saw some "GIF89a;" or things like this) - if (offset == (strlen("GIF89a") + sizeof(screen_desc) + 1)) { - offset = strlen("GIF89a"); + if (CL_SUCCESS == status) { + if (parse_error) { + // Some recovery (I saw some "GIF89a;" or things like this) + if (offset == (strlen("GIF89a") + sizeof(screen_desc) + 1)) { + offset = strlen("GIF89a"); + } } - } - // 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; + // 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); + status = cli_magic_scan_nested_fmap_type(map, offset, map->len - offset, ctx, CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); + goto done; + } } done: diff --git a/libclamav/gpt.c b/libclamav/gpt.c index 378fb5e14f..48710fe134 100644 --- a/libclamav/gpt.c +++ b/libclamav/gpt.c @@ -63,12 +63,12 @@ enum GPT_SCANSTATE { BOTH }; -static int gpt_scan_partitions(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize); -static int gpt_validate_header(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize); -static int gpt_check_mbr(cli_ctx *ctx, size_t sectorsize); +static cl_error_t gpt_scan_partitions(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize); +static cl_error_t gpt_validate_header(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize); +static cl_error_t gpt_check_mbr(cli_ctx *ctx, size_t sectorsize); static void gpt_printSectors(cli_ctx *ctx, size_t sectorsize); static void gpt_printGUID(uint8_t GUID[], const char *msg); -static int gpt_partition_intersection(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize); +static cl_error_t gpt_partition_intersection(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize); /* returns 0 on failing to detect sectorsize */ size_t gpt_detect_size(fmap_t *map) @@ -99,11 +99,11 @@ size_t gpt_detect_size(fmap_t *map) } /* attempts to detect sector size is input as 0 */ -int cli_scangpt(cli_ctx *ctx, size_t sectorsize) +cl_error_t cli_scangpt(cli_ctx *ctx, size_t sectorsize) { + cl_error_t status = CL_SUCCESS; struct gpt_header phdr, shdr; enum GPT_SCANSTATE state = INVALID; - int ret = CL_CLEAN, detection = CL_CLEAN; size_t maplen; off_t pos = 0; @@ -111,7 +111,8 @@ int cli_scangpt(cli_ctx *ctx, size_t sectorsize) if (!ctx || !ctx->fmap) { cli_errmsg("cli_scangpt: Invalid context\n"); - return CL_ENULLARG; + status = CL_ENULLARG; + goto done; } /* sector size calculation */ @@ -121,7 +122,8 @@ int cli_scangpt(cli_ctx *ctx, size_t sectorsize) } if (sectorsize == 0) { cli_dbgmsg("cli_scangpt: could not determine sector size\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* size of total file must be a multiple of the sector size */ @@ -129,16 +131,14 @@ int cli_scangpt(cli_ctx *ctx, size_t sectorsize) if ((maplen % sectorsize) != 0) { cli_dbgmsg("cli_scangpt: File sized %lu is not a multiple of sector size %lu\n", (unsigned long)maplen, (unsigned long)sectorsize); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* check the protective mbr */ - ret = gpt_check_mbr(ctx, sectorsize); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = gpt_check_mbr(ctx, sectorsize); + if (status != CL_SUCCESS) { + goto done; } pos = GPT_PRIMARY_HDR_LBA * sectorsize; /* sector 1 (second sector) is the primary gpt header */ @@ -147,7 +147,8 @@ int cli_scangpt(cli_ctx *ctx, size_t sectorsize) cli_dbgmsg("cli_scangpt: Using primary GPT header\n"); if (fmap_readn(ctx->fmap, &phdr, pos, sizeof(phdr)) != sizeof(phdr)) { cli_dbgmsg("cli_scangpt: Invalid primary GPT header\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } pos = maplen - sectorsize; /* last sector is the secondary gpt header */ @@ -161,13 +162,15 @@ int cli_scangpt(cli_ctx *ctx, size_t sectorsize) /* read secondary gpt header */ if (fmap_readn(ctx->fmap, &shdr, pos, sizeof(shdr)) != sizeof(shdr)) { cli_dbgmsg("cli_scangpt: Invalid secondary GPT header\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } if (gpt_validate_header(ctx, shdr, sectorsize)) { cli_dbgmsg("cli_scangpt: Secondary GPT header is invalid\n"); cli_dbgmsg("cli_scangpt: Disk is unusable\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } } else { cli_dbgmsg("cli_scangpt: Checking secondary GPT header\n"); @@ -194,19 +197,13 @@ int cli_scangpt(cli_ctx *ctx, size_t sectorsize) /* check that the partition table has no intersections - HEURISTICS */ if (SCAN_HEURISTIC_PARTITION_INTXN && (ctx->dconf->other & OTHER_CONF_PRTNINTXN)) { - ret = gpt_partition_intersection(ctx, phdr, sectorsize); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = gpt_partition_intersection(ctx, phdr, sectorsize); + if (status != CL_SUCCESS) { + goto done; } - ret = gpt_partition_intersection(ctx, shdr, sectorsize); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = gpt_partition_intersection(ctx, shdr, sectorsize); + if (status != CL_SUCCESS) { + goto done; } } @@ -214,58 +211,51 @@ int cli_scangpt(cli_ctx *ctx, size_t sectorsize) switch (state) { case PRIMARY_ONLY: cli_dbgmsg("cli_scangpt: Scanning primary GPT partitions only\n"); - ret = gpt_scan_partitions(ctx, phdr, sectorsize); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = gpt_scan_partitions(ctx, phdr, sectorsize); + if (status != CL_SUCCESS) { + goto done; } break; case SECONDARY_ONLY: cli_dbgmsg("cli_scangpt: Scanning secondary GPT partitions only\n"); - ret = gpt_scan_partitions(ctx, shdr, sectorsize); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = gpt_scan_partitions(ctx, shdr, sectorsize); + if (status != CL_SUCCESS) { + goto done; } break; case BOTH: cli_dbgmsg("cli_scangpt: Scanning primary GPT partitions\n"); - ret = gpt_scan_partitions(ctx, phdr, sectorsize); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = gpt_scan_partitions(ctx, phdr, sectorsize); + if (status != CL_SUCCESS) { + goto done; } cli_dbgmsg("cli_scangpt: Scanning secondary GPT partitions\n"); - ret = gpt_scan_partitions(ctx, shdr, sectorsize); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = gpt_scan_partitions(ctx, shdr, sectorsize); + if (status != CL_SUCCESS) { + goto done; } break; default: cli_dbgmsg("cli_scangpt: State is invalid\n"); } - return detection; + status = CL_SUCCESS; + +done: + return status; } -static int gpt_scan_partitions(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize) +static cl_error_t gpt_scan_partitions(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize) { + cl_error_t status = CL_SUCCESS; struct gpt_partition_entry gpe; - int ret = CL_CLEAN, detection = CL_CLEAN; - size_t maplen, part_size = 0; + size_t maplen, part_size = 0; size_t pos = 0, part_off = 0; unsigned i = 0, j = 0; uint32_t max_prtns = 0; + char *namestr = NULL; + /* convert endian to host */ hdr.signature = be64_to_host(hdr.signature); hdr.revision = be32_to_host(hdr.revision); @@ -304,7 +294,8 @@ static int gpt_scan_partitions(cli_ctx *ctx, struct gpt_header hdr, size_t secto /* read in partition entry */ if (fmap_readn(ctx->fmap, &gpe, pos, sizeof(gpe)) != sizeof(gpe)) { cli_dbgmsg("cli_scangpt: Invalid GPT partition entry\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* convert the endian to host */ @@ -327,9 +318,8 @@ static int gpt_scan_partitions(cli_ctx *ctx, struct gpt_header hdr, size_t secto } else if (((gpe.lastLBA + 1) * sectorsize) > maplen) { /* partition exists outside bounds of the file map */ } else { - char *namestr = NULL; - namestr = (char *)cli_utf16toascii((char *)gpe.name, 72); + // It's okay if namestr is NULL. /* print partition entry data for debug */ cli_dbgmsg("GPT Partition Entry %u:\n", i); @@ -344,17 +334,14 @@ static int gpt_scan_partitions(cli_ctx *ctx, struct gpt_header hdr, size_t secto /* send the partition to cli_magic_scan_nested_fmap_type */ part_off = gpe.firstLBA * sectorsize; part_size = (gpe.lastLBA - gpe.firstLBA + 1) * sectorsize; + status = cli_magic_scan_nested_fmap_type(ctx->fmap, part_off, part_size, ctx, CL_TYPE_PART_ANY, namestr, LAYER_ATTRIBUTES_NONE); + if (status != CL_SUCCESS) { + goto done; + } - ret = cli_magic_scan_nested_fmap_type(ctx->fmap, part_off, part_size, ctx, - CL_TYPE_PART_ANY, namestr, LAYER_ATTRIBUTES_NONE); if (NULL != namestr) { free(namestr); - } - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + namestr = NULL; } } @@ -366,11 +353,17 @@ static int gpt_scan_partitions(cli_ctx *ctx, struct gpt_header hdr, size_t secto cli_dbgmsg("cli_scangpt: max partitions reached\n"); } - return detection; +done: + + if (NULL != namestr) { + free(namestr); + } + return status; } -static int gpt_validate_header(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize) +static cl_error_t gpt_validate_header(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize) { + cl_error_t status = CL_SUCCESS; uint32_t crc32_calc, crc32_ref; uint64_t tableLastLBA, lastLBA; size_t maplen, ptable_start, ptable_len; @@ -385,7 +378,8 @@ static int gpt_validate_header(cli_ctx *ctx, struct gpt_header hdr, size_t secto if (crc32_calc != crc32_ref) { cli_dbgmsg("cli_scangpt: GPT header checksum mismatch\n"); gpt_parsemsg("%x != %x\n", crc32_calc, crc32_ref); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* convert endian to host to check partition table */ @@ -415,56 +409,66 @@ static int gpt_validate_header(cli_ctx *ctx, struct gpt_header hdr, size_t secto if (hdr.signature != GPT_SIGNATURE) { cli_dbgmsg("cli_scangpt: Invalid GPT header signature %llx\n", (long long unsigned)hdr.signature); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* check header size */ if (hdr.headerSize != sizeof(hdr)) { cli_dbgmsg("cli_scangpt: GPT header size does not match stated size\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* check reserved value == 0 */ if (hdr.reserved != GPT_HDR_RESERVED) { cli_dbgmsg("cli_scangpt: GPT header reserved is not expected value\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* check that sectors are in a valid configuration */ if (!((hdr.currentLBA == GPT_PRIMARY_HDR_LBA && hdr.backupLBA == lastLBA) || (hdr.currentLBA == lastLBA && hdr.backupLBA == GPT_PRIMARY_HDR_LBA))) { cli_dbgmsg("cli_scangpt: GPT secondary header is not last LBA\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } if (hdr.firstUsableLBA > hdr.lastUsableLBA) { cli_dbgmsg("cli_scangpt: GPT first usable sectors is after last usable sector\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } if (hdr.firstUsableLBA <= GPT_PRIMARY_HDR_LBA || hdr.lastUsableLBA >= lastLBA) { cli_dbgmsg("cli_scangpt: GPT usable sectors intersects header sector\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } if ((hdr.tableStartLBA <= hdr.firstUsableLBA && tableLastLBA >= hdr.firstUsableLBA) || (hdr.tableStartLBA >= hdr.firstUsableLBA && hdr.tableStartLBA <= hdr.lastUsableLBA)) { cli_dbgmsg("cli_scangpt: GPT usable sectors intersects partition table\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } if (hdr.tableStartLBA <= GPT_PRIMARY_HDR_LBA || tableLastLBA >= lastLBA) { cli_dbgmsg("cli_scangpt: GPT partition table intersects header sector\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* check that valid table entry size */ if (hdr.tableEntrySize != sizeof(struct gpt_partition_entry)) { cli_dbgmsg("cli_scangpt: cannot parse gpt with partition entry sized %u\n", hdr.tableEntrySize); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* check valid table */ if ((ptable_start + ptable_len) > maplen) { cli_dbgmsg("cli_scangpt: GPT partition table extends over fmap limit\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /** END HEADER CHECKS **/ @@ -475,17 +479,20 @@ static int gpt_validate_header(cli_ctx *ctx, struct gpt_header hdr, size_t secto if (crc32_calc != hdr.tableCRC32) { cli_dbgmsg("cli_scangpt: GPT partition table checksum mismatch\n"); gpt_parsemsg("%x != %x\n", crc32_calc, hdr.tableCRC32); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } - return CL_SUCCESS; +done: + + return status; } -static int gpt_check_mbr(cli_ctx *ctx, size_t sectorsize) +static cl_error_t gpt_check_mbr(cli_ctx *ctx, size_t sectorsize) { + cl_error_t status = CL_SUCCESS; struct mbr_boot_record pmbr; size_t pos = 0, mbr_base = 0; - int ret = CL_CLEAN; unsigned i = 0; /* read the mbr */ @@ -494,7 +501,8 @@ static int gpt_check_mbr(cli_ctx *ctx, size_t sectorsize) if (fmap_readn(ctx->fmap, &pmbr, pos, sizeof(pmbr)) != sizeof(pmbr)) { cli_dbgmsg("cli_scangpt: Invalid primary MBR header\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* convert mbr */ @@ -528,7 +536,9 @@ static int gpt_check_mbr(cli_ctx *ctx, size_t sectorsize) /* check if the MBR and GPT partitions align - heuristic */ /* scan the MBR partitions - additional scans */ - return ret; +done: + + return status; } static void gpt_printSectors(cli_ctx *ctx, size_t sectorsize) @@ -582,16 +592,16 @@ static void gpt_printGUID(uint8_t GUID[], const char *msg) GUID[8], GUID[9], GUID[10], GUID[11], GUID[12], GUID[13], GUID[14], GUID[15]); } -static int gpt_partition_intersection(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize) +static cl_error_t gpt_partition_intersection(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize) { + cl_error_t status = CL_SUCCESS; + cl_error_t ret; partition_intersection_list_t prtncheck; struct gpt_partition_entry gpe; unsigned i, pitxn; - int ret = CL_CLEAN, tmp = CL_CLEAN; size_t pos; size_t maplen; uint32_t max_prtns = 0; - int virus_found = 0; maplen = ctx->fmap->len; @@ -613,8 +623,8 @@ static int gpt_partition_intersection(cli_ctx *ctx, struct gpt_header hdr, size_ /* read in partition entry */ if (fmap_readn(ctx->fmap, &gpe, pos, sizeof(gpe)) != sizeof(gpe)) { cli_dbgmsg("cli_scangpt: Invalid GPT partition entry\n"); - partition_intersection_list_free(&prtncheck); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* convert the endian to host */ @@ -629,22 +639,19 @@ static int gpt_partition_intersection(cli_ctx *ctx, struct gpt_header hdr, size_ } else if (((gpe.lastLBA + 1) * sectorsize) > maplen) { /* partition exists outside bounds of the file map */ } else { - tmp = partition_intersection_list_check(&prtncheck, &pitxn, gpe.firstLBA, gpe.lastLBA - gpe.firstLBA + 1); - if (tmp != CL_CLEAN) { - if (tmp == CL_VIRUS) { + ret = partition_intersection_list_check(&prtncheck, &pitxn, gpe.firstLBA, gpe.lastLBA - gpe.firstLBA + 1); + if (ret != CL_SUCCESS) { + if (ret == CL_VIRUS) { cli_dbgmsg("cli_scangpt: detected intersection with partitions " "[%u, %u]\n", pitxn, i); - ret = cli_append_virus(ctx, PRTN_INTXN_DETECTION); - if (ret == CL_VIRUS) - virus_found = 1; - if (SCAN_ALLMATCHES || ret == CL_CLEAN) - tmp = 0; - else - goto leave; + status = cli_append_potentially_unwanted(ctx, "Heuristics.GPTPartitionIntersection"); + if (status != CL_SUCCESS) { + goto done; + } } else { - ret = tmp; - goto leave; + status = ret; + goto done; } } } @@ -653,9 +660,8 @@ static int gpt_partition_intersection(cli_ctx *ctx, struct gpt_header hdr, size_ pos += hdr.tableEntrySize; } -leave: +done: + partition_intersection_list_free(&prtncheck); - if (virus_found) - return CL_VIRUS; - return ret; + return status; } diff --git a/libclamav/gpt.h b/libclamav/gpt.h index 39ab124aff..bc79c26ae7 100644 --- a/libclamav/gpt.h +++ b/libclamav/gpt.h @@ -25,7 +25,7 @@ #include "clamav-config.h" #endif -#include "clamav-types.h" +#include "clamav.h" #include "others.h" /* GPT sector size is normally 512 bytes be can be set to much larger @@ -94,6 +94,6 @@ struct gpt_partition_entry { #endif size_t gpt_detect_size(fmap_t *map); -int cli_scangpt(cli_ctx *ctx, size_t sectorsize); +cl_error_t cli_scangpt(cli_ctx *ctx, size_t sectorsize); #endif diff --git a/libclamav/hashtab.c b/libclamav/hashtab.c index a30f05c448..83558ed6a7 100644 --- a/libclamav/hashtab.c +++ b/libclamav/hashtab.c @@ -172,7 +172,7 @@ static inline void PROFILE_REPORT(const struct cli_hashtable *s) #define PROFILE_REPORT(s) #endif -int cli_hashtab_init(struct cli_hashtable *s, size_t capacity) +cl_error_t cli_hashtab_init(struct cli_hashtable *s, size_t capacity) { if (!s) return CL_ENULLARG; @@ -181,15 +181,16 @@ int cli_hashtab_init(struct cli_hashtable *s, size_t capacity) capacity = nearest_power(capacity); s->htable = cli_calloc(capacity, sizeof(*s->htable)); - if (!s->htable) + if (!s->htable) { return CL_EMEM; + } s->capacity = capacity; s->used = 0; s->maxfill = 8 * capacity / 10; - return 0; + return CL_SUCCESS; } -int cli_htu32_init(struct cli_htu32 *s, size_t capacity, mpool_t *mempool) +cl_error_t cli_htu32_init(struct cli_htu32 *s, size_t capacity, mpool_t *mempool) { if (!s) return CL_ENULLARG; @@ -198,12 +199,13 @@ int cli_htu32_init(struct cli_htu32 *s, size_t capacity, mpool_t *mempool) capacity = nearest_power(capacity); s->htable = MPOOL_CALLOC(mempool, capacity, sizeof(*s->htable)); - if (!s->htable) + if (!s->htable) { return CL_EMEM; + } s->capacity = capacity; s->used = 0; s->maxfill = 8 * capacity / 10; - return 0; + return CL_SUCCESS; } static inline uint32_t hash32shift(uint32_t key) @@ -296,7 +298,6 @@ const struct cli_htu32_element *cli_htu32_find(const struct cli_htu32 *s, uint32 return NULL; /* not found */ } -/* linear enumeration - start with current = NULL, returns next item if present or NULL if not */ const struct cli_htu32_element *cli_htu32_next(const struct cli_htu32 *s, const struct cli_htu32_element *current) { size_t ncur; @@ -320,7 +321,7 @@ const struct cli_htu32_element *cli_htu32_next(const struct cli_htu32 *s, const return NULL; } -static int cli_hashtab_grow(struct cli_hashtable *s) +static cl_error_t cli_hashtab_grow(struct cli_hashtable *s) { const size_t new_capacity = nearest_power(s->capacity + 1); struct cli_element *htable; @@ -377,7 +378,7 @@ static int cli_hashtab_grow(struct cli_hashtable *s) #define cli_htu32_grow(A, B) cli_htu32_grow(A) #endif -static int cli_htu32_grow(struct cli_htu32 *s, mpool_t *mempool) +static cl_error_t cli_htu32_grow(struct cli_htu32 *s, mpool_t *mempool) { const size_t new_capacity = nearest_power(s->capacity + 1); struct cli_htu32_element *htable = MPOOL_CALLOC(mempool, new_capacity, sizeof(*s->htable)); @@ -482,13 +483,13 @@ const struct cli_element *cli_hashtab_insert(struct cli_hashtable *s, const char return NULL; } -int cli_htu32_insert(struct cli_htu32 *s, const struct cli_htu32_element *item, mpool_t *mempool) +cl_error_t cli_htu32_insert(struct cli_htu32 *s, const struct cli_htu32_element *item, mpool_t *mempool) { + cl_error_t ret; struct cli_htu32_element *element; struct cli_htu32_element *deleted_element = NULL; size_t tries = 1; size_t idx; - int ret; if (!s) return CL_ENULLARG; @@ -513,14 +514,14 @@ int cli_htu32_insert(struct cli_htu32 *s, const struct cli_htu32_element *item, } *element = *item; s->used++; - return 0; + return CL_SUCCESS; } else if (element->key == DELETED_HTU32_KEY) { deleted_element = element; element->key = 0; } else if (item->key == element->key) { PROFILE_DATA_UPDATE(s, tries); element->data = item->data; /* key found, update */ - return 0; + return CL_SUCCESS; } else { idx = (idx + tries++) % s->capacity; element = &s->htable[idx]; @@ -592,7 +593,7 @@ size_t cli_htu32_numitems(struct cli_htu32 *s) return s->capacity; } -int cli_hashtab_store(const struct cli_hashtable *s, FILE *out) +cl_error_t cli_hashtab_store(const struct cli_hashtable *s, FILE *out) { size_t i; for (i = 0; i < s->capacity; i++) { @@ -604,7 +605,7 @@ int cli_hashtab_store(const struct cli_hashtable *s, FILE *out) return CL_SUCCESS; } -int cli_hashtab_generate_c(const struct cli_hashtable *s, const char *name) +cl_error_t cli_hashtab_generate_c(const struct cli_hashtable *s, const char *name) { size_t i; printf("/* TODO: include GPL headers */\n"); @@ -625,10 +626,10 @@ int cli_hashtab_generate_c(const struct cli_hashtable *s, const char *name) printf("\n};\n"); PROFILE_REPORT(s); - return 0; + return CL_SUCCESS; } -int cli_hashtab_load(FILE *in, struct cli_hashtable *s) +cl_error_t cli_hashtab_load(FILE *in, struct cli_hashtable *s) { char line[1024]; while (fgets(line, sizeof(line), in)) { @@ -640,9 +641,7 @@ int cli_hashtab_load(FILE *in, struct cli_hashtable *s) return CL_SUCCESS; } -/* Initialize hashset. @initial_capacity is rounded to nearest power of 2. - * Load factor is between 50 and 99. When capacity*load_factor/100 is reached, the hashset is growed */ -int cli_hashset_init(struct cli_hashset *hs, size_t initial_capacity, uint8_t load_factor) +cl_error_t cli_hashset_init(struct cli_hashset *hs, size_t initial_capacity, uint8_t load_factor) { if (load_factor < 50 || load_factor > 99) { cli_dbgmsg(MODULE_NAME "Invalid load factor: %u, using default of 80%%\n", load_factor); @@ -665,10 +664,10 @@ int cli_hashset_init(struct cli_hashset *hs, size_t initial_capacity, uint8_t lo cli_errmsg("hashtab.c: Unable to allocate memory for hs->bitmap\n"); return CL_EMEM; } - return 0; + return CL_SUCCESS; } -int cli_hashset_init_pool(struct cli_hashset *hs, size_t initial_capacity, uint8_t load_factor, mpool_t *mempool) +cl_error_t cli_hashset_init_pool(struct cli_hashset *hs, size_t initial_capacity, uint8_t load_factor, mpool_t *mempool) { if (load_factor < 50 || load_factor > 99) { cli_dbgmsg(MODULE_NAME "Invalid load factor: %u, using default of 80%%\n", load_factor); @@ -691,7 +690,7 @@ int cli_hashset_init_pool(struct cli_hashset *hs, size_t initial_capacity, uint8 cli_errmsg("hashtab.c: Unable to allocate/initialize memory for hs->keys\n"); return CL_EMEM; } - return 0; + return CL_SUCCESS; } void cli_hashset_destroy(struct cli_hashset *hs) @@ -746,23 +745,25 @@ static void cli_hashset_addkey_internal(struct cli_hashset *hs, const uint32_t k } } -static int cli_hashset_grow(struct cli_hashset *hs) +static cl_error_t cli_hashset_grow(struct cli_hashset *hs) { struct cli_hashset new_hs; size_t i; - int rc; + cl_error_t rc; /* in-place growing is not possible, since the new keys * will hash to different locations. */ cli_dbgmsg(MODULE_NAME "Growing hashset, used: %u, capacity: %u\n", hs->count, hs->capacity); /* create a bigger hashset */ - if (hs->mempool) + if (hs->mempool) { rc = cli_hashset_init_pool(&new_hs, hs->capacity << 1, hs->limit * 100 / hs->capacity, hs->mempool); - else + } else { rc = cli_hashset_init(&new_hs, hs->capacity << 1, hs->limit * 100 / hs->capacity); - if (rc != 0) + } + if (rc != CL_SUCCESS) { return rc; + } /* and copy keys */ for (i = 0; i < hs->capacity; i++) { if (BITMAP_CONTAINS(hs->bitmap, i)) { @@ -773,39 +774,39 @@ static int cli_hashset_grow(struct cli_hashset *hs) cli_hashset_destroy(hs); /* replace old hashset with new one */ *hs = new_hs; - return 0; + return CL_SUCCESS; } -int cli_hashset_addkey(struct cli_hashset *hs, const uint32_t key) +cl_error_t cli_hashset_addkey(struct cli_hashset *hs, const uint32_t key) { /* check that we didn't reach the load factor. * Even if we don't know yet whether we'd add this key */ if (hs->count + 1 > hs->limit) { - int rc = cli_hashset_grow(hs); - if (rc) { + cl_error_t rc = cli_hashset_grow(hs); + if (rc != CL_SUCCESS) { return rc; } } cli_hashset_addkey_internal(hs, key); - return 0; + return CL_SUCCESS; } -int cli_hashset_removekey(struct cli_hashset *hs, const uint32_t key) +cl_error_t cli_hashset_removekey(struct cli_hashset *hs, const uint32_t key) { const size_t idx = cli_hashset_search(hs, key); if (BITMAP_CONTAINS(hs->bitmap, idx)) { BITMAP_REMOVE(hs->bitmap, idx); hs->keys[idx] = 0; hs->count--; - return 0; + return CL_SUCCESS; } - return -1; + return CL_ERROR; } -int cli_hashset_contains(const struct cli_hashset *hs, const uint32_t key) +bool cli_hashset_contains(const struct cli_hashset *hs, const uint32_t key) { const size_t idx = cli_hashset_search(hs, key); - return BITMAP_CONTAINS(hs->bitmap, idx); + return BITMAP_CONTAINS(hs->bitmap, idx) != 0; } ssize_t cli_hashset_toarray(const struct cli_hashset *hs, uint32_t **array) @@ -814,12 +815,13 @@ ssize_t cli_hashset_toarray(const struct cli_hashset *hs, uint32_t **array) uint32_t *arr; if (!array) { - return CL_ENULLARG; + return -1; } + *array = arr = cli_malloc(hs->count * sizeof(*arr)); if (!arr) { cli_errmsg("hashtab.c: Unable to allocate memory for array\n"); - return CL_EMEM; + return -1; } for (i = 0, j = 0; i < hs->capacity && j < hs->count; i++) { @@ -835,71 +837,103 @@ void cli_hashset_init_noalloc(struct cli_hashset *hs) memset(hs, 0, sizeof(*hs)); } -int cli_hashset_contains_maybe_noalloc(const struct cli_hashset *hs, const uint32_t key) +bool cli_hashset_contains_maybe_noalloc(const struct cli_hashset *hs, const uint32_t key) { - if (!hs->keys) - return 0; + if (!hs->keys) { + return false; + } + return cli_hashset_contains(hs, key); } -int cli_map_init(struct cli_map *m, int32_t keysize, int32_t valuesize, - int32_t capacity) +cl_error_t cli_map_init(struct cli_map *m, int32_t keysize, int32_t valuesize, + int32_t capacity) { - if (keysize <= 0 || valuesize < 0 || capacity <= 0) - return -CL_EARG; + cl_error_t ret; + + if (keysize <= 0 || valuesize < 0 || capacity <= 0) { + return CL_EARG; + } + memset(m, 0, sizeof(*m)); - cli_hashtab_init(&m->htab, 16); + + ret = cli_hashtab_init(&m->htab, 16); + if (CL_SUCCESS != ret) { + return ret; + } + m->keysize = keysize; m->valuesize = valuesize; m->last_insert = -1; m->last_find = -1; - return 0; + + return CL_SUCCESS; } -int cli_map_addkey(struct cli_map *m, const void *key, int32_t keysize) +cl_error_t cli_map_addkey(struct cli_map *m, const void *key, int32_t keysize) { uint32_t n; struct cli_element *el; - if (m->keysize != keysize) - return -CL_EARG; + + if (m->keysize != keysize) { + return CL_EARG; + } + el = cli_hashtab_find(&m->htab, key, keysize); if (el) { + // already exists m->last_insert = (int32_t)el->data; - return 0; + return CL_ECREAT; } n = m->nvalues + 1; if (m->valuesize) { void *v; + v = cli_realloc(m->u.sized_values, n * m->valuesize); - if (!v) - return -CL_EMEM; + if (!v) { + return CL_EMEM; + } + m->u.sized_values = v; memset((char *)m->u.sized_values + (n - 1) * m->valuesize, 0, m->valuesize); } else { struct cli_map_value *v; + v = cli_realloc(m->u.unsized_values, n * sizeof(*m->u.unsized_values)); - if (!v) - return -CL_EMEM; + if (!v) { + return CL_EMEM; + } + m->u.unsized_values = v; memset(&m->u.unsized_values[n - 1], 0, sizeof(*m->u.unsized_values)); } m->nvalues = n; - if (!cli_hashtab_insert(&m->htab, key, keysize, (const cli_element_data)(n - 1))) - return -CL_EMEM; + if (!cli_hashtab_insert(&m->htab, key, keysize, (const cli_element_data)(n - 1))) { + return CL_EMEM; + } + m->last_insert = n - 1; - return 1; + return CL_SUCCESS; } -int cli_map_removekey(struct cli_map *m, const void *key, int32_t keysize) +cl_error_t cli_map_removekey(struct cli_map *m, const void *key, int32_t keysize) { struct cli_element *el; - if (m->keysize != keysize) - return -CL_EARG; + + if (m->keysize != keysize) { + return CL_EARG; + } + el = cli_hashtab_find(&m->htab, key, keysize); - if (!el) - return 0; - if ((int32_t)el->data >= (int32_t)m->nvalues || (int32_t)el->data < 0) - return -CL_EARG; + if (!el) { + // not found, can't remove + return CL_EUNLINK; + } + + if ((int32_t)el->data >= (int32_t)m->nvalues || (int32_t)el->data < 0) { + return CL_EARG; + } + if (!m->valuesize) { struct cli_map_value *v = &m->u.unsized_values[(int32_t)el->data]; free(v->value); @@ -909,72 +943,99 @@ int cli_map_removekey(struct cli_map *m, const void *key, int32_t keysize) char *v = (char *)m->u.sized_values + (int32_t)el->data * m->valuesize; memset(v, 0, m->valuesize); } + cli_hashtab_delete(&m->htab, key, keysize); - return 1; + + return CL_SUCCESS; } -int cli_map_setvalue(struct cli_map *m, const void *value, int32_t valuesize) +cl_error_t cli_map_setvalue(struct cli_map *m, const void *value, int32_t valuesize) { - if ((m->valuesize && m->valuesize != valuesize) || (uint32_t)(m->last_insert) >= m->nvalues || m->last_insert < 0) - return -CL_EARG; + if ((m->valuesize && m->valuesize != valuesize) || (uint32_t)(m->last_insert) >= m->nvalues || m->last_insert < 0) { + return CL_EARG; + } + if (m->valuesize) { memcpy((char *)m->u.sized_values + m->last_insert * m->valuesize, value, valuesize); } else { struct cli_map_value *v = &m->u.unsized_values[m->last_insert]; - if (v->value) + + if (v->value) { free(v->value); + } + v->value = cli_malloc(valuesize); if (!v->value) { cli_errmsg("hashtab.c: Unable to allocate memory for v->value\n"); - return -CL_EMEM; + return CL_EMEM; } + memcpy(v->value, value, valuesize); v->valuesize = valuesize; } - return 0; + return CL_SUCCESS; } -int cli_map_find(struct cli_map *m, const void *key, int32_t keysize) +cl_error_t cli_map_find(struct cli_map *m, const void *key, int32_t keysize) { struct cli_element *el; - if (m->keysize != keysize) - return -CL_EARG; + if (m->keysize != keysize) { + return CL_EARG; + } + el = cli_hashtab_find(&m->htab, key, keysize); - if (!el) - return 0; + if (!el) { + // not found + return CL_EACCES; + } + m->last_find = (int32_t)el->data; - return 1; + + return CL_SUCCESS; } int cli_map_getvalue_size(struct cli_map *m) { - if (m->valuesize) + if (m->valuesize) { return m->valuesize; - if (m->last_find < 0 || (uint32_t)(m->last_find) >= m->nvalues) - return -CL_EARG; + } + + if (m->last_find < 0 || (uint32_t)(m->last_find) >= m->nvalues) { + return -1; + } + return m->u.unsized_values[m->last_find].valuesize; } void *cli_map_getvalue(struct cli_map *m) { - if (m->last_find < 0 || (uint32_t)(m->last_find) >= m->nvalues) + if (m->last_find < 0 || (uint32_t)(m->last_find) >= m->nvalues) { return NULL; - if (m->valuesize) + } + + if (m->valuesize) { return (char *)m->u.sized_values + m->last_find * m->valuesize; + } + return m->u.unsized_values[m->last_find].value; } void cli_map_delete(struct cli_map *m) { cli_hashtab_free(&m->htab); + if (!m->valuesize) { unsigned i; - for (i = 0; i < m->nvalues; i++) + + for (i = 0; i < m->nvalues; i++) { free(m->u.unsized_values[i].value); + } + free(m->u.unsized_values); } else { free(m->u.sized_values); } + memset(m, 0, sizeof(*m)); } diff --git a/libclamav/hashtab.h b/libclamav/hashtab.h index e17fd58d4a..4c8fec13f1 100644 --- a/libclamav/hashtab.h +++ b/libclamav/hashtab.h @@ -30,10 +30,26 @@ #include #include #include +#include -#include "clamav-types.h" +#include "clamav.h" #include "clamav-config.h" #include "mpool.h" + +/******************************************************************************/ +/* A hash table. + * + * There are two types: + * 1. hashtable: + * The key is a const char* (string) + * The value (data) is a buffer, stored as a size_t (instead of a void *) and an offset. + * + * 2. htu32 (hashtable uint32_t) + * Th ekey is a uint32_t number + * The value (data) is a buffer, stored as either a size_t, or as a void *, and an offset. + */ +/******************************************************************************/ + typedef size_t cli_element_data; /* define this for debugging/profiling purposes only, NOT in production/release code */ @@ -81,15 +97,81 @@ struct cli_hashtable { STRUCT_PROFILE }; -int cli_hashtab_generate_c(const struct cli_hashtable *s, const char *name); +/** + * @brief Generate C source code that represents the given hash table + * + * Comment: We don't really use this. + * + * @param s + * @param name Some string name for the elements of this generated table. + * @return cl_error_t + */ +cl_error_t cli_hashtab_generate_c(const struct cli_hashtable *s, const char *name); + struct cli_element *cli_hashtab_find(const struct cli_hashtable *s, const char *key, const size_t len); -int cli_hashtab_init(struct cli_hashtable *s, size_t capacity); + +/** + * @brief Create a new hashtab with a given capacity. + * + * @param s + * @param capacity + * @return cl_error_t + */ +cl_error_t cli_hashtab_init(struct cli_hashtable *s, size_t capacity); + +/** + * @brief Insert a new key with data into the hashtable. + * + * @param s + * @param key + * @param len + * @param data + * @return const struct cli_element* + */ const struct cli_element *cli_hashtab_insert(struct cli_hashtable *s, const char *key, const size_t len, const cli_element_data data); + +/** + * @brief Delete a key from the hash table + * + * @param s + * @param key + * @param len + */ void cli_hashtab_delete(struct cli_hashtable *s, const char *key, const size_t len); + +/** + * @brief Remove all keys from the hashtable + * + * @param s + */ void cli_hashtab_clear(struct cli_hashtable *s); + +/** + * @brief Free the hash table + * + * This will clear the hash table first. You don't need to clear it manually first. + * + * @param s + */ void cli_hashtab_free(struct cli_hashtable *s); -int cli_hashtab_load(FILE *in, struct cli_hashtable *s); -int cli_hashtab_store(const struct cli_hashtable *s, FILE *out); + +/** + * @brief Load a hash table from a file. (unpickle!) + * + * @param in + * @param s + * @return cl_error_t + */ +cl_error_t cli_hashtab_load(FILE *in, struct cli_hashtable *s); + +/** + * @brief Write a hash table to a file. (pickle!) + * + * @param s + * @param out + * @return cl_error_t + */ +cl_error_t cli_hashtab_store(const struct cli_hashtable *s, FILE *out); struct cli_htu32_element { uint32_t key; @@ -108,21 +190,121 @@ struct cli_htu32 { STRUCT_PROFILE }; -#ifndef USE_MPOOL -#define cli_htu32_init(A, B, C) cli_htu32_init(A, B) -#define cli_htu32_insert(A, B, C) cli_htu32_insert(A, B) -#define cli_htu32_free(A, B) cli_htu32_free(A) +#ifdef USE_MPOOL + +/** + * @brief A macro to wrap cli_htu32_init() where you can assume MEMPOOL is enabled, + * but will replace the last partment with NULL if MEMPOOL is not enabled. + */ +#define CLI_HTU32_INIT(A, B, C) cli_htu32_init(A, B, C) +/** + * @brief A macro to wrap cli_htu32_insert() where you can assume MEMPOOL is enabled, + * but will replace the last partment with NULL if MEMPOOL is not enabled. + */ +#define CLI_HTU32_INSERT(A, B, C) cli_htu32_insert(A, B, C) +/** + * @brief A macro to wrap cli_htu32_free() where you can assume MEMPOOL is enabled, + * but will replace the last partment with NULL if MEMPOOL is not enabled. + */ +#define CLI_HTU32_FREE(A, B) cli_htu32_free(A, B) + +#else + +/** + * @brief A macro to wrap cli_htu32_init() where you can assume MEMPOOL is enabled, + * but will replace the last partment with NULL if MEMPOOL is not enabled. + */ +#define CLI_HTU32_INIT(A, B, C) cli_htu32_init(A, B, NULL) +/** + * @brief A macro to wrap cli_htu32_insert() where you can assume MEMPOOL is enabled, + * but will replace the last partment with NULL if MEMPOOL is not enabled. + */ +#define CLI_HTU32_INSERT(A, B, C) cli_htu32_insert(A, B, NULL) +/** + * @brief A macro to wrap cli_htu32_free() where you can assume MEMPOOL is enabled, + * but will replace the last partment with NULL if MEMPOOL is not enabled. + */ +#define CLI_HTU32_FREE(A, B) cli_htu32_free(A, NULL) + #endif -int cli_htu32_init(struct cli_htu32 *s, size_t capacity, mpool_t *mempool); -int cli_htu32_insert(struct cli_htu32 *s, const struct cli_htu32_element *item, mpool_t *mempool); + +/** + * @brief Initialize a new u32 hashtable. + * + * @param s + * @param capacity + * @param mempool If MEMPOOL not enabled, this can be NULL. + * @return cl_error_t + */ +cl_error_t cli_htu32_init(struct cli_htu32 *s, size_t capacity, mpool_t *mempool); + +/** + * @brief Insert a new element into the u32 hashtable. + * + * @param s + * @param item + * @param mempool + * @return cl_error_t + */ +cl_error_t cli_htu32_insert(struct cli_htu32 *s, const struct cli_htu32_element *item, mpool_t *mempool); + +/** + * @brief Free the u32 hashtable. + * + * This will clear the hash table first. You don't need to clear it manually first. + * + * @param s + * @param mempool + */ +void cli_htu32_free(struct cli_htu32 *s, mpool_t *mempool); + +/** + * @brief Find a sepcific element by key in the u32 hashtable. + * + * @param s + * @param key + * @return const struct cli_htu32_element* + */ const struct cli_htu32_element *cli_htu32_find(const struct cli_htu32 *s, uint32_t key); + +/** + * @brief Remove a specific element from the u32 hashtable. + * + * @param s + * @param key + */ void cli_htu32_delete(struct cli_htu32 *s, uint32_t key); + +/** + * @brief Remove all elements from the u32 hashtable. + * + * @param s + */ void cli_htu32_clear(struct cli_htu32 *s); -void cli_htu32_free(struct cli_htu32 *s, mpool_t *mempool); + +/** + * @brief Get the next element in the table, following the provided element + * + * Use this to enumerate the table linearly. + * + * @param s + * @param current If you feed it NULL, it will give you the first element. + * @return const struct cli_htu32_element* Will return the next element, or NULL if there are no further elements. + */ const struct cli_htu32_element *cli_htu32_next(const struct cli_htu32 *s, const struct cli_htu32_element *current); + +/** + * @brief Get the number of items in the u32 hashtable. + * + * @param s + * @return size_t + */ size_t cli_htu32_numitems(struct cli_htu32 *s); +/******************************************************************************/ /* a hashtable that stores the values too */ +/******************************************************************************/ + struct cli_map_value { void *value; int32_t valuesize; @@ -140,17 +322,97 @@ struct cli_map { int32_t last_insert; int32_t last_find; }; -int cli_map_init(struct cli_map *m, int32_t keysize, int32_t valuesize, - int32_t capacity); -int cli_map_addkey(struct cli_map *m, const void *key, int32_t keysize); -int cli_map_removekey(struct cli_map *m, const void *key, int32_t keysize); -int cli_map_setvalue(struct cli_map *m, const void *value, int32_t valuesize); -int cli_map_find(struct cli_map *m, const void *key, int32_t keysize); + +/** + * @brief Initialize a new map + * + * @param m + * @param keysize + * @param valuesize + * @param capacity + * @return cl_error_t CL_SUCCESS on success + * @return cl_error_t CL_E* if some error occured + */ +cl_error_t cli_map_init(struct cli_map *m, int32_t keysize, int32_t valuesize, + int32_t capacity); + +/** + * @brief add key to the map + * + * @param m + * @param key + * @param keysize + * @return cl_error_t CL_SUCCESS if added. + * @return cl_error_t CL_ECREAT if already present. + * @return cl_error_t CL_E* if some error occured. + */ +cl_error_t cli_map_addkey(struct cli_map *m, const void *key, int32_t keysize); + +/** + * @brief remove key from the map + * + * @param m + * @param key + * @param keysize + * @return cl_error_t CL_SUCCESS if removed. + * @return cl_error_t CL_EUNLINK if not present, so didn't need to be removed. + * @return cl_error_t CL_E* if some error occured. + */ +cl_error_t cli_map_removekey(struct cli_map *m, const void *key, int32_t keysize); + +/** + * @brief set the value for the last inserted key with map_addkey + * + * @param m + * @param value + * @param valuesize + * @return cl_error_t CL_SUCCESS on success + * @return cl_error_t CL_E* if some error occured + */ +cl_error_t cli_map_setvalue(struct cli_map *m, const void *value, int32_t valuesize); + +/** + * @brief find key in the map + * + * @param m + * @param key + * @param keysize + * @return cl_error_t CL_SUCCESS if found + * @return cl_error_t CL_EACCES if NOT found + * @return cl_error_t CL_E* if some error occured. + */ +cl_error_t cli_map_find(struct cli_map *m, const void *key, int32_t keysize); + +/** + * @brief get the size of value obtained during the last map_find + * + * @param m + * @return int the value size on success + * @return int -1 on failure + */ int cli_map_getvalue_size(struct cli_map *m); + +/** + * @brief get the value obtained during the last map_find + * + * @param m + * @return void* the value on success + * @return void* NULL on failure + */ void *cli_map_getvalue(struct cli_map *m); + +/** + * @brief delete the map + * + * @param m + */ void cli_map_delete(struct cli_map *m); -/* A set of unique keys. */ +/******************************************************************************/ +/* A set of unique keys (no values). + * The keys are just uint32_t numbers. */ +/******************************************************************************/ + struct cli_hashset { uint32_t *keys; uint32_t *bitmap; @@ -161,21 +423,102 @@ struct cli_hashset { uint32_t limit; }; -int cli_hashset_init(struct cli_hashset *hs, size_t initial_capacity, uint8_t load_factor); -int cli_hashset_init_pool(struct cli_hashset *hs, size_t initial_capacity, uint8_t load_factor, mpool_t *mempool); -int cli_hashset_addkey(struct cli_hashset *hs, const uint32_t key); -int cli_hashset_removekey(struct cli_hashset *hs, const uint32_t key); -int cli_hashset_contains(const struct cli_hashset *hs, const uint32_t key); -int cli_hashset_clear(struct cli_hashset *hs); +/** + * @brief Initialize hashset. + * + * When capacity * (load_factor/100) is reached, the hashset is growed. + * + * @param hs + * @param initial_capacity is rounded to nearest power of 2. + * @param load_factor is between 50 and 99. + * @return cl_error_t + */ +cl_error_t cli_hashset_init(struct cli_hashset *hs, size_t initial_capacity, uint8_t load_factor); + +/** + * @brief Initialize hashset using the clamav MEMPOOL instead of just malloc/realloc. + * + * Comment: not presently used in any parsers or signature loaders or anything. + * + * @param hs + * @param initial_capacity is rounded to nearest power of 2. + * @param load_factor is between 50 and 99. + * @param mempool the mempool + * @return cl_error_t + */ +cl_error_t cli_hashset_init_pool(struct cli_hashset *hs, size_t initial_capacity, uint8_t load_factor, mpool_t *mempool); + +/** + * @brief Add a key to the hashset. + * + * @param hs + * @param key + * @return cl_error_t + */ +cl_error_t cli_hashset_addkey(struct cli_hashset *hs, const uint32_t key); + +/** + * @brief Remove a key from the hashset + * + * @param hs + * @param key + * @return cl_error_t + */ +cl_error_t cli_hashset_removekey(struct cli_hashset *hs, const uint32_t key); + +/** + * @brief Find out if hashset contains akey + * + * @param hs + * @param key + * @return true If found + * @return false If not found + */ +bool cli_hashset_contains(const struct cli_hashset *hs, const uint32_t key); + +/** + * @brief Destroy/deallocate a hashset. + * + * @param hs + */ void cli_hashset_destroy(struct cli_hashset *hs); + +/** + * @brief Convert the hashset to an array of uint32_t's + * + * It will allocate a 0-length array! You are still responsible for freeing it if + * it returns 0! + * + * You don't need to free anything if it returns -1. + * + * @param hs + * @param [out] array Allocated array of the length returned. Caller must free it. + * @return ssize_t The length of the array if success, or else -1 if failed. + */ ssize_t cli_hashset_toarray(const struct cli_hashset *hs, uint32_t **array); -int cli_hashset_removekey(struct cli_hashset *hs, const uint32_t key); -/* Initializes the set without allocating memory, you can do lookups on it +/** + * @brief Initializes the set without allocating memory + * + * Initializes the set without allocating memory, you can do lookups on it * using _contains_maybe_noalloc. You need to initialize it using _init - * before using _addkey or _removekey though */ + * before using _addkey or _removekey though + * + * @param hs + */ void cli_hashset_init_noalloc(struct cli_hashset *hs); -/* this works like its _contains counterpart above, except that the hashset may - * have not been initialized by _init, only by _init_noalloc */ -int cli_hashset_contains_maybe_noalloc(const struct cli_hashset *hs, const uint32_t key); + +/** + * @brief + * + * this works like cli_hashset_contains (above), except that the hashset may + * have not been initialized by _init, only by _init_noalloc + * + * @param hs + * @param key + * @return true If found + * @return false If not found + */ +bool cli_hashset_contains_maybe_noalloc(const struct cli_hashset *hs, const uint32_t key); + #endif diff --git a/libclamav/hfsplus.c b/libclamav/hfsplus.c index 47fbe13352..98a83e540c 100644 --- a/libclamav/hfsplus.c +++ b/libclamav/hfsplus.c @@ -47,14 +47,14 @@ static void nodedescriptor_print(const char *, hfsNodeDescriptor *); static void forkdata_to_host(hfsPlusForkData *); static void forkdata_print(const char *, hfsPlusForkData *); -static int hfsplus_volumeheader(cli_ctx *, hfsPlusVolumeHeader **); -static int hfsplus_readheader(cli_ctx *, hfsPlusVolumeHeader *, hfsNodeDescriptor *, - hfsHeaderRecord *, int, const char *); +static cl_error_t hfsplus_volumeheader(cli_ctx *, hfsPlusVolumeHeader **); +static cl_error_t hfsplus_readheader(cli_ctx *, hfsPlusVolumeHeader *, hfsNodeDescriptor *, + hfsHeaderRecord *, int, const char *); static cl_error_t hfsplus_scanfile(cli_ctx *, hfsPlusVolumeHeader *, hfsHeaderRecord *, hfsPlusForkData *, const char *, char **, char *); -static int hfsplus_validate_catalog(cli_ctx *, hfsPlusVolumeHeader *, hfsHeaderRecord *); -static int hfsplus_fetch_node(cli_ctx *, hfsPlusVolumeHeader *, hfsHeaderRecord *, - hfsHeaderRecord *, hfsPlusForkData *, uint32_t, uint8_t *); +static cl_error_t hfsplus_validate_catalog(cli_ctx *, hfsPlusVolumeHeader *, hfsHeaderRecord *); +static cl_error_t hfsplus_fetch_node(cli_ctx *, hfsPlusVolumeHeader *, hfsHeaderRecord *, + hfsHeaderRecord *, hfsPlusForkData *, uint32_t, uint8_t *); static cl_error_t hfsplus_walk_catalog(cli_ctx *, hfsPlusVolumeHeader *, hfsHeaderRecord *, hfsHeaderRecord *, hfsHeaderRecord *, const char *); @@ -128,7 +128,7 @@ static void forkdata_print(const char *pfx, hfsPlusForkData *fork) } /* Read and convert the HFS+ volume header */ -static int hfsplus_volumeheader(cli_ctx *ctx, hfsPlusVolumeHeader **header) +static cl_error_t hfsplus_volumeheader(cli_ctx *ctx, hfsPlusVolumeHeader **header) { hfsPlusVolumeHeader *volHeader; const uint8_t *mPtr; @@ -209,8 +209,8 @@ static int hfsplus_volumeheader(cli_ctx *ctx, hfsPlusVolumeHeader **header) } /* Read and convert the header node */ -static int hfsplus_readheader(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsNodeDescriptor *nodeDesc, - hfsHeaderRecord *headerRec, int headerType, const char *name) +static cl_error_t hfsplus_readheader(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsNodeDescriptor *nodeDesc, + hfsHeaderRecord *headerRec, int headerType, const char *name) { const uint8_t *mPtr = NULL; off_t offset; @@ -317,11 +317,11 @@ static int hfsplus_readheader(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsN static cl_error_t hfsplus_scanfile(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *extHeader, hfsPlusForkData *fork, const char *dirname, char **filename, char *orig_filename) { + cl_error_t status = CL_SUCCESS; hfsPlusExtentDescriptor *currExt; const uint8_t *mPtr = NULL; char *tmpname = NULL; int ofd; - cl_error_t ret = CL_CLEAN; uint64_t targetSize; uint32_t outputBlocks = 0; uint8_t ext; @@ -331,7 +331,7 @@ static cl_error_t hfsplus_scanfile(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, /* bad record checks */ if (!fork || (fork->logicalSize == 0) || (fork->totalBlocks == 0)) { cli_dbgmsg("hfsplus_scanfile: Empty file.\n"); - return CL_CLEAN; + goto done; } /* check limits */ @@ -339,19 +339,20 @@ static cl_error_t hfsplus_scanfile(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, #if SIZEOF_LONG < 8 if (targetSize > ULONG_MAX) { cli_dbgmsg("hfsplus_scanfile: File too large for limit check.\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } #endif - ret = cli_checklimits("hfsplus_scanfile", ctx, (unsigned long)targetSize, 0, 0); - if (ret != CL_CLEAN) { - return ret; + status = cli_checklimits("hfsplus_scanfile", ctx, (unsigned long)targetSize, 0, 0); + if (status != CL_SUCCESS) { + goto done; } /* open file */ - ret = cli_gentempfd(dirname, &tmpname, &ofd); - if (ret != CL_CLEAN) { + status = cli_gentempfd(dirname, &tmpname, &ofd); + if (status != CL_SUCCESS) { cli_dbgmsg("hfsplus_scanfile: Cannot generate temporary file.\n"); - return ret; + goto done; } cli_dbgmsg("hfsplus_scanfile: Extracting to %s\n", tmpname); @@ -367,6 +368,7 @@ static cl_error_t hfsplus_scanfile(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, cli_dbgmsg("hfsplus_scanfile: output all blocks, remaining size " STDu64 "\n", targetSize); break; } + /* Prepare extent */ if (ext < 8) { currExt = &(fork->extents[ext]); @@ -374,85 +376,107 @@ static cl_error_t hfsplus_scanfile(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, } else { cli_dbgmsg("hfsplus_scanfile: need next extent from ExtentOverflow\n"); /* Not implemented yet */ - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } + /* have extent, so validate and get block range */ if ((currExt->startBlock == 0) || (currExt->blockCount == 0)) { cli_dbgmsg("hfsplus_scanfile: next extent empty, done\n"); break; } + if ((currExt->startBlock & 0x10000000) && (currExt->blockCount & 0x10000000)) { cli_dbgmsg("hfsplus_scanfile: next extent illegal!\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } + currBlock = currExt->startBlock; endBlock = currExt->startBlock + currExt->blockCount - 1; if ((currBlock > volHeader->totalBlocks) || (endBlock > volHeader->totalBlocks) || (currExt->blockCount > volHeader->totalBlocks)) { cli_dbgmsg("hfsplus_scanfile: bad extent!\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } + /* Write the blocks, walking the map */ while (currBlock <= endBlock) { size_t to_write = MIN(targetSize, volHeader->blockSize); size_t written; off_t offset = currBlock * volHeader->blockSize; + /* move map to next block */ mPtr = fmap_need_off_once(ctx->fmap, offset, volHeader->blockSize); if (!mPtr) { cli_errmsg("hfsplus_scanfile: map error\n"); - ret = CL_EMAP; - break; + status = CL_EMAP; + goto done; } + written = cli_writen(ofd, mPtr, to_write); if (written != to_write) { cli_errmsg("hfsplus_scanfile: write error\n"); - ret = CL_EWRITE; - break; + status = CL_EWRITE; + goto done; } + targetSize -= to_write; outputSize += to_write; currBlock++; + if (targetSize == 0) { cli_dbgmsg("hfsplus_scanfile: all data written\n"); break; } + if (outputBlocks >= fork->totalBlocks) { cli_dbgmsg("hfsplus_scanfile: output all blocks, remaining size " STDu64 "\n", targetSize); break; } } + /* Finished the extent, move to next */ ext++; - } while (ret == CL_CLEAN); + } while (status == CL_SUCCESS); - /* if successful so far, scan the output */ + /* Now that we're done, ... + * A) if filename output param is provided, just pass back the filename. + * B) otherwise scan the file. + */ if (filename) { *filename = tmpname; + } else { - if (ret == CL_CLEAN) { - ret = cli_magic_scan_desc(ofd, tmpname, ctx, orig_filename, LAYER_ATTRIBUTES_NONE); + status = cli_magic_scan_desc(ofd, tmpname, ctx, orig_filename, LAYER_ATTRIBUTES_NONE); + if (status != CL_SUCCESS) { + goto done; } - if (!ctx->engine->keeptmp) { - if (cli_unlink(tmpname)) { - ret = CL_EUNLINK; - } - } - free(tmpname); + /* TODO: Scan overlay if outputBlocks >= fork->totalBlocks ? */ } +done: + if (ofd >= 0) { close(ofd); } + if ((NULL == filename) || // output param not provided, which means we should clean up the temp file, + (status != CL_SUCCESS)) { // or we failed, so we should clean up the temp file. + + if (tmpname) { + if (!ctx->engine->keeptmp) { + (void)cli_unlink(tmpname); + } + free(tmpname); + } + } - return ret; + return status; } /* Calculate true node limit for catalogFile */ -static int hfsplus_validate_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *catHeader) +static cl_error_t hfsplus_validate_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *catHeader) { hfsPlusForkData *catFork; @@ -476,14 +500,14 @@ static int hfsplus_validate_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader } /* Check if an attribute is present in the attribute map */ -static cl_error_t hfsplus_check_attribute(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *attrHeader, uint32_t expectedCnid, const uint8_t name[], uint32_t nameLen, int *found, uint8_t record[], unsigned *recordSize) +static cl_error_t hfsplus_check_attribute(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *attrHeader, uint32_t expectedCnid, const uint8_t name[], uint32_t nameLen, int *found, uint8_t record[], size_t *recordSize) { + cl_error_t status = CL_SUCCESS; uint16_t nodeSize, recordNum, topOfOffsets; uint16_t recordStart, nextDist, nextStart; uint8_t *nodeBuf = NULL; uint32_t thisNode, nodeLimit, nodesScanned = 0; - cl_error_t ret = CL_SUCCESS; - int foundAttr = 0; + bool foundAttr = false; if (found) { *found = 0; @@ -503,12 +527,13 @@ static cl_error_t hfsplus_check_attribute(cli_ctx *ctx, hfsPlusVolumeHeader *vol cli_dbgmsg("hfsplus_check_attribute: failed to acquire node buffer, " "size " STDu32 "\n", nodeSize); - return CL_EMEM; + status = CL_EMEM; + goto done; } /* Walk catalog leaf nodes, and scan contents of each */ /* Because we want to scan them all, the index nodes add no value */ - while (ret == CL_CLEAN && !foundAttr) { + while (status == CL_SUCCESS && !foundAttr) { hfsNodeDescriptor nodeDesc; if (thisNode == 0) { @@ -521,10 +546,10 @@ static cl_error_t hfsplus_check_attribute(cli_ctx *ctx, hfsPlusVolumeHeader *vol } /* fetch node into buffer */ - ret = hfsplus_fetch_node(ctx, volHeader, attrHeader, NULL, &(volHeader->attributesFile), thisNode, nodeBuf); - if (ret != CL_CLEAN) { + status = hfsplus_fetch_node(ctx, volHeader, attrHeader, NULL, &(volHeader->attributesFile), thisNode, nodeBuf); + if (status != CL_SUCCESS) { cli_dbgmsg("hfsplus_check_attribute: node fetch failed.\n"); - break; + goto done; } memcpy(&nodeDesc, nodeBuf, 14); @@ -533,13 +558,13 @@ static cl_error_t hfsplus_check_attribute(cli_ctx *ctx, hfsPlusVolumeHeader *vol nodedescriptor_print("leaf attribute node", &nodeDesc); if ((nodeDesc.kind != HFS_NODEKIND_LEAF) || (nodeDesc.height != 1)) { cli_dbgmsg("hfsplus_check_attribute: invalid leaf node!\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } if ((nodeSize / 4) < nodeDesc.numRecords) { cli_dbgmsg("hfsplus_check_attribute: too many leaf records for one node!\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } /* Walk this node's records and scan */ @@ -557,15 +582,15 @@ static cl_error_t hfsplus_check_attribute(cli_ctx *ctx, hfsPlusVolumeHeader *vol /* Check record location */ if ((nextStart > topOfOffsets - 1) || (nextStart < recordStart)) { cli_dbgmsg("hfsplus_check_attribute: bad record location %x for %u!\n", nextStart, recordNum); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } recordStart = nextStart; if (recordStart + sizeof(attrKey) >= topOfOffsets) { cli_dbgmsg("hfsplus_check_attribute: Not enough data for an attribute key at location %x for %u!\n", nextStart, recordNum); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } memcpy(&attrKey, &nodeBuf[recordStart], sizeof(attrKey)); @@ -581,14 +606,14 @@ static cl_error_t hfsplus_check_attribute(cli_ctx *ctx, hfsPlusVolumeHeader *vol if (recordStart + attrKey.keyLength + 4 >= topOfOffsets) { cli_dbgmsg("hfsplus_check_attribute: key too long for location %x for %u!\n", nextStart, recordNum); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } if (recordStart + sizeof(hfsPlusAttributeKey) + attrKey.nameLength >= topOfOffsets) { cli_dbgmsg("hfsplus_check_attribute: Attribute name is longer than expected: %u\n", attrKey.nameLength); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } if (attrKey.cnid == expectedCnid && attrKey.nameLength * 2 == nameLen && memcmp(&nodeBuf[recordStart + 14], name, nameLen) == 0) { @@ -601,37 +626,39 @@ static cl_error_t hfsplus_check_attribute(cli_ctx *ctx, hfsPlusVolumeHeader *vol continue; } - if (found) { - *found = 1; - } - if (attrRec.attributeSize > *recordSize) { - ret = CL_EMAXSIZE; - break; + status = CL_EFORMAT; + goto done; } memcpy(record, &(nodeBuf[recordStart + sizeof(hfsPlusAttributeKey) + attrKey.nameLength * 2 + sizeof(attrRec)]), attrRec.attributeSize); *recordSize = attrRec.attributeSize; - ret = CL_SUCCESS; - foundAttr = 1; + if (found) { + *found = 1; + } + + foundAttr = true; break; } } } +done: + if (nodeBuf != NULL) { free(nodeBuf); nodeBuf = NULL; } - return ret; + + return status; } /* Fetch a node's contents into the buffer */ -static int hfsplus_fetch_node(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *catHeader, - hfsHeaderRecord *extHeader, hfsPlusForkData *catFork, uint32_t node, uint8_t *buff) +static cl_error_t hfsplus_fetch_node(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *catHeader, + hfsHeaderRecord *extHeader, hfsPlusForkData *catFork, uint32_t node, uint8_t *buff) { - int foundBlock = 0; + bool foundBlock = false; uint64_t catalogOffset; uint32_t startBlock, startOffset; uint32_t endBlock, endSize; @@ -666,7 +693,7 @@ static int hfsplus_fetch_node(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsH for (curBlock = startBlock; curBlock <= endBlock; ++curBlock) { - foundBlock = 0; + foundBlock = false; searchBlock = curBlock; /* Find which extent has that block */ for (extentNum = 0; extentNum < 8; extentNum++) { @@ -686,7 +713,7 @@ static int hfsplus_fetch_node(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsH if (searchBlock < currExt->blockCount) { cli_dbgmsg("hfsplus_fetch_node: found block in extent " STDu32 "\n", extentNum); realFileBlock = currExt->startBlock + searchBlock; - foundBlock = 1; + foundBlock = true; break; } else { cli_dbgmsg("hfsplus_fetch_node: not in extent " STDu32 "\n", extentNum); @@ -694,7 +721,7 @@ static int hfsplus_fetch_node(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsH } } - if (foundBlock == 0) { + if (foundBlock == false) { cli_dbgmsg("hfsplus_fetch_node: not in first 8 extents\n"); cli_dbgmsg("hfsplus_fetch_node: finding this node requires extent overflow support\n"); return CL_EFORMAT; @@ -726,6 +753,7 @@ static int hfsplus_fetch_node(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsH static cl_error_t hfsplus_seek_to_cmpf_resource(int fd, size_t *size) { + cl_error_t status = CL_SUCCESS; hfsPlusResourceHeader resourceHeader; hfsPlusResourceMap resourceMap; hfsPlusResourceType resourceType; @@ -735,16 +763,15 @@ static cl_error_t hfsplus_seek_to_cmpf_resource(int fd, size_t *size) int curInstanceIdx = 0; size_t dataOffset; uint32_t dataLength; - cl_error_t ret = CL_SUCCESS; if (!size) { - ret = CL_ENULLARG; + status = CL_ENULLARG; goto done; } if (cli_readn(fd, &resourceHeader, sizeof(resourceHeader)) != sizeof(resourceHeader)) { cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to read resource header from temporary file\n"); - ret = CL_EREAD; + status = CL_EREAD; goto done; } @@ -757,13 +784,13 @@ static cl_error_t hfsplus_seek_to_cmpf_resource(int fd, size_t *size) if (lseek(fd, resourceHeader.mapOffset, SEEK_SET) != resourceHeader.mapOffset) { cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to seek to map in temporary file\n"); - ret = CL_ESEEK; + status = CL_ESEEK; goto done; } if (cli_readn(fd, &resourceMap, sizeof(resourceMap)) != sizeof(resourceMap)) { cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to read resource map from temporary file\n"); - ret = CL_EREAD; + status = CL_EREAD; goto done; } @@ -775,7 +802,7 @@ static cl_error_t hfsplus_seek_to_cmpf_resource(int fd, size_t *size) for (i = 0; i < resourceMap.typeCount + 1; ++i) { if (cli_readn(fd, &resourceType, sizeof(resourceType)) != sizeof(resourceType)) { cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to read resource type from temporary file\n"); - ret = CL_EREAD; + status = CL_EREAD; goto done; } resourceType.instanceCount = be16_to_host(resourceType.instanceCount); @@ -784,7 +811,7 @@ static cl_error_t hfsplus_seek_to_cmpf_resource(int fd, size_t *size) if (memcmp(resourceType.type, "cmpf", 4) == 0) { if (cmpfInstanceIdx != -1) { cli_dbgmsg("hfsplus_seek_to_cmpf_resource: There are several cmpf resource types in the file\n"); - ret = CL_EFORMAT; + status = CL_EFORMAT; goto done; } @@ -797,19 +824,19 @@ static cl_error_t hfsplus_seek_to_cmpf_resource(int fd, size_t *size) if (cmpfInstanceIdx < 0) { cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Didn't find cmpf resource type\n"); - ret = CL_EFORMAT; + status = CL_EFORMAT; goto done; } if (lseek(fd, cmpfInstanceIdx * sizeof(hfsPlusReferenceEntry), SEEK_CUR) < 0) { cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to seek to instance index\n"); - ret = CL_ESEEK; + status = CL_ESEEK; goto done; } if (cli_readn(fd, &entry, sizeof(entry)) != sizeof(entry)) { cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to read resource entry from temporary file\n"); - ret = CL_EREAD; + status = CL_EREAD; goto done; } @@ -817,45 +844,60 @@ static cl_error_t hfsplus_seek_to_cmpf_resource(int fd, size_t *size) if (lseek(fd, resourceHeader.dataOffset + dataOffset, SEEK_SET) < 0) { cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to seek to data offset\n"); - ret = CL_ESEEK; + status = CL_ESEEK; goto done; } if (cli_readn(fd, &dataLength, sizeof(dataLength)) != sizeof(dataLength)) { cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to read data length from temporary file\n"); - ret = CL_EREAD; + status = CL_EREAD; goto done; } *size = be32_to_host(dataLength); + done: - return ret; + return status; } -static int hfsplus_read_block_table(int fd, uint32_t *numBlocks, hfsPlusResourceBlockTable **table) +/** + * @brief Read the table from the provided file. + * + * The caller is responsible for freeing the table. + * + * @param fd File descriptor of the file to read from. + * @param [out] numBlocks Number of blocks in the table, as determined from reading the file. + * @param [out] table Will be allocated and populated with table data. + * @return cl_error_t CL_SUCCESS on success, CL_E* on failure. + */ +static cl_error_t hfsplus_read_block_table(int fd, uint32_t *numBlocks, hfsPlusResourceBlockTable **table) { + cl_error_t status = CL_SUCCESS; uint32_t i; if (!table || !numBlocks) { - return CL_ENULLARG; + status = CL_ENULLARG; + goto done; } if (cli_readn(fd, numBlocks, sizeof(*numBlocks)) != sizeof(*numBlocks)) { cli_dbgmsg("hfsplus_read_block_table: Failed to read block count\n"); - return CL_EREAD; + status = CL_EREAD; + goto done; } *numBlocks = le32_to_host(*numBlocks); // Let's do a little little endian just for fun, shall we? *table = cli_malloc(sizeof(hfsPlusResourceBlockTable) * *numBlocks); if (!*table) { cli_dbgmsg("hfsplus_read_block_table: Failed to allocate memory for block table\n"); - return CL_EMEM; + status = CL_EMEM; + goto done; } if (cli_readn(fd, *table, *numBlocks * sizeof(hfsPlusResourceBlockTable)) != *numBlocks * sizeof(hfsPlusResourceBlockTable)) { cli_dbgmsg("hfsplus_read_block_table: Failed to read table\n"); - free(*table); - return CL_EREAD; + status = CL_EREAD; + goto done; } for (i = 0; i < *numBlocks; ++i) { @@ -863,15 +905,21 @@ static int hfsplus_read_block_table(int fd, uint32_t *numBlocks, hfsPlusResource (*table)[i].length = le32_to_host((*table)[i].length); } - return CL_SUCCESS; +done: + if (CL_SUCCESS != status) { + if (NULL != table) { + free(*table); + *table = NULL; + } + } + return status; } /* Given the catalog and other details, scan all the volume contents */ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *catHeader, hfsHeaderRecord *extHeader, hfsHeaderRecord *attrHeader, const char *dirname) { - cl_error_t ret = CL_SUCCESS; - unsigned int has_alerts = 0; + cl_error_t status = CL_SUCCESS; uint32_t thisNode, nodeLimit, nodesScanned = 0; uint16_t nodeSize, recordNum, topOfOffsets; uint16_t recordStart, nextDist, nextStart; @@ -879,9 +927,14 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea const uint8_t COMPRESSED_ATTR[] = {0, 'c', 0, 'o', 0, 'm', 0, '.', 0, 'a', 0, 'p', 0, 'p', 0, 'l', 0, 'e', 0, '.', 0, 'd', 0, 'e', 0, 'c', 0, 'm', 0, 'p', 0, 'f', 0, 's'}; char *tmpname = NULL; uint8_t *uncompressed = NULL; + char *resourceFile = NULL; + int ifd = -1; int ofd = -1; char *name_utf8 = NULL; size_t name_utf8_size = 0; + bool extracted_file = false; + + hfsPlusResourceBlockTable *table = NULL; nodeLimit = MIN(catHeader->totalNodes, HFSPLUS_NODE_LIMIT); thisNode = catHeader->firstLeafNode; @@ -898,23 +951,23 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea /* Walk catalog leaf nodes, and scan contents of each */ /* Because we want to scan them all, the index nodes add no value */ - while (ret == CL_SUCCESS) { + while (status == CL_SUCCESS) { hfsNodeDescriptor nodeDesc; if (thisNode == 0) { cli_dbgmsg("hfsplus_walk_catalog: reached end of leaf nodes.\n"); - break; + goto done; } if (nodesScanned++ > nodeLimit) { cli_dbgmsg("hfsplus_walk_catalog: node scan limit reached.\n"); - break; + goto done; } /* fetch node into buffer */ - ret = hfsplus_fetch_node(ctx, volHeader, catHeader, extHeader, &(volHeader->catalogFile), thisNode, nodeBuf); - if (ret != CL_SUCCESS) { + status = hfsplus_fetch_node(ctx, volHeader, catHeader, extHeader, &(volHeader->catalogFile), thisNode, nodeBuf); + if (status != CL_SUCCESS) { cli_dbgmsg("hfsplus_walk_catalog: node fetch failed.\n"); - break; + goto done; } memcpy(&nodeDesc, nodeBuf, 14); @@ -923,13 +976,13 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea nodedescriptor_print("leaf node", &nodeDesc); if ((nodeDesc.kind != HFS_NODEKIND_LEAF) || (nodeDesc.height != 1)) { cli_dbgmsg("hfsplus_walk_catalog: invalid leaf node!\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } if ((nodeSize / 4) < nodeDesc.numRecords) { cli_dbgmsg("hfsplus_walk_catalog: too many leaf records for one node!\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } /* Walk this node's records and scan */ @@ -948,8 +1001,8 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea /* Check record location */ if ((nextStart > topOfOffsets - 1) || (nextStart < recordStart)) { cli_dbgmsg("hfsplus_walk_catalog: bad record location %x for %u!\n", nextStart, recordNum); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } recordStart = nextStart; /* Get record key length */ @@ -959,8 +1012,8 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea if (recordStart + keylen + 4 >= topOfOffsets) { cli_dbgmsg("hfsplus_walk_catalog: key too long for location %x for %u!\n", nextStart, recordNum); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } /* Collect filename */ if (keylen >= 6) { @@ -993,8 +1046,8 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea /* Check file record location */ if (recordStart + keylen + 2 + sizeof(hfsPlusCatalogFile) >= topOfOffsets) { cli_dbgmsg("hfsplus_walk_catalog: not enough bytes for file record!\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } memcpy(&fileRec, &(nodeBuf[recordStart + keylen + 2]), sizeof(hfsPlusCatalogFile)); @@ -1004,7 +1057,7 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea if ((fileRec.permissions.fileMode & HFS_MODE_TYPEMASK) == HFS_MODE_FILE) { int compressed = 0; uint8_t attribute[8192]; - unsigned attributeSize = sizeof(attribute); + size_t attributeSize = sizeof(attribute); /* Convert forks and scan */ forkdata_to_host(&(fileRec.dataFork)); @@ -1022,8 +1075,8 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea if (attributeSize < sizeof(header)) { cli_warnmsg("hfsplus_walk_catalog: Error: Compression attribute size is less than the compression header\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } memcpy(&header, attribute, sizeof(header)); @@ -1038,15 +1091,15 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea if (header.magic != DECMPFS_HEADER_MAGIC) { cli_dbgmsg("hfsplus_walk_catalog: Unexpected magic value for compression header: 0x%08x\n", header.magic); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } /* open file */ - ret = cli_gentempfd(dirname, &tmpname, &ofd); - if (ret != CL_SUCCESS) { + status = cli_gentempfd(dirname, &tmpname, &ofd); + if (status != CL_SUCCESS) { cli_dbgmsg("hfsplus_walk_catalog: Cannot generate temporary file.\n"); - break; + goto done; } cli_dbgmsg("Found compressed file type %u size %" PRIu64 "\n", header.compressionType, header.fileSize); @@ -1055,16 +1108,15 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea size_t written; if (attributeSize < sizeof(header) + 1) { cli_dbgmsg("hfsplus_walk_catalog: Unexpected end of stream, no compression flag\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } if ((attribute[sizeof(header)] & 0x0f) == 0x0f) { // Data is stored uncompressed if (attributeSize - sizeof(header) - 1 != header.fileSize) { cli_dbgmsg("hfsplus_walk_catalog: Expected file size different from size of data available\n"); - free(tmpname); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } written = cli_writen(ofd, &attribute[sizeof(header) + 1], header.fileSize); @@ -1074,15 +1126,15 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea if (header.fileSize > 65536) { cli_dbgmsg("hfsplus_walk_catalog: Uncompressed file seems too big, something is probably wrong\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } uncompressed = malloc(header.fileSize); if (!uncompressed) { cli_dbgmsg("hfsplus_walk_catalog: Failed to allocate memory for the uncompressed file contents\n"); - ret = CL_EMEM; - break; + status = CL_EMEM; + goto done; } stream.zalloc = Z_NULL; @@ -1106,37 +1158,39 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea cli_dbgmsg("hfsplus_walk_catalog: inflateinit2: zlib stream error!\n"); break; default: - cli_dbgmsg("hfsplus_walk_catalog: inflateInit2: unknown error %d\n", ret); + cli_dbgmsg("hfsplus_walk_catalog: inflateInit2: unknown error %d\n", z_ret); break; } - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } z_ret = inflate(&stream, Z_NO_FLUSH); if (z_ret != Z_OK && z_ret != Z_STREAM_END) { - cli_dbgmsg("hfsplus_walk_catalog: inflateSync failed to extract compressed stream (%d)\n", ret); - ret = CL_EFORMAT; - break; + cli_dbgmsg("hfsplus_walk_catalog: inflateSync failed to extract compressed stream (%d)\n", z_ret); + status = CL_EFORMAT; + goto done; } z_ret = inflateEnd(&stream); if (z_ret == Z_STREAM_ERROR) { - cli_dbgmsg("hfsplus_walk_catalog: inflateEnd failed (%d)\n", ret); + cli_dbgmsg("hfsplus_walk_catalog: inflateEnd failed (%d)\n", z_ret); } written = cli_writen(ofd, uncompressed, header.fileSize); + + extracted_file = true; + free(uncompressed); uncompressed = NULL; } if (written != header.fileSize) { cli_errmsg("hfsplus_walk_catalog: write error\n"); - ret = CL_EWRITE; - break; + status = CL_EWRITE; + goto done; } - ret = CL_SUCCESS; break; } case HFSPLUS_COMPRESSION_RESOURCE: { @@ -1146,9 +1200,7 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea // Ideally we should check that there is only one // resource, that its type is correct, and that its // name is cmpf. - char *resourceFile = NULL; - int ifd = -1; - size_t written = 0; + size_t written = 0; // 4096 is an approximative value, there should be // at least 16 (resource header) + 30 (map header) + @@ -1156,44 +1208,42 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea // attribute) if (fileRec.resourceFork.logicalSize < 4096) { cli_dbgmsg("hfsplus_walk_catalog: Error: Expected more data in the compressed resource fork\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } - if ((ret = hfsplus_scanfile(ctx, volHeader, extHeader, &(fileRec.resourceFork), dirname, &resourceFile, name_utf8)) != CL_SUCCESS) { + if ((status = hfsplus_scanfile(ctx, volHeader, extHeader, &(fileRec.resourceFork), dirname, &resourceFile, name_utf8)) != CL_SUCCESS) { cli_dbgmsg("hfsplus_walk_catalog: Error while extracting the resource fork\n"); - if (resourceFile) { - free(resourceFile); - } - break; + goto done; } if (NULL == resourceFile) { cli_dbgmsg("hfsplus_walk_catalog: Error: hfsplus_scanfile returned no resource file\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } - if ((ifd = safe_open(resourceFile, O_RDONLY | O_BINARY)) == -1) { + if (-1 == (ifd = safe_open(resourceFile, O_RDONLY | O_BINARY))) { cli_dbgmsg("hfsplus_walk_catalog: Failed to open temporary file %s\n", resourceFile); - ret = CL_EOPEN; + status = CL_EOPEN; + goto done; } else { size_t resourceLen; - if ((ret = hfsplus_seek_to_cmpf_resource(ifd, &resourceLen)) != CL_SUCCESS) { + if (CL_SUCCESS != (status = hfsplus_seek_to_cmpf_resource(ifd, &resourceLen))) { cli_dbgmsg("hfsplus_walk_catalog: Failed to find cmpf resource in resource fork\n"); } else { - hfsPlusResourceBlockTable *table = NULL; uint32_t numBlocks; uint32_t dataOffset = lseek(ifd, 0, SEEK_CUR); - if ((ret = hfsplus_read_block_table(ifd, &numBlocks, &table)) != CL_SUCCESS) { + if (CL_SUCCESS != (status = hfsplus_read_block_table(ifd, &numBlocks, &table))) { cli_dbgmsg("hfsplus_walk_catalog: Failed to read block table\n"); } else { uint8_t block[4096]; - uint8_t uncompressed[4096]; + uint8_t uncompressed_block[4096]; unsigned curBlock; - for (curBlock = 0; ret == CL_SUCCESS && curBlock < numBlocks; ++curBlock) { + for (curBlock = 0; status == CL_SUCCESS && curBlock < numBlocks; ++curBlock) { + int z_ret; off_t blockOffset = dataOffset + table[curBlock].offset; size_t curOffset; size_t readLen; @@ -1205,8 +1255,8 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea if (lseek(ifd, blockOffset, SEEK_SET) != blockOffset) { cli_dbgmsg("hfsplus_walk_catalog: Failed to seek to beginning of block\n"); - ret = CL_ESEEK; - break; + status = CL_ESEEK; + goto done; } for (curOffset = 0; curOffset < table[curBlock].length;) { @@ -1217,8 +1267,8 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea if (cli_readn(ifd, block, readLen) != readLen) { cli_dbgmsg("hfsplus_walk_catalog: Failed to read block from temporary file\n"); - ret = CL_EREAD; - break; + status = CL_EREAD; + goto done; } if (streamBeginning) { @@ -1231,13 +1281,13 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea stream.opaque = Z_NULL; stream.avail_in = readLen; stream.next_in = block; - stream.avail_out = sizeof(uncompressed); - stream.next_out = uncompressed; + stream.avail_out = sizeof(uncompressed_block); + stream.next_out = uncompressed_block; - if ((ret = inflateInit2(&stream, 15)) != Z_OK) { - cli_dbgmsg("hfsplus_walk_catalog: inflateInit2 failed (%d)\n", ret); - ret = CL_EFORMAT; - break; + if (Z_OK != (z_ret = inflateInit2(&stream, 15))) { + cli_dbgmsg("hfsplus_walk_catalog: inflateInit2 failed (%d)\n", z_ret); + status = CL_EFORMAT; + goto done; } } } @@ -1245,46 +1295,47 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea if (streamCompressed) { stream.avail_in = readLen; stream.next_in = block; - stream.avail_out = sizeof(uncompressed); - stream.next_out = uncompressed; + stream.avail_out = sizeof(uncompressed_block); + stream.next_out = uncompressed_block; while (stream.avail_in > 0) { - ret = inflate(&stream, Z_NO_FLUSH); - if (ret != Z_OK && ret != Z_STREAM_END) { - cli_dbgmsg("hfsplus_walk_catalog: Failed to extract (%d)\n", ret); - ret = CL_EFORMAT; - break; + z_ret = inflate(&stream, Z_NO_FLUSH); + if (z_ret != Z_OK && z_ret != Z_STREAM_END) { + cli_dbgmsg("hfsplus_walk_catalog: Failed to extract (%d)\n", z_ret); + status = CL_EFORMAT; + goto done; } - ret = CL_SUCCESS; - if (cli_writen(ofd, &uncompressed, sizeof(uncompressed) - stream.avail_out) != sizeof(uncompressed) - stream.avail_out) { + + if (cli_writen(ofd, &uncompressed_block, sizeof(uncompressed_block) - stream.avail_out) != sizeof(uncompressed_block) - stream.avail_out) { cli_dbgmsg("hfsplus_walk_catalog: Failed to write to temporary file\n"); - ret = CL_EWRITE; - break; + status = CL_EWRITE; + goto done; } - written += sizeof(uncompressed) - stream.avail_out; - stream.avail_out = sizeof(uncompressed); - stream.next_out = uncompressed; + written += sizeof(uncompressed_block) - stream.avail_out; + stream.avail_out = sizeof(uncompressed_block); + stream.next_out = uncompressed_block; + + extracted_file = true; } } else { if (cli_writen(ofd, &block[streamBeginning ? 1 : 0], readLen - (streamBeginning ? 1 : 0)) != readLen - (streamBeginning ? 1 : 0)) { cli_dbgmsg("hfsplus_walk_catalog: Failed to write to temporary file\n"); - ret = CL_EWRITE; - break; + status = CL_EWRITE; + goto done; } written += readLen - (streamBeginning ? 1 : 0); + + extracted_file = true; } curOffset += readLen; streamBeginning = 0; } - if (ret == CL_SUCCESS) { - if ((ret = inflateEnd(&stream)) != Z_OK) { - cli_dbgmsg("hfsplus_walk_catalog: inflateEnd failed (%d)\n", ret); - ret = CL_EFORMAT; - } else { - ret = CL_SUCCESS; - } + if (Z_OK != (z_ret = inflateEnd(&stream))) { + cli_dbgmsg("hfsplus_walk_catalog: inflateEnd failed (%d)\n", z_ret); + status = CL_EFORMAT; + goto done; } } @@ -1300,7 +1351,8 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea if (!ctx->engine->keeptmp) { if (cli_unlink(resourceFile)) { - ret = CL_EUNLINK; + status = CL_EUNLINK; + goto done; } } free(resourceFile); @@ -1315,74 +1367,49 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea } if (tmpname) { - if (ret == CL_SUCCESS) { + if (extracted_file) { cli_dbgmsg("hfsplus_walk_catalog: Extracted to %s\n", tmpname); - /* if successful so far, scan the output */ - ret = cli_magic_scan_desc(ofd, tmpname, ctx, name_utf8, LAYER_ATTRIBUTES_NONE); - - if (ret == CL_VIRUS) { - has_alerts = 1; - if (SCAN_ALLMATCHES) { - /* Continue scanning in SCAN_ALLMATCHES mode */ - cli_dbgmsg("hfsplus_walk_catalog: Compressed file alert, continuing"); - ret = CL_SUCCESS; - } + /* Scan the extracted file */ + status = cli_magic_scan_desc(ofd, tmpname, ctx, name_utf8, LAYER_ATTRIBUTES_NONE); + if (status != CL_SUCCESS) { + goto done; } } if (!ctx->engine->keeptmp) { if (cli_unlink(tmpname)) { - ret = CL_EUNLINK; + status = CL_EUNLINK; + goto done; } } free(tmpname); tmpname = NULL; } + if (ofd >= 0) { close(ofd); ofd = -1; } - - if (ret != CL_SUCCESS) { - break; - } } + /* Scan data fork */ if (fileRec.dataFork.logicalSize) { - ret = hfsplus_scanfile(ctx, volHeader, extHeader, &(fileRec.dataFork), dirname, NULL, name_utf8); - } - /* Check return code */ - if (ret == CL_VIRUS) { - has_alerts = 1; - if (SCAN_ALLMATCHES) { - /* Continue scanning in SCAN_ALLMATCHES mode */ - cli_dbgmsg("hfsplus_walk_catalog: data fork alert, continuing"); - ret = CL_CLEAN; + status = hfsplus_scanfile(ctx, volHeader, extHeader, &(fileRec.dataFork), dirname, NULL, name_utf8); + if (status != CL_SUCCESS) { + cli_dbgmsg("hfsplus_walk_catalog: data fork retcode %d\n", status); + goto done; } } - if (ret != CL_SUCCESS) { - cli_dbgmsg("hfsplus_walk_catalog: data fork retcode %d\n", ret); - break; - } /* Scan resource fork */ if (fileRec.resourceFork.logicalSize) { - ret = hfsplus_scanfile(ctx, volHeader, extHeader, &(fileRec.resourceFork), dirname, NULL, name_utf8); - } - /* Check return code */ - if (ret == CL_VIRUS) { - has_alerts = 1; - if (SCAN_ALLMATCHES) { - /* Continue scanning in SCAN_ALLMATCHES mode */ - cli_dbgmsg("hfsplus_walk_catalog: resource fork alert, continuing"); - ret = CL_CLEAN; + status = hfsplus_scanfile(ctx, volHeader, extHeader, &(fileRec.resourceFork), dirname, NULL, name_utf8); + if (status != CL_SUCCESS) { + cli_dbgmsg("hfsplus_walk_catalog: resource fork retcode %d", status); + goto done; } } - if (ret != CL_SUCCESS) { - cli_dbgmsg("hfsplus_walk_catalog: resource fork retcode %d", ret); - break; - } } else { cli_dbgmsg("hfsplus_walk_catalog: record mode %o is not File\n", fileRec.permissions.fileMode); } @@ -1392,38 +1419,62 @@ static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHea name_utf8 = NULL; } } - /* if return code, exit loop, message already logged */ - if (ret != CL_SUCCESS) { - break; - } /* After that, proceed to next node */ if (thisNode == nodeDesc.fLink) { - /* Future heuristic */ + /* TODO: Add heuristic alert? */ cli_warnmsg("hfsplus_walk_catalog: simple cycle detected!\n"); - ret = CL_EFORMAT; - break; + status = CL_EFORMAT; + goto done; } else { thisNode = nodeDesc.fLink; } } - free(nodeBuf); +done: + if (table) { + free(table); + } + if (-1 != ifd) { + close(ifd); + } + if (-1 != ofd) { + close(ofd); + } + if (NULL != resourceFile) { + if (!ctx->engine->keeptmp) { + (void)cli_unlink(resourceFile); + } + free(resourceFile); + } + if (NULL != tmpname) { + if (!ctx->engine->keeptmp) { + if (cli_unlink(tmpname)) { + status = CL_EUNLINK; + goto done; + } + } + free(tmpname); + } + if (NULL != nodeBuf) { + free(nodeBuf); + } if (NULL != name_utf8) { free(name_utf8); } - - if (has_alerts) { - ret = CL_VIRUS; + if (NULL != uncompressed) { + free(uncompressed); } - return ret; + + return status; } /* Base scan function for scanning HFS+ or HFSX partitions */ cl_error_t cli_scanhfsplus(cli_ctx *ctx) { + cl_error_t status = CL_SUCCESS; + cl_error_t ret; char *targetdir = NULL; - cl_error_t ret = CL_SUCCESS; hfsPlusVolumeHeader *volHeader = NULL; hfsNodeDescriptor catFileDesc; hfsHeaderRecord catFileHeader; @@ -1435,14 +1486,15 @@ cl_error_t cli_scanhfsplus(cli_ctx *ctx) if (!ctx || !ctx->fmap) { cli_errmsg("cli_scanhfsplus: Invalid context\n"); - return CL_ENULLARG; + status = CL_ENULLARG; + goto done; } cli_dbgmsg("cli_scanhfsplus: scanning partition content\n"); /* first, read volume header contents */ - ret = hfsplus_volumeheader(ctx, &volHeader); - if (ret != CL_SUCCESS) { - goto freeHeader; + status = hfsplus_volumeheader(ctx, &volHeader); + if (status != CL_SUCCESS) { + goto done; } /* @@ -1456,14 +1508,14 @@ cli_dbgmsg("sizeof(hfsNodeDescriptor) is %lu\n", sizeof(hfsNodeDescriptor)); */ /* Get root node (header node) of extent overflow file */ - ret = hfsplus_readheader(ctx, volHeader, &extentFileDesc, &extentFileHeader, HFS_FILETREE_EXTENTS, "extentFile"); - if (ret != CL_SUCCESS) { - goto freeHeader; + status = hfsplus_readheader(ctx, volHeader, &extentFileDesc, &extentFileHeader, HFS_FILETREE_EXTENTS, "extentFile"); + if (status != CL_SUCCESS) { + goto done; } /* Get root node (header node) of catalog file */ - ret = hfsplus_readheader(ctx, volHeader, &catFileDesc, &catFileHeader, HFS_FILETREE_CATALOG, "catalogFile"); - if (ret != CL_SUCCESS) { - goto freeHeader; + status = hfsplus_readheader(ctx, volHeader, &catFileDesc, &catFileHeader, HFS_FILETREE_CATALOG, "catalogFile"); + if (status != CL_SUCCESS) { + goto done; } /* Get root node (header node) of attributes file */ @@ -1472,48 +1524,50 @@ cli_dbgmsg("sizeof(hfsNodeDescriptor) is %lu\n", sizeof(hfsNodeDescriptor)); hasAttributesFileHeader = 1; } else { hasAttributesFileHeader = 0; - ret = CL_SUCCESS; } /* Create temp folder for contents */ if (!(targetdir = cli_gentemp_with_prefix(ctx->sub_tmpdir, "hfsplus-tmp"))) { cli_errmsg("cli_scanhfsplus: cli_gentemp failed\n"); - ret = CL_ETMPDIR; - goto freeHeader; + status = CL_ETMPDIR; + goto done; } if (mkdir(targetdir, 0700)) { cli_errmsg("cli_scanhfsplus: Cannot create temporary directory %s\n", targetdir); - ret = CL_ETMPDIR; - goto freeDirname; + status = CL_ETMPDIR; + goto done; } cli_dbgmsg("cli_scanhfsplus: Extracting into %s\n", targetdir); /* Can build and scan catalog file if we want *** ret = hfsplus_scanfile(ctx, volHeader, &extentFileHeader, &(volHeader->catalogFile), targetdir); */ - if (ret == CL_SUCCESS) { - ret = hfsplus_validate_catalog(ctx, volHeader, &catFileHeader); - if (ret == CL_SUCCESS) { - cli_dbgmsg("cli_scanhfsplus: validation successful\n"); - } else { - cli_dbgmsg("cli_scanhfsplus: validation returned %d : %s\n", ret, cl_strerror(ret)); - } + + status = hfsplus_validate_catalog(ctx, volHeader, &catFileHeader); + if (status == CL_SUCCESS) { + cli_dbgmsg("cli_scanhfsplus: validation successful\n"); + } else { + cli_dbgmsg("cli_scanhfsplus: validation returned %d : %s\n", status, cl_strerror(status)); + goto done; } /* Walk through catalog to identify files to scan */ - if (ret == CL_SUCCESS) { - ret = hfsplus_walk_catalog(ctx, volHeader, &catFileHeader, &extentFileHeader, hasAttributesFileHeader ? &attributesFileHeader : NULL, targetdir); - cli_dbgmsg("cli_scanhfsplus: walk catalog finished\n"); + status = hfsplus_walk_catalog(ctx, volHeader, &catFileHeader, &extentFileHeader, hasAttributesFileHeader ? &attributesFileHeader : NULL, targetdir); + if (status != CL_SUCCESS) { + goto done; } - /* Clean up extracted content, if needed */ - if (!ctx->engine->keeptmp) { - cli_rmdirs(targetdir); +done: + if (NULL != targetdir) { + /* Clean up extracted content, if needed */ + if (!ctx->engine->keeptmp) { + (void)cli_rmdirs(targetdir); + } + free(targetdir); + } + if (NULL != volHeader) { + free(volHeader); } -freeDirname: - free(targetdir); -freeHeader: - free(volHeader); - return ret; + return status; } diff --git a/libclamav/hwp.c b/libclamav/hwp.c index d93710cff3..afca2d8f49 100644 --- a/libclamav/hwp.c +++ b/libclamav/hwp.c @@ -1861,15 +1861,10 @@ static cl_error_t hwp3_cb(void *cbdata, int fd, const char *filepath, cli_ctx *c while (!last && ((ret = parsehwp3_infoblk_1(ctx, map, &offset, &last)) == CL_SUCCESS)) continue; /* scan the uncompressed stream - both compressed and uncompressed cases [ALLMATCH] */ - if ((ret == CL_SUCCESS) || ((SCAN_ALLMATCHES) && (ret == CL_VIRUS))) { - cl_error_t subret = ret; - size_t dlen = offset - start; + if (ret == CL_SUCCESS) { + size_t dlen = offset - start; - ret = cli_magic_scan_nested_fmap_type(map, start, dlen, ctx, - CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); - - if (ret == CL_SUCCESS) - ret = subret; + ret = cli_magic_scan_nested_fmap_type(map, start, dlen, ctx, CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); } if (dmap) diff --git a/libclamav/iowrap.c b/libclamav/iowrap.c index e2eabc1696..b60a94c3c3 100644 --- a/libclamav/iowrap.c +++ b/libclamav/iowrap.c @@ -19,7 +19,7 @@ * MA 02110-1301, USA. * */ - +#include "clamav.h" #include "iowrap.h" #include @@ -43,9 +43,9 @@ int filter_memcpy(unsigned int code, struct _EXCEPTION_POINTERS *ep) } #endif -int cli_memcpy(void *target, const void *source, unsigned long size) +cl_error_t cli_memcpy(void *target, const void *source, unsigned long size) { - int ret = 0; + cl_error_t ret = CL_SUCCESS; #ifdef _WIN32 __try { @@ -53,7 +53,7 @@ int cli_memcpy(void *target, const void *source, unsigned long size) memcpy(target, source, size); #ifdef _WIN32 } __except (filter_memcpy(GetExceptionCode(), GetExceptionInformation())) { - ret = 1; + ret = CL_EACCES; } #endif return ret; diff --git a/libclamav/iowrap.h b/libclamav/iowrap.h index bd192f6864..df25fe46ea 100644 --- a/libclamav/iowrap.h +++ b/libclamav/iowrap.h @@ -37,11 +37,17 @@ #include #endif -/* - * cli_memcpy is an io wrapper that will allow ClamAV to minimize impact of - * adding SEH logic around map accesses where Windows might raise an error +/** + * @brief memcpy that will not crash if Windows fails to read from memory mapped file. + * + * cli_memcpy is an io wrapper that will allow ClamAV to minimize impact of adding SEH logic around map accesses where Windows might raise an error. + * + * @param target + * @param source + * @param size + * @return cl_error_t */ -int cli_memcpy(void *target, const void *source, unsigned long size); +cl_error_t cli_memcpy(void *target, const void *source, unsigned long size); #ifdef _WIN32 int filter_memcpy(unsigned int code, struct _EXCEPTION_POINTERS *ep); diff --git a/libclamav/ishield.c b/libclamav/ishield.c index f831f8f111..ed09f3fd55 100644 --- a/libclamav/ishield.c +++ b/libclamav/ishield.c @@ -184,29 +184,33 @@ struct IS_FILEITEM { #pragma pack #endif -static int is_dump_and_scan(cli_ctx *ctx, off_t off, size_t fsize); +static cl_error_t is_dump_and_scan(cli_ctx *ctx, off_t off, size_t fsize); static const uint8_t skey[] = {0xec, 0xca, 0x79, 0xf8}; /* ~0x13, ~0x35, ~0x86, ~0x07 */ /* Extracts the content of MSI based IS */ -int cli_scanishield_msi(cli_ctx *ctx, off_t off) +cl_error_t cli_scanishield_msi(cli_ctx *ctx, off_t off) { + cl_error_t ret; const uint8_t *buf; unsigned int fcount, scanned = 0; - int ret; fmap_t *map = ctx->fmap; cli_dbgmsg("in ishield-msi\n"); if (!(buf = fmap_need_off_once(map, off, 0x20))) { cli_dbgmsg("ishield-msi: short read for header\n"); - return CL_CLEAN; + return CL_SUCCESS; } + off += 0x20; - if (cli_readint32(buf + 8) | cli_readint32(buf + 0xc) | cli_readint32(buf + 0x10) | cli_readint32(buf + 0x14) | cli_readint32(buf + 0x18) | cli_readint32(buf + 0x1c)) - return CL_CLEAN; + if (cli_readint32(buf + 8) | cli_readint32(buf + 0xc) | cli_readint32(buf + 0x10) | cli_readint32(buf + 0x14) | cli_readint32(buf + 0x18) | cli_readint32(buf + 0x1c)) { + return CL_SUCCESS; + } + if (!(fcount = cli_readint32(buf))) { cli_dbgmsg("ishield-msi: no files?\n"); - return CL_CLEAN; + return CL_SUCCESS; } + while (fcount--) { struct IS_FB fb; uint8_t obuf[BUFSIZ], *key = (uint8_t *)&fb.fname; @@ -219,15 +223,18 @@ int cli_scanishield_msi(cli_ctx *ctx, off_t off) if (fmap_readn(map, &fb, off, sizeof(fb)) != sizeof(fb)) { cli_dbgmsg("ishield-msi: short read for fileblock\n"); - return CL_CLEAN; + return CL_SUCCESS; } + off += sizeof(fb); fb.fname[sizeof(fb.fname) - 1] = '\0'; - csize = le64_to_host(fb.csize); + + csize = le64_to_host(fb.csize); if (!CLI_ISCONTAINED_0_TO(map->len, off, csize)) { cli_dbgmsg("ishield-msi: next stream is out of file, giving up\n"); - return CL_CLEAN; + return CL_SUCCESS; } + if (ctx->engine->maxfilesize && csize > ctx->engine->maxfilesize) { cli_dbgmsg("ishield-msi: skipping stream due to size limits (%lu vs %lu)\n", (unsigned long int)csize, (unsigned long int)ctx->engine->maxfilesize); off += csize; @@ -235,7 +242,9 @@ int cli_scanishield_msi(cli_ctx *ctx, off_t off) } keylen = strlen((const char *)key); - if (!keylen) return CL_CLEAN; + if (!keylen) { + return CL_SUCCESS; + } filename = cli_strdup((const char *)key); @@ -247,6 +256,7 @@ int cli_scanishield_msi(cli_ctx *ctx, off_t off) } return CL_EMEM; } + if ((ofd = open(tempfile, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) < 0) { cli_dbgmsg("ishield-msi: failed to create file %s\n", tempfile); free(tempfile); @@ -256,11 +266,15 @@ int cli_scanishield_msi(cli_ctx *ctx, off_t off) return CL_ECREAT; } - for (i = 0; i < keylen; i++) + for (i = 0; i < keylen; i++) { key[i] ^= skey[i & 3]; + } + memset(&z, 0, sizeof(z)); inflateInit(&z); + ret = CL_SUCCESS; + while (csize) { uint8_t buf2[BUFSIZ]; z.avail_in = MIN(csize, sizeof(buf2)); @@ -326,8 +340,9 @@ int cli_scanishield_msi(cli_ctx *ctx, off_t off) free(filename); } - if (ret != CL_CLEAN) + if (ret != CL_SUCCESS) { return ret; + } scanned++; if (ctx->engine->maxfiles && scanned >= ctx->engine->maxfiles) { @@ -335,7 +350,7 @@ int cli_scanishield_msi(cli_ctx *ctx, off_t off) return CL_EMAXFILES; } } - return CL_CLEAN; + return CL_SUCCESS; } struct IS_CABSTUFF { @@ -350,23 +365,22 @@ struct IS_CABSTUFF { }; static void md5str(uint8_t *sum); -static int is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c); -static int is_extract_cab(cli_ctx *ctx, uint64_t off, uint64_t size, uint64_t csize); +static cl_error_t is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c); +static cl_error_t is_extract_cab(cli_ctx *ctx, uint64_t off, uint64_t size, uint64_t csize); /* Extract the content of older (non-MSI) IS */ -int cli_scanishield(cli_ctx *ctx, off_t off, size_t sz) +cl_error_t cli_scanishield(cli_ctx *ctx, off_t off, size_t sz) { + cl_error_t ret = CL_SUCCESS; const char *fname, *path, *version, *strsz, *data; char *eostr; - int ret = CL_CLEAN; long fsize; off_t coff = off; struct IS_CABSTUFF c = {NULL, -1, 0, 0}; fmap_t *map = ctx->fmap; unsigned fc = 0; - int virus_found = 0; - while (ret == CL_CLEAN) { + while (ret == CL_SUCCESS) { fname = fmap_need_offstr(map, coff, 2048); if (!fname) break; coff += strlen(fname) + 1; @@ -392,14 +406,11 @@ int cli_scanishield(cli_ctx *ctx, off_t off, size_t sz) (size_t)(data - fname) >= sz - fsize) break; cli_dbgmsg("ishield: @%lx found file %s (%s) - version %s - size %lu\n", (unsigned long int)coff, fname, path, version, (unsigned long int)fsize); - if (cli_matchmeta(ctx, fname, fsize, fsize, 0, fc++, 0, NULL) == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - ret = CL_VIRUS; - break; - } - ret = CL_CLEAN; - virus_found = 1; + if (CL_SUCCESS != cli_matchmeta(ctx, fname, fsize, fsize, 0, fc++, 0, NULL)) { + ret = CL_VIRUS; + break; } + sz -= (data - fname) + fsize; if (!strncasecmp(fname, "data", 4)) { @@ -441,47 +452,55 @@ int cli_scanishield(cli_ctx *ctx, off_t off, size_t sz) coff += fsize; } - if (ret == CL_CLEAN && (c.cabcnt || c.hdr != -1)) { - if ((ret = is_parse_hdr(ctx, &c)) == CL_CLEAN) { + if ((ret == CL_SUCCESS) && + (c.cabcnt || c.hdr != -1)) { + + if (CL_SUCCESS == (ret = is_parse_hdr(ctx, &c))) { unsigned int i; if (c.hdr != -1) { cli_dbgmsg("ishield: scanning data1.hdr\n"); ret = is_dump_and_scan(ctx, c.hdr, c.hdrsz); } - for (i = 0; i < c.cabcnt && ret == CL_CLEAN; i++) { + for (i = 0; i < c.cabcnt && ret == CL_SUCCESS; i++) { cli_dbgmsg("ishield: scanning data%u.cab\n", c.cabs[i].cabno); ret = is_dump_and_scan(ctx, c.cabs[i].off, c.cabs[i].sz); } - } else if (ret == CL_BREAK) - ret = CL_CLEAN; + + } else if (ret == CL_BREAK) { + ret = CL_SUCCESS; + } + } + + if (c.cabs) { + free(c.cabs); } - if (c.cabs) free(c.cabs); - if (virus_found != 0) - return CL_VIRUS; return ret; } /* Utility func to scan a fd @ a given offset and size */ -static int is_dump_and_scan(cli_ctx *ctx, off_t off, size_t fsize) +static cl_error_t is_dump_and_scan(cli_ctx *ctx, off_t off, size_t fsize) { char *fname; const char *buf; - int ofd, ret = CL_CLEAN; + cl_error_t ofd, ret = CL_SUCCESS; fmap_t *map = ctx->fmap; if (!fsize) { cli_dbgmsg("ishield: skipping empty file\n"); - return CL_CLEAN; + return CL_SUCCESS; } - if (!(fname = cli_gentemp(ctx->sub_tmpdir))) + + if (!(fname = cli_gentemp(ctx->sub_tmpdir))) { return CL_EMEM; + } if ((ofd = open(fname, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) < 0) { cli_errmsg("ishield: failed to create file %s\n", fname); free(fname); return CL_ECREAT; } + while (fsize) { size_t rd = MIN(fsize, map->pgsz); if (!(buf = fmap_need_off_once(map, off, rd))) { @@ -496,6 +515,7 @@ static int is_dump_and_scan(cli_ctx *ctx, off_t off, size_t fsize) fsize -= rd; off += rd; } + if (!fsize) { cli_dbgmsg("ishield: extracted to %s\n", fname); if (lseek(ofd, 0, SEEK_SET) == -1) { @@ -504,15 +524,21 @@ static int is_dump_and_scan(cli_ctx *ctx, off_t off, size_t fsize) } ret = cli_magic_scan_desc(ofd, fname, ctx, NULL, LAYER_ATTRIBUTES_NONE); } + close(ofd); - if (!ctx->engine->keeptmp) - if (cli_unlink(fname)) ret = CL_EUNLINK; + + if (!ctx->engine->keeptmp) { + if (cli_unlink(fname)) { + ret = CL_EUNLINK; + } + } + free(fname); return ret; } /* Process data1.hdr and extracts all the available files from dataX.cab */ -static int is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c) +static cl_error_t is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c) { uint32_t h1_data_off, objs_files_cnt, objs_dirs_off; unsigned int off, i, scanned = 0; @@ -526,26 +552,27 @@ static int is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c) if (!c->hdr || !c->hdrsz || !c->cabcnt) { cli_dbgmsg("is_parse_hdr: inconsistent hdr, maybe a false match\n"); - return CL_CLEAN; + return CL_SUCCESS; } if (!(h1 = fmap_need_off(map, c->hdr, c->hdrsz))) { cli_dbgmsg("is_parse_hdr: not enough room for H1\n"); - return CL_CLEAN; + return CL_SUCCESS; } + hdr = (char *)h1; h1_data_off = le32_to_host(h1->data_off); objs = (struct IS_OBJECTS *)fmap_need_ptr(map, hdr + h1_data_off, sizeof(*objs)); if (!objs) { cli_dbgmsg("is_parse_hdr: not enough room for OBJECTS\n"); - return CL_CLEAN; + return CL_SUCCESS; } cli_dbgmsg("is_parse_hdr: magic %x, unk1 %x, unk2 %x, data_off %x, data_sz %x\n", h1->magic, h1->unk1, h1->unk2, h1_data_off, h1->data_sz); if (le32_to_host(h1->magic) != 0x28635349) { cli_dbgmsg("is_parse_hdr: bad magic. wrong version?\n"); - return CL_CLEAN; + return CL_SUCCESS; } fmap_unneed_ptr(map, h1, sizeof(*h1)); @@ -557,7 +584,7 @@ static int is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c) /* if(!CLI_ISCONTAINED(hdr, c->hdrsz, ((char *)cmp), sizeof(*cmp))) { */ /* cli_dbgmsg("is_extract: not enough room for COMPONENT\n"); */ /* free(hdr); */ - /* return CL_CLEAN; */ + /* return CL_SUCCESS; */ /* } */ /* cli_errmsg("%06u\t%s\n", i, &hdr[le32_to_host(cmp->str_name_off) + h1_data_off]); */ /* spam_strarray(hdr, h1_data_off + cmp->sub_comp_offs_array, h1_data_off, cmp->sub_comp_cnt); */ @@ -630,7 +657,7 @@ static int is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c) else { if (file_size) { unsigned int j; - int cabret = CL_CLEAN; + cl_error_t cabret = CL_SUCCESS; if (ctx->engine->maxfilesize && file_csize > ctx->engine->maxfilesize) { cli_dbgmsg("is_parse_hdr: skipping file due to size limits (%lu vs %lu)\n", (unsigned long int)file_csize, (unsigned long int)ctx->engine->maxfilesize); @@ -652,18 +679,18 @@ static int is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c) } cabret = is_extract_cab(ctx, file_stream_off + c->cabs[j].off, file_size, file_csize); } else { - ret = CL_CLEAN; + ret = CL_SUCCESS; cli_dbgmsg("is_parse_hdr: stream out of file\n"); } } else { - ret = CL_CLEAN; + ret = CL_SUCCESS; cli_dbgmsg("is_parse_hdr: data%u.cab not available\n", cabno); } if (cabret == CL_BREAK) { - ret = CL_CLEAN; - cabret = CL_CLEAN; + ret = CL_SUCCESS; + cabret = CL_SUCCESS; } - if (cabret != CL_CLEAN) { + if (cabret != CL_SUCCESS) { if (file_name != emptyname) fmap_unneed_ptr(map, (void *)file_name, strlen(file_name) + 1); if (dir_name != emptyname) @@ -684,7 +711,7 @@ static int is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c) fmap_unneed_ptr(map, (void *)dir_name, strlen(dir_name) + 1); fmap_unneed_ptr(map, file, sizeof(*file)); } else { - ret = CL_CLEAN; + ret = CL_SUCCESS; cli_dbgmsg("is_parse_hdr: FILEITEM out of bounds\n"); } off += sizeof(*file); @@ -707,12 +734,13 @@ static void md5str(uint8_t *sum) #define IS_CABBUFSZ 65536 -static int is_extract_cab(cli_ctx *ctx, uint64_t off, uint64_t size, uint64_t csize) +static cl_error_t is_extract_cab(cli_ctx *ctx, uint64_t off, uint64_t size, uint64_t csize) { + cl_error_t ret = CL_SUCCESS; const uint8_t *inbuf; uint8_t *outbuf; char *tempfile; - int ofd, ret = CL_CLEAN; + int ofd; z_stream z; uint64_t outsz = 0; int success = 0; diff --git a/libclamav/ishield.h b/libclamav/ishield.h index 7bd11cfd55..e5c1e589e5 100644 --- a/libclamav/ishield.h +++ b/libclamav/ishield.h @@ -24,7 +24,7 @@ #include "others.h" -int cli_scanishield_msi(cli_ctx *ctx, off_t off); -int cli_scanishield(cli_ctx *ctx, off_t off, size_t sz); +cl_error_t cli_scanishield_msi(cli_ctx *ctx, off_t off); +cl_error_t cli_scanishield(cli_ctx *ctx, off_t off, size_t sz); #endif diff --git a/libclamav/iso9660.c b/libclamav/iso9660.c index 51938f4978..e94325a954 100644 --- a/libclamav/iso9660.c +++ b/libclamav/iso9660.c @@ -54,13 +54,15 @@ static const void *needblock(const iso9660_t *iso, unsigned int block, int temp) return fmap_need_off(ctx->fmap, iso->base_offset + loff, iso->blocksz); } -static int iso_scan_file(const iso9660_t *iso, unsigned int block, unsigned int len) +static cl_error_t iso_scan_file(const iso9660_t *iso, unsigned int block, unsigned int len) { char *tmpf; - int fd, ret = CL_SUCCESS; + int fd = -1; + cl_error_t ret = CL_SUCCESS; - if (cli_gentempfd(iso->ctx->sub_tmpdir, &tmpf, &fd) != CL_SUCCESS) + if (cli_gentempfd(iso->ctx->sub_tmpdir, &tmpf, &fd) != CL_SUCCESS) { return CL_ETMPFILE; + } cli_dbgmsg("iso_scan_file: dumping to %s\n", tmpf); while (len) { @@ -81,8 +83,9 @@ static int iso_scan_file(const iso9660_t *iso, unsigned int block, unsigned int block++; } - if (!len) + if (!len) { ret = cli_magic_scan_desc(fd, tmpf, iso->ctx, iso->buf, LAYER_ATTRIBUTES_NONE); + } close(fd); if (!iso->ctx->engine->keeptmp) { @@ -117,18 +120,17 @@ static char *iso_string(iso9660_t *iso, const void *src, unsigned int len) return iso->buf; } -static int iso_parse_dir(iso9660_t *iso, unsigned int block, unsigned int len) +static cl_error_t iso_parse_dir(iso9660_t *iso, unsigned int block, unsigned int len) { - cli_ctx *ctx = iso->ctx; - int ret = CL_CLEAN; - int viruses_found = 0; + cli_ctx *ctx = iso->ctx; + cl_error_t ret = CL_SUCCESS; if (len < 34) { cli_dbgmsg("iso_parse_dir: Directory too small, skipping\n"); - return CL_CLEAN; + return CL_SUCCESS; } - for (; len && ret == CL_CLEAN; block++, len -= MIN(len, iso->blocksz)) { + for (; len && ret == CL_SUCCESS; block++, len -= MIN(len, iso->blocksz)) { const uint8_t *dir, *dir_orig; unsigned int dirsz; @@ -137,15 +139,18 @@ static int iso_parse_dir(iso9660_t *iso, unsigned int block, unsigned int len) return CL_BREAK; } - if (cli_hashset_contains(&iso->dir_blocks, block)) + if (cli_hashset_contains(&iso->dir_blocks, block)) { continue; + } - if ((ret = cli_hashset_addkey(&iso->dir_blocks, block)) != CL_CLEAN) + if (CL_SUCCESS != (ret = cli_hashset_addkey(&iso->dir_blocks, block))) { return ret; + } dir = dir_orig = needblock(iso, block, 0); - if (!dir) - return CL_CLEAN; + if (!dir) { + return CL_SUCCESS; + } for (dirsz = MIN(iso->blocksz, len);;) { unsigned int entrysz = *dir, fileoff, filesz; @@ -187,11 +192,8 @@ static int iso_parse_dir(iso9660_t *iso, unsigned int block, unsigned int len) cli_dbgmsg("iso_parse_dir: %s '%s': off %x - size %x - flags %x - unit size %x - gap size %x - volume %u\n", (dir[25] & 2) ? "Directory" : "File", iso->buf, fileoff, filesz, dir[25], dir[26], dir[27], cli_readint32(&dir[28]) & 0xffff); ret = cli_matchmeta(ctx, iso->buf, filesz, filesz, 0, 0, 0, NULL); - if (ret == CL_VIRUS) { - viruses_found = 1; - if (!SCAN_ALLMATCHES) - break; - ret = CL_CLEAN; + if (ret != CL_SUCCESS) { + break; } if (dir[26] || dir[27]) @@ -201,16 +203,14 @@ static int iso_parse_dir(iso9660_t *iso, unsigned int block, unsigned int len) if (dir[25] & 2) { ret = iso_parse_dir(iso, fileoff, filesz); } else { - if (cli_checklimits("ISO9660", ctx, filesz, 0, 0) != CL_SUCCESS) + if (CL_SUCCESS != cli_checklimits("ISO9660", ctx, filesz, 0, 0)) { cli_dbgmsg("iso_parse_dir: Skipping overlimit file\n"); - else + } else { ret = iso_scan_file(iso, fileoff, filesz); + } } - if (ret == CL_VIRUS) { - viruses_found = 1; - if (!SCAN_ALLMATCHES) - break; - ret = CL_CLEAN; + if (ret != CL_SUCCESS) { + break; } } dirsz -= entrysz; @@ -219,35 +219,34 @@ static int iso_parse_dir(iso9660_t *iso, unsigned int block, unsigned int len) fmap_unneed_ptr(ctx->fmap, dir_orig, iso->blocksz); } - if (viruses_found == 1) - return CL_VIRUS; + return ret; } -int cli_scaniso(cli_ctx *ctx, size_t offset) +cl_error_t cli_scaniso(cli_ctx *ctx, size_t offset) { const uint8_t *privol, *next; iso9660_t iso; int i; if (offset < 32768) - return CL_CLEAN; /* Need 16 sectors at least 2048 bytes long */ + return CL_SUCCESS; /* Need 16 sectors at least 2048 bytes long */ privol = fmap_need_off(ctx->fmap, offset, 2448 + 6); if (!privol) - return CL_CLEAN; + return CL_SUCCESS; next = (uint8_t *)cli_memstr((char *)privol + 2049, 2448 + 6 - 2049, "CD001", 5); if (!next) - return CL_CLEAN; /* Find next volume descriptor */ + return CL_SUCCESS; /* Find next volume descriptor */ iso.sectsz = (next - privol) - 1; if (iso.sectsz * 16 > offset) - return CL_CLEAN; /* Need room for 16 system sectors */ + return CL_SUCCESS; /* Need room for 16 system sectors */ iso.blocksz = cli_readint32(privol + 128) & 0xffff; if (iso.blocksz != 512 && iso.blocksz != 1024 && iso.blocksz != 2048) - return CL_CLEAN; /* Likely not a cdrom image */ + return CL_SUCCESS; /* Likely not a cdrom image */ iso.base_offset = offset - iso.sectsz * 16; iso.joliet = 0; @@ -327,7 +326,7 @@ int cli_scaniso(cli_ctx *ctx, size_t offset) if (privol[156 + 26] || privol[156 + 27]) { cli_dbgmsg("cli_scaniso: Interleaved root directory is not supported\n"); - return CL_CLEAN; + return CL_SUCCESS; } iso.ctx = ctx; @@ -337,6 +336,6 @@ int cli_scaniso(cli_ctx *ctx, size_t offset) i = iso_parse_dir(&iso, cli_readint32(privol + 156 + 2) + privol[156 + 1], cli_readint32(privol + 156 + 10)); cli_hashset_destroy(&iso.dir_blocks); if (i == CL_BREAK) - return CL_CLEAN; + return CL_SUCCESS; return i; } diff --git a/libclamav/iso9660.h b/libclamav/iso9660.h index 4192a3c074..09ec9c3787 100644 --- a/libclamav/iso9660.h +++ b/libclamav/iso9660.h @@ -24,6 +24,6 @@ #include "others.h" -int cli_scaniso(cli_ctx *ctx, size_t offset); +cl_error_t cli_scaniso(cli_ctx *ctx, size_t offset); #endif diff --git a/libclamav/jpeg.c b/libclamav/jpeg.c index 5f9ee1624f..1b7f652682 100644 --- a/libclamav/jpeg.c +++ b/libclamav/jpeg.c @@ -311,7 +311,7 @@ static cl_error_t jpeg_check_photoshop_8bim(cli_ctx *ctx, size_t *off) cl_error_t cli_parsejpeg(cli_ctx *ctx) { - cl_error_t status = CL_CLEAN; + cl_error_t status = CL_SUCCESS; fmap_t *map = NULL; jpeg_marker_t marker, prev_marker, prev_segment = JPEG_MARKER_NOT_A_MARKER_0x00; @@ -334,15 +334,17 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) } map = ctx->fmap; - if (fmap_readn(map, buff, offset, 4) != 4) + if (fmap_readn(map, buff, offset, 4) != 4) { goto done; /* Ignore */ + } - if (!memcmp(buff, "\xff\xd8\xff", 3)) + if (!memcmp(buff, "\xff\xd8\xff", 3)) { offset = 2; - else if (!memcmp(buff, "\xff\xd9\xff\xd8", 4)) + } else if (!memcmp(buff, "\xff\xd9\xff\xd8", 4)) { offset = 4; - else + } else { goto done; /* Not a JPEG file */ + } while (1) { segment++; @@ -354,8 +356,7 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) } else { if (SCAN_HEURISTIC_BROKEN_MEDIA) { cli_errmsg("JPEG: Failed to read marker, file corrupted?\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.CantReadMarker"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.CantReadMarker"); } else { cli_dbgmsg("Failed to read marker, file corrupted?\n"); } @@ -370,8 +371,7 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) if (i == 16) { if (SCAN_HEURISTIC_BROKEN_MEDIA) { cli_warnmsg("JPEG: Spurious bytes before segment %u\n", segment); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SpuriousBytesBeforeSegment"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SpuriousBytesBeforeSegment"); } else { cli_dbgmsg("Spurious bytes before segment %u\n", segment); } @@ -388,7 +388,7 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) if (buff[0] == 0x00) { if ((buff[1] == 0x00) || (buff[1] == 0x01)) { /* Found exploit */ - status = cli_append_virus(ctx, "Heuristics.Exploit.W32.MS04-028"); + status = cli_append_potentially_unwanted(ctx, "Heuristics.Exploit.W32.MS04-028"); goto done; } } @@ -398,8 +398,7 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) if (fmap_readn(map, &len_u16, offset, sizeof(len_u16)) != sizeof(len_u16)) { if (SCAN_HEURISTIC_BROKEN_MEDIA) { cli_errmsg("JPEG: Failed to read the segment size, file corrupted?\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.CantReadSegmentSize"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.CantReadSegmentSize"); } else { cli_dbgmsg("Failed to read the segment size, file corrupted?\n"); } @@ -411,8 +410,7 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) if (len < 2) { if (SCAN_HEURISTIC_BROKEN_MEDIA) { cli_warnmsg("JPEG: Invalid segment size\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.InvalidSegmentSize"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.InvalidSegmentSize"); } else { cli_dbgmsg("Invalid segment size\n"); } @@ -421,8 +419,7 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) if (len >= map->len - offset + sizeof(len_u16)) { if (SCAN_HEURISTIC_BROKEN_MEDIA) { cli_warnmsg("JPEG: Segment data out of file\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SegmentDataOutOfFile"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SegmentDataOutOfFile"); } else { cli_dbgmsg("Segment data out of file\n"); } @@ -444,8 +441,7 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) if (found_app && num_JFIF > 0) { cli_warnmsg("JPEG: Duplicate Application Marker found (JFIF)\n"); cli_warnmsg("JPEG: Already observed JFIF: %d, Exif: %d, SPIFF: %d\n", num_JFIF, num_Exif, num_SPIFF); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFdupAppMarker"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFdupAppMarker"); goto done; } if (!(segment == 1 || @@ -457,14 +453,12 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) * If segment 1 wasn't a comment or Exif, then the file structure is unusual. */ cli_warnmsg("JPEG: JFIF marker at wrong position, found in segment # %d\n", segment); cli_warnmsg("JPEG: Already observed JFIF: %d, Exif: %d, SPIFF: %d\n", num_JFIF, num_Exif, num_SPIFF); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFmarkerBadPosition"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFmarkerBadPosition"); goto done; } if (len < 16) { cli_warnmsg("JPEG: JFIF header too short\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFheaderTooShort"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFheaderTooShort"); goto done; } } @@ -490,21 +484,18 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) if (found_app && (num_Exif > 0 || num_SPIFF > 0)) { cli_warnmsg("JPEG: Duplicate Application Marker found (Exif)\n"); cli_warnmsg("JPEG: Already observed JFIF: %d, Exif: %d, SPIFF: %d\n", num_JFIF, num_Exif, num_SPIFF); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifDupAppMarker"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifDupAppMarker"); goto done; } if (segment > 3 && !found_comment && num_JFIF > 0) { /* If Exif was found after segment 3 and previous segments weren't a comment or JFIF, something is unusual. */ cli_warnmsg("JPEG: Exif marker at wrong position\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifHeaderBadPosition"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifHeaderBadPosition"); goto done; } if (len < 16) { cli_warnmsg("JPEG: Exif header too short\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifHeaderTooShort"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifHeaderTooShort"); goto done; } } @@ -546,20 +537,17 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) if (found_app) { cli_warnmsg("JPEG: Duplicate Application Marker found (SPIFF)\n"); cli_warnmsg("JPEG: Already observed JFIF: %d, Exif: %d, SPIFF: %d\n", num_JFIF, num_Exif, num_SPIFF); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFdupAppMarker"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFdupAppMarker"); goto done; } if (segment != 1 && (segment != 2 || !found_comment)) { cli_warnmsg("JPEG: SPIFF marker at wrong position\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFmarkerBadPosition"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFmarkerBadPosition"); goto done; } if (len < 16) { cli_warnmsg("JPEG: SPIFF header too short\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFheaderTooShort"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFheaderTooShort"); goto done; } } @@ -657,8 +645,7 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) if (found_app) { if (SCAN_HEURISTIC_BROKEN_MEDIA) { cli_warnmsg("JPEG: Application Marker before JPG7\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.AppMarkerBeforeJPG7"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.AppMarkerBeforeJPG7"); goto done; } } @@ -681,8 +668,7 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) */ if (SCAN_HEURISTIC_BROKEN_MEDIA) { cli_warnmsg("JPEG: No image in jpeg\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.NoImages"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.NoImages"); } goto done; @@ -700,8 +686,7 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) if (SCAN_HEURISTIC_BROKEN_MEDIA) { if (prev_segment != JPEG_MARKER_SEGMENT_DTI) { cli_warnmsg("JPEG: No DTI segment before DTT\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.DTTMissingDTISegment"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.DTTMissingDTISegment"); goto done; } } @@ -716,10 +701,5 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx) } done: - if (status == CL_EPARSE) { - /* We added with cli_append_possibly_unwanted so it will alert at the end if nothing else matches. */ - status = CL_CLEAN; - } - return status; } diff --git a/libclamav/libmspack.c b/libclamav/libmspack.c index 04a28e0476..b64104d14a 100644 --- a/libclamav/libmspack.c +++ b/libclamav/libmspack.c @@ -341,26 +341,30 @@ static struct mspack_system mspack_sys_fmap_ops = { .copy = mspack_fmap_copy, }; -int cli_scanmscab(cli_ctx *ctx, off_t sfx_offset) +cl_error_t cli_scanmscab(cli_ctx *ctx, off_t sfx_offset) { - struct mscab_decompressor *cab_d; - struct mscabd_cabinet *cab_h; - struct mscabd_file *cab_f; - int ret = 0; + cl_error_t ret = CL_SUCCESS; + struct mscab_decompressor *cab_d = NULL; + struct mscabd_cabinet *cab_h = NULL; + struct mscabd_file *cab_f = NULL; int files; - int virus_num = 0; struct mspack_name mspack_fmap = { .fmap = ctx->fmap, .org = sfx_offset, }; struct mspack_system_ex ops_ex; + + char *tmp_fname = NULL; + bool tempfile_exists = false; + memset(&ops_ex, 0, sizeof(struct mspack_system_ex)); ops_ex.ops = mspack_sys_fmap_ops; cab_d = mspack_create_cab_decompressor(&ops_ex.ops); if (!cab_d) { cli_dbgmsg("%s() failed at %d\n", __func__, __LINE__); - return CL_EUNPACK; + ret = CL_EUNPACK; + goto done; } cab_d->set_param(cab_d, MSCABD_PARAM_FIXMSZIP, 1); @@ -369,31 +373,26 @@ int cli_scanmscab(cli_ctx *ctx, off_t sfx_offset) #endif cab_h = cab_d->open(cab_d, (char *)&mspack_fmap); - if (!cab_h) { - ret = CL_EFORMAT; + if (NULL == cab_h) { cli_dbgmsg("%s() failed at %d\n", __func__, __LINE__); - goto out_dest; + ret = CL_EFORMAT; + goto done; } + files = 0; for (cab_f = cab_h->files; cab_f; cab_f = cab_f->next) { uint64_t max_size; - char *tmp_fname = NULL; ret = cli_matchmeta(ctx, cab_f->filename, 0, cab_f->length, 0, files, 0, NULL); - if (ret) { - if (ret == CL_VIRUS) { - virus_num++; - if (!SCAN_ALLMATCHES) - break; - } - goto out_close; + if (CL_SUCCESS != ret) { + goto done; } if (ctx->engine->maxscansize) { if (ctx->scansize >= ctx->engine->maxscansize) { ret = CL_CLEAN; - break; + goto done; } } @@ -422,94 +421,107 @@ int cli_scanmscab(cli_ctx *ctx, off_t sfx_offset) tmp_fname = cli_gentemp(ctx->sub_tmpdir); if (!tmp_fname) { ret = CL_EMEM; - break; + goto done; } ops_ex.max_size = max_size; + /* scan */ ret = cab_d->extract(cab_d, cab_f, tmp_fname); - if (ret) + if (ret) { /* Failed to extract. Try to scan what is there */ cli_dbgmsg("%s() failed to extract %d\n", __func__, ret); + } + tempfile_exists = true; // probably ret = cli_magic_scan_file(tmp_fname, ctx, cab_f->filename, LAYER_ATTRIBUTES_NONE); if (CL_EOPEN == ret) { - ret = CL_CLEAN; - } else if (CL_VIRUS == ret) { - virus_num++; + // okay so the file didn't actually get extracted. That's okay, we'll move on. + tempfile_exists = false; + ret = CL_SUCCESS; + } else if (CL_SUCCESS != ret) { + goto done; } - if (!ctx->engine->keeptmp) { - if (!access(tmp_fname, R_OK) && cli_unlink(tmp_fname)) { - free(tmp_fname); + if (!ctx->engine->keeptmp && tempfile_exists) { + if (cli_unlink(tmp_fname)) { ret = CL_EUNLINK; - break; + goto done; } } + free(tmp_fname); + tmp_fname = NULL; + files++; - if (ret == CL_VIRUS && SCAN_ALLMATCHES) - continue; - if (ret) - break; } -out_close: - cab_d->close(cab_d, cab_h); -out_dest: - mspack_destroy_cab_decompressor(cab_d); - if (virus_num) - return CL_VIRUS; +done: + + if (NULL != tmp_fname) { + if (!ctx->engine->keeptmp && tempfile_exists) { + (void)cli_unlink(tmp_fname); + } + + free(tmp_fname); + } + + if (NULL != cab_d) { + if (NULL != cab_h) { + cab_d->close(cab_d, cab_h); + } + mspack_destroy_cab_decompressor(cab_d); + } + return ret; } -int cli_scanmschm(cli_ctx *ctx) +cl_error_t cli_scanmschm(cli_ctx *ctx) { - struct mschm_decompressor *mschm_d; - struct mschmd_header *mschm_h; - struct mschmd_file *mschm_f; - int ret = CL_CLEAN; // Default CLEAN in case CHM contains no files. + cl_error_t ret = CL_SUCCESS; + struct mschm_decompressor *mschm_d = NULL; + struct mschmd_header *mschm_h = NULL; + struct mschmd_file *mschm_f = NULL; int files; - int virus_num = 0; struct mspack_name mspack_fmap = { .fmap = ctx->fmap, }; struct mspack_system_ex ops_ex; + + char *tmp_fname = NULL; + bool tempfile_exists = false; + memset(&ops_ex, 0, sizeof(struct mspack_system_ex)); ops_ex.ops = mspack_sys_fmap_ops; mschm_d = mspack_create_chm_decompressor(&ops_ex.ops); if (!mschm_d) { cli_dbgmsg("%s() failed at %d\n", __func__, __LINE__); - return CL_EUNPACK; + ret = CL_EUNPACK; + goto done; } mschm_h = mschm_d->open(mschm_d, (char *)&mspack_fmap); if (!mschm_h) { - ret = CL_EFORMAT; cli_dbgmsg("%s() failed at %d\n", __func__, __LINE__); - goto out_dest; + ret = CL_EFORMAT; + goto done; } + files = 0; for (mschm_f = mschm_h->files; mschm_f; mschm_f = mschm_f->next) { uint64_t max_size; - char *tmp_fname; ret = cli_matchmeta(ctx, mschm_f->filename, 0, mschm_f->length, 0, files, 0, NULL); - if (ret) { - if (ret == CL_VIRUS) { - virus_num++; - if (!SCAN_ALLMATCHES) - break; - } - goto out_close; + if (CL_SUCCESS != ret) { + goto done; } if (ctx->engine->maxscansize) { if (ctx->scansize >= ctx->engine->maxscansize) { ret = CL_CLEAN; - break; + goto done; } } @@ -535,47 +547,60 @@ int cli_scanmschm(cli_ctx *ctx) } } - ops_ex.max_size = max_size; - tmp_fname = cli_gentemp(ctx->sub_tmpdir); if (!tmp_fname) { ret = CL_EMEM; break; } + ops_ex.max_size = max_size; + /* scan */ ret = mschm_d->extract(mschm_d, mschm_f, tmp_fname); - if (ret) + if (ret) { /* Failed to extract. Try to scan what is there */ cli_dbgmsg("%s() failed to extract %d\n", __func__, ret); + } + tempfile_exists = true; // probably ret = cli_magic_scan_file(tmp_fname, ctx, mschm_f->filename, LAYER_ATTRIBUTES_NONE); if (CL_EOPEN == ret) { - ret = CL_CLEAN; - } else if (CL_VIRUS == ret) { - virus_num++; + // okay so the file didn't actually get extracted. That's okay, we'll move on. + tempfile_exists = false; + ret = CL_SUCCESS; + } else if (CL_SUCCESS != ret) { + goto done; } - if (!ctx->engine->keeptmp) { - if (!access(tmp_fname, R_OK) && cli_unlink(tmp_fname)) { - free(tmp_fname); + if (!ctx->engine->keeptmp && tempfile_exists) { + if (cli_unlink(tmp_fname)) { ret = CL_EUNLINK; - break; + goto done; } } + free(tmp_fname); + tmp_fname = NULL; + files++; - if (ret == CL_VIRUS && SCAN_ALLMATCHES) - continue; - if (ret) - break; } -out_close: - mschm_d->close(mschm_d, mschm_h); -out_dest: - mspack_destroy_chm_decompressor(mschm_d); - if (virus_num) - return CL_VIRUS; +done: + + if (NULL != tmp_fname) { + if (!ctx->engine->keeptmp && tempfile_exists) { + (void)cli_unlink(tmp_fname); + } + + free(tmp_fname); + } + + if (NULL != mschm_d) { + if (NULL != mschm_h) { + mschm_d->close(mschm_d, mschm_h); + } + mspack_destroy_chm_decompressor(mschm_d); + } + return ret; } diff --git a/libclamav/macho.c b/libclamav/macho.c index 5ebbfc71a8..0ebcc7ed61 100644 --- a/libclamav/macho.c +++ b/libclamav/macho.c @@ -168,13 +168,11 @@ struct macho_fat_arch { uint32_t align; }; -#define RETURN_BROKEN \ - if (matcher) \ - return -1; \ - if (SCAN_HEURISTIC_BROKEN) { \ - if (CL_VIRUS == cli_append_virus(ctx, "Heuristics.Broken.Executable")) \ - return CL_VIRUS; \ - } \ +#define RETURN_BROKEN \ + if (SCAN_HEURISTIC_BROKEN) { \ + if (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable")) \ + return CL_VIRUS; \ + } \ return CL_EFORMAT static uint32_t cli_rawaddr(uint32_t vaddr, struct cli_exe_section *sects, uint16_t nsects, unsigned int *err) @@ -197,7 +195,7 @@ static uint32_t cli_rawaddr(uint32_t vaddr, struct cli_exe_section *sects, uint1 return vaddr - sects[i].rva + sects[i].raw; } -int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) +cl_error_t cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) { struct macho_hdr hdr; struct macho_load_cmd load_cmd; @@ -205,7 +203,8 @@ int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) struct macho_segment_cmd64 segment_cmd64; struct macho_section section; struct macho_section64 section64; - unsigned int i, j, sect = 0, conv, m64, nsects, matcher = 0; + unsigned int i, j, sect = 0, conv, m64, nsects; + bool get_fileinfo = false; unsigned int arch = 0, ep = 0, err; struct cli_exe_section *sections = NULL; char name[16]; @@ -213,7 +212,7 @@ int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) ssize_t at; if (fileinfo) { - matcher = 1; + get_fileinfo = true; // TODO This code assumes fileinfo->offset == 0, which might not always // be the case. For now just print this debug message and continue on @@ -224,7 +223,7 @@ int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) if (fmap_readn(map, &hdr, 0, sizeof(hdr)) != sizeof(hdr)) { cli_dbgmsg("cli_scanmacho: Can't read header\n"); - return matcher ? -1 : CL_EFORMAT; + return CL_EFORMAT; } at = sizeof(hdr); @@ -242,44 +241,44 @@ int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) m64 = 1; } else { cli_dbgmsg("cli_scanmacho: Incorrect magic\n"); - return matcher ? -1 : CL_EFORMAT; + return CL_EFORMAT; } switch (EC32(hdr.cpu_type, conv)) { case 7: - if (!matcher) + if (!get_fileinfo) cli_dbgmsg("MACHO: CPU Type: Intel 32-bit\n"); arch = 1; break; case 7 | 0x1000000: - if (!matcher) + if (!get_fileinfo) cli_dbgmsg("MACHO: CPU Type: Intel 64-bit\n"); break; case 12: - if (!matcher) + if (!get_fileinfo) cli_dbgmsg("MACHO: CPU Type: ARM\n"); break; case 14: - if (!matcher) + if (!get_fileinfo) cli_dbgmsg("MACHO: CPU Type: SPARC\n"); break; case 18: - if (!matcher) + if (!get_fileinfo) cli_dbgmsg("MACHO: CPU Type: POWERPC 32-bit\n"); arch = 2; break; case 18 | 0x1000000: - if (!matcher) + if (!get_fileinfo) cli_dbgmsg("MACHO: CPU Type: POWERPC 64-bit\n"); arch = 3; break; default: - if (!matcher) + if (!get_fileinfo) cli_dbgmsg("MACHO: CPU Type: ** UNKNOWN ** (%u)\n", EC32(hdr.cpu_type, conv)); break; } - if (!matcher) switch (EC32(hdr.filetype, conv)) { + if (!get_fileinfo) switch (EC32(hdr.filetype, conv)) { case 0x1: /* MH_OBJECT */ cli_dbgmsg("MACHO: Filetype: Relocatable object file\n"); break; @@ -311,7 +310,7 @@ int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) cli_dbgmsg("MACHO: Filetype: ** UNKNOWN ** (0x%x)\n", EC32(hdr.filetype, conv)); } - if (!matcher) { + if (!get_fileinfo) { cli_dbgmsg("MACHO: Number of load commands: %u\n", EC32(hdr.ncmds, conv)); cli_dbgmsg("MACHO: Size of load commands: %u\n", EC32(hdr.sizeofcmds, conv)); } @@ -362,7 +361,7 @@ int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) strncpy(name, segment_cmd.segname, sizeof(name)); name[sizeof(name) - 1] = '\0'; } - if (!matcher) { + if (!get_fileinfo) { cli_dbgmsg("MACHO: Segment name: %s\n", name); cli_dbgmsg("MACHO: Number of sections: %u\n", nsects); } @@ -372,14 +371,14 @@ int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) RETURN_BROKEN; } if (!nsects) { - if (!matcher) + if (!get_fileinfo) cli_dbgmsg("MACHO: ------------------\n"); continue; } sections = (struct cli_exe_section *)cli_realloc2(sections, (sect + nsects) * sizeof(struct cli_exe_section)); if (!sections) { cli_errmsg("cli_scanmacho: Can't allocate memory for 'sections'\n"); - return matcher ? -1 : CL_EMEM; + return CL_EMEM; } for (j = 0; j < nsects; j++) { @@ -417,7 +416,7 @@ int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) strncpy(name, section.sectname, sizeof(name)); name[sizeof(name) - 1] = '\0'; } - if (!matcher) { + if (!get_fileinfo) { cli_dbgmsg("MACHO: --- Section %u ---\n", sect); cli_dbgmsg("MACHO: Name: %s\n", name); cli_dbgmsg("MACHO: Virtual address: 0x%x\n", (unsigned int)sections[sect].rva); @@ -428,7 +427,7 @@ int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) } sect++; } - if (!matcher) + if (!get_fileinfo) cli_dbgmsg("MACHO: ------------------\n"); } else if (arch && (load_cmd.cmd == 0x4 || load_cmd.cmd == 0x5)) { /* LC_(UNIX)THREAD */ @@ -477,7 +476,7 @@ int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) default: cli_errmsg("cli_scanmacho: Invalid arch setting!\n"); free(sections); - return matcher ? -1 : CL_EARG; + return CL_EARG; } } else { if (EC32(load_cmd.cmdsize, conv) > sizeof(load_cmd)) @@ -486,43 +485,43 @@ int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) } if (ep) { - if (!matcher) + if (!get_fileinfo) cli_dbgmsg("Entry Point: 0x%x\n", ep); if (sections) { ep = cli_rawaddr(ep, sections, sect, &err); if (err) { cli_dbgmsg("cli_scanmacho: Can't calculate EP offset\n"); free(sections); - return matcher ? -1 : CL_EFORMAT; + return CL_EFORMAT; } - if (!matcher) + if (!get_fileinfo) cli_dbgmsg("Entry Point file offset: %u\n", ep); } } - if (matcher) { + if (get_fileinfo) { fileinfo->ep = ep; fileinfo->nsections = sect; fileinfo->sections = sections; - return 0; } else { free(sections); - return CL_SUCCESS; } + + return CL_SUCCESS; } -int cli_machoheader(cli_ctx *ctx, struct cli_exe_info *fileinfo) +cl_error_t cli_machoheader(cli_ctx *ctx, struct cli_exe_info *fileinfo) { return cli_scanmacho(ctx, fileinfo); } -int cli_scanmacho_unibin(cli_ctx *ctx) +cl_error_t cli_scanmacho_unibin(cli_ctx *ctx) { struct macho_fat_header fat_header; struct macho_fat_arch fat_arch; - unsigned int conv, i, matcher = 0; - int ret = CL_CLEAN; - fmap_t *map = ctx->fmap; + unsigned int conv, i; + cl_error_t ret = CL_SUCCESS; + fmap_t *map = ctx->fmap; ssize_t at; if (fmap_readn(map, &fat_header, 0, sizeof(fat_header)) != sizeof(fat_header)) { @@ -569,61 +568,63 @@ int cli_scanmacho_unibin(cli_ctx *ctx) RETURN_BROKEN; } - ret = cli_magic_scan_nested_fmap_type(map, fat_arch.offset, fat_arch.size, ctx, - CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); - if (ret == CL_VIRUS) + ret = cli_magic_scan_nested_fmap_type(map, fat_arch.offset, fat_arch.size, ctx, CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); + if (ret != CL_SUCCESS) { break; + } } return ret; /* result from the last binary */ } -int cli_unpackmacho(cli_ctx *ctx) +cl_error_t cli_unpackmacho(cli_ctx *ctx) { - char *tempfile; - int ndesc; + cl_error_t ret = CL_SUCCESS; + char *tempfile = NULL; + int ndesc = -1; struct cli_bc_ctx *bc_ctx; - int ret; /* Bytecode BC_MACHO_UNPACKER hook */ bc_ctx = cli_bytecode_context_alloc(); if (!bc_ctx) { - cli_errmsg("cli_scanelf: can't allocate memory for bc_ctx\n"); - return CL_EMEM; + cli_errmsg("cli_unpackmacho: can't allocate memory for bc_ctx\n"); + ret = CL_EMEM; + goto done; } cli_bytecode_context_setctx(bc_ctx, ctx); + cli_dbgmsg("Running bytecode hook\n"); ret = cli_bytecode_runhook(ctx, ctx->engine, bc_ctx, BC_MACHO_UNPACKER, ctx->fmap); - switch (ret) { - case CL_VIRUS: - cli_bytecode_context_destroy(bc_ctx); - return CL_VIRUS; - case CL_SUCCESS: - ndesc = cli_bytecode_context_getresult_file(bc_ctx, &tempfile); - cli_bytecode_context_destroy(bc_ctx); - if (ndesc != -1 && tempfile) { - if (ctx->engine->keeptmp) - cli_dbgmsg("cli_scanmacho: Unpacked and rebuilt executable saved in %s\n", tempfile); - else - cli_dbgmsg("cli_scanmacho: Unpacked and rebuilt executable\n"); - lseek(ndesc, 0, SEEK_SET); - cli_dbgmsg("***** Scanning rebuilt Mach-O file *****\n"); - if (CL_VIRUS == cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE)) { - close(ndesc); - CLI_TMPUNLK(); - free(tempfile); - return CL_VIRUS; - } - close(ndesc); - CLI_TMPUNLK(); - free(tempfile); - return CL_SUCCESS; - } - break; - default: - cli_bytecode_context_destroy(bc_ctx); + cli_dbgmsg("Finished running bytecode hook\n"); + if (CL_SUCCESS == ret) { + // check for unpacked/rebuilt executable + ndesc = cli_bytecode_context_getresult_file(bc_ctx, &tempfile); + if (ndesc != -1 && tempfile) { + cli_dbgmsg("cli_unpackmacho: Unpacked and rebuilt Mach-O executable saved in %s\n", tempfile); + + lseek(ndesc, 0, SEEK_SET); + + cli_dbgmsg("***** Scanning rebuilt Mach-O file *****\n"); + ret = cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE); + } + } + +done: + // cli_bytecode_context_getresult_file() gives up ownership of temp file, so we must clean it up. + if (-1 != ndesc) { + close(ndesc); + } + if (NULL != tempfile) { + if (!ctx->engine->keeptmp) { + (void)cli_unlink(tempfile); + } + free(tempfile); + } + + if (NULL != bc_ctx) { + cli_bytecode_context_destroy(bc_ctx); } - return CL_CLEAN; + return ret; } diff --git a/libclamav/macho.h b/libclamav/macho.h index b8423a6822..ffb99c8b10 100644 --- a/libclamav/macho.h +++ b/libclamav/macho.h @@ -26,9 +26,9 @@ #include "execs.h" #include "fmap.h" -int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo); -int cli_machoheader(cli_ctx *ctx, struct cli_exe_info *fileinfo); -int cli_scanmacho_unibin(cli_ctx *ctx); -int cli_unpackmacho(cli_ctx *ctx); +cl_error_t cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo); +cl_error_t cli_machoheader(cli_ctx *ctx, struct cli_exe_info *fileinfo); +cl_error_t cli_scanmacho_unibin(cli_ctx *ctx); +cl_error_t cli_unpackmacho(cli_ctx *ctx); #endif diff --git a/libclamav/matcher-ac.c b/libclamav/matcher-ac.c index f1e45a1344..ede7f84dac 100644 --- a/libclamav/matcher-ac.c +++ b/libclamav/matcher-ac.c @@ -1549,7 +1549,7 @@ cl_error_t cli_ac_initdata(struct cli_ac_data *data, uint32_t partsigs, uint32_t cl_error_t cli_ac_caloff(const struct cli_matcher *root, struct cli_ac_data *data, const struct cli_target_info *info) { - int ret; + cl_error_t ret; unsigned int i; struct cli_ac_patt *patt; @@ -1560,7 +1560,7 @@ cl_error_t cli_ac_caloff(const struct cli_matcher *root, struct cli_ac_data *dat patt = root->ac_reloff[i]; if (!info) { data->offset[patt->offset_min] = CLI_OFF_NONE; - } else if ((ret = cli_caloff(NULL, info, root->type, patt->offdata, &data->offset[patt->offset_min], &data->offset[patt->offset_max]))) { + } else if (CL_SUCCESS != (ret = cli_caloff(NULL, info, root->type, patt->offdata, &data->offset[patt->offset_min], &data->offset[patt->offset_max]))) { cli_errmsg("cli_ac_caloff: Can't calculate relative offset in signature for %s\n", patt->virname); return ret; } else if ((data->offset[patt->offset_min] != CLI_OFF_NONE) && (data->offset[patt->offset_min] + patt->length[1] > info->fsize)) { @@ -1886,7 +1886,7 @@ cl_error_t cli_ac_scanbuff( realoff = offset + matchstart; if (pt->offdata[0] == CLI_OFF_VERSION) { - if (!cli_hashset_contains_maybe_noalloc(mdata->vinfo, realoff)) { + if (false == cli_hashset_contains_maybe_noalloc(mdata->vinfo, realoff)) { ptN = ptN->next_same; continue; } @@ -1994,12 +1994,13 @@ cl_error_t cli_ac_scanbuff( if (pt->type == CL_TYPE_IGNORED && (!pt->rtype || ftype == pt->rtype)) return CL_TYPE_IGNORED; - if ((pt->type > type || pt->type >= CL_TYPE_SFX || pt->type == CL_TYPE_MSEXE) && (!pt->rtype || ftype == pt->rtype)) { + if ((pt->type > type || pt->type >= CL_TYPE_SFX || pt->type == CL_TYPE_MSEXE) && + (pt->rtype == CL_TYPE_ANY || ftype == pt->rtype)) { + cli_dbgmsg("Matched signature for file type %s\n", pt->virname); type = pt->type; - if (ftoffset && - (!*ftoffset || (*ftoffset)->cnt < MAX_EMBEDDED_OBJ || type == CL_TYPE_ZIPSFX) && - (type >= CL_TYPE_SFX || ((ftype == CL_TYPE_MSEXE || ftype == CL_TYPE_ZIP || ftype == CL_TYPE_MSOLE2) && type == CL_TYPE_MSEXE))) { + if ((ftoffset != NULL) && + ((*ftoffset == NULL) || (*ftoffset)->cnt < MAX_EMBEDDED_OBJ || type == CL_TYPE_ZIPSFX) && (type >= CL_TYPE_SFX || ((ftype == CL_TYPE_MSEXE || ftype == CL_TYPE_ZIP || ftype == CL_TYPE_MSOLE2) && type == CL_TYPE_MSEXE))) { /* FIXME: the first offset in the array is most likely the correct one but * it may happen it is not */ @@ -2057,14 +2058,16 @@ cl_error_t cli_ac_scanbuff( } else { /* old type signature */ if (pt->type) { - if (pt->type == CL_TYPE_IGNORED && (!pt->rtype || ftype == pt->rtype)) + if (pt->type == CL_TYPE_IGNORED && (pt->rtype == CL_TYPE_ANY || ftype == pt->rtype)) return CL_TYPE_IGNORED; - if ((pt->type > type || pt->type >= CL_TYPE_SFX || pt->type == CL_TYPE_MSEXE) && (!pt->rtype || ftype == pt->rtype)) { + if ((pt->type > type || pt->type >= CL_TYPE_SFX || pt->type == CL_TYPE_MSEXE) && + (pt->rtype == CL_TYPE_ANY || ftype == pt->rtype)) { cli_dbgmsg("Matched signature for file type %s at %u\n", pt->virname, realoff); type = pt->type; - if (ftoffset && (!*ftoffset || (*ftoffset)->cnt < MAX_EMBEDDED_OBJ || type == CL_TYPE_ZIPSFX) && (type == CL_TYPE_MBR || type >= CL_TYPE_SFX || ((ftype == CL_TYPE_MSEXE || ftype == CL_TYPE_ZIP || ftype == CL_TYPE_MSOLE2) && type == CL_TYPE_MSEXE))) { + if ((ftoffset != NULL) && + ((*ftoffset == NULL) || (*ftoffset)->cnt < MAX_EMBEDDED_OBJ || type == CL_TYPE_ZIPSFX) && (type == CL_TYPE_MBR || type >= CL_TYPE_SFX || ((ftype == CL_TYPE_MSEXE || ftype == CL_TYPE_ZIP || ftype == CL_TYPE_MSOLE2) && type == CL_TYPE_MSEXE))) { if (ac_addtype(ftoffset, type, realoff, ctx)) return CL_EMEM; diff --git a/libclamav/matcher-hash-types.h b/libclamav/matcher-hash-types.h new file mode 100644 index 0000000000..57f192cd4d --- /dev/null +++ b/libclamav/matcher-hash-types.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * Copyright (C) 2010-2013 Sourcefire, Inc. + * + * Authors: aCaB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#ifndef __MATCHER_HASH_TYPES_H +#define __MATCHER_HASH_TYPES_H + +typedef enum cli_hash_type { + CLI_HASH_MD5 = 0, + CLI_HASH_SHA1, + CLI_HASH_SHA256, + + /* new hash types go above this line */ + CLI_HASH_AVAIL_TYPES +} cli_hash_type_t; + +#define CLI_HASHLEN_MD5 16 +#define CLI_HASHLEN_SHA1 20 +#define CLI_HASHLEN_SHA256 32 +#define CLI_HASHLEN_MAX 32 + +#endif diff --git a/libclamav/matcher-hash.c b/libclamav/matcher-hash.c index 7c9c842132..ea5e075383 100644 --- a/libclamav/matcher-hash.c +++ b/libclamav/matcher-hash.c @@ -28,7 +28,7 @@ int hm_addhash_str(struct cli_matcher *root, const char *strhash, uint32_t size, const char *virusname) { - enum CLI_HASH_TYPE type; + cli_hash_type_t type; char binhash[CLI_HASHLEN_MAX]; int hlen; @@ -71,7 +71,7 @@ const unsigned int hashlen[] = { CLI_HASHLEN_SHA1, CLI_HASHLEN_SHA256}; -int hm_addhash_bin(struct cli_matcher *root, const void *binhash, enum CLI_HASH_TYPE type, uint32_t size, const char *virusname) +int hm_addhash_bin(struct cli_matcher *root, const void *binhash, cli_hash_type_t type, uint32_t size, const char *virusname) { const unsigned int hlen = hashlen[type]; const struct cli_htu32_element *item; @@ -83,7 +83,7 @@ int hm_addhash_bin(struct cli_matcher *root, const void *binhash, enum CLI_HASH_ /* size non-zero, find sz_hash element in size-driven hashtable */ ht = &root->hm.sizehashes[type]; if (!root->hm.sizehashes[type].capacity) { - i = cli_htu32_init(ht, 64, root->mempool); + i = CLI_HTU32_INIT(ht, 64, root->mempool); if (i) return i; } @@ -98,7 +98,7 @@ int hm_addhash_bin(struct cli_matcher *root, const void *binhash, enum CLI_HASH_ htitem.key = size; htitem.data.as_ptr = szh; - i = cli_htu32_insert(ht, &htitem, root->mempool); + i = CLI_HTU32_INSERT(ht, &htitem, root->mempool); if (i) { cli_errmsg("hm_addhash_bin: failed to add item to hashtab"); MPOOL_FREE(root->mempool, szh); @@ -192,7 +192,7 @@ static void hm_sort(struct cli_sz_hash *szh, size_t l, size_t r, unsigned int ke /* flush both size-specific and agnostic hash sets */ void hm_flush(struct cli_matcher *root) { - enum CLI_HASH_TYPE type; + cli_hash_type_t type; unsigned int keylen; struct cli_sz_hash *szh; @@ -225,23 +225,22 @@ void hm_flush(struct cli_matcher *root) } } -int cli_hm_have_size(const struct cli_matcher *root, enum CLI_HASH_TYPE type, uint32_t size) +int cli_hm_have_size(const struct cli_matcher *root, cli_hash_type_t type, uint32_t size) { return (size && size != 0xffffffff && root && root->hm.sizehashes[type].capacity && cli_htu32_find(&root->hm.sizehashes[type], size)); } -int cli_hm_have_wild(const struct cli_matcher *root, enum CLI_HASH_TYPE type) +int cli_hm_have_wild(const struct cli_matcher *root, cli_hash_type_t type) { return (root && root->hwild.hashes[type].items); } -int cli_hm_have_any(const struct cli_matcher *root, enum CLI_HASH_TYPE type) +int cli_hm_have_any(const struct cli_matcher *root, cli_hash_type_t type) { return (root && (root->hwild.hashes[type].items || root->hm.sizehashes[type].capacity)); } -/* cli_hm_scan will scan only size-specific hashes, if any */ -static int hm_scan(const unsigned char *digest, const char **virname, const struct cli_sz_hash *szh, enum CLI_HASH_TYPE type) +static int hm_scan(const unsigned char *digest, const char **virname, const struct cli_sz_hash *szh, cli_hash_type_t type) { unsigned int keylen; size_t l, r; @@ -273,7 +272,7 @@ static int hm_scan(const unsigned char *digest, const char **virname, const stru } /* cli_hm_scan will scan only size-specific hashes, if any */ -int cli_hm_scan(const unsigned char *digest, uint32_t size, const char **virname, const struct cli_matcher *root, enum CLI_HASH_TYPE type) +int cli_hm_scan(const unsigned char *digest, uint32_t size, const char **virname, const struct cli_matcher *root, cli_hash_type_t type) { const struct cli_htu32_element *item; struct cli_sz_hash *szh; @@ -291,7 +290,7 @@ int cli_hm_scan(const unsigned char *digest, uint32_t size, const char **virname } /* cli_hm_scan_wild will scan only size-agnostic hashes, if any */ -int cli_hm_scan_wild(const unsigned char *digest, const char **virname, const struct cli_matcher *root, enum CLI_HASH_TYPE type) +int cli_hm_scan_wild(const unsigned char *digest, const char **virname, const struct cli_matcher *root, cli_hash_type_t type) { if (!digest || !root || !root->hwild.hashes[type].items) return CL_CLEAN; @@ -302,7 +301,7 @@ int cli_hm_scan_wild(const unsigned char *digest, const char **virname, const st /* free both size-specific and agnostic hash sets */ void hm_free(struct cli_matcher *root) { - enum CLI_HASH_TYPE type; + cli_hash_type_t type; if (!root) return; @@ -323,7 +322,7 @@ void hm_free(struct cli_matcher *root) MPOOL_FREE(root->mempool, szh->virusnames); MPOOL_FREE(root->mempool, szh); } - cli_htu32_free(ht, root->mempool); + CLI_HTU32_FREE(ht, root->mempool); } for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++) { diff --git a/libclamav/matcher-hash.h b/libclamav/matcher-hash.h index 896d9ccc20..c41fba42fc 100644 --- a/libclamav/matcher-hash.h +++ b/libclamav/matcher-hash.h @@ -27,22 +27,9 @@ #endif #include "clamav-types.h" +#include "matcher-hash-types.h" #include "hashtab.h" -enum CLI_HASH_TYPE { - CLI_HASH_MD5 = 0, - CLI_HASH_SHA1, - CLI_HASH_SHA256, - - /* new hash types go above this line */ - CLI_HASH_AVAIL_TYPES -}; - -#define CLI_HASHLEN_MD5 16 -#define CLI_HASHLEN_SHA1 20 -#define CLI_HASHLEN_SHA256 32 -#define CLI_HASHLEN_MAX 32 - struct cli_sz_hash { uint8_t *hash_array; const char **virusnames; @@ -58,13 +45,13 @@ struct cli_hash_wild { }; int hm_addhash_str(struct cli_matcher *root, const char *strhash, uint32_t size, const char *virusname); -int hm_addhash_bin(struct cli_matcher *root, const void *binhash, enum CLI_HASH_TYPE type, uint32_t size, const char *virusname); +int hm_addhash_bin(struct cli_matcher *root, const void *binhash, cli_hash_type_t type, uint32_t size, const char *virusname); void hm_flush(struct cli_matcher *root); -int cli_hm_scan(const unsigned char *digest, uint32_t size, const char **virname, const struct cli_matcher *root, enum CLI_HASH_TYPE type); -int cli_hm_scan_wild(const unsigned char *digest, const char **virname, const struct cli_matcher *root, enum CLI_HASH_TYPE type); -int cli_hm_have_size(const struct cli_matcher *root, enum CLI_HASH_TYPE type, uint32_t size); -int cli_hm_have_wild(const struct cli_matcher *root, enum CLI_HASH_TYPE type); -int cli_hm_have_any(const struct cli_matcher *root, enum CLI_HASH_TYPE type); +int cli_hm_scan(const unsigned char *digest, uint32_t size, const char **virname, const struct cli_matcher *root, cli_hash_type_t type); +int cli_hm_scan_wild(const unsigned char *digest, const char **virname, const struct cli_matcher *root, cli_hash_type_t type); +int cli_hm_have_size(const struct cli_matcher *root, cli_hash_type_t type, uint32_t size); +int cli_hm_have_wild(const struct cli_matcher *root, cli_hash_type_t type); +int cli_hm_have_any(const struct cli_matcher *root, cli_hash_type_t type); void hm_free(struct cli_matcher *root); #endif diff --git a/libclamav/matcher-pcre.c b/libclamav/matcher-pcre.c index e5678536c9..a0da207e7c 100644 --- a/libclamav/matcher-pcre.c +++ b/libclamav/matcher-pcre.c @@ -580,9 +580,9 @@ cl_error_t cli_pcre_scanbuf(const unsigned char *buffer, uint32_t length, const unsigned int i, evalcnt = 0; uint64_t evalids = 0; uint32_t global, encompass, rolling; - int rc = 0, options = 0; - uint32_t offset = 0; - uint8_t viruses_found = 0; + int rc = 0; + int options = 0; + uint32_t offset = 0; if ((root->pcre_metas == 0) || (!root->pcre_metatable) || (ctx && ctx->dconf && !(ctx->dconf->pcre & PCRE_CONF_SUPPORT))) return CL_SUCCESS; @@ -731,15 +731,19 @@ cl_error_t cli_pcre_scanbuf(const unsigned char *buffer, uint32_t length, const newres->offset = adjbuffer + p_res.match[0]; *res = newres; } else { - ret = CL_CLEAN; - viruses_found = 1; - if (ctx) - ret = cli_append_virus(ctx, "test"); - if (virname) + ret = CL_VIRUS; + + if (virname) { *virname = "test"; - if (!ctx || !SCAN_ALLMATCHES) - if (ret != CL_CLEAN) + } + + // ctx is not provided in the unit tests. + if (ctx) { + ret = cli_append_virus(ctx, "test"); + if (ret != CL_SUCCESS) { break; + } + } } } } @@ -764,8 +768,6 @@ cl_error_t cli_pcre_scanbuf(const unsigned char *buffer, uint32_t length, const /* free match results */ cli_pcre_results_free(&p_res); - if (ret == CL_SUCCESS && viruses_found) - return CL_VIRUS; return ret; } diff --git a/libclamav/matcher.c b/libclamav/matcher.c index f7d7bf607b..8990cfcf08 100644 --- a/libclamav/matcher.c +++ b/libclamav/matcher.c @@ -116,7 +116,6 @@ static inline cl_error_t matcher_run(const struct cli_matcher *root, struct filter_match_info info; uint32_t orig_length, orig_offset; const unsigned char *orig_buffer; - unsigned int viruses_found = 0; if (root->filter) { if (filter_search_ext(root->filter, buffer, length, &info) == -1) { @@ -151,31 +150,23 @@ static inline cl_error_t matcher_run(const struct cli_matcher *root, } else { ret = cli_bm_scanbuff(buffer, length, virname, NULL, root, offset, tinfo, offdata, ctx); } - if (ret != CL_CLEAN) { + if (ret != CL_SUCCESS) { if (ret != CL_VIRUS) return ret; - /* else (ret == CL_VIRUS) */ - if (SCAN_ALLMATCHES) - viruses_found = 1; - else { - ret = cli_append_virus(ctx, *virname); - if (ret != CL_CLEAN) - return ret; - } + + ret = cli_append_virus(ctx, *virname); + if (ret != CL_SUCCESS) + return ret; } } perf_log_tries(acmode, 0, length); ret = cli_ac_scanbuff(buffer, length, virname, NULL, acres, root, mdata, offset, ftype, ftoffset, acmode, ctx); - if (ret != CL_CLEAN) { + if (ret != CL_SUCCESS) { if (ret == CL_VIRUS) { - if (SCAN_ALLMATCHES) - viruses_found = 1; - else { - ret = cli_append_virus(ctx, *virname); - if (ret != CL_CLEAN) - return ret; - } + ret = cli_append_virus(ctx, *virname); + if (ret != CL_SUCCESS) + return ret; } else if (ret > CL_TYPENO && acmode & AC_SCAN_VIR) { saved_ret = ret; } else { @@ -257,12 +248,12 @@ static inline cl_error_t matcher_run(const struct cli_matcher *root, #endif /* HAVE_PCRE */ /* end experimental fragment */ - if (ctx && !SCAN_ALLMATCHES && ret == CL_VIRUS) { - return cli_append_virus(ctx, *virname); - } - if (ctx && SCAN_ALLMATCHES && viruses_found) { - return CL_VIRUS; + if (ctx && ret == CL_VIRUS) { + ret = cli_append_virus(ctx, *virname); + if (ret != CL_SUCCESS) + return ret; } + if (saved_ret && ret == CL_CLEAN) { return saved_ret; } @@ -273,9 +264,9 @@ static inline cl_error_t matcher_run(const struct cli_matcher *root, cl_error_t cli_scan_buff(const unsigned char *buffer, uint32_t length, uint32_t offset, cli_ctx *ctx, cli_file_t ftype, struct cli_ac_data **acdata) { cl_error_t ret = CL_CLEAN; - unsigned int i = 0, j = 0, viruses_found = 0; - struct cli_ac_data mdata; - struct cli_matcher *groot, *troot = NULL; + unsigned int i = 0, j = 0; + struct cli_ac_data matcher_data; + struct cli_matcher *generic_ac_root, *target_ac_root = NULL; const char *virname = NULL; const struct cl_engine *engine = ctx->engine; @@ -284,52 +275,68 @@ cl_error_t cli_scan_buff(const unsigned char *buffer, uint32_t length, uint32_t return CL_ENULLARG; } - groot = engine->root[0]; /* generic signatures */ + generic_ac_root = engine->root[0]; /* generic signatures */ + + if (ftype != CL_TYPE_ANY) { + // Identify the target type, to find the matcher root for that target. - if (ftype) { for (i = 1; i < CLI_MTARGETS; i++) { for (j = 0; j < cli_mtargets[i].target_count; ++j) { if (cli_mtargets[i].target[j] == ftype) { - troot = ctx->engine->root[i]; - break; + // Identified the target type, now get the matcher root for that target. + target_ac_root = ctx->engine->root[i]; + break; // Break out of inner loop } } - if (troot) break; + if (target_ac_root) break; } } - if (troot) { + if (target_ac_root) { + /* If a target-specific specific signature root was found for the given file type, match with it. */ - if (!acdata && (ret = cli_ac_initdata(&mdata, troot->ac_partsigs, troot->ac_lsigs, troot->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN))) - return ret; + if (!acdata) { + // no ac matcher data was provided, so we need to initialize our own. + ret = cli_ac_initdata(&matcher_data, target_ac_root->ac_partsigs, target_ac_root->ac_lsigs, target_ac_root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN); + if (CL_SUCCESS != ret) { + return ret; + } + } - ret = matcher_run(troot, buffer, length, &virname, acdata ? (acdata[0]) : (&mdata), offset, NULL, ftype, NULL, AC_SCAN_VIR, PCRE_SCAN_BUFF, NULL, ctx->fmap, NULL, NULL, ctx); + ret = matcher_run(target_ac_root, buffer, length, &virname, + acdata ? (acdata[0]) : (&matcher_data), + offset, NULL, ftype, NULL, AC_SCAN_VIR, PCRE_SCAN_BUFF, NULL, ctx->fmap, NULL, NULL, ctx); - if (!acdata) - cli_ac_freedata(&mdata); + if (!acdata) { + // no longer need our AC local matcher data (if using) + cli_ac_freedata(&matcher_data); + } - if (ret == CL_EMEM) + if (ret == CL_EMEM || ret == CL_VIRUS) { return ret; - if (ret == CL_VIRUS) { - viruses_found = 1; - if (ctx && !SCAN_ALLMATCHES) { - return ret; - } } - } - virname = NULL; + // reset virname back to NULL for matching with the generic AC root. + virname = NULL; + } - if (!acdata && (ret = cli_ac_initdata(&mdata, groot->ac_partsigs, groot->ac_lsigs, groot->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN))) - return ret; + if (!acdata) { + // no ac matcher data was provided, so we need to initialize our own. + ret = cli_ac_initdata(&matcher_data, generic_ac_root->ac_partsigs, generic_ac_root->ac_lsigs, generic_ac_root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN); + if (CL_SUCCESS != ret) { + return ret; + } + } - ret = matcher_run(groot, buffer, length, &virname, acdata ? (acdata[1]) : (&mdata), offset, NULL, ftype, NULL, AC_SCAN_VIR, PCRE_SCAN_BUFF, NULL, ctx->fmap, NULL, NULL, ctx); + ret = matcher_run(generic_ac_root, buffer, length, &virname, + acdata ? (acdata[1]) : (&matcher_data), + offset, NULL, ftype, NULL, AC_SCAN_VIR, PCRE_SCAN_BUFF, NULL, ctx->fmap, NULL, NULL, ctx); - if (!acdata) - cli_ac_freedata(&mdata); + if (!acdata) { + // no longer need our AC local matcher data (if using) + cli_ac_freedata(&matcher_data); + } - if (viruses_found) - return CL_VIRUS; return ret; } @@ -339,7 +346,7 @@ cl_error_t cli_scan_buff(const unsigned char *buffer, uint32_t length, uint32_t * offdata[2]: max shift * offdata[3]: section number */ -cl_error_t cli_caloff(const char *offstr, const struct cli_target_info *info, unsigned int target, uint32_t *offdata, uint32_t *offset_min, uint32_t *offset_max) +cl_error_t cli_caloff(const char *offstr, const struct cli_target_info *info, cli_target_t target, uint32_t *offdata, uint32_t *offset_min, uint32_t *offset_max) { char offcpy[65] = {0}; unsigned int n = 0, val = 0; @@ -446,7 +453,7 @@ cl_error_t cli_caloff(const char *offstr, const struct cli_target_info *info, un if (offdata[0] != CLI_OFF_ANY && offdata[0] != CLI_OFF_ABSOLUTE && offdata[0] != CLI_OFF_EOF_MINUS && offdata[0] != CLI_OFF_MACRO) { - if (target != 1 && target != 6 && target != 9) { + if (target != TARGET_PE && target != TARGET_ELF && target != TARGET_MACHO) { cli_errmsg("cli_caloff: Invalid offset type for target %u\n", target); return CL_EMALFDB; } @@ -526,22 +533,27 @@ void cli_targetinfo_init(struct cli_target_info *info) cli_exe_info_init(&(info->exeinfo), 0); } -void cli_targetinfo(struct cli_target_info *info, unsigned int target, cli_ctx *ctx) +void cli_targetinfo(struct cli_target_info *info, cli_target_t target, cli_ctx *ctx) { - int (*einfo)(cli_ctx *, struct cli_exe_info *) = NULL; + cl_error_t (*einfo)(cli_ctx *, struct cli_exe_info *) = NULL; info->fsize = ctx->fmap->len; - if (target == 1) - einfo = cli_pe_targetinfo; - else if (target == 6) - einfo = cli_elfheader; - else if (target == 9) - einfo = cli_machoheader; - else - return; + switch (target) { + case TARGET_PE: + einfo = cli_pe_targetinfo; + break; + case TARGET_ELF: + einfo = cli_elfheader; + break; + case TARGET_MACHO: + einfo = cli_machoheader; + break; + default: + return; + } - if (einfo(ctx, &info->exeinfo)) + if (CL_SUCCESS != einfo(ctx, &info->exeinfo)) info->status = -1; else info->status = 1; @@ -578,7 +590,7 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname) while (stack_index >= 0) { map = ctx->recursion_stack[stack_index].fmap; - if (CL_SUCCESS != fmap_get_MD5(map, &digest)) { + if (CL_SUCCESS != fmap_get_hash(map, &digest, CLI_HASH_MD5)) { cli_dbgmsg("cli_check_fp: Failed to get a hash for the map at stack index # %u\n", stack_index); stack_index--; continue; @@ -731,7 +743,6 @@ int32_t cli_bcapi_matchicon(struct cli_bc_ctx *ctx, const uint8_t *grp1, int32_t { cl_error_t ret; char group1[128], group2[128]; - const char **oldvirname; struct cli_exe_info info; // TODO This isn't a good check, since EP will be zero for DLLs and @@ -744,8 +755,7 @@ int32_t cli_bcapi_matchicon(struct cli_bc_ctx *ctx, const uint8_t *grp1, int32_t if ((size_t)grp1len > sizeof(group1) - 1 || (size_t)grp2len > sizeof(group2) - 1) return -1; - oldvirname = ((cli_ctx *)ctx->ctx)->virname; - ((cli_ctx *)ctx->ctx)->virname = NULL; + memcpy(group1, grp1, grp1len); memcpy(group2, grp2, grp2len); group1[grp1len] = 0; @@ -763,14 +773,13 @@ int32_t cli_bcapi_matchicon(struct cli_bc_ctx *ctx, const uint8_t *grp1, int32_t info.nsections = ctx->hooks.pedata->nsections; info.hdr_size = ctx->hooks.pedata->hdr_size; cli_dbgmsg("bytecode matchicon %s %s\n", group1, group2); - ret = matchicon(ctx->ctx, &info, group1[0] ? group1 : NULL, + ret = matchicon(ctx->ctx, &info, group1[0] ? group1 : NULL, group2[0] ? group2 : NULL); - ((cli_ctx *)ctx->ctx)->virname = oldvirname; + return (int32_t)ret; } -cl_error_t cli_scan_desc(int desc, cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct cli_matched_type **ftoffset, - unsigned int acmode, struct cli_ac_result **acres, const char *name, uint32_t attributes) +cl_error_t cli_scan_desc(int desc, cli_ctx *ctx, cli_file_t ftype, bool filetype_only, struct cli_matched_type **ftoffset, unsigned int acmode, struct cli_ac_result **acres, const char *name, uint32_t attributes) { cl_error_t status = CL_CLEAN; int empty; @@ -792,7 +801,7 @@ cl_error_t cli_scan_desc(int desc, cli_ctx *ctx, cli_file_t ftype, uint8_t ftonl goto done; } - status = cli_scan_fmap(ctx, ftype, ftonly, ftoffset, acmode, acres, NULL); + status = cli_scan_fmap(ctx, ftype, filetype_only, ftoffset, acmode, acres, NULL); map->dont_cache_flag = ctx->fmap->dont_cache_flag; /* Set the parent layer's "don't cache" flag to match the child. TODO: This may not be needed since `emax_reached()` should've @@ -841,88 +850,93 @@ static cl_error_t lsig_eval(cli_ctx *ctx, struct cli_matcher *root, struct cli_a if (status != CL_SUCCESS) return status; - if (cli_ac_chklsig(exp, exp_end, acdata->lsigcnt[lsid], &evalcnt, &evalids, 0) == 1) { - if (ac_lsig->tdb.container && ac_lsig->tdb.container[0] != cli_recursion_stack_get_type(ctx, -2)) + if (cli_ac_chklsig(exp, exp_end, acdata->lsigcnt[lsid], &evalcnt, &evalids, 0) != 1) { + // Logical expression did not match. + goto done; + } + + // Logical expression matched. + // Need to check the other conditions, like target description block, icon group, bytecode, etc. + + if (ac_lsig->tdb.container && ac_lsig->tdb.container[0] != cli_recursion_stack_get_type(ctx, -2)) + goto done; + if (ac_lsig->tdb.intermediates && !intermediates_eval(ctx, ac_lsig)) + goto done; + if (ac_lsig->tdb.filesize && (ac_lsig->tdb.filesize[0] > ctx->fmap->len || ac_lsig->tdb.filesize[1] < ctx->fmap->len)) + goto done; + + if (ac_lsig->tdb.ep || ac_lsig->tdb.nos) { + if (!target_info || target_info->status != 1) goto done; - if (ac_lsig->tdb.intermediates && !intermediates_eval(ctx, ac_lsig)) + if (ac_lsig->tdb.ep && (ac_lsig->tdb.ep[0] > target_info->exeinfo.ep || ac_lsig->tdb.ep[1] < target_info->exeinfo.ep)) goto done; - if (ac_lsig->tdb.filesize && (ac_lsig->tdb.filesize[0] > ctx->fmap->len || ac_lsig->tdb.filesize[1] < ctx->fmap->len)) + if (ac_lsig->tdb.nos && (ac_lsig->tdb.nos[0] > target_info->exeinfo.nsections || ac_lsig->tdb.nos[1] < target_info->exeinfo.nsections)) goto done; + } - if (ac_lsig->tdb.ep || ac_lsig->tdb.nos) { - if (!target_info || target_info->status != 1) + if (ac_lsig->tdb.handlertype) { + // This logical signature has a handler type, which means it's effectively a complex file type signature. + // Instead of alerting, we'll make a duplicate fmap (add recursion depth, to prevent infinite loops) and + // scan the file with the handler type. + + if (hash && 0 != memcmp(ctx->handlertype_hash, hash, 16)) { + /* + * Create an fmap window into our current fmap using the original offset & length, and rescan as the new type + * + * TODO: Unsure if creating an fmap is the right move, or if we should rescan with the current fmap as-is, + * since it's not really a container so much as it is type reassignment. This new fmap layer protect agains + * a possible infinite loop by applying the scan recursion limit, but maybe there's a better way? + * Testing with both HandlerType type reassignment sigs + Container/Intermediates sigs should indicate if + * a change is needed. + */ + new_map = fmap_duplicate(ctx->fmap, 0, ctx->fmap->len, ctx->fmap->name); + if (NULL == new_map) { + status = CL_EMEM; + cli_dbgmsg("Failed to duplicate the current fmap for a re-scan as a different type.\n"); goto done; - if (ac_lsig->tdb.ep && (ac_lsig->tdb.ep[0] > target_info->exeinfo.ep || ac_lsig->tdb.ep[1] < target_info->exeinfo.ep)) - goto done; - if (ac_lsig->tdb.nos && (ac_lsig->tdb.nos[0] > target_info->exeinfo.nsections || ac_lsig->tdb.nos[1] < target_info->exeinfo.nsections)) - goto done; - } + } - if (hash && ac_lsig->tdb.handlertype) { - if (0 != memcmp(ctx->handlertype_hash, hash, 16)) { - /* - * Create an fmap window into our current fmap using the original offset & length, and rescan as the new type - * - * TODO: Unsure if creating an fmap is the right move, or if we should rescan with the current fmap as-is, - * since it's not really a container so much as it is type reassignment. This new fmap layer protect agains - * a possible infinite loop by applying the scan recursion limit, but maybe there's a better way? - * Testing with both HandlerType type reassignment sigs + Container/Intermediates sigs should indicate if - * a change is needed. - */ - new_map = fmap_duplicate(ctx->fmap, 0, ctx->fmap->len, ctx->fmap->name); - if (NULL == new_map) { - status = CL_EMEM; - cli_dbgmsg("Failed to duplicate the current fmap for a re-scan as a different type.\n"); - goto done; - } + memcpy(ctx->handlertype_hash, hash, 16); - memcpy(ctx->handlertype_hash, hash, 16); + status = cli_recursion_stack_push(ctx, new_map, ac_lsig->tdb.handlertype[0], true, LAYER_ATTRIBUTES_NONE); /* Perform scan with child fmap */ + if (CL_SUCCESS != status) { + cli_dbgmsg("Failed to re-scan fmap as a new type.\n"); + goto done; + } - status = cli_recursion_stack_push(ctx, new_map, ac_lsig->tdb.handlertype[0], true, LAYER_ATTRIBUTES_NONE); /* Perform scan with child fmap */ - if (CL_SUCCESS != status) { - cli_dbgmsg("Failed to re-scan fmap as a new type.\n"); - goto done; - } + status = cli_magic_scan(ctx, ac_lsig->tdb.handlertype[0]); - status = cli_magic_scan(ctx, ac_lsig->tdb.handlertype[0]); + (void)cli_recursion_stack_pop(ctx); /* Restore the parent fmap */ - (void)cli_recursion_stack_pop(ctx); /* Restore the parent fmap */ + goto done; + } + } - if (CL_VIRUS == status) { - status = CL_VIRUS; - goto done; - } + if (ac_lsig->tdb.icongrp1 || ac_lsig->tdb.icongrp2) { + // Logical sig depends on icon match. Check for the icon match. - goto done; - } + if (!target_info || target_info->status != TARGET_PE) { + // Icon group feature only applies to PE files, so target description must match a PE file. + // This is a signature issue and should have been caught at load time, but just in case, we're checking again here. + goto done; } - if (ac_lsig->tdb.icongrp1 || ac_lsig->tdb.icongrp2) { - if (!target_info || target_info->status != 1) { - goto done; - } - - if (CL_VIRUS == matchicon(ctx, &target_info->exeinfo, ac_lsig->tdb.icongrp1, ac_lsig->tdb.icongrp2)) { - if (!ac_lsig->bc_idx) { - status = cli_append_virus(ctx, ac_lsig->virname); - if (status != CL_CLEAN) { - goto done; - } - } else if (CL_VIRUS == cli_bytecode_runlsig(ctx, target_info, &ctx->engine->bcs, ac_lsig->bc_idx, acdata->lsigcnt[lsid], acdata->lsigsuboff_first[lsid], ctx->fmap)) { - status = CL_VIRUS; - goto done; - } - } + if (CL_VIRUS != matchicon(ctx, &target_info->exeinfo, ac_lsig->tdb.icongrp1, ac_lsig->tdb.icongrp2)) { + // No icon match! goto done; } - if (!ac_lsig->bc_idx) { - status = cli_append_virus(ctx, ac_lsig->virname); - if (status != CL_CLEAN) { - goto done; - } + } + + if (!ac_lsig->bc_idx) { + // Logical sig does not depend on bytecode match. Report the virus. + status = cli_append_virus(ctx, ac_lsig->virname); + if (status != CL_SUCCESS) { + goto done; } - if (CL_VIRUS == cli_bytecode_runlsig(ctx, target_info, &ctx->engine->bcs, ac_lsig->bc_idx, acdata->lsigcnt[lsid], acdata->lsigsuboff_first[lsid], ctx->fmap)) { - status = CL_VIRUS; + } else { + // Logical sig depends on bytecode match. Check for the bytecode match. + status = cli_bytecode_runlsig(ctx, target_info, &ctx->engine->bcs, ac_lsig->bc_idx, acdata->lsigcnt[lsid], acdata->lsigsuboff_first[lsid], ctx->fmap); + if (CL_SUCCESS != status) { goto done; } } @@ -967,102 +981,135 @@ static cl_error_t yara_eval(cli_ctx *ctx, struct cli_matcher *root, struct cli_a cl_error_t cli_exp_eval(cli_ctx *ctx, struct cli_matcher *root, struct cli_ac_data *acdata, struct cli_target_info *target_info, const char *hash) { - uint8_t viruses_found = 0; uint32_t i; - cl_error_t rc = CL_SUCCESS; + cl_error_t status = CL_SUCCESS; for (i = 0; i < root->ac_lsigs; i++) { - if (root->ac_lsigtable[i]->type == CLI_LSIG_NORMAL) - rc = lsig_eval(ctx, root, acdata, target_info, hash, i); + if (root->ac_lsigtable[i]->type == CLI_LSIG_NORMAL) { + status = lsig_eval(ctx, root, acdata, target_info, hash, i); + } #ifdef HAVE_YARA - else if (root->ac_lsigtable[i]->type == CLI_YARA_NORMAL || root->ac_lsigtable[i]->type == CLI_YARA_OFFSET) - rc = yara_eval(ctx, root, acdata, target_info, hash, i); + else if (root->ac_lsigtable[i]->type == CLI_YARA_NORMAL || root->ac_lsigtable[i]->type == CLI_YARA_OFFSET) { + status = yara_eval(ctx, root, acdata, target_info, hash, i); + } #endif - if (rc == CL_VIRUS) { - viruses_found = 1; - if (SCAN_ALLMATCHES) - continue; + + if (CL_SUCCESS != status) { + break; + } + + if (cli_checktimelimit(ctx) != CL_SUCCESS) { + cli_dbgmsg("Exceeded scan time limit while evaluating logical and yara signatures (max: %u)\n", ctx->engine->maxscantime); + status = CL_ETIMEOUT; break; } } - if (viruses_found) - return CL_VIRUS; - return CL_CLEAN; + + return status; } -cl_error_t cli_scan_fmap(cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct cli_matched_type **ftoffset, unsigned int acmode, struct cli_ac_result **acres, unsigned char *refhash) +cl_error_t cli_scan_fmap(cli_ctx *ctx, cli_file_t ftype, bool filetype_only, struct cli_matched_type **ftoffset, unsigned int acmode, struct cli_ac_result **acres, unsigned char *refhash) { const unsigned char *buff; cl_error_t ret = CL_CLEAN, type = CL_CLEAN; - int compute_hash[CLI_HASH_AVAIL_TYPES]; - unsigned int i = 0, j = 0, bm_offmode = 0; - uint32_t maxpatlen, bytes, offset = 0; - struct cli_ac_data gdata, tdata; - struct cli_bm_off toff; - struct cli_pcre_off gpoff, tpoff; - unsigned char digest[CLI_HASH_AVAIL_TYPES][32]; - struct cli_matcher *groot = NULL, *troot = NULL; + bool compute_hash[CLI_HASH_AVAIL_TYPES]; + unsigned int i = 0, j = 0; + uint32_t maxpatlen, bytes, offset = 0; + + struct cli_ac_data generic_ac_data; + bool gdata_initialized = false; + + struct cli_ac_data target_ac_data; + bool tdata_initialized = false; + + struct cli_bm_off bm_offsets_table; + bool bm_offsets_table_initialized = false; + + struct cli_pcre_off generic_pcre_offsets_table; + bool generic_pcre_offsets_table_initialized = false; + + struct cli_pcre_off target_pcre_offsets_table; + bool target_pcre_offsets_table_initialized = false; + + unsigned char digest[CLI_HASH_AVAIL_TYPES][CLI_HASHLEN_MAX]; + + struct cli_matcher *generic_ac_root = NULL, *target_ac_root = NULL; + struct cli_target_info info; + bool info_initialized = false; + struct cli_matcher *hdb, *fp; - const char *virname; - uint32_t viruses_found = 0; - void *md5ctx, *sha1ctx, *sha256ctx; + + void *md5ctx = NULL; + void *sha1ctx = NULL; + void *sha256ctx = NULL; if (!ctx->engine) { cli_errmsg("cli_scan_fmap: engine == NULL\n"); - return CL_ENULLARG; + ret = CL_ENULLARG; + goto done; } md5ctx = cl_hash_init("md5"); - if (!(md5ctx)) - return CL_EMEM; + if (!(md5ctx)) { + ret = CL_EMEM; + goto done; + } sha1ctx = cl_hash_init("sha1"); if (!(sha1ctx)) { - cl_hash_destroy(md5ctx); - return CL_EMEM; + ret = CL_EMEM; + goto done; } sha256ctx = cl_hash_init("sha256"); if (!(sha256ctx)) { - cl_hash_destroy(md5ctx); - cl_hash_destroy(sha1ctx); - return CL_EMEM; + ret = CL_EMEM; + goto done; } - if (!ftonly) - groot = ctx->engine->root[0]; /* generic signatures */ + if (!filetype_only) { + generic_ac_root = ctx->engine->root[0]; /* generic signatures */ + } + + if (ftype != CL_TYPE_ANY) { + // Identify the target type, to find the matcher root for that target. - if (ftype) { for (i = 1; i < CLI_MTARGETS; i++) { for (j = 0; j < cli_mtargets[i].target_count; ++j) { if (cli_mtargets[i].target[j] == ftype) { - troot = ctx->engine->root[i]; - break; + // Identified the target type, now get the matcher root for that target. + target_ac_root = ctx->engine->root[i]; + break; // Break out of inner loop } } - if (troot) break; + if (target_ac_root) break; } } - if (ftonly) { - if (!troot) { - cl_hash_destroy(md5ctx); - cl_hash_destroy(sha1ctx); - cl_hash_destroy(sha256ctx); - return CL_CLEAN; + if (!generic_ac_root) { + if (!target_ac_root) { + // Don't have a matcher root for either generic signatures or target-specific signatures. + // Nothing to do! + ret = CL_CLEAN; + goto done; } - maxpatlen = troot->maxpatlen; + // Only have a matcher root for target-specific signatures. + maxpatlen = target_ac_root->maxpatlen; } else { - if (troot) - maxpatlen = MAX(troot->maxpatlen, groot->maxpatlen); - else - maxpatlen = groot->maxpatlen; + if (target_ac_root) { + // Have both generic and target-specific signatures. + maxpatlen = MAX(target_ac_root->maxpatlen, generic_ac_root->maxpatlen); + } else { + // Only have generic signatures. + maxpatlen = generic_ac_root->maxpatlen; + } } cli_targetinfo_init(&info); cli_targetinfo(&info, i, ctx); + info_initialized = true; if (-1 == info.status) { cli_dbgmsg("cli_scan_fmap: Failed to successfully parse the executable header. " @@ -1094,98 +1141,93 @@ cl_error_t cli_scan_fmap(cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct if (1 == info.status && i == 1) { ret = cli_check_auth_header(ctx, &(info.exeinfo)); - - if ((ret == CL_VIRUS || ret == CL_VERIFIED) && !SCAN_ALLMATCHES) { - cli_targetinfo_destroy(&info); - cl_hash_destroy(md5ctx); - cl_hash_destroy(sha1ctx); - cl_hash_destroy(sha256ctx); - return ret; + if (ret == CL_VIRUS || ret == CL_VERIFIED) { + goto done; } ret = CL_CLEAN; } - if (!ftonly) { - if ((ret = cli_ac_initdata(&gdata, groot->ac_partsigs, groot->ac_lsigs, groot->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN)) || - (ret = cli_ac_caloff(groot, &gdata, &info))) { - cli_targetinfo_destroy(&info); - cl_hash_destroy(md5ctx); - cl_hash_destroy(sha1ctx); - cl_hash_destroy(sha256ctx); - return ret; + if (!filetype_only) { + /* If we're not doing a filetype-only scan, so we definitely need to include generic signatures. + So initialize the ac data for the generic signatures root. */ + + ret = cli_ac_initdata(&generic_ac_data, generic_ac_root->ac_partsigs, generic_ac_root->ac_lsigs, generic_ac_root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN); + if (CL_SUCCESS != ret) { + goto done; } - if ((ret = cli_pcre_recaloff(groot, &gpoff, &info, ctx))) { - cli_ac_freedata(&gdata); - cli_targetinfo_destroy(&info); - cl_hash_destroy(md5ctx); - cl_hash_destroy(sha1ctx); - cl_hash_destroy(sha256ctx); - return ret; + gdata_initialized = true; + + /* Recalculate the relative offsets in ac sigs (e.g. those that are based on pe/elf/macho section start/end). */ + ret = cli_ac_caloff(generic_ac_root, &generic_ac_data, &info); + if (CL_SUCCESS != ret) { + goto done; + } + + /* Recalculate the pcre offsets. + This does an allocation, that we will need to free later. */ + ret = cli_pcre_recaloff(generic_ac_root, &generic_pcre_offsets_table, &info, ctx); + if (CL_SUCCESS != ret) { + goto done; } + generic_pcre_offsets_table_initialized = true; } - if (troot) { - if ((ret = cli_ac_initdata(&tdata, troot->ac_partsigs, troot->ac_lsigs, troot->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN)) || - (ret = cli_ac_caloff(troot, &tdata, &info))) { - if (!ftonly) { - cli_ac_freedata(&gdata); - cli_pcre_freeoff(&gpoff); - } - cli_targetinfo_destroy(&info); - cl_hash_destroy(md5ctx); - cl_hash_destroy(sha1ctx); - cl_hash_destroy(sha256ctx); - return ret; + if (target_ac_root) { + /* We have to match against target-specific signatures. + So initialize the ac data for the target-specific signatures root. */ + + ret = cli_ac_initdata(&target_ac_data, target_ac_root->ac_partsigs, target_ac_root->ac_lsigs, target_ac_root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN); + if (CL_SUCCESS != ret) { + goto done; } - if (troot->bm_offmode) { - if (ctx->fmap->len >= CLI_DEFAULT_BM_OFFMODE_FSIZE) { - if ((ret = cli_bm_initoff(troot, &toff, &info))) { - if (!ftonly) { - cli_ac_freedata(&gdata); - cli_pcre_freeoff(&gpoff); - } + tdata_initialized = true; - cli_ac_freedata(&tdata); - cli_targetinfo_destroy(&info); - cl_hash_destroy(md5ctx); - cl_hash_destroy(sha1ctx); - cl_hash_destroy(sha256ctx); - return ret; - } + /* Recalculate the relative offsets in ac sigs (e.g. those that are based on pe/elf/macho section start/end). */ + ret = cli_ac_caloff(target_ac_root, &target_ac_data, &info); + if (CL_SUCCESS != ret) { + goto done; + } - bm_offmode = 1; + if (target_ac_root->bm_offmode) { + if (ctx->fmap->len >= CLI_DEFAULT_BM_OFFMODE_FSIZE) { + /* Recalculate the relative offsets in boyer-moore signatures (e.g. those that are based on pe/elf/macho section start/end). */ + ret = cli_bm_initoff(target_ac_root, &bm_offsets_table, &info); + if (CL_SUCCESS != ret) { + goto done; + } + bm_offsets_table_initialized = true; } } - if ((ret = cli_pcre_recaloff(troot, &tpoff, &info, ctx))) { - if (!ftonly) { - cli_ac_freedata(&gdata); - cli_pcre_freeoff(&gpoff); - } - cli_ac_freedata(&tdata); - if (bm_offmode) - cli_bm_freeoff(&toff); - cli_targetinfo_destroy(&info); - cl_hash_destroy(md5ctx); - cl_hash_destroy(sha1ctx); - cl_hash_destroy(sha256ctx); - return ret; + /* Recalculate the pcre offsets. + This does an allocation, that we will need to free later. */ + ret = cli_pcre_recaloff(target_ac_root, &target_pcre_offsets_table, &info, ctx); + if (CL_SUCCESS != ret) { + goto done; } + target_pcre_offsets_table_initialized = true; } hdb = ctx->engine->hm_hdb; fp = ctx->engine->hm_fp; - if (!ftonly && hdb) { + if (!filetype_only && hdb) { + /* We're not just doing file typing, we're checking for viruses. + So we need to compute the hash sigs, if there are any. + + Computing the hash in chunks the same size and time that we do for + matching with the AC & BM pattern matchers is an optimization so we + we can do both processes while the cache is still hot. */ + if (!refhash) { if (cli_hm_have_size(hdb, CLI_HASH_MD5, ctx->fmap->len) || cli_hm_have_size(fp, CLI_HASH_MD5, ctx->fmap->len) || cli_hm_have_wild(hdb, CLI_HASH_MD5) || cli_hm_have_wild(fp, CLI_HASH_MD5)) { - compute_hash[CLI_HASH_MD5] = 1; + compute_hash[CLI_HASH_MD5] = true; } else { - compute_hash[CLI_HASH_MD5] = 0; + compute_hash[CLI_HASH_MD5] = false; } } else { compute_hash[CLI_HASH_MD5] = 0; @@ -1196,78 +1238,55 @@ cl_error_t cli_scan_fmap(cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct cli_hm_have_wild(hdb, CLI_HASH_SHA1) || cli_hm_have_size(fp, CLI_HASH_SHA1, ctx->fmap->len) || cli_hm_have_wild(fp, CLI_HASH_SHA1)) { - compute_hash[CLI_HASH_SHA1] = 1; + compute_hash[CLI_HASH_SHA1] = true; } else { - compute_hash[CLI_HASH_SHA1] = 0; + compute_hash[CLI_HASH_SHA1] = false; } if (cli_hm_have_size(hdb, CLI_HASH_SHA256, ctx->fmap->len) || cli_hm_have_wild(hdb, CLI_HASH_SHA256) || cli_hm_have_size(fp, CLI_HASH_SHA256, ctx->fmap->len) || cli_hm_have_wild(fp, CLI_HASH_SHA256)) { - compute_hash[CLI_HASH_SHA256] = 1; + compute_hash[CLI_HASH_SHA256] = true; } else { - compute_hash[CLI_HASH_SHA256] = 0; + compute_hash[CLI_HASH_SHA256] = false; } } while (offset < ctx->fmap->len) { + if (cli_checktimelimit(ctx) != CL_SUCCESS) { + cli_dbgmsg("Exceeded scan time limit while scanning fmap (max: %u)\n", ctx->engine->maxscantime); + ret = CL_ETIMEOUT; + goto done; + } + bytes = MIN(ctx->fmap->len - offset, SCANBUFF); if (!(buff = fmap_need_off_once(ctx->fmap, offset, bytes))) break; if (ctx->scanned) *ctx->scanned += bytes / CL_COUNT_PRECISION; - if (troot) { - virname = NULL; - ret = matcher_run(troot, buff, bytes, &virname, &tdata, offset, &info, ftype, ftoffset, acmode, PCRE_SCAN_FMAP, acres, ctx->fmap, bm_offmode ? &toff : NULL, &tpoff, ctx); + if (target_ac_root) { + const char *virname = NULL; - if (virname) { - /* virname already appended by matcher_run */ - viruses_found = 1; - } - if ((ret == CL_VIRUS && !SCAN_ALLMATCHES) || ret == CL_EMEM) { - if (!ftonly) { - cli_ac_freedata(&gdata); - cli_pcre_freeoff(&gpoff); - } - - cli_ac_freedata(&tdata); - if (bm_offmode) - cli_bm_freeoff(&toff); - cli_pcre_freeoff(&tpoff); - - cli_targetinfo_destroy(&info); - cl_hash_destroy(md5ctx); - cl_hash_destroy(sha1ctx); - cl_hash_destroy(sha256ctx); - return ret; + ret = matcher_run(target_ac_root, buff, bytes, &virname, &target_ac_data, offset, + &info, ftype, ftoffset, acmode, PCRE_SCAN_FMAP, acres, ctx->fmap, + bm_offsets_table_initialized ? &bm_offsets_table : NULL, + &target_pcre_offsets_table, ctx); + if (ret == CL_VIRUS || ret == CL_EMEM) { + goto done; } } - if (!ftonly) { - virname = NULL; - ret = matcher_run(groot, buff, bytes, &virname, &gdata, offset, &info, ftype, ftoffset, acmode, PCRE_SCAN_FMAP, acres, ctx->fmap, NULL, &gpoff, ctx); + if (!filetype_only) { + const char *virname = NULL; - if (virname) { - /* virname already appended by matcher_run */ - viruses_found = 1; - } - if ((ret == CL_VIRUS && !SCAN_ALLMATCHES) || ret == CL_EMEM) { - cli_ac_freedata(&gdata); - cli_pcre_freeoff(&gpoff); - if (troot) { - cli_ac_freedata(&tdata); - if (bm_offmode) - cli_bm_freeoff(&toff); - cli_pcre_freeoff(&tpoff); - } - - cli_targetinfo_destroy(&info); - cl_hash_destroy(md5ctx); - cl_hash_destroy(sha1ctx); - cl_hash_destroy(sha256ctx); - return ret; + ret = matcher_run(generic_ac_root, buff, bytes, &virname, &generic_ac_data, offset, + &info, ftype, ftoffset, acmode, PCRE_SCAN_FMAP, acres, ctx->fmap, + NULL, + &generic_pcre_offsets_table, ctx); + if (ret == CL_VIRUS || ret == CL_EMEM) { + goto done; } else if ((acmode & AC_SCAN_FT) && ((cli_file_t)ret >= CL_TYPENO)) { if (ret > type) type = ret; @@ -1294,105 +1313,120 @@ cl_error_t cli_scan_fmap(cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct offset += bytes - maxpatlen; } - if (!ftonly && hdb) { - enum CLI_HASH_TYPE hashtype, hashtype2; + if (!filetype_only && hdb) { + /* We're not just doing file typing, we're scanning for malware. + So we need to check the hash sigs, if there are any. */ + + cli_hash_type_t hashtype; if (compute_hash[CLI_HASH_MD5]) { cl_finish_hash(md5ctx, digest[CLI_HASH_MD5]); md5ctx = NULL; + + // Save the MD5 hash for later use (e.g. in FP checks). + fmap_set_hash(ctx->fmap, digest[CLI_HASH_MD5], CLI_HASH_MD5); } - if (refhash) + if (refhash) { + // Set "compute_hash" to 1 because we'll use this later to know if we have a hash to check. compute_hash[CLI_HASH_MD5] = 1; + } + if (compute_hash[CLI_HASH_SHA1]) { cl_finish_hash(sha1ctx, digest[CLI_HASH_SHA1]); sha1ctx = NULL; + + // Save the SHA1 hash for later use (e.g. in FP checks). + fmap_set_hash(ctx->fmap, digest[CLI_HASH_SHA1], CLI_HASH_SHA1); } if (compute_hash[CLI_HASH_SHA256]) { cl_finish_hash(sha256ctx, digest[CLI_HASH_SHA256]); sha256ctx = NULL; + + // Save the SHA256 hash for later use (e.g. in FP checks). + fmap_set_hash(ctx->fmap, digest[CLI_HASH_SHA256], CLI_HASH_SHA256); } - virname = NULL; for (hashtype = CLI_HASH_MD5; hashtype < CLI_HASH_AVAIL_TYPES; hashtype++) { + const char *virname = NULL; const char *virname_w = NULL; - int found = 0; /* If no hash, skip to next type */ - if (!compute_hash[hashtype]) + if (!compute_hash[hashtype]) { continue; - - /* Do hash scan */ - if ((ret = cli_hm_scan(digest[hashtype], ctx->fmap->len, &virname, hdb, hashtype)) == CL_VIRUS) { - found += 1; - } - if (!found || SCAN_ALLMATCHES) { - if ((ret = cli_hm_scan_wild(digest[hashtype], &virname_w, hdb, hashtype)) == CL_VIRUS) - found += 2; } - /* If found, do immediate hash-only FP check */ - if (found && fp) { - for (hashtype2 = CLI_HASH_MD5; hashtype2 < CLI_HASH_AVAIL_TYPES; hashtype2++) { - if (!compute_hash[hashtype2]) - continue; - if (cli_hm_scan(digest[hashtype2], ctx->fmap->len, NULL, fp, hashtype2) == CL_VIRUS) { - found = 0; - ret = CL_CLEAN; - break; - } else if (cli_hm_scan_wild(digest[hashtype2], NULL, fp, hashtype2) == CL_VIRUS) { - found = 0; - ret = CL_CLEAN; - break; - } + /* Do hash scan checking hash sigs with specific size */ + ret = cli_hm_scan(digest[hashtype], ctx->fmap->len, &virname, hdb, hashtype); + if (ret == CL_VIRUS) { + /* Matched with size-based hash ... */ + ret = cli_append_virus(ctx, virname); + if (ret != CL_SUCCESS) { + goto done; } } - /* If matched size-based hash ... */ - if (found % 2) { - viruses_found = 1; - ret = cli_append_virus(ctx, virname); - if (ret != CL_CLEAN && !SCAN_ALLMATCHES) - break; - virname = NULL; - } - /* If matched size-agnostic hash ... */ - if (found > 1) { - viruses_found = 1; - ret = cli_append_virus(ctx, virname_w); - if (ret != CL_CLEAN && !SCAN_ALLMATCHES) - break; + /* Do hash scan checking hash sigs with wildcard size */ + ret = cli_hm_scan_wild(digest[hashtype], &virname_w, hdb, hashtype); + if (ret == CL_VIRUS) { + /* Matched with size-agnostic hash ... */ + ret = cli_append_virus(ctx, virname_w); + if (ret != CL_SUCCESS) { + goto done; + } } } } - cl_hash_destroy(md5ctx); - cl_hash_destroy(sha1ctx); - cl_hash_destroy(sha256ctx); + /* + * Evaluate the logical expressions for clamav logical signatures and YARA rules. + */ + // Evalute for the target-specific signature AC matches. + if (NULL != target_ac_root) { + if (ret != CL_VIRUS) { + ret = cli_exp_eval(ctx, target_ac_root, &target_ac_data, &info, (const char *)refhash); + } + } + + // Evalute for the generic signature AC matches. + if (NULL != generic_ac_root) { + if (ret != CL_VIRUS) { + ret = cli_exp_eval(ctx, generic_ac_root, &generic_ac_data, &info, (const char *)refhash); + } + } - if (troot) { - if (ret != CL_VIRUS || SCAN_ALLMATCHES) - ret = cli_exp_eval(ctx, troot, &tdata, &info, (const char *)refhash); - if (ret == CL_VIRUS) - viruses_found++; +done: + if (NULL != md5ctx) { + cl_hash_destroy(md5ctx); + } + if (NULL != sha1ctx) { + cl_hash_destroy(sha1ctx); + } + if (NULL != sha256ctx) { + cl_hash_destroy(sha256ctx); + } - cli_ac_freedata(&tdata); - if (bm_offmode) - cli_bm_freeoff(&toff); - cli_pcre_freeoff(&tpoff); + if (gdata_initialized) { + cli_ac_freedata(&generic_ac_data); + } + if (tdata_initialized) { + cli_ac_freedata(&target_ac_data); } - if (groot) { - if (ret != CL_VIRUS || SCAN_ALLMATCHES) - ret = cli_exp_eval(ctx, groot, &gdata, &info, (const char *)refhash); - cli_ac_freedata(&gdata); - cli_pcre_freeoff(&gpoff); + if (generic_pcre_offsets_table_initialized) { + cli_pcre_freeoff(&generic_pcre_offsets_table); + } + if (target_pcre_offsets_table_initialized) { + cli_pcre_freeoff(&target_pcre_offsets_table); } - cli_targetinfo_destroy(&info); + if (info_initialized) { + cli_targetinfo_destroy(&info); + } - if (SCAN_ALLMATCHES && viruses_found) { - return CL_VIRUS; + if (bm_offsets_table_initialized) { + cli_bm_freeoff(&bm_offsets_table); } + if (ret == CL_VIRUS) { return CL_VIRUS; } @@ -1412,25 +1446,26 @@ cl_error_t cli_scan_fmap(cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct cl_error_t cli_matchmeta(cli_ctx *ctx, const char *fname, size_t fsizec, size_t fsizer, int encrypted, unsigned int filepos, int res1, void *res2) { const struct cli_cdb *cdb; - unsigned int viruses_found = 0; - cl_error_t ret = CL_CLEAN; + cl_error_t ret = CL_SUCCESS; cli_dbgmsg("CDBNAME:%s:%llu:%s:%llu:%llu:%d:%u:%u:%p\n", cli_ftname(cli_recursion_stack_get_type(ctx, -1)), (long long unsigned)fsizec, fname, (long long unsigned)fsizec, (long long unsigned)fsizer, encrypted, filepos, res1, res2); - if (ctx->engine && ctx->engine->cb_meta) + if (ctx->engine && ctx->engine->cb_meta) { if (ctx->engine->cb_meta(cli_ftname(cli_recursion_stack_get_type(ctx, -1)), fsizec, fname, fsizer, encrypted, filepos, ctx->cb_ctx) == CL_VIRUS) { cli_dbgmsg("inner file blocked by callback: %s\n", fname); ret = cli_append_virus(ctx, "Detected.By.Callback"); - viruses_found++; - if (!SCAN_ALLMATCHES || ret != CL_CLEAN) + if (ret != CL_SUCCESS) { return ret; + } } + } - if (!ctx->engine || !(cdb = ctx->engine->cdb)) + if (NULL == ctx->engine || (NULL == (cdb = ctx->engine->cdb))) { return CL_CLEAN; + } do { if (cdb->ctype != CL_TYPE_ANY && cdb->ctype != cli_recursion_stack_get_type(ctx, -1)) @@ -1451,13 +1486,11 @@ cl_error_t cli_matchmeta(cli_ctx *ctx, const char *fname, size_t fsizec, size_t continue; ret = cli_append_virus(ctx, cdb->virname); - viruses_found++; - if (!SCAN_ALLMATCHES || ret != CL_CLEAN) + if (ret != CL_SUCCESS) { return ret; + } } while ((cdb = cdb->next)); - if (SCAN_ALLMATCHES && viruses_found) - return CL_VIRUS; - return CL_CLEAN; + return ret; } diff --git a/libclamav/matcher.h b/libclamav/matcher.h index a04373f99a..e01fb8f8b9 100644 --- a/libclamav/matcher.h +++ b/libclamav/matcher.h @@ -203,11 +203,29 @@ struct cli_cdb { struct cli_cdb *next; }; +typedef enum { + TARGET_GENERIC = 0, + TARGET_PE = 1, + TARGET_OLE2 = 2, + TARGET_HTML = 3, + TARGET_MAIL = 4, + TARGET_GRAPHICS = 5, + TARGET_ELF = 6, + TARGET_ASCII = 7, + TARGET_NOT_USED = 8, + TARGET_MACHO = 9, + TARGET_PDF = 10, + TARGET_FLASH = 11, + TARGET_JAVA = 12, + TARGET_INTERNAL = 13, + TARGET_OTHER = 14, +} cli_target_t; + #define CLI_MAX_TARGETS 10 /* maximum filetypes for a specific target */ struct cli_mtarget { cli_file_t target[CLI_MAX_TARGETS]; const char *name; - uint8_t idx; /* idx of matcher */ + cli_target_t idx; /* idx of matcher */ uint8_t ac_only; uint8_t enable_prefiltering; uint8_t target_count; /* must be synced with non-zero values in the target array */ @@ -216,21 +234,21 @@ struct cli_mtarget { #define CLI_MTARGETS 15 static const struct cli_mtarget cli_mtargets[CLI_MTARGETS] = { /* All types for target, name, idx, ac_only, pre-filtering?, # of types */ - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "GENERIC", 0, 0, 1, 1}, - {{CL_TYPE_MSEXE, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "PE", 1, 0, 1, 1}, - {{CL_TYPE_MSOLE2, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "OLE2", 2, 1, 0, 1}, - {{CL_TYPE_HTML, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "HTML", 3, 1, 0, 1}, - {{CL_TYPE_MAIL, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "MAIL", 4, 1, 1, 1}, - {{CL_TYPE_GRAPHICS, CL_TYPE_GIF, CL_TYPE_PNG, CL_TYPE_JPEG, CL_TYPE_TIFF, 0, 0, 0, 0, 0}, "GRAPHICS", 5, 1, 0, 5}, - {{CL_TYPE_ELF, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "ELF", 6, 1, 0, 1}, - {{CL_TYPE_TEXT_ASCII, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "ASCII", 7, 1, 1, 1}, - {{CL_TYPE_ERROR, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "NOT USED", 8, 1, 0, 1}, - {{CL_TYPE_MACHO, CL_TYPE_MACHO_UNIBIN, 0, 0, 0, 0, 0, 0, 0, 0}, "MACH-O", 9, 1, 0, 2}, - {{CL_TYPE_PDF, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "PDF", 10, 1, 0, 1}, - {{CL_TYPE_SWF, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "FLASH", 11, 1, 0, 1}, - {{CL_TYPE_JAVA, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "JAVA", 12, 1, 0, 1}, - {{CL_TYPE_INTERNAL, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "INTERNAL", 13, 1, 0, 1}, - {{CL_TYPE_OTHER, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "OTHER", 14, 1, 0, 1}}; + {{CL_TYPE_ANY, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "GENERIC", TARGET_GENERIC, 0, 1, 1}, + {{CL_TYPE_MSEXE, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "PE", TARGET_PE, 0, 1, 1}, + {{CL_TYPE_MSOLE2, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "OLE2", TARGET_OLE2, 1, 0, 1}, + {{CL_TYPE_HTML, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "HTML", TARGET_HTML, 1, 0, 1}, + {{CL_TYPE_MAIL, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "MAIL", TARGET_MAIL, 1, 1, 1}, + {{CL_TYPE_GRAPHICS, CL_TYPE_GIF, CL_TYPE_PNG, CL_TYPE_JPEG, CL_TYPE_TIFF, 0, 0, 0, 0, 0}, "GRAPHICS", TARGET_GRAPHICS, 1, 0, 5}, + {{CL_TYPE_ELF, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "ELF", TARGET_ELF, 1, 0, 1}, + {{CL_TYPE_TEXT_ASCII, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "ASCII", TARGET_ASCII, 1, 1, 1}, + {{CL_TYPE_ERROR, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "NOT USED", TARGET_NOT_USED, 1, 0, 1}, + {{CL_TYPE_MACHO, CL_TYPE_MACHO_UNIBIN, 0, 0, 0, 0, 0, 0, 0, 0}, "MACH-O", TARGET_MACHO, 1, 0, 2}, + {{CL_TYPE_PDF, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "PDF", TARGET_PDF, 1, 0, 1}, + {{CL_TYPE_SWF, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "FLASH", TARGET_FLASH, 1, 0, 1}, + {{CL_TYPE_JAVA, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "JAVA", TARGET_JAVA, 1, 0, 1}, + {{CL_TYPE_INTERNAL, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "INTERNAL", TARGET_INTERNAL, 1, 0, 1}, + {{CL_TYPE_OTHER, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "OTHER", TARGET_OTHER, 1, 0, 1}}; // clang-format off @@ -251,18 +269,32 @@ static const struct cli_mtarget cli_mtargets[CLI_MTARGETS] = { /** * @brief Non-magic scan matching using a file buffer for input. Older API * - * This function is lower-level, requiring a call to `cli_exp_eval()` after the - * match to evaluate logical signatures and yara rules. - * + * This function is lower-level than the *magic_scan* functions from scanners. * This function does not perform file type magic identification and does not use * the file format scanners. * + * Unlike the similar functions `cli_scan_desc()` and `cli_scan_fmap()` (below), + * this function: + * + * - REQUIRES a call to `cli_exp_eval()` after the match to evaluate logical + * signatures and yara rules. + * + * - Does NOT support filetype detection. + * + * - Does NOT perform hash-based matching. + * + * - Does NOT support AC, BM, or PCRE relative-offset signature matching. + * + * - DOES support passing in externally initialized AC matcher data + * * @param buffer The buffer to be matched. * @param length The length of the buffer or amount of bytets to match. * @param offset Offset into the buffer from which to start matching. * @param ctx The scanning context. * @param ftype If specified, may limit signature matching trie by target type corresponding with the specified CL_TYPE - * @param[in,out] acdata A list of pattern maching data structs to contain match results, one for each pattern matching trie. + * @param[in,out] acdata (optional) A list of pattern maching data structs to contain match results, one for generic signatures and one for target-specific signatures. + * If not provided, the matcher results are lost, outside of this function's return value. + * Required if you want to evaluate logical expressions afterwards. * @return cl_error_t */ cl_error_t cli_scan_buff(const unsigned char *buffer, uint32_t length, uint32_t offset, cli_ctx *ctx, cli_file_t ftype, struct cli_ac_data **acdata); @@ -270,40 +302,53 @@ cl_error_t cli_scan_buff(const unsigned char *buffer, uint32_t length, uint32_t /** * @brief Non-magic scan matching using a file descriptor for input. * + * This function is lower-level than the *magic_scan* functions from scanners. * This function does not perform file type magic identification and does not use * the file format scanners. * - * This function uses the newer cli_scan_fmap() scanning API. + * This function does signature matching for generic signatures, target-specific + * signatures, and file type recognition signatures to detect embedded files or + * to correct the current file type. + * + * This function is just a wrapper for `cli_scan_fmap()` that converts the file + * to an fmap and scans it. * * @param desc File descriptor to be used for input * @param ctx The scanning context. * @param ftype If specified, may limit signature matching trie by target type corresponding with the specified CL_TYPE - * @param ftonly Boolean indicating if the scan is for file-type detection only. - * @param[out] ftoffset A list of file type signature matches with their corresponding offsets. + * @param filetype_only Boolean indicating if the scan is for file-type detection only. + * @param[out] ftoffset (optional) A list of file type signature matches with their corresponding offsets. If provided, will output the file type signature matches. * @param acmode Use AC_SCAN_VIR and AC_SCAN_FT to set scanning modes. * @param[out] acres A list of cli_ac_result AC pattern matching results. * @param name (optional) Original name of the file (to set fmap name metadata) * @param attributes Layer attributes for the thing to be scanned. * @return cl_error_t */ -cl_error_t cli_scan_desc(int desc, cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct cli_matched_type **ftoffset, - unsigned int acmode, struct cli_ac_result **acres, const char *name, uint32_t attributes); +cl_error_t cli_scan_desc(int desc, cli_ctx *ctx, cli_file_t ftype, bool filetype_only, struct cli_matched_type **ftoffset, unsigned int acmode, struct cli_ac_result **acres, const char *name, uint32_t attributes); /** * @brief Non-magic scan matching of the current fmap in the scan context. Newer API. * + * This function is lower-level than the *magic_scan* functions from scanners. + * This function does not perform file type magic identification and does not use + * the file format scanners. + * + * This function does signature matching for generic signatures, target-specific + * signatures, and file type recognition signatures to detect embedded files or + * to correct the current file type. + * * This API will invoke cli_exp_eval() for you. * * @param ctx The scanning context. * @param ftype If specified, may limit signature matching trie by target type corresponding with the specified CL_TYPE - * @param ftonly Boolean indicating if the scan is for file-type detection only. - * @param[out] ftoffset A list of file type signature matches with their corresponding offsets. + * @param filetype_only Boolean indicating if the scan is for file-type detection only. + * @param[out] ftoffset (optional) A list of file type signature matches with their corresponding offsets. If provided, will output the file type signature matches. * @param acmode Use AC_SCAN_VIR and AC_SCAN_FT to set scanning modes. * @param[out] acres A list of cli_ac_result AC pattern matching results. * @param refhash MD5 hash of the current file, used to save time creating hashes and to limit scan recursion for the HandlerType logical signature FTM feature. * @return cl_error_t */ -cl_error_t cli_scan_fmap(cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct cli_matched_type **ftoffset, unsigned int acmode, struct cli_ac_result **acres, unsigned char *refhash); +cl_error_t cli_scan_fmap(cli_ctx *ctx, cli_file_t ftype, bool filetype_only, struct cli_matched_type **ftoffset, unsigned int acmode, struct cli_ac_result **acres, unsigned char *refhash); /** * @brief Evaluate logical signatures and yara rules given the AC matching results diff --git a/libclamav/mbox.c b/libclamav/mbox.c index 2880271e79..927d9d55e0 100644 --- a/libclamav/mbox.c +++ b/libclamav/mbox.c @@ -568,15 +568,15 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) break; case MAXREC: retcode = CL_EMAXREC; - cli_append_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxRecursion"); // Doing this now because it's actually tracking email recursion,- - // not fmap recursion, but it still is aborting with stuff not scanned. - // Also, we didn't have access to the ctx when this happened earlier. + cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxRecursion"); // Doing this now because it's actually tracking email recursion,- + // not fmap recursion, but it still is aborting with stuff not scanned. + // Also, we didn't have access to the ctx when this happened earlier. break; case MAXFILES: retcode = CL_EMAXFILES; - cli_append_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); // Doing this now because it's actually tracking email parts,- - // not actual files, but it still is aborting with stuff not scanned. - // Also, we didn't have access to the ctx when this happened earlier. + cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); // Doing this now because it's actually tracking email parts,- + // not actual files, but it still is aborting with stuff not scanned. + // Also, we didn't have access to the ctx when this happened earlier. break; case VIRUS: retcode = CL_VIRUS; @@ -593,13 +593,6 @@ cli_parse_mbox(const char *dir, cli_ctx *ctx) messageDestroy(body); } - if ((retcode == CL_CLEAN) && ctx->found_possibly_unwanted && - (*ctx->virname == NULL || SCAN_ALLMATCHES)) { - retcode = cli_append_virus(ctx, "Heuristics.Phishing.Email"); - - ctx->found_possibly_unwanted = 0; - } - cli_dbgmsg("cli_mbox returning %d\n", retcode); return retcode; @@ -746,7 +739,7 @@ hitLineFoldCnt(const char *const line, size_t *lineFoldCnt, cli_ctx *ctx, bool * if ((*lineFoldCnt) >= HEURISTIC_EMAIL_MAX_LINE_FOLDS_PER_HEADER) { if (SCAN_HEURISTIC_EXCEEDS_MAX) { - cli_append_virus(ctx, "Heuristics.Limits.Exceeded.EmailLineFoldCnt"); + cli_append_potentially_unwanted(ctx, "Heuristics.Limits.Exceeded.EmailLineFoldCnt"); *heuristicFound = true; } @@ -762,7 +755,7 @@ haveTooManyHeaderBytes(size_t totalLen, cli_ctx *ctx, bool *heuristicFound) if (totalLen > HEURISTIC_EMAIL_MAX_HEADER_BYTES) { if (SCAN_HEURISTIC_EXCEEDS_MAX) { - cli_append_virus(ctx, "Heuristics.Limits.Exceeded.EmailHeaderBytes"); + cli_append_potentially_unwanted(ctx, "Heuristics.Limits.Exceeded.EmailHeaderBytes"); *heuristicFound = true; } @@ -777,7 +770,7 @@ haveTooManyEmailHeaders(size_t totalHeaderCnt, cli_ctx *ctx, bool *heuristicFoun if (totalHeaderCnt > HEURISTIC_EMAIL_MAX_HEADERS) { if (SCAN_HEURISTIC_EXCEEDS_MAX) { - cli_append_virus(ctx, "Heuristics.Limits.Exceeded.EmailHeaders"); + cli_append_potentially_unwanted(ctx, "Heuristics.Limits.Exceeded.EmailHeaders"); *heuristicFound = true; } @@ -792,7 +785,7 @@ haveTooManyMIMEPartsPerMessage(size_t mimePartCnt, cli_ctx *ctx, mbox_status *rc if (mimePartCnt >= HEURISTIC_EMAIL_MAX_MIME_PARTS_PER_MESSAGE) { if (SCAN_HEURISTIC_EXCEEDS_MAX) { - cli_append_virus(ctx, "Heuristics.Limits.Exceeded.EmailMIMEPartsPerMessage"); + cli_append_potentially_unwanted(ctx, "Heuristics.Limits.Exceeded.EmailMIMEPartsPerMessage"); *rc = VIRUS; } @@ -807,7 +800,7 @@ haveTooManyMIMEArguments(size_t argCnt, cli_ctx *ctx, bool *heuristicFound) if (argCnt >= HEURISTIC_EMAIL_MAX_ARGUMENTS_PER_HEADER) { if (SCAN_HEURISTIC_EXCEEDS_MAX) { - cli_append_virus(ctx, "Heuristics.Limits.Exceeded.EmailMIMEArguments"); + cli_append_potentially_unwanted(ctx, "Heuristics.Limits.Exceeded.EmailMIMEArguments"); *heuristicFound = true; } diff --git a/libclamav/mbr.c b/libclamav/mbr.c index 97395499a3..13550eb96c 100644 --- a/libclamav/mbr.c +++ b/libclamav/mbr.c @@ -41,10 +41,6 @@ //#define DEBUG_MBR_PARSE //#define DEBUG_EBR_PARSE -#ifndef PRTN_INTXN_DETECTION -#define PRTN_INTXN_DETECTION "heuristic.mbrprtnintersect" -#endif - #ifdef DEBUG_MBR_PARSE #define mbr_parsemsg(...) cli_dbgmsg(__VA_ARGS__) #else @@ -64,14 +60,14 @@ enum MBR_STATE { SEEN_EMPTY }; -static int mbr_scanextprtn(cli_ctx *ctx, unsigned *prtncount, size_t extlba, - size_t extlbasize, size_t sectorsize); -static int mbr_check_mbr(struct mbr_boot_record *record, size_t maplen, size_t sectorsize); -static int mbr_check_ebr(struct mbr_boot_record *record); -static int mbr_primary_partition_intersection(cli_ctx *ctx, struct mbr_boot_record mbr, size_t sectorsize); -static int mbr_extended_partition_intersection(cli_ctx *ctx, unsigned *prtncount, size_t extlba, size_t sectorsize); +static cl_error_t mbr_scanextprtn(cli_ctx *ctx, unsigned *prtncount, size_t extlba, + size_t extlbasize, size_t sectorsize); +static cl_error_t mbr_check_mbr(struct mbr_boot_record *record, size_t maplen, size_t sectorsize); +static cl_error_t mbr_check_ebr(struct mbr_boot_record *record); +static cl_error_t mbr_primary_partition_intersection(cli_ctx *ctx, struct mbr_boot_record mbr, size_t sectorsize); +static cl_error_t mbr_extended_partition_intersection(cli_ctx *ctx, unsigned *prtncount, size_t extlba, size_t sectorsize); -int cli_mbr_check(const unsigned char *buff, size_t len, size_t maplen) +cl_error_t cli_mbr_check(const unsigned char *buff, size_t len, size_t maplen) { struct mbr_boot_record mbr; size_t mbr_base = 0; @@ -91,7 +87,7 @@ int cli_mbr_check(const unsigned char *buff, size_t len, size_t maplen) return mbr_check_mbr(&mbr, maplen, sectorsize); } -int cli_mbr_check2(cli_ctx *ctx, size_t sectorsize) +cl_error_t cli_mbr_check2(cli_ctx *ctx, size_t sectorsize) { struct mbr_boot_record mbr; size_t pos = 0, mbr_base = 0; @@ -135,11 +131,11 @@ int cli_mbr_check2(cli_ctx *ctx, size_t sectorsize) } /* sets sectorsize to default value if specified to be 0 */ -int cli_scanmbr(cli_ctx *ctx, size_t sectorsize) +cl_error_t cli_scanmbr(cli_ctx *ctx, size_t sectorsize) { + cl_error_t status = CL_SUCCESS; struct mbr_boot_record mbr; enum MBR_STATE state = SEEN_NOTHING; - int ret = CL_CLEAN, detection = CL_CLEAN; size_t pos = 0, mbr_base = 0, partoff = 0; unsigned i = 0, prtncount = 0; size_t maplen, partsize; @@ -148,7 +144,8 @@ int cli_scanmbr(cli_ctx *ctx, size_t sectorsize) if (!ctx || !ctx->fmap) { cli_errmsg("cli_scanmbr: Invalid context\n"); - return CL_ENULLARG; + status = CL_ENULLARG; + goto done; } /* sector size calculation, actual value is OS dependent */ @@ -162,7 +159,8 @@ int cli_scanmbr(cli_ctx *ctx, size_t sectorsize) if ((maplen % sectorsize) != 0) { cli_dbgmsg("cli_scanmbr: File sized %lu is not a multiple of sector size %lu\n", (unsigned long)maplen, (unsigned long)sectorsize); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* sector 0 (first sector) is the master boot record */ @@ -171,36 +169,33 @@ int cli_scanmbr(cli_ctx *ctx, size_t sectorsize) /* read the master boot record */ if (fmap_readn(ctx->fmap, &mbr, pos, sizeof(mbr)) != sizeof(mbr)) { cli_dbgmsg("cli_scanmbr: Invalid master boot record\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* convert the little endian to host, include the internal */ mbr_convert_to_host(&mbr); /* MBR checks */ - ret = mbr_check_mbr(&mbr, maplen, sectorsize); - if (ret != CL_CLEAN) { - return ret; + status = mbr_check_mbr(&mbr, maplen, sectorsize); + if (status != CL_SUCCESS) { + status = status; + goto done; } /* MBR is valid, examine bootstrap code */ - ret = cli_magic_scan_nested_fmap_type(ctx->fmap, 0, sectorsize, ctx, - CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = cli_magic_scan_nested_fmap_type(ctx->fmap, 0, sectorsize, ctx, CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); + if (status != CL_SUCCESS) { + status = status; + goto done; } /* check that the partition table has no intersections - HEURISTICS */ if (SCAN_HEURISTIC_PARTITION_INTXN && (ctx->dconf->other & OTHER_CONF_PRTNINTXN)) { - ret = mbr_primary_partition_intersection(ctx, mbr, sectorsize); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = mbr_primary_partition_intersection(ctx, mbr, sectorsize); + if (status != CL_SUCCESS) { + status = status; + goto done; } } @@ -227,13 +222,11 @@ int cli_scanmbr(cli_ctx *ctx, size_t sectorsize) } state = SEEN_EXTENDED; /* used only to detect multiple extended partitions */ - ret = mbr_scanextprtn(ctx, &prtncount, mbr.entries[i].firstLBA, - mbr.entries[i].numLBA, sectorsize); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = mbr_scanextprtn(ctx, &prtncount, mbr.entries[i].firstLBA, + mbr.entries[i].numLBA, sectorsize); + if (status != CL_SUCCESS) { + status = status; + goto done; } } else { prtncount++; @@ -241,13 +234,10 @@ int cli_scanmbr(cli_ctx *ctx, size_t sectorsize) partoff = mbr.entries[i].firstLBA * sectorsize; partsize = mbr.entries[i].numLBA * sectorsize; mbr_parsemsg("cli_magic_scan_nested_fmap_type: [%u, +%u)\n", partoff, partsize); - ret = cli_magic_scan_nested_fmap_type(ctx->fmap, partoff, partsize, ctx, - CL_TYPE_PART_ANY, NULL, LAYER_ATTRIBUTES_NONE); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = cli_magic_scan_nested_fmap_type(ctx->fmap, partoff, partsize, ctx, CL_TYPE_PART_ANY, NULL, LAYER_ATTRIBUTES_NONE); + if (status != CL_SUCCESS) { + status = status; + goto done; } } } @@ -256,14 +246,16 @@ int cli_scanmbr(cli_ctx *ctx, size_t sectorsize) cli_dbgmsg("cli_scanmbr: maximum partitions reached\n"); } - return detection; +done: + + return status; } -static int mbr_scanextprtn(cli_ctx *ctx, unsigned *prtncount, size_t extlba, size_t extlbasize, size_t sectorsize) +static cl_error_t mbr_scanextprtn(cli_ctx *ctx, unsigned *prtncount, size_t extlba, size_t extlbasize, size_t sectorsize) { + cl_error_t status = CL_CLEAN; struct mbr_boot_record ebr; enum MBR_STATE state = SEEN_NOTHING; - int ret = CL_CLEAN, detection = CL_CLEAN; size_t pos = 0, mbr_base = 0, logiclba = 0, extoff = 0, partoff = 0; size_t partsize, extsize; unsigned i = 0, j = 0; @@ -282,16 +274,18 @@ static int mbr_scanextprtn(cli_ctx *ctx, unsigned *prtncount, size_t extlba, siz pos += (logiclba * sectorsize) + mbr_base; if (fmap_readn(ctx->fmap, &ebr, pos, sizeof(ebr)) != sizeof(ebr)) { cli_dbgmsg("cli_scanebr: Invalid extended boot record\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* convert the little endian to host */ mbr_convert_to_host(&ebr); /* EBR checks */ - ret = mbr_check_ebr(&ebr); - if (ret != CL_CLEAN) { - return ret; + status = mbr_check_ebr(&ebr); + if (status != CL_SUCCESS) { + status = status; + goto done; } /* update state */ @@ -329,7 +323,8 @@ static int mbr_scanextprtn(cli_ctx *ctx, unsigned *prtncount, size_t extlba, siz break; default: cli_warnmsg("cli_scanebr: undefined state for EBR parsing\n"); - return CL_EPARSE; + status = CL_EPARSE; + goto done; } } else if (ebr.entries[j].type == MBR_EXTENDED) { switch (state) { @@ -345,10 +340,12 @@ static int mbr_scanextprtn(cli_ctx *ctx, unsigned *prtncount, size_t extlba, siz case SEEN_EXTENDED: cli_warnmsg("cli_scanebr: detected a logical boot record " "with multiple extended partition records\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; default: cli_dbgmsg("cli_scanebr: undefined state for EBR parsing\n"); - return CL_EPARSE; + status = CL_EPARSE; + goto done; } logiclba = ebr.entries[j].firstLBA; @@ -373,23 +370,22 @@ static int mbr_scanextprtn(cli_ctx *ctx, unsigned *prtncount, size_t extlba, siz break; default: cli_dbgmsg("cli_scanebr: undefined state for EBR parsing\n"); - return CL_EPARSE; + status = CL_EPARSE; + goto done; } partoff = (extlba + logiclba + ebr.entries[j].firstLBA) * sectorsize; partsize = ebr.entries[j].numLBA * sectorsize; if (partoff + partsize > extoff + extsize) { cli_dbgmsg("cli_scanebr: Invalid extended partition entry\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } - ret = cli_magic_scan_nested_fmap_type(ctx->fmap, partoff, partsize, ctx, - CL_TYPE_PART_ANY, NULL, LAYER_ATTRIBUTES_NONE); - if (ret != CL_CLEAN) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) - detection = CL_VIRUS; - else - return ret; + status = cli_magic_scan_nested_fmap_type(ctx->fmap, partoff, partsize, ctx, CL_TYPE_PART_ANY, NULL, LAYER_ATTRIBUTES_NONE); + if (status != CL_SUCCESS) { + status = status; + goto done; } } } else { @@ -399,7 +395,8 @@ static int mbr_scanextprtn(cli_ctx *ctx, unsigned *prtncount, size_t extlba, siz "entry at index %u\n", j); /* should we attempt to use these entries? */ - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } } } @@ -407,7 +404,9 @@ static int mbr_scanextprtn(cli_ctx *ctx, unsigned *prtncount, size_t extlba, siz cli_dbgmsg("cli_scanmbr: examined %u logical partitions\n", i); - return detection; +done: + + return status; } void mbr_convert_to_host(struct mbr_boot_record *record) @@ -424,71 +423,84 @@ void mbr_convert_to_host(struct mbr_boot_record *record) record->signature = be16_to_host(record->signature); } -static int mbr_check_mbr(struct mbr_boot_record *record, size_t maplen, size_t sectorsize) +static cl_error_t mbr_check_mbr(struct mbr_boot_record *record, size_t maplen, size_t sectorsize) { - unsigned i = 0; - size_t partoff = 0; - size_t partsize = 0; + cl_error_t status = CL_SUCCESS; + unsigned i = 0; + size_t partoff = 0; + size_t partsize = 0; for (i = 0; i < MBR_MAX_PARTITION_ENTRIES; ++i) { /* check status */ if ((record->entries[i].status != MBR_STATUS_INACTIVE) && (record->entries[i].status != MBR_STATUS_ACTIVE)) { cli_dbgmsg("cli_scanmbr: Invalid boot record status\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } partoff = record->entries[i].firstLBA * sectorsize; partsize = record->entries[i].numLBA * sectorsize; if (partoff + partsize > maplen) { cli_dbgmsg("cli_scanmbr: Invalid partition entry\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } } /* check the signature */ if (record->signature != MBR_SIGNATURE) { cli_dbgmsg("cli_scanmbr: Invalid boot record signature\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* check the maplen */ if ((maplen / sectorsize) < 2) { cli_dbgmsg("cli_scanmbr: bootstrap code or file is too small to hold disk image\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } - return CL_CLEAN; +done: + + return status; } -static int mbr_check_ebr(struct mbr_boot_record *record) +static cl_error_t mbr_check_ebr(struct mbr_boot_record *record) { - unsigned i = 0; + cl_error_t status = CL_SUCCESS; + unsigned i = 0; for (i = 0; i < MBR_MAX_PARTITION_ENTRIES - 2; ++i) { /* check status */ if ((record->entries[i].status != MBR_STATUS_INACTIVE) && (record->entries[i].status != MBR_STATUS_ACTIVE)) { cli_dbgmsg("cli_scanmbr: Invalid boot record status\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } } /* check the signature */ if (record->signature != MBR_SIGNATURE) { cli_dbgmsg("cli_scanmbr: Invalid boot record signature\n"); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } - return CL_CLEAN; +done: + + return status; } /* this includes the overall bounds of extended partitions */ -static int mbr_primary_partition_intersection(cli_ctx *ctx, struct mbr_boot_record mbr, size_t sectorsize) +static cl_error_t mbr_primary_partition_intersection(cli_ctx *ctx, struct mbr_boot_record mbr, size_t sectorsize) { + cl_error_t status = CL_CLEAN; + cl_error_t ret; partition_intersection_list_t prtncheck; unsigned i = 0, pitxn = 0, prtncount = 0; - int ret = CL_CLEAN, tmp = CL_CLEAN; partition_intersection_list_init(&prtncheck); @@ -497,39 +509,30 @@ static int mbr_primary_partition_intersection(cli_ctx *ctx, struct mbr_boot_reco /* empty partition entry */ prtncount++; } else { - tmp = partition_intersection_list_check(&prtncheck, &pitxn, mbr.entries[i].firstLBA, + ret = partition_intersection_list_check(&prtncheck, &pitxn, mbr.entries[i].firstLBA, mbr.entries[i].numLBA); - if (tmp != CL_CLEAN) { - if (tmp == CL_VIRUS) { + if (ret != CL_CLEAN) { + if (ret == CL_VIRUS) { cli_dbgmsg("cli_scanmbr: detected intersection with partitions " "[%u, %u]\n", pitxn, i); - ret = cli_append_virus(ctx, PRTN_INTXN_DETECTION); - if (SCAN_ALLMATCHES || ret == CL_CLEAN) - tmp = 0; - else - goto leave; + status = cli_append_potentially_unwanted(ctx, "Heuristics.MBRPartitionnIntersect"); + if (status != CL_SUCCESS) { + goto done; + } } else { - ret = tmp; - goto leave; + status = ret; + goto done; } } if (mbr.entries[i].type == MBR_EXTENDED) { /* check the logical partitions */ - tmp = mbr_extended_partition_intersection(ctx, &prtncount, + ret = mbr_extended_partition_intersection(ctx, &prtncount, mbr.entries[i].firstLBA, sectorsize); - if (tmp != CL_CLEAN) { - if (SCAN_ALLMATCHES && (tmp == CL_VIRUS)) { - ret = tmp; - tmp = 0; - } else if (tmp == CL_VIRUS) { - partition_intersection_list_free(&prtncheck); - return CL_VIRUS; - } else { - partition_intersection_list_free(&prtncheck); - return tmp; - } + if (ret != CL_SUCCESS) { + status = ret; + goto done; } } else { prtncount++; @@ -537,20 +540,21 @@ static int mbr_primary_partition_intersection(cli_ctx *ctx, struct mbr_boot_reco } } -leave: +done: partition_intersection_list_free(&prtncheck); - return ret; + return status; } /* checks internal logical partitions */ -static int mbr_extended_partition_intersection(cli_ctx *ctx, unsigned *prtncount, size_t extlba, size_t sectorsize) +static cl_error_t mbr_extended_partition_intersection(cli_ctx *ctx, unsigned *prtncount, size_t extlba, size_t sectorsize) { + cl_error_t status = CL_CLEAN; + cl_error_t ret; struct mbr_boot_record ebr; partition_intersection_list_t prtncheck; unsigned i, pitxn; - int ret = CL_CLEAN, tmp = CL_CLEAN, mbr_base = 0; + int mbr_base = 0; size_t pos = 0, logiclba = 0; - int virus_found = 0; mbr_base = sectorsize - sizeof(struct mbr_boot_record); @@ -566,7 +570,8 @@ static int mbr_extended_partition_intersection(cli_ctx *ctx, unsigned *prtncount if (fmap_readn(ctx->fmap, &ebr, pos, sizeof(ebr)) != sizeof(ebr)) { cli_dbgmsg("cli_scanebr: Invalid extended boot record\n"); partition_intersection_list_free(&prtncheck); - return CL_EFORMAT; + status = CL_EFORMAT; + goto done; } /* convert the little endian to host */ @@ -576,22 +581,19 @@ static int mbr_extended_partition_intersection(cli_ctx *ctx, unsigned *prtncount (*prtncount)++; /* assume that logical record is first and extended is second */ - tmp = partition_intersection_list_check(&prtncheck, &pitxn, logiclba, ebr.entries[0].numLBA); - if (tmp != CL_CLEAN) { - if (tmp == CL_VIRUS) { + ret = partition_intersection_list_check(&prtncheck, &pitxn, logiclba, ebr.entries[0].numLBA); + if (ret != CL_CLEAN) { + if (ret == CL_VIRUS) { cli_dbgmsg("cli_scanebr: detected intersection with partitions " "[%u, %u]\n", pitxn, i); - ret = cli_append_virus(ctx, PRTN_INTXN_DETECTION); - if (ret == CL_VIRUS) - virus_found = 1; - if (SCAN_ALLMATCHES || ret == CL_CLEAN) - tmp = 0; - else - goto leave; + status = cli_append_potentially_unwanted(ctx, "Heuristics.MBRPartitionnIntersect"); + if (status == CL_VIRUS) { + goto done; + } } else { - ret = tmp; - goto leave; + status = ret; + goto done; } } @@ -606,9 +608,8 @@ static int mbr_extended_partition_intersection(cli_ctx *ctx, unsigned *prtncount ++i; } while (logiclba != 0 && (*prtncount) < ctx->engine->maxpartitions); -leave: +done: partition_intersection_list_free(&prtncheck); - if (virus_found) - return CL_VIRUS; - return ret; + + return status; } diff --git a/libclamav/mbr.h b/libclamav/mbr.h index 46a3c8a647..4fd25117b8 100644 --- a/libclamav/mbr.h +++ b/libclamav/mbr.h @@ -84,9 +84,9 @@ struct mbr_boot_record { #pragma pack #endif -int cli_mbr_check(const unsigned char *buff, size_t len, size_t maplen); -int cli_mbr_check2(cli_ctx *ctx, size_t sectorsize); -int cli_scanmbr(cli_ctx *ctx, size_t sectorsize); +cl_error_t cli_mbr_check(const unsigned char *buff, size_t len, size_t maplen); +cl_error_t cli_mbr_check2(cli_ctx *ctx, size_t sectorsize); +cl_error_t cli_scanmbr(cli_ctx *ctx, size_t sectorsize); void mbr_convert_to_host(struct mbr_boot_record *record); #endif diff --git a/libclamav/msxml_parser.c b/libclamav/msxml_parser.c index 0049af88e9..efc148e754 100644 --- a/libclamav/msxml_parser.c +++ b/libclamav/msxml_parser.c @@ -163,13 +163,14 @@ static int msxml_parse_value(json_object *wrkptr, const char *arrname, const xml #endif /* HAVE_JSON */ #define MAX_ATTRIBS 20 -static int msxml_parse_element(struct msxml_ctx *mxctx, xmlTextReaderPtr reader, int rlvl, void *jptr) +static cl_error_t msxml_parse_element(struct msxml_ctx *mxctx, xmlTextReaderPtr reader, int rlvl, void *jptr) { const xmlChar *element_name = NULL; const xmlChar *node_name = NULL, *node_value = NULL; const struct key_entry *keyinfo; struct attrib_entry attribs[MAX_ATTRIBS]; - int ret, virus = 0, state, node_type, endtag = 0, num_attribs = 0; + cl_error_t ret; + int state, node_type, endtag = 0, num_attribs = 0; cli_ctx *ctx = mxctx->ictx->ctx; #if HAVE_JSON json_object *root = mxctx->ictx->root; @@ -188,7 +189,7 @@ static int msxml_parse_element(struct msxml_ctx *mxctx, xmlTextReaderPtr reader, #if HAVE_JSON if (track_json(mxctx)) { - int tmp = cli_json_parse_error(root, "MSXML_RECURSIVE_LIMIT"); + cl_error_t tmp = cli_json_parse_error(root, "MSXML_RECURSIVE_LIMIT"); if (tmp != CL_SUCCESS) return tmp; } @@ -219,7 +220,7 @@ static int msxml_parse_element(struct msxml_ctx *mxctx, xmlTextReaderPtr reader, cli_dbgmsg("msxml_parse_element: element tag node nameless\n"); #if HAVE_JSON if (track_json(mxctx)) { - int tmp = cli_json_parse_error(root, "MSXML_NAMELESS_ELEMENT"); + cl_error_t tmp = cli_json_parse_error(root, "MSXML_NAMELESS_ELEMENT"); if (tmp != CL_SUCCESS) return tmp; } @@ -369,10 +370,8 @@ static int msxml_parse_element(struct msxml_ctx *mxctx, xmlTextReaderPtr reader, switch (node_type) { case XML_READER_TYPE_ELEMENT: ret = msxml_parse_element(mxctx, reader, rlvl + 1, thisjobj ? thisjobj : parent); - if (ret != CL_SUCCESS || (!SCAN_ALLMATCHES && ret == CL_VIRUS)) { + if (ret != CL_SUCCESS) { return ret; - } else if (SCAN_ALLMATCHES && ret == CL_VIRUS) { - virus = 1; } break; @@ -417,13 +416,12 @@ static int msxml_parse_element(struct msxml_ctx *mxctx, xmlTextReaderPtr reader, ret = mxctx->scan_cb(of, tempfile, ctx, num_attribs, attribs, mxctx->scan_data); close(of); - if (!(ctx->engine->keeptmp)) + if (!(ctx->engine->keeptmp)) { cli_unlink(tempfile); + } free(tempfile); - if (ret != CL_SUCCESS && (ret != CL_VIRUS || (!SCAN_ALLMATCHES && ret == CL_VIRUS))) { + if (ret != CL_SUCCESS) { return ret; - } else if (SCAN_ALLMATCHES && ret == CL_VIRUS) { - virus = 1; } } @@ -467,10 +465,8 @@ static int msxml_parse_element(struct msxml_ctx *mxctx, xmlTextReaderPtr reader, if (!(ctx->engine->keeptmp)) cli_unlink(tempfile); free(tempfile); - if (ret != CL_SUCCESS && (ret != CL_VIRUS || (!SCAN_ALLMATCHES && ret == CL_VIRUS))) { + if (ret != CL_SUCCESS) { return ret; - } else if (SCAN_ALLMATCHES && ret == CL_VIRUS) { - virus = 1; } } @@ -491,10 +487,8 @@ static int msxml_parse_element(struct msxml_ctx *mxctx, xmlTextReaderPtr reader, #else ret = mxctx->comment_cb((const char *)node_value, ctx, NULL, mxctx->comment_data); #endif - if (ret != CL_SUCCESS && (ret != CL_VIRUS || (!SCAN_ALLMATCHES && ret == CL_VIRUS))) { + if (ret != CL_SUCCESS) { return ret; - } else if (SCAN_ALLMATCHES && ret == CL_VIRUS) { - virus = 1; } } @@ -549,23 +543,25 @@ static int msxml_parse_element(struct msxml_ctx *mxctx, xmlTextReaderPtr reader, break; case XML_READER_TYPE_END_ELEMENT: cli_msxmlmsg("msxml_parse_element: END ELEMENT %s [%d]: %s\n", node_name, node_type, node_value); - return (virus ? CL_VIRUS : CL_SUCCESS); + return CL_SUCCESS; default: cli_dbgmsg("msxml_parse_element: unhandled xml primary node %s [%d]: %s\n", node_name, node_type, node_value); } - return (virus ? CL_VIRUS : CL_SUCCESS); + return CL_SUCCESS; } /* reader initialization and closing handled by caller */ -int cli_msxml_parse_document(cli_ctx *ctx, xmlTextReaderPtr reader, const struct key_entry *keys, const size_t num_keys, uint32_t flags, struct msxml_ctx *mxctx) +cl_error_t cli_msxml_parse_document(cli_ctx *ctx, xmlTextReaderPtr reader, const struct key_entry *keys, const size_t num_keys, uint32_t flags, struct msxml_ctx *mxctx) { struct msxml_ctx reserve; struct msxml_ictx ictx; - int state, virus = 0, ret = CL_SUCCESS; + int state; + cl_error_t ret = CL_SUCCESS; - if (!ctx) + if (!ctx) { return CL_ENULLARG; + } if (!mxctx) { memset(&reserve, 0, sizeof(reserve)); @@ -604,27 +600,25 @@ int cli_msxml_parse_document(cli_ctx *ctx, xmlTextReaderPtr reader, const struct #else ret = msxml_parse_element(mxctx, reader, 0, NULL); #endif - if (ret == CL_SUCCESS) - ; - else if (SCAN_ALLMATCHES && ret == CL_VIRUS) { - /* non-allmatch simply propagates it down to return through ret */ - virus = 1; - } else if (ret == CL_VIRUS || ret == CL_ETIMEOUT || ret == CL_BREAK) { - cli_dbgmsg("cli_msxml_parse_document: encountered halt event in parsing xml document\n"); - break; - } else { - cli_warnmsg("cli_msxml_parse_document: encountered issue in parsing xml document\n"); - break; + if (ret != CL_SUCCESS) { + if (ret == CL_VIRUS || ret == CL_ETIMEOUT || ret == CL_BREAK) { + cli_dbgmsg("cli_msxml_parse_document: encountered halt event in parsing xml document\n"); + break; + } else { + cli_warnmsg("cli_msxml_parse_document: encountered issue in parsing xml document\n"); + break; + } } } - if (state == -1) + if (state == -1) { ret = CL_EPARSE; + } #if HAVE_JSON /* Parse General Error Handler */ if (ictx.flags & MSXML_FLAG_JSON) { - int tmp = CL_SUCCESS; + cl_error_t tmp = CL_SUCCESS; switch (ret) { case CL_SUCCESS: @@ -650,8 +644,9 @@ int cli_msxml_parse_document(cli_ctx *ctx, xmlTextReaderPtr reader, const struct break; } - if (tmp) + if (tmp) { return tmp; + } } #endif @@ -665,7 +660,7 @@ int cli_msxml_parse_document(cli_ctx *ctx, xmlTextReaderPtr reader, const struct ret = CL_SUCCESS; } - return (virus ? CL_VIRUS : ret); + return ret; } #endif /* HAVE_LIBXML2 */ diff --git a/libclamav/msxml_parser.h b/libclamav/msxml_parser.h index 34b90338a7..ff3f6c2f6d 100644 --- a/libclamav/msxml_parser.h +++ b/libclamav/msxml_parser.h @@ -86,7 +86,7 @@ struct msxml_ctx { struct msxml_ictx *ictx; }; -int cli_msxml_parse_document(cli_ctx *ctx, xmlTextReaderPtr reader, const struct key_entry *keys, const size_t num_keys, uint32_t flags, struct msxml_ctx *mxctx); +cl_error_t cli_msxml_parse_document(cli_ctx *ctx, xmlTextReaderPtr reader, const struct key_entry *keys, const size_t num_keys, uint32_t flags, struct msxml_ctx *mxctx); #endif /* HAVE_LIBXML2 */ diff --git a/libclamav/nsis/nulsft.c b/libclamav/nsis/nulsft.c index 6655b6b34d..48ead45444 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) - ret = cli_scan_desc(nsist.ofd, ctx, 0, 0, NULL, AC_SCAN_VIR, NULL, NULL, LAYER_ATTRIBUTES_NONE); /// TODO: Extract file names - else + 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 { 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/ole2_extract.c b/libclamav/ole2_extract.c index e4bcd8fa41..d555497590 100644 --- a/libclamav/ole2_extract.c +++ b/libclamav/ole2_extract.c @@ -578,14 +578,13 @@ static int ole2_walk_property_tree(ole2_header_t *hdr, const char *dir, int32_t int32_t idx, current_block, i, curindex; char *dirname; ole2_list_t node_list; - int ret, func_ret; + cl_error_t ret; #if HAVE_JSON char *name; int toval = 0; #endif ole2_listmsg("ole2_walk_property_tree() called\n"); - func_ret = CL_SUCCESS; ole2_list_init(&node_list); ole2_listmsg("rec_level: %d\n", rec_level); @@ -599,7 +598,7 @@ static int ole2_walk_property_tree(ole2_header_t *hdr, const char *dir, int32_t // Note: engine->max_recursion_level is re-purposed here out of convenience. // ole2 recursion does not leverage the ctx->recursion_stack stack. cli_dbgmsg("OLE2: Recursion limit reached (max: %d)\n", ctx->engine->max_recursion_level); - cli_append_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxRecursion"); + cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxRecursion"); return CL_EMAXREC; } @@ -687,12 +686,8 @@ static int ole2_walk_property_tree(ole2_header_t *hdr, const char *dir, int32_t if ((int)(prop_block[idx].child) != -1) { ret = ole2_walk_property_tree(hdr, dir, prop_block[idx].child, handler, rec_level + 1, file_count, ctx, scansize); if (ret != CL_SUCCESS) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) { - func_ret = ret; - } else { - ole2_list_delete(&node_list); - return ret; - } + ole2_list_delete(&node_list); + return ret; } } if ((int)(prop_block[idx].prev) != -1) { @@ -712,7 +707,7 @@ static int ole2_walk_property_tree(ole2_header_t *hdr, const char *dir, int32_t ole2_listmsg("file node\n"); if (ctx && ctx->engine->maxfiles && ((*file_count > ctx->engine->maxfiles) || (ctx->scannedfiles > ctx->engine->maxfiles - *file_count))) { cli_dbgmsg("OLE2: files limit reached (max: %u)\n", ctx->engine->maxfiles); - cli_append_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); + cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); ole2_list_delete(&node_list); return CL_EMAXFILES; } @@ -722,13 +717,9 @@ static int ole2_walk_property_tree(ole2_header_t *hdr, const char *dir, int32_t ole2_listmsg("running file handler\n"); ret = handler(hdr, &prop_block[idx], dir, ctx); if (ret != CL_SUCCESS) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) { - func_ret = ret; - } else { - ole2_listmsg("file handler returned %d\n", ret); - ole2_list_delete(&node_list); - return ret; - } + ole2_listmsg("file handler returned %d\n", ret); + ole2_list_delete(&node_list); + return ret; } } else { cli_dbgmsg("OLE2: filesize exceeded\n"); @@ -736,12 +727,8 @@ static int ole2_walk_property_tree(ole2_header_t *hdr, const char *dir, int32_t if ((int)(prop_block[idx].child) != -1) { ret = ole2_walk_property_tree(hdr, dir, prop_block[idx].child, handler, rec_level, file_count, ctx, scansize); if (ret != CL_SUCCESS) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) { - func_ret = ret; - } else { - ole2_list_delete(&node_list); - return ret; - } + ole2_list_delete(&node_list); + return ret; } } if ((int)(prop_block[idx].prev) != -1) { @@ -792,14 +779,11 @@ static int ole2_walk_property_tree(ole2_header_t *hdr, const char *dir, int32_t if ((int)(prop_block[idx].child) != -1) { ret = ole2_walk_property_tree(hdr, dirname, prop_block[idx].child, handler, rec_level + 1, file_count, ctx, scansize); if (ret != CL_SUCCESS) { - if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) { - func_ret = ret; - } else { - ole2_list_delete(&node_list); - if (dirname) - free(dirname); - return ret; + ole2_list_delete(&node_list); + if (dirname) { + free(dirname); } + return ret; } } if (dirname) { @@ -826,7 +810,7 @@ static int ole2_walk_property_tree(ole2_header_t *hdr, const char *dir, int32_t ole2_listmsg("loop ended: %d %d\n", ole2_list_size(&node_list), ole2_list_is_empty(&node_list)); } ole2_list_delete(&node_list); - return func_ret; + return CL_SUCCESS; } /* Write file Handler - write the contents of the entry to a file */ diff --git a/libclamav/others.c b/libclamav/others.c index 7ea357689a..6167bb3b07 100644 --- a/libclamav/others.c +++ b/libclamav/others.c @@ -339,7 +339,7 @@ unsigned int cl_retflevel(void) return CL_FLEVEL; } -const char *cl_strerror(int clerror) +const char *cl_strerror(cl_error_t clerror) { switch (clerror) { /* libclamav specific codes */ @@ -727,7 +727,7 @@ cl_error_t cl_engine_set_num(struct cl_engine *engine, enum cl_engine_field fiel } else { engine->engine_options &= ~(ENGINE_OPTIONS_DISABLE_CACHE); if (!(engine->cache)) - cli_cache_init(engine); + clean_cache_init(engine); } break; case CL_ENGINE_DISABLE_PE_STATS: @@ -1089,14 +1089,14 @@ cl_error_t cl_engine_settings_free(struct cl_settings *settings) return CL_SUCCESS; } -void cli_append_virus_if_heur_exceedsmax(cli_ctx *ctx, char *vname) +void cli_append_potentially_unwanted_if_heur_exceedsmax(cli_ctx *ctx, char *vname) { if (!ctx->limit_exceeded) { ctx->limit_exceeded = true; // guard against adding an alert (or metadata) a million times for non-fatal exceeds-max conditions // TODO: consider changing this from a bool to a threshold so we could at least see more than 1 limits exceeded if (SCAN_HEURISTIC_EXCEEDS_MAX) { - cli_append_possibly_unwanted(ctx, vname); + cli_append_potentially_unwanted(ctx, vname); cli_dbgmsg("%s: scanning may be incomplete and additional analysis needed for this file.\n", vname); } @@ -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_virus_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_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFileSize"); + 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_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); - ctx->abort_scan = true; + cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); + + // 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; } @@ -1202,7 +1210,10 @@ cl_error_t cli_checktimelimit(cli_ctx *ctx) } if (CL_ETIMEOUT == ret) { - cli_append_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxScanTime"); + 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: @@ -1279,7 +1290,7 @@ char *cli_hashfile(const char *filename, int type) /* Function: unlink unlink() with error checking */ -int cli_unlink(const char *pathname) +cl_error_t cli_unlink(const char *pathname) { if (unlink(pathname) == -1) { #ifdef _WIN32 @@ -1287,62 +1298,80 @@ int cli_unlink(const char *pathname) * even if the user has permissions to delete the file. */ if (-1 == _chmod(pathname, _S_IWRITE)) { char err[128]; - cli_warnmsg("cli_unlink: _chmod failure - %s\n", cli_strerror(errno, err, sizeof(err))); - return 1; + cli_warnmsg("cli_unlink: _chmod failure for %s - %s\n", pathname, cli_strerror(errno, err, sizeof(err))); + return CL_EUNLINK; } else if (unlink(pathname) == -1) { char err[128]; - cli_warnmsg("cli_unlink: unlink failure - %s\n", cli_strerror(errno, err, sizeof(err))); - return 1; + cli_warnmsg("cli_unlink: unlink failure for %s - %s\n", pathname, cli_strerror(errno, err, sizeof(err))); + return CL_EUNLINK; } - return 0; + return CL_SUCCESS; #else char err[128]; - cli_warnmsg("cli_unlink: unlink failure - %s\n", cli_strerror(errno, err, sizeof(err))); - return 1; + cli_warnmsg("cli_unlink: unlink failure for %s - %s\n", pathname, cli_strerror(errno, err, sizeof(err))); + return CL_EUNLINK; #endif } - return 0; + return CL_SUCCESS; } -void cli_virus_found_cb(cli_ctx *ctx) +void cli_virus_found_cb(cli_ctx *ctx, const char *virname) { - if (ctx->engine->cb_virus_found) - ctx->engine->cb_virus_found(fmap_fd(ctx->fmap), (const char *)*ctx->virname, ctx->cb_ctx); + if (ctx->engine->cb_virus_found) { + ctx->engine->cb_virus_found( + fmap_fd(ctx->fmap), + virname, + ctx->cb_ctx); + } } -cl_error_t cli_append_possibly_unwanted(cli_ctx *ctx, const char *virname) +/** + * @brief Add an indicator to the scan evidence. + * + * @param ctx + * @param virname Name of the indicator + * @param type Type of the indicator + * @return Returns CL_SUCCESS if added and IS in ALLMATCH mode, or if was PUA and not in HEURISTIC-PRECEDENCE-mode. + * @return Returns CL_VIRUS if added and NOT in ALLMATCH mode, or if was PUA and not in ALLMATCH but IS in HEURISTIC-PRECEDENCE-mode. + * @return Returns some other error code like CL_ERROR or CL_EMEM if something went wrong. + */ +static cl_error_t append_virus(cli_ctx *ctx, const char *virname, IndicatorType type) { - if (SCAN_ALLMATCHES) { - return cli_append_virus(ctx, virname); - } else if (SCAN_HEURISTIC_PRECEDENCE) { - return cli_append_virus(ctx, virname); - } else if (ctx->num_viruses == 0 && ctx->virname != NULL && *ctx->virname == NULL) { - ctx->found_possibly_unwanted = 1; - ctx->num_viruses++; - *ctx->virname = virname; - } - return CL_CLEAN; -} + cl_error_t status = CL_ERROR; + FFIError *add_indicator_error = NULL; + bool add_successful; -cl_error_t cli_append_virus(cli_ctx *ctx, const char *virname) -{ - if (ctx->virname == NULL) { - return CL_CLEAN; + char *location = NULL; + + if (ctx->evidence == NULL) { + // evidence storage not initialized, cannot continue. + status = CL_SUCCESS; + goto done; } + if ((ctx->fmap != NULL) && (ctx->recursion_stack != NULL) && (CL_VIRUS != cli_check_fp(ctx, virname))) { - return CL_CLEAN; + // FP signature found for one of the layers. Ignore indicator. + status = CL_SUCCESS; + goto done; } - if (!SCAN_ALLMATCHES && ctx->num_viruses != 0) { - if (SCAN_HEURISTIC_PRECEDENCE) { - return CL_CLEAN; - } + + add_successful = evidence_add_indicator( + ctx->evidence, + virname, + type, + &add_indicator_error); + if (!add_successful) { + cli_errmsg("Failed to add indicator to scan evidence: %s\n", ffierror_fmt(add_indicator_error)); + status = CL_ERROR; + goto done; } - ctx->num_viruses++; - *ctx->virname = virname; - cli_virus_found_cb(ctx); + if (type == IndicatorType_Strong) { + // Run that virus callback which in clamscan says " FOUND" + cli_virus_found_cb(ctx, virname); + } #if HAVE_JSON if (SCAN_COLLECT_METADATA && ctx->wrkproperty) { @@ -1351,33 +1380,88 @@ cl_error_t cli_append_virus(cli_ctx *ctx, const char *virname) arrobj = json_object_new_array(); if (NULL == arrobj) { cli_errmsg("cli_append_virus: no memory for json virus array\n"); - return CL_EMEM; + status = CL_EMEM; + goto done; } json_object_object_add(ctx->wrkproperty, "Viruses", arrobj); } virobj = json_object_new_string(virname); if (NULL == virobj) { cli_errmsg("cli_append_virus: no memory for json virus name object\n"); - return CL_EMEM; + status = CL_EMEM; + goto done; } json_object_array_add(arrobj, virobj); } #endif - return CL_VIRUS; + + if (SCAN_ALLMATCHES) { + // Never break. + status = CL_SUCCESS; + } else { + // Usually break. + 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: { + status = CL_SUCCESS; + break; + } + default: { + status = CL_SUCCESS; + } + } + } + +done: + if (NULL != location) { + free(location); + } + + return status; +} + +cl_error_t cli_append_potentially_unwanted(cli_ctx *ctx, const char *virname) +{ + if (SCAN_HEURISTIC_PRECEDENCE) { + return append_virus(ctx, virname, IndicatorType_Strong); + } else { + return append_virus(ctx, virname, IndicatorType_PotentiallyUnwanted); + } +} + +cl_error_t cli_append_virus(cli_ctx *ctx, const char *virname) +{ + if ((strncmp(virname, "PUA.", 4) == 0) || + (strncmp(virname, "Heuristics.", 11) == 0) || + (strncmp(virname, "BC.Heuristics.", 14) == 0)) { + return cli_append_potentially_unwanted(ctx, virname); + } else { + return append_virus(ctx, virname, IndicatorType_Strong); + } } const char *cli_get_last_virus(const cli_ctx *ctx) { - if (!ctx || !ctx->virname || !(*ctx->virname)) + if (!ctx || !ctx->evidence) { return NULL; - return *ctx->virname; + } + + return evidence_get_last_alert(ctx->evidence); } const char *cli_get_last_virus_str(const cli_ctx *ctx) { const char *ret; - if ((ret = cli_get_last_virus(ctx))) + + if (NULL != (ret = cli_get_last_virus(ctx))) { return ret; + } + return ""; } @@ -1389,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; @@ -1400,7 +1484,7 @@ cl_error_t cli_recursion_stack_push(cli_ctx *ctx, cl_fmap_t *map, cli_file_t typ cli_dbgmsg("cli_recursion_stack_push: Archive recursion limit exceeded (%u, max: %u)\n", ctx->recursion_level, ctx->engine->max_recursion_level); 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. - cli_append_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxRecursion"); + cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxRecursion"); status = CL_EMAXREC; goto done; } diff --git a/libclamav/others.h b/libclamav/others.h index ff041571a0..e6e06932e2 100644 --- a/libclamav/others.h +++ b/libclamav/others.h @@ -190,21 +190,21 @@ typedef struct recursion_level_tag { bool calculated_image_fuzzy_hash; /* Used for image/graphics files to store a fuzzy hash. */ } recursion_level_t; +typedef void *evidence_t; + /* internal clamav context */ typedef struct cli_ctx_tag { - char *target_filepath; /**< (optional) The filepath of the original scan target. */ - const char *sub_filepath; /**< (optional) The filepath of the current file being parsed. May be a temp file. */ - char *sub_tmpdir; /**< The directory to store tmp files at this recursion depth. */ - const char **virname; - unsigned int num_viruses; + char *target_filepath; /* (optional) The filepath of the original scan target. */ + const char *sub_filepath; /* (optional) The filepath of the current file being parsed. May be a temp file. */ + char *sub_tmpdir; /* The directory to store tmp files at this recursion depth. */ + evidence_t evidence; /* Stores the evidence for this scan to alert (alerting indicators). */ unsigned long int *scanned; const struct cli_matcher *root; const struct cl_engine *engine; uint64_t scansize; struct cl_scan_options *options; unsigned int scannedfiles; - unsigned int found_possibly_unwanted; - 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. */ @@ -730,11 +730,23 @@ cl_error_t cli_append_virus(cli_ctx *ctx, const char *virname); * @param virname The alert name. * @return cl_error_t CL_VIRUS if scan should be halted due to an alert, CL_CLEAN if scan should continue. */ -cl_error_t cli_append_possibly_unwanted(cli_ctx *ctx, const char *virname); +cl_error_t cli_append_potentially_unwanted(cli_ctx *ctx, const char *virname); + +/** + * @brief If the SCAN_HEURISTIC_EXCEEDS_MAX option is enabled, append a "potentially unwanted" indicator. + * + * There is no return value because the caller should select the appropriate "CL_EMAX*" error code regardless + * of whether or not an FP sig is found, or allmatch is enabled, or whatever. + * That is, the scan must not continue because of an FP sig. + * + * @param ctx The scan context. + * @param virname The name of the potentially unwanted indicator. + */ +void cli_append_potentially_unwanted_if_heur_exceedsmax(cli_ctx *ctx, char *virname); const char *cli_get_last_virus(const cli_ctx *ctx); const char *cli_get_last_virus_str(const cli_ctx *ctx); -void cli_virus_found_cb(cli_ctx *ctx); +void cli_virus_found_cb(cli_ctx *ctx, const char *virname); /** * @brief Push a new fmap onto our scan recursion stack. @@ -955,7 +967,15 @@ char *cli_strdup(const char *s); int cli_rmdirs(const char *dirname); char *cli_hashstream(FILE *fs, unsigned char *digcpy, int type); char *cli_hashfile(const char *filename, int type); -int cli_unlink(const char *pathname); + +/** + * @brief unlink() with error checking + * + * @param pathname the file path to unlink + * @return cl_error_t CL_SUCCESS if successful, CL_EUNLINK if unlink() failed + */ +cl_error_t cli_unlink(const char *pathname); + size_t cli_readn(int fd, void *buff, size_t count); size_t cli_writen(int fd, const void *buff, size_t count); const char *cli_gettmpdir(void); @@ -1056,8 +1076,8 @@ void cli_bitset_free(bitset_t *bs); int cli_bitset_set(bitset_t *bs, unsigned long bit_offset); int cli_bitset_test(bitset_t *bs, unsigned long bit_offset); const char *cli_ctime(const time_t *timep, char *buf, const size_t bufsize); -void cli_append_virus_if_heur_exceedsmax(cli_ctx *, char *); -cl_error_t cli_checklimits(const char *, cli_ctx *, unsigned long, unsigned long, unsigned long); + +cl_error_t cli_checklimits(const char *who, cli_ctx *ctx, unsigned long need1, unsigned long need2, unsigned long need3); /** * @brief Call before scanning a file to determine if we should scan it, skip it, or abort the entire scanning process. diff --git a/libclamav/partition_intersection.c b/libclamav/partition_intersection.c index 7c2aad6e5d..a003eb4699 100644 --- a/libclamav/partition_intersection.c +++ b/libclamav/partition_intersection.c @@ -26,22 +26,22 @@ #include "others.h" #include "partition_intersection.h" -static int partition_intersection_list_is_empty(partition_intersection_list_t* list) +static bool partition_intersection_list_is_empty(partition_intersection_list_t* list) { return (list->Head == NULL); } -int partition_intersection_list_init(partition_intersection_list_t* list) +cl_error_t partition_intersection_list_init(partition_intersection_list_t* list) { list->Head = NULL; list->Size = 0; return CL_SUCCESS; } -int partition_intersection_list_check(partition_intersection_list_t* list, unsigned* pitxn, off_t start, size_t size) +cl_error_t partition_intersection_list_check(partition_intersection_list_t* list, unsigned* pitxn, off_t start, size_t size) { partition_intersection_node_t *new_node, *check_node; - int ret = CL_CLEAN; + cl_error_t ret = CL_CLEAN; *pitxn = list->Size; @@ -84,7 +84,7 @@ int partition_intersection_list_check(partition_intersection_list_t* list, unsig return ret; } -int partition_intersection_list_free(partition_intersection_list_t* list) +cl_error_t partition_intersection_list_free(partition_intersection_list_t* list) { partition_intersection_node_t* next = NULL; diff --git a/libclamav/partition_intersection.h b/libclamav/partition_intersection.h index 4bfba8fbc3..fbda7430fe 100644 --- a/libclamav/partition_intersection.h +++ b/libclamav/partition_intersection.h @@ -28,8 +28,6 @@ #include "clamav-types.h" #include "others.h" -#define PRTN_INTXN_DETECTION "heuristic.partitionintersection" - struct partition_intersection_node; typedef struct partition_intersection_node { off_t Start; @@ -42,8 +40,8 @@ typedef struct partition_intersection_list { size_t Size; /* for debug */ } partition_intersection_list_t; -int partition_intersection_list_init(partition_intersection_list_t *list); -int partition_intersection_list_check(partition_intersection_list_t *list, unsigned *pitxn, off_t start, size_t size); -int partition_intersection_list_free(partition_intersection_list_t *list); +cl_error_t partition_intersection_list_init(partition_intersection_list_t *list); +cl_error_t partition_intersection_list_check(partition_intersection_list_t *list, unsigned *pitxn, off_t start, size_t size); +cl_error_t partition_intersection_list_free(partition_intersection_list_t *list); #endif diff --git a/libclamav/pdf.c b/libclamav/pdf.c index de37f23826..2fc5203409 100644 --- a/libclamav/pdf.c +++ b/libclamav/pdf.c @@ -1431,7 +1431,6 @@ static int pdf_scan_contents(int fd, struct pdf_struct *pdf) cl_error_t pdf_extract_obj(struct pdf_struct *pdf, struct pdf_obj *obj, uint32_t flags) { - cli_ctx *ctx = pdf->ctx; char fullname[PATH_MAX + 1]; int fout = -1; size_t sum = 0; @@ -1648,7 +1647,7 @@ cl_error_t pdf_extract_obj(struct pdf_struct *pdf, struct pdf_obj *obj, uint32_t * make a best effort to keep parsing... * Unless we were unable to allocate memory.*/ if (CL_EMEM == rc) { - goto err; + goto really_done; } if (CL_EPARSE == rc) { rc = CL_SUCCESS; @@ -1694,7 +1693,7 @@ cl_error_t pdf_extract_obj(struct pdf_struct *pdf, struct pdf_obj *obj, uint32_t if (dparams) pdf_free_dict(dparams); - if ((rc == CL_VIRUS) && !SCAN_ALLMATCHES) { + if (rc == CL_VIRUS) { sum = 0; /* prevents post-filter scan */ goto done; } @@ -1815,33 +1814,37 @@ cl_error_t pdf_extract_obj(struct pdf_struct *pdf, struct pdf_obj *obj, uint32_t /* TODO: invoke bytecode on this pdf obj with metainformation associated */ lseek(fout, 0, SEEK_SET); rc2 = cli_magic_scan_desc(fout, fullname, pdf->ctx, NULL, LAYER_ATTRIBUTES_NONE); - if (rc2 == CL_VIRUS || rc == CL_SUCCESS) + if (rc2 != CL_SUCCESS) { rc = rc2; + goto really_done; + } - if ((rc == CL_CLEAN) || ((rc == CL_VIRUS) && SCAN_ALLMATCHES)) { + if ((rc == CL_CLEAN) || (rc == CL_VIRUS)) { unsigned int dumpid = 0; for (dumpid = 0; dumpid < pdf->nobjs; dumpid++) { if (pdf->objs[dumpid] == obj) break; } rc2 = run_pdf_hooks(pdf, PDF_PHASE_POSTDUMP, fout, dumpid); - if (rc2 == CL_VIRUS) + if (rc2 == CL_VIRUS) { rc = rc2; + goto really_done; + } } - if (((rc == CL_CLEAN) || ((rc == CL_VIRUS) && SCAN_ALLMATCHES)) && (obj->flags & (1 << OBJ_CONTENTS))) { + if (((rc == CL_CLEAN) || (rc == CL_VIRUS)) && (obj->flags & (1 << OBJ_CONTENTS))) { lseek(fout, 0, SEEK_SET); - cli_dbgmsg("pdf_extract_obj: dumping contents %u %u\n", obj->id >> 8, obj->id & 0xff); + cli_dbgmsg("pdf_extract_obj: dumping contents from obj %u %u\n", obj->id >> 8, obj->id & 0xff); rc2 = pdf_scan_contents(fout, pdf); - if (rc2 == CL_VIRUS) + if (rc2 != CL_SUCCESS) { rc = rc2; - - noisy_msg(pdf, "pdf_extract_obj: extracted text from obj %u %u\n", obj->id >> 8, obj->id & 0xff); + goto really_done; + } } } -err: +really_done: close(fout); if (CL_EMEM != rc) { @@ -3271,7 +3274,6 @@ cl_error_t pdf_find_and_parse_objs_in_objstm(struct pdf_struct *pdf, struct objs { cl_error_t status = CL_EFORMAT; cl_error_t retval = CL_EPARSE; - int32_t alerts = 0; uint32_t badobjects = 0; size_t i = 0; @@ -3299,7 +3301,7 @@ cl_error_t pdf_find_and_parse_objs_in_objstm(struct pdf_struct *pdf, struct objs obj = NULL; if (cli_checktimelimit(pdf->ctx) != CL_SUCCESS) { - cli_errmsg("Timeout reached in the PDF parser while parsing object stream.\n"); + cli_dbgmsg("Timeout reached in the PDF parser while parsing object stream.\n"); status = CL_ETIMEOUT; goto done; } @@ -3319,7 +3321,7 @@ cl_error_t pdf_find_and_parse_objs_in_objstm(struct pdf_struct *pdf, struct objs cli_dbgmsg("pdf_find_and_parse_objs_in_objstm: Found object %u %u in object stream at offset: %u\n", obj->id >> 8, obj->id & 0xff, obj->start); if (cli_checktimelimit(pdf->ctx) != CL_SUCCESS) { - cli_errmsg("Timeout reached in the PDF parser while parsing object stream.\n"); + cli_dbgmsg("Timeout reached in the PDF parser while parsing object stream.\n"); status = CL_ETIMEOUT; goto done; } @@ -3328,10 +3330,7 @@ cl_error_t pdf_find_and_parse_objs_in_objstm(struct pdf_struct *pdf, struct objs pdf_parseobj(pdf, obj); } - if (alerts) { - status = CL_VIRUS; - goto done; - } else if (badobjects) { + if (badobjects) { status = CL_EFORMAT; goto done; } @@ -3346,11 +3345,10 @@ cl_error_t pdf_find_and_parse_objs_in_objstm(struct pdf_struct *pdf, struct objs * @brief Search pdf buffer for objects. Parse each and then extract each. * * @param pdf Pdf struct that keeps track of all information found in the PDF. - * @param[in,out] alerts The number of alerts, relevant in ALLMATCH mode. * * @return cl_error_t Error code. */ -cl_error_t pdf_find_and_extract_objs(struct pdf_struct *pdf, uint32_t *alerts) +static cl_error_t pdf_find_and_extract_objs(struct pdf_struct *pdf) { cl_error_t status = CL_SUCCESS; int32_t rv = 0; @@ -3358,7 +3356,7 @@ cl_error_t pdf_find_and_extract_objs(struct pdf_struct *pdf, uint32_t *alerts) uint32_t badobjects = 0; cli_ctx *ctx = NULL; - if (NULL == pdf || NULL == alerts) { + if (NULL == pdf) { cli_errmsg("pdf_find_and_extract_objs: Invalid arguments.\n"); status = CL_EARG; goto done; @@ -3380,7 +3378,7 @@ cl_error_t pdf_find_and_extract_objs(struct pdf_struct *pdf, uint32_t *alerts) struct pdf_obj *obj = pdf->objs[i]; if (cli_checktimelimit(pdf->ctx) != CL_SUCCESS) { - cli_errmsg("pdf_find_and_extract_objs: Timeout reached in the PDF parser while parsing objects.\n"); + cli_dbgmsg("pdf_find_and_extract_objs: Timeout reached in the PDF parser while parsing objects.\n"); status = CL_ETIMEOUT; goto done; @@ -3400,53 +3398,40 @@ cl_error_t pdf_find_and_extract_objs(struct pdf_struct *pdf, uint32_t *alerts) /* It is encrypted, and a password/key needs to be supplied to decrypt. * This doesn't trigger for PDFs that are encrypted but don't need * a password to decrypt */ - status = cli_append_virus(pdf->ctx, "Heuristics.Encrypted.PDF"); - if (status == CL_VIRUS) { - *alerts += 1; - if (SCAN_ALLMATCHES) - status = CL_CLEAN; - } + status = cli_append_potentially_unwanted(pdf->ctx, "Heuristics.Encrypted.PDF"); } if (CL_SUCCESS == status) { status = run_pdf_hooks(pdf, PDF_PHASE_PARSED, -1, -1); cli_dbgmsg("pdf_find_and_extract_objs: (parsed hooks) returned %d\n", status); - if (status == CL_VIRUS) { - *alerts += 1; - if (SCAN_ALLMATCHES) { - status = CL_CLEAN; - } - } } - /* extract PDF objs */ - for (i = 0; !status && i < pdf->nobjs; i++) { - struct pdf_obj *obj = pdf->objs[i]; + if (CL_SUCCESS == status) { + /* extract PDF objs */ + for (i = 0; !status && i < pdf->nobjs; i++) { + struct pdf_obj *obj = pdf->objs[i]; - if (cli_checktimelimit(pdf->ctx) != CL_SUCCESS) { - cli_errmsg("pdf_find_and_extract_objs: Timeout reached in the PDF parser while extracting objects.\n"); + if (cli_checktimelimit(pdf->ctx) != CL_SUCCESS) { + cli_dbgmsg("pdf_find_and_extract_objs: Timeout reached in the PDF parser while extracting objects.\n"); - status = CL_ETIMEOUT; - goto done; - } + status = CL_ETIMEOUT; + goto done; + } - status = pdf_extract_obj(pdf, obj, PDF_EXTRACT_OBJ_SCAN); - switch (status) { - case CL_EFORMAT: - /* Don't halt on one bad object */ - cli_dbgmsg("pdf_find_and_extract_objs: Format error when extracting object, skipping to the next object.\n"); - badobjects++; - pdf->stats.ninvalidobjs++; - status = CL_CLEAN; - break; - case CL_VIRUS: - *alerts += 1; - if (SCAN_ALLMATCHES) { + status = pdf_extract_obj(pdf, obj, PDF_EXTRACT_OBJ_SCAN); + switch (status) { + case CL_EFORMAT: + /* Don't halt on one bad object */ + cli_dbgmsg("pdf_find_and_extract_objs: Format error when extracting object, skipping to the next object.\n"); + badobjects++; + pdf->stats.ninvalidobjs++; status = CL_CLEAN; - } - break; - default: - break; + break; + case CL_VIRUS: + break; + default: + break; + } } } @@ -3478,7 +3463,7 @@ cl_error_t cli_pdf(const char *dir, cli_ctx *ctx, off_t offset) unsigned long xref; long temp_long; const char *pdfver, *tmp, *start, *eofmap, *q, *eof; - unsigned i, alerts = 0; + unsigned i; unsigned int objs_found = 0; #if HAVE_JSON json_object *pdfobj = NULL; @@ -3652,11 +3637,7 @@ cl_error_t cli_pdf(const char *dir, cli_ctx *ctx, off_t offset) pdf.startoff = offset; rc = run_pdf_hooks(&pdf, PDF_PHASE_PRE, -1, -1); - if ((rc == CL_VIRUS) && SCAN_ALLMATCHES) { - cli_dbgmsg("cli_pdf: (pre hooks) returned %d\n", rc); - alerts++; - rc = CL_CLEAN; - } else if (rc) { + if (CL_SUCCESS != rc) { cli_dbgmsg("cli_pdf: (pre hooks) returning %d\n", rc); rc = rc == CL_BREAK ? CL_CLEAN : rc; @@ -3668,7 +3649,7 @@ cl_error_t cli_pdf(const char *dir, cli_ctx *ctx, off_t offset) * This methodology adds objects from object streams. */ objs_found = pdf.nobjs; - rc = pdf_find_and_extract_objs(&pdf, &alerts); + rc = pdf_find_and_extract_objs(&pdf); if (CL_EMEM == rc) { cli_dbgmsg("cli_pdf: pdf_find_and_extract_objs had an allocation failure\n"); @@ -3682,20 +3663,14 @@ cl_error_t cli_pdf(const char *dir, cli_ctx *ctx, off_t offset) if (pdf.flags & (1 << ENCRYPTED_PDF)) pdf.flags &= ~((1 << BAD_FLATESTART) | (1 << BAD_STREAMSTART) | (1 << BAD_ASCIIDECODE)); - if (pdf.flags && !rc) { + if (pdf.flags && CL_SUCCESS == rc) { cli_dbgmsg("cli_pdf: flags 0x%02x\n", pdf.flags); rc = run_pdf_hooks(&pdf, PDF_PHASE_END, -1, -1); - if (rc == CL_VIRUS) { - alerts++; - if (SCAN_ALLMATCHES) { - rc = CL_CLEAN; - } - } - if (!rc && SCAN_HEURISTICS && (ctx->dconf->other & OTHER_CONF_PDFNAMEOBJ)) { + if (CL_SUCCESS == rc && SCAN_HEURISTICS && (ctx->dconf->other & OTHER_CONF_PDFNAMEOBJ)) { if (pdf.flags & (1 << ESCAPED_COMMON_PDFNAME)) { /* for example /Fl#61te#44#65#63#6f#64#65 instead of /FlateDecode */ - cli_append_possibly_unwanted(ctx, "Heuristics.PDF.ObfuscatedNameObject"); + rc = cli_append_potentially_unwanted(ctx, "Heuristics.PDF.ObfuscatedNameObject"); } } #if 0 @@ -3704,7 +3679,7 @@ cl_error_t cli_pdf(const char *dir, cli_ctx *ctx, off_t offset) pdf.flags &= ~ (1 << BAD_ASCIIDECODE); if (pdf.flags & (1 << MANY_FILTERS)) pdf.flags &= ~ (1 << BAD_ASCIIDECODE); - if (!rc && (pdf.flags & + if (CL_SUCCESS == rc && (pdf.flags & ((1 << BAD_PDF_TOOMANYOBJS) | (1 << BAD_STREAM_FILTERS) | (1< 0) { + if (CL_SUCCESS == rc && pdf.stats.ninvalidobjs > 0) { rc = CL_EFORMAT; } diff --git a/libclamav/pdf.h b/libclamav/pdf.h index 3a03f19f13..f109c9d25b 100644 --- a/libclamav/pdf.h +++ b/libclamav/pdf.h @@ -197,7 +197,6 @@ void pdf_free_array(struct pdf_array *array); void pdf_print_dict(struct pdf_dict *dict, unsigned long depth); void pdf_print_array(struct pdf_array *array, unsigned long depth); -cl_error_t pdf_find_and_extract_objs(struct pdf_struct *pdf, uint32_t *alerts); cl_error_t pdf_find_and_parse_objs_in_objstm(struct pdf_struct *pdf, struct objstm_struct *objstm); #endif diff --git a/libclamav/pdfdecode.c b/libclamav/pdfdecode.c index 473cfcd43a..382cabac60 100644 --- a/libclamav/pdfdecode.c +++ b/libclamav/pdfdecode.c @@ -111,7 +111,6 @@ size_t pdf_decodestream( { struct pdf_token *token = NULL; size_t bytes_scanned = 0; - cli_ctx *ctx = NULL; if (!status) { /* invalid args, and no way to pass back the status code */ @@ -124,8 +123,6 @@ size_t pdf_decodestream( goto done; } - ctx = pdf->ctx; - if (!stream || !streamlen || fout < 0) { cli_dbgmsg("pdf_decodestream: no filters or stream on obj %u %u\n", obj->id >> 8, obj->id & 0xff); *status = CL_ENULLARG; @@ -156,14 +153,14 @@ size_t pdf_decodestream( *status = CL_EMEM; goto done; } + memcpy(token->content, stream, streamlen); token->length = streamlen; cli_dbgmsg("pdf_decodestream: detected %lu applied filters\n", (long unsigned)(obj->numfilters)); bytes_scanned = pdf_decodestream_internal(pdf, obj, params, token, fout, status, objstm); - - if ((CL_VIRUS == *status) && !SCAN_ALLMATCHES) { + if (CL_VIRUS == *status) { goto done; } @@ -224,10 +221,8 @@ static size_t pdf_decodestream_internal( struct pdf_struct *pdf, struct pdf_obj *obj, struct pdf_dict *params, struct pdf_token *token, int fout, cl_error_t *status, struct objstm_struct *objstm) { - cl_error_t vir = CL_CLEAN; cl_error_t retval = CL_SUCCESS; size_t bytes_scanned = 0; - cli_ctx *ctx = NULL; const char *filter = NULL; uint32_t i; @@ -242,7 +237,6 @@ static size_t pdf_decodestream_internal( goto done; } - ctx = pdf->ctx; *status = CL_SUCCESS; /* @@ -323,29 +317,25 @@ static size_t pdf_decodestream_internal( } if (retval != CL_SUCCESS) { - if (retval == CL_VIRUS && SCAN_ALLMATCHES) { - vir = CL_VIRUS; - } else { - const char *reason; - - switch (retval) { - case CL_VIRUS: - *status = CL_VIRUS; - reason = "detection"; - break; - case CL_BREAK: - *status = CL_SUCCESS; - reason = "decoding break"; - break; - default: - *status = CL_EPARSE; - reason = "decoding error"; - break; - } + const char *reason; - cli_dbgmsg("pdf_decodestream_internal: stopping after %d (of %u) filters (reason: %s)\n", i, obj->numfilters, reason); - break; + switch (retval) { + case CL_VIRUS: + *status = CL_VIRUS; + reason = "detection"; + break; + case CL_BREAK: + *status = CL_SUCCESS; + reason = "decoding break"; + break; + default: + *status = CL_EPARSE; + reason = "decoding error"; + break; } + + cli_dbgmsg("pdf_decodestream_internal: stopping after %d (of %u) filters (reason: %s)\n", i, obj->numfilters, reason); + break; } token->success++; @@ -376,7 +366,7 @@ static size_t pdf_decodestream_internal( } if ((NULL != objstm) && - ((CL_SUCCESS == *status) || ((CL_VIRUS == *status) && SCAN_ALLMATCHES))) { + (CL_SUCCESS == *status)) { unsigned int objs_found = pdf->nobjs; /* @@ -406,9 +396,6 @@ static size_t pdf_decodestream_internal( done: - if (vir == CL_VIRUS) - *status = CL_VIRUS; - return bytes_scanned; } diff --git a/libclamav/pdfng.c b/libclamav/pdfng.c index 2ab65f793d..032cfa726a 100644 --- a/libclamav/pdfng.c +++ b/libclamav/pdfng.c @@ -1008,6 +1008,7 @@ struct pdf_array *pdf_parse_array(struct pdf_struct *pdf, struct pdf_obj *obj, s } /* Not a dictionary. Intentionally fall through. */ + /* fall-through */ case '(': val = pdf_parse_string(pdf, obj, begin, end - objstart, NULL, &begin, NULL); begin += 2; diff --git a/libclamav/pe.c b/libclamav/pe.c index 27a57a002c..f5dcea993b 100644 --- a/libclamav/pe.c +++ b/libclamav/pe.c @@ -88,6 +88,8 @@ #include "json_api.h" +#include "clamav_rust.h" + #define DCONF ctx->dconf->pe #define PE_IMAGE_DOS_SIGNATURE 0x5a4d /* MZ */ @@ -191,47 +193,46 @@ free(tempfile); \ break; -#define CLI_UNPRESULTS_(NAME, FSGSTUFF, EXPR, GOOD, FREEME) \ - switch (EXPR) { \ - case GOOD: /* Unpacked and rebuilt */ \ - if (ctx->engine->keeptmp) \ - cli_dbgmsg(NAME ": Unpacked and rebuilt executable saved in %s\n", tempfile); \ - else \ - cli_dbgmsg(NAME ": Unpacked and rebuilt executable\n"); \ - cli_multifree FREEME; \ - cli_exe_info_destroy(peinfo); \ - lseek(ndesc, 0, SEEK_SET); \ - cli_dbgmsg("***** Scanning rebuilt PE file *****\n"); \ - SHA_OFF; \ - if (CL_VIRUS == cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE)) { \ - close(ndesc); \ - SHA_RESET; \ - CLI_TMPUNLK(); \ - free(tempfile); \ - return CL_VIRUS; \ - } \ - SHA_RESET; \ - close(ndesc); \ - CLI_TMPUNLK(); \ - free(tempfile); \ - return CL_CLEAN; \ - \ - FSGSTUFF; \ - \ - default: \ - cli_dbgmsg(NAME ": Unpacking failed\n"); \ - close(ndesc); \ - if (cli_unlink(tempfile)) { \ - cli_exe_info_destroy(peinfo); \ - free(tempfile); \ - cli_multifree FREEME; \ - return CL_EUNLINK; \ - } \ - cli_multifree FREEME; \ - free(tempfile); \ - } - +#define CLI_UNPRESULTS_(NAME, FSGSTUFF, EXPR, GOOD, FREEME) \ + switch (EXPR) { \ + case GOOD: /* Unpacked and rebuilt */ \ + cli_dbgmsg(NAME ": Unpacked and rebuilt executable saved in %s\n", tempfile); \ + cli_multifree FREEME; \ + cli_exe_info_destroy(peinfo); \ + lseek(ndesc, 0, SEEK_SET); \ + cli_dbgmsg("***** Scanning rebuilt PE file *****\n"); \ + SHA_OFF; \ + if (CL_SUCCESS != (ret = cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE))) { \ + close(ndesc); \ + SHA_RESET; \ + CLI_TMPUNLK(); \ + free(tempfile); \ + return ret; \ + } \ + SHA_RESET; \ + close(ndesc); \ + CLI_TMPUNLK(); \ + free(tempfile); \ + return CL_CLEAN; \ + \ + FSGSTUFF; \ + \ + default: \ + cli_dbgmsg(NAME ": Unpacking failed\n"); \ + close(ndesc); \ + if (cli_unlink(tempfile)) { \ + cli_exe_info_destroy(peinfo); \ + free(tempfile); \ + cli_multifree FREEME; \ + return CL_EUNLINK; \ + } \ + cli_multifree FREEME; \ + free(tempfile); \ + } + +// The GOOD parameter indicates what a successful unpacking should return. #define CLI_UNPRESULTS(NAME, EXPR, GOOD, FREEME) CLI_UNPRESULTS_(NAME, (void)0, EXPR, GOOD, FREEME) + // TODO The second argument to FSGCASE below should match what gets freed as // indicated by FREEME, otherwise a memory leak can occur (as currently used, // it looks like dest can get leaked by these macros). @@ -555,15 +556,15 @@ static unsigned int cli_hashsect(fmap_t *map, struct cli_exe_section *s, unsigne } /* check hash section sigs */ -static int scan_pe_mdb(cli_ctx *ctx, struct cli_exe_section *exe_section) +static cl_error_t scan_pe_mdb(cli_ctx *ctx, struct cli_exe_section *exe_section) { struct cli_matcher *mdb_sect = ctx->engine->hm_mdb; unsigned char *hashset[CLI_HASH_AVAIL_TYPES]; const char *virname = NULL; int foundsize[CLI_HASH_AVAIL_TYPES]; int foundwild[CLI_HASH_AVAIL_TYPES]; - enum CLI_HASH_TYPE type; - int ret = CL_CLEAN; + cli_hash_type_t type; + cl_error_t ret = CL_CLEAN; unsigned char *md5 = NULL; /* pick hashtypes to generate */ @@ -625,20 +626,14 @@ static int scan_pe_mdb(cli_ctx *ctx, struct cli_exe_section *exe_section) for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++) { if (foundsize[type] && cli_hm_scan(hashset[type], exe_section->rsz, &virname, mdb_sect, type) == CL_VIRUS) { ret = cli_append_virus(ctx, virname); - if (ret != CL_CLEAN) { - if (ret != CL_VIRUS) - break; - else if (!SCAN_ALLMATCHES) - break; + if (ret != CL_SUCCESS) { + break; } } if (foundwild[type] && cli_hm_scan_wild(hashset[type], &virname, mdb_sect, type) == CL_VIRUS) { ret = cli_append_virus(ctx, virname); - if (ret != CL_CLEAN) { - if (ret != CL_VIRUS) - break; - else if (!SCAN_ALLMATCHES) - break; + if (ret != CL_SUCCESS) { + break; } } } @@ -2251,7 +2246,7 @@ static inline int hash_impfns(cli_ctx *ctx, void **hashctx, uint32_t *impsz, str unsigned int err = 0; int num_fns = 0, ret = CL_SUCCESS; const char *buffer; - enum CLI_HASH_TYPE type; + cli_hash_type_t type; #if HAVE_JSON json_object *imptbl = NULL; #else @@ -2419,7 +2414,7 @@ static cl_error_t hash_imptbl(cli_ctx *ctx, unsigned char **digest, uint32_t *im uint32_t impoff, offset; const char *buffer; void *hashctx[CLI_HASH_AVAIL_TYPES] = {0}; - enum CLI_HASH_TYPE type; + cli_hash_type_t type; int nimps = 0; unsigned int err; int first = 1; @@ -2563,15 +2558,15 @@ static cl_error_t hash_imptbl(cli_ctx *ctx, unsigned char **digest, uint32_t *im return status; } -static int scan_pe_imp(cli_ctx *ctx, struct cli_exe_info *peinfo) +static cl_error_t scan_pe_imp(cli_ctx *ctx, struct cli_exe_info *peinfo) { struct cli_matcher *imp = ctx->engine->hm_imp; unsigned char *hashset[CLI_HASH_AVAIL_TYPES]; const char *virname = NULL; int genhash[CLI_HASH_AVAIL_TYPES]; uint32_t impsz = 0; - enum CLI_HASH_TYPE type; - int ret = CL_CLEAN; + cli_hash_type_t type; + cl_error_t ret = CL_CLEAN; /* pick hashtypes to generate */ for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++) { @@ -2637,20 +2632,14 @@ static int scan_pe_imp(cli_ctx *ctx, struct cli_exe_info *peinfo) for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++) { if (cli_hm_scan(hashset[type], impsz, &virname, imp, type) == CL_VIRUS) { ret = cli_append_virus(ctx, virname); - if (ret != CL_CLEAN) { - if (ret != CL_VIRUS) - break; - else if (!SCAN_ALLMATCHES) - break; + if (ret != CL_SUCCESS) { + break; } } if (cli_hm_scan_wild(hashset[type], &virname, imp, type) == CL_VIRUS) { cli_append_virus(ctx, virname); - if (ret != CL_CLEAN) { - if (ret != CL_VIRUS) - break; - else if (!SCAN_ALLMATCHES) - break; + if (ret != CL_SUCCESS) { + break; } } } @@ -2784,7 +2773,10 @@ int cli_scanpe(cli_ctx *ctx) int (*upxfn)(const char *, uint32_t, char *, uint32_t *, uint32_t, uint32_t, uint32_t) = NULL; const char *src = NULL; char *dest = NULL; - int ndesc, ret = CL_CLEAN, upack = 0; + int ndesc; + cl_error_t ret = CL_SUCCESS; + cl_error_t peheader_ret; + int upack = 0; size_t fsize; struct cli_bc_ctx *bc_ctx; fmap_t *map; @@ -2792,7 +2784,6 @@ int cli_scanpe(cli_ctx *ctx) #ifdef HAVE__INTERNAL__SHA_COLLECT int sha_collect = ctx->sha_collect; #endif - uint32_t viruses_found = 0; #if HAVE_JSON int toval = 0; struct json_object *pe_json = NULL; @@ -2832,41 +2823,45 @@ int cli_scanpe(cli_ctx *ctx) cli_exe_info_init(peinfo, 0); - ret = cli_peheader(map, peinfo, opts, ctx); + peheader_ret = cli_peheader(map, peinfo, opts, ctx); // Warn the user if PE header parsing failed - if it's a binary that runs // successfully on Windows, we need to relax our PE parsing standards so // that we make sure the executable gets scanned appropriately -#define PE_HDR_PARSE_FAIL_CONSEQUENCE "won't attempt .mdb / .imp / PE-specific BC rule matching or exe unpacking\n" + switch (peheader_ret) { + case CL_EFORMAT: + ret = CL_SUCCESS; + if (DETECT_BROKEN_PE) { + ret = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable"); + } + cli_dbgmsg("cli_scanpe: PE header appears broken - won't attempt .mdb / .imp / PE-specific BC rule matching or exe unpacking\n"); + cli_exe_info_destroy(peinfo); + return ret; - if (CLI_PEHEADER_RET_BROKEN_PE == ret) { - if (DETECT_BROKEN_PE) { - // TODO Handle allmatch - ret = cli_append_virus(ctx, "Heuristics.Broken.Executable"); + case CL_ERROR: + ret = CL_SUCCESS; + cli_dbgmsg("cli_scanpe: An error occurred when parsing the PE header - won't attempt .mdb / .imp / PE-specific BC rule matching or exe unpacking\n"); cli_exe_info_destroy(peinfo); return ret; - } - cli_dbgmsg("cli_scanpe: PE header appears broken - " PE_HDR_PARSE_FAIL_CONSEQUENCE); - cli_exe_info_destroy(peinfo); - return CL_CLEAN; - } else if (CLI_PEHEADER_RET_JSON_TIMEOUT == ret) { - cli_dbgmsg("cli_scanpe: JSON creation timed out - " PE_HDR_PARSE_FAIL_CONSEQUENCE); - cli_exe_info_destroy(peinfo); - return CL_ETIMEOUT; - } else if (CLI_PEHEADER_RET_GENERIC_ERROR == ret) { - cli_dbgmsg("cli_scanpe: An error occurred when parsing the PE header - " PE_HDR_PARSE_FAIL_CONSEQUENCE); - cli_exe_info_destroy(peinfo); - return CL_CLEAN; + case CL_ETIMEOUT: + ret = CL_ETIMEOUT; + cli_dbgmsg("cli_scanpe: JSON creation timed out - won't attempt .mdb / .imp / PE-specific BC rule matching or exe unpacking\n"); + cli_exe_info_destroy(peinfo); + return ret; + + default: + break; } if (!peinfo->is_pe32plus) { /* PE */ - if (DCONF & PE_CONF_UPACK) + if (DCONF & PE_CONF_UPACK) { upack = (EC16(peinfo->file_hdr.SizeOfOptionalHeader) == 0x148); + } } - for (i = 0; i < peinfo->nsections; i++) { + for (i = 0; i < peinfo->nsections; i++) { if (peinfo->sections[i].rsz) { /* Don't bother with virtual only sections */ // TODO Regarding the commented out check below: // This used to check that the section name was NULL, but now that @@ -2881,18 +2876,13 @@ int cli_scanpe(cli_ctx *ctx) /* check hash section sigs */ if ((DCONF & PE_CONF_MD5SECT) && ctx->engine->hm_mdb) { ret = scan_pe_mdb(ctx, &(peinfo->sections[i])); - if (ret != CL_CLEAN) { - if (ret == CL_VIRUS && !SCAN_ALLMATCHES) { - cli_dbgmsg("------------------------------------\n"); - cli_exe_info_destroy(peinfo); - return ret; - } else if (ret != CL_VIRUS) { + if (ret != CL_SUCCESS) { + if (ret != CL_VIRUS) { cli_errmsg("cli_scanpe: scan_pe_mdb failed: %s!\n", cl_strerror(ret)); - - cli_dbgmsg("------------------------------------\n"); - cli_exe_info_destroy(peinfo); - return ret; } + cli_dbgmsg("------------------------------------\n"); + cli_exe_info_destroy(peinfo); + return ret; } } } @@ -2915,7 +2905,7 @@ int cli_scanpe(cli_ctx *ctx) /* CLI_UNPTEMP("cli_scanpe: DISASM",(peinfo->sections,0)); */ /* if(disasmbuf((unsigned char*)epbuff, epsize, ndesc)) */ - /* ret = cli_scan_desc(ndesc, ctx, CL_TYPE_PE_DISASM, 1, NULL, AC_SCAN_VIR); */ + /* ret = cli_scan_desc(ndesc, ctx, CL_TYPE_PE_DISASM, true, NULL, AC_SCAN_VIR); */ /* close(ndesc); */ /* if(ret == CL_VIRUS) { */ /* cli_exe_info_destroy(peinfo); */ @@ -2928,8 +2918,7 @@ int cli_scanpe(cli_ctx *ctx) if (peinfo->overlay_start && peinfo->overlay_size > 0) { ret = cli_scanishield(ctx, peinfo->overlay_start, peinfo->overlay_size); - if (ret != CL_CLEAN) { - // TODO Handle allmatch + if (ret != CL_SUCCESS) { cli_exe_info_destroy(peinfo); return ret; } @@ -2968,10 +2957,11 @@ int cli_scanpe(cli_ctx *ctx) break; case CL_VIRUS: case CL_BREAK: - // TODO Handle allmatch cli_exe_info_destroy(peinfo); cli_bytecode_context_destroy(bc_ctx); return ret == CL_VIRUS ? CL_VIRUS : CL_CLEAN; + default: + break; } cli_bytecode_context_destroy(bc_ctx); @@ -2990,9 +2980,6 @@ int cli_scanpe(cli_ctx *ctx) cli_warnmsg("cli_scanpe: NULL argument supplied\n"); break; case CL_VIRUS: - if (SCAN_ALLMATCHES) - break; - /* intentional fall-through */ case CL_BREAK: cli_exe_info_destroy(peinfo); return ret == CL_VIRUS ? CL_VIRUS : CL_CLEAN; @@ -3009,18 +2996,10 @@ int cli_scanpe(cli_ctx *ctx) if (pt) { pt += 15; if ((((uint32_t)cli_readint32(pt) ^ (uint32_t)cli_readint32(pt + 4)) == 0x505a4f) && (((uint32_t)cli_readint32(pt + 8) ^ (uint32_t)cli_readint32(pt + 12)) == 0xffffb) && (((uint32_t)cli_readint32(pt + 16) ^ (uint32_t)cli_readint32(pt + 20)) == 0xb8)) { - ret = cli_append_virus(ctx, "Heuristics.W32.Parite.B"); - if (ret != CL_CLEAN) { - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - cli_exe_info_destroy(peinfo); - return ret; - } else - viruses_found++; - } else { - cli_exe_info_destroy(peinfo); - return ret; - } + ret = cli_append_potentially_unwanted(ctx, "Heuristics.W32.Parite.B"); + if (ret != CL_SUCCESS) { + cli_exe_info_destroy(peinfo); + return ret; } } } @@ -3145,18 +3124,10 @@ int cli_scanpe(cli_ctx *ctx) break; case KZSLOOP: if (op == kzdsize + 0x48 && *kzcode == 0x75 && kzlen - (int8_t)kzcode[1] - 3 <= kzinitlen && kzlen - (int8_t)kzcode[1] >= kzxorlen) { - ret = cli_append_virus(ctx, "Heuristics.W32.Kriz"); - if (ret != CL_CLEAN) { - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - cli_exe_info_destroy(peinfo); - return ret; - } else - viruses_found++; - } else { - cli_exe_info_destroy(peinfo); - return ret; - } + ret = cli_append_potentially_unwanted(ctx, "Heuristics.W32.Kriz"); + if (ret != CL_SUCCESS) { + cli_exe_info_destroy(peinfo); + return ret; } } cli_dbgmsg("cli_scanpe: kriz: loop out of bounds, corrupted sample?\n"); @@ -3182,18 +3153,10 @@ int cli_scanpe(cli_ctx *ctx) if ((tbuff = fmap_need_off_once(map, peinfo->sections[peinfo->nsections - 1].raw + rsize - bw, 4096))) { if (cli_memstr(tbuff, 4091, "\xe8\x2c\x61\x00\x00", 5)) { - ret = cli_append_virus(ctx, dam ? "Heuristics.W32.Magistr.A.dam" : "Heuristics.W32.Magistr.A"); - if (ret != CL_CLEAN) { - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - cli_exe_info_destroy(peinfo); - return ret; - } else - viruses_found++; - } else { - cli_exe_info_destroy(peinfo); - return ret; - } + ret = cli_append_potentially_unwanted(ctx, dam ? "Heuristics.W32.Magistr.A.dam" : "Heuristics.W32.Magistr.A"); + if (ret != CL_SUCCESS) { + cli_exe_info_destroy(peinfo); + return ret; } } } @@ -3203,18 +3166,10 @@ int cli_scanpe(cli_ctx *ctx) if ((tbuff = fmap_need_off_once(map, peinfo->sections[peinfo->nsections - 1].raw + rsize - bw, 4096))) { if (cli_memstr(tbuff, 4091, "\xe8\x04\x72\x00\x00", 5)) { - ret = cli_append_virus(ctx, dam ? "Heuristics.W32.Magistr.B.dam" : "Heuristics.W32.Magistr.B"); - if (ret != CL_CLEAN) { - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - cli_exe_info_destroy(peinfo); - return ret; - } else - viruses_found++; - } else { - cli_exe_info_destroy(peinfo); - return ret; - } + ret = cli_append_potentially_unwanted(ctx, dam ? "Heuristics.W32.Magistr.B.dam" : "Heuristics.W32.Magistr.B"); + if (ret != CL_SUCCESS) { + cli_exe_info_destroy(peinfo); + return ret; } } } @@ -3280,20 +3235,11 @@ int cli_scanpe(cli_ctx *ctx) continue; if ((jump = cli_readint32(code)) == 0x60ec8b55 || (code[4] == 0x0ec && ((jump == 0x83ec8b55 && code[6] == 0x60) || (jump == 0x81ec8b55 && !code[7] && !code[8])))) { - ret = cli_append_virus(ctx, "Heuristics.W32.Polipos.A"); - if (ret != CL_CLEAN) { - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - free(jumps); - cli_exe_info_destroy(peinfo); - return ret; - } else - viruses_found++; - } else { - free(jumps); - cli_exe_info_destroy(peinfo); - return ret; - } + ret = cli_append_potentially_unwanted(ctx, "Heuristics.W32.Polipos.A"); + if (ret != CL_SUCCESS) { + free(jumps); + cli_exe_info_destroy(peinfo); + return ret; } } } @@ -3315,20 +3261,11 @@ int cli_scanpe(cli_ctx *ctx) } else { cli_parseres_special(EC32(peinfo->dirs[2].VirtualAddress), EC32(peinfo->dirs[2].VirtualAddress), map, peinfo, fsize, 0, 0, &m, stats); if ((ret = cli_detect_swizz(stats)) == CL_VIRUS) { - ret = cli_append_virus(ctx, "Heuristics.Trojan.Swizzor.Gen"); - if (ret != CL_CLEAN) { - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - free(stats); - cli_exe_info_destroy(peinfo); - return ret; - } else - viruses_found++; - } else { - free(stats); - cli_exe_info_destroy(peinfo); - return ret; - } + ret = cli_append_potentially_unwanted(ctx, "Heuristics.Trojan.Swizzor.Gen"); + if (ret != CL_SUCCESS) { + free(stats); + cli_exe_info_destroy(peinfo); + return ret; } } } @@ -4026,12 +3963,13 @@ int cli_scanpe(cli_ctx *ctx) cli_dbgmsg("***** Scanning decompressed file *****\n"); SHA_OFF; - if ((ret = cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE)) == CL_VIRUS) { + ret = cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE); + if (CL_SUCCESS != ret) { close(ndesc); SHA_RESET; CLI_TMPUNLK(); free(tempfile); - return CL_VIRUS; + return ret; } SHA_RESET; @@ -4185,6 +4123,7 @@ int cli_scanpe(cli_ctx *ctx) !memcmp(epbuff + 0x63 + offset, "\xaa\xe2\xcc", 3) && (fsize >= peinfo->sections[peinfo->nsections - 1].raw + 0xC6 + ecx + offset)) { + size_t num_alerts; char *spinned; if ((spinned = (char *)cli_malloc(fsize)) == NULL) { @@ -4205,25 +4144,23 @@ int cli_scanpe(cli_ctx *ctx) cli_jsonstr(pe_json, "Packer", "yC"); #endif - do { - unsigned int yc_unp_num_viruses = ctx->num_viruses; - const char *yc_unp_virname = NULL; - - if (ctx->virname) - yc_unp_virname = ctx->virname[0]; + // record number of alerts before unpacking and scanning + num_alerts = evidence_num_alerts(ctx->evidence); - cli_dbgmsg("%d,%d,%d,%d\n", peinfo->nsections - 1, peinfo->e_lfanew, ecx, offset); - CLI_UNPTEMP("cli_scanpe: yC", (spinned, 0)); - CLI_UNPRESULTS("cli_scanpe: yC", (yc_decrypt(ctx, spinned, fsize, peinfo->sections, peinfo->nsections - 1, peinfo->e_lfanew, ndesc, ecx, offset)), 0, (spinned, 0)); + cli_dbgmsg("%d,%d,%d,%d\n", peinfo->nsections - 1, peinfo->e_lfanew, ecx, offset); + CLI_UNPTEMP("cli_scanpe: yC", (spinned, 0)); + CLI_UNPRESULTS("cli_scanpe: yC", (yc_decrypt(ctx, spinned, fsize, peinfo->sections, peinfo->nsections - 1, peinfo->e_lfanew, ndesc, ecx, offset)), 0, (spinned, 0)); - if (SCAN_ALLMATCHES && yc_unp_num_viruses != ctx->num_viruses) { - cli_exe_info_destroy(peinfo); - return CL_VIRUS; - } else if (ctx->virname && yc_unp_virname != ctx->virname[0]) { - cli_exe_info_destroy(peinfo); - return CL_VIRUS; - } - } while (0); + // Unpacking may have added new alerts if the bounds-check failed. + // Compare number of alerts now with number of alerts before unpacking/scanning. + // If the number of alerts has increased, then bail. + // + // This preserves the intention of https://github.com/Cisco-Talos/clamav/commit/771c23099893f02f1316960fbe84f62b115a3556 + // although that commit had it bailing if a match occured even in allmatch-mode, which we do not want to do. + if (!SCAN_ALLMATCHES && num_alerts != evidence_num_alerts(ctx->evidence)) { + cli_exe_info_destroy(peinfo); + return CL_VIRUS; + } } } @@ -4473,7 +4410,6 @@ int cli_scanpe(cli_ctx *ctx) case CL_VIRUS: cli_exe_info_destroy(peinfo); cli_bytecode_context_destroy(bc_ctx); - // TODO Handle allmatch return CL_VIRUS; case CL_SUCCESS: ndesc = cli_bytecode_context_getresult_file(bc_ctx, &tempfile); @@ -4494,13 +4430,10 @@ int cli_scanpe(cli_ctx *ctx) return CL_ETIMEOUT; #endif - if (SCAN_ALLMATCHES && viruses_found) - return CL_VIRUS; - - return CL_CLEAN; + return CL_SUCCESS; } -int cli_pe_targetinfo(cli_ctx *ctx, struct cli_exe_info *peinfo) +cl_error_t cli_pe_targetinfo(cli_ctx *ctx, struct cli_exe_info *peinfo) { return cli_peheader(ctx->fmap, peinfo, CLI_PEHEADER_OPT_EXTRACT_VINFO, NULL); } @@ -4533,10 +4466,9 @@ int cli_pe_targetinfo(cli_ctx *ctx, struct cli_exe_info *peinfo) * rsz is just set to 0 for it. * @param ctx The overarching cli_ctx. This is required with certain opts, but * optional otherwise. - * @return If the PE header is parsed successfully, CLI_PEHEADER_RET_SUCCESS - * is returned. If it seems like the PE is broken, - * CLI_PEHEADER_RET_BROKEN_PE is returned. Otherwise, one of the - * other error codes is returned. + * @return If the PE header is parsed successfully, CL_SUCCESS is returned. + * If it seems like the PE is broken, CL_EFORMAT is returned. + * Otherwise, one of the other error codes is returned. * The caller MUST destroy peinfo, regardless of what this function * returns. * @@ -4563,8 +4495,10 @@ int cli_pe_targetinfo(cli_ctx *ctx, struct cli_exe_info *peinfo) * * TODO Same as above but with JSON creation */ -int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ctx *ctx) +cl_error_t cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ctx *ctx) { + cl_error_t ret = CL_ERROR; + uint16_t e_magic; /* DOS signature ("MZ") */ const char *archtype = NULL, *subsystem = NULL; time_t timestamp; @@ -4586,7 +4520,6 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct int native = 0; size_t read; - int ret = CLI_PEHEADER_RET_GENERIC_ERROR; #if HAVE_JSON int toval = 0; struct json_object *pe_json = NULL; @@ -4620,7 +4553,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct if (fmap_readn(map, &(peinfo->e_lfanew), peinfo->offset + 58 + sizeof(e_magic), sizeof(peinfo->e_lfanew)) != sizeof(peinfo->e_lfanew)) { /* truncated header? */ cli_dbgmsg("cli_peheader: Unable to read e_lfanew - truncated header?\n"); - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } @@ -4815,7 +4748,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct cli_dbgmsg("cli_peheader: Invalid NumberOfSections (0)\n"); } } - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } @@ -4848,14 +4781,14 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct pe_add_heuristic_property(ctx, "BadOptionalHeaderSize"); } #endif - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } at = peinfo->offset + peinfo->e_lfanew + sizeof(struct pe_image_file_hdr); if (fmap_readn(map, &(peinfo->pe_opt.opt32), at, sizeof(struct pe_image_optional_hdr32)) != sizeof(struct pe_image_optional_hdr32)) { cli_dbgmsg("cli_peheader: Can't read optional file header\n"); - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } stored_opt_hdr_size = sizeof(struct pe_image_optional_hdr32); @@ -4874,13 +4807,13 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct pe_add_heuristic_property(ctx, "BadOptionalHeaderSizePE32Plus"); } #endif - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } if (fmap_readn(map, (void *)(((size_t) & (peinfo->pe_opt.opt64)) + sizeof(struct pe_image_optional_hdr32)), at, OPT_HDR_SIZE_DIFF) != OPT_HDR_SIZE_DIFF) { cli_dbgmsg("cli_peheader: Can't read additional optional file header bytes\n"); - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } @@ -5063,7 +4996,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct if (!native && (!salign || (salign % 0x1000))) { cli_dbgmsg("cli_peheader: Bad section alignment\n"); if (opts & CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS) { - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } } @@ -5071,7 +5004,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct if (!native && (!falign || (falign % 0x200))) { cli_dbgmsg("cli_peheader: Bad file alignment\n"); if (opts & CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS) { - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } } @@ -5101,7 +5034,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct if (opt_hdr_size < (stored_opt_hdr_size + data_dirs_size)) { cli_dbgmsg("cli_peheader: SizeOfOptionalHeader too small (doesn't include data dir size)\n"); - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } @@ -5152,7 +5085,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct read = fmap_readn(map, section_hdrs, at, peinfo->nsections * sizeof(struct pe_image_section_hdr)); if ((read == (size_t)-1) || (read != peinfo->nsections * sizeof(struct pe_image_section_hdr))) { cli_dbgmsg("cli_peheader: Can't read section header - possibly broken PE file\n"); - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } at += sizeof(struct pe_image_section_hdr) * peinfo->nsections; @@ -5198,7 +5131,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct if (opts & CLI_PEHEADER_OPT_REMOVE_MISSING_SECTIONS) { if (peinfo->nsections == 1) { - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } @@ -5241,7 +5174,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct add_section_info(ctx, &peinfo->sections[i]); if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS) { - ret = CLI_PEHEADER_RET_JSON_TIMEOUT; + ret = CL_ETIMEOUT; goto done; } } @@ -5285,7 +5218,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct if (!salign || (section->urva % salign)) { /* Bad section alignment */ cli_dbgmsg("cli_peheader: Broken PE - section's VirtualAddress is misaligned\n"); if (opts & CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS) { - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } } @@ -5294,7 +5227,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct // section? Why the exception for uraw? if (section->urva >> 31 || section->uvsz >> 31 || (section->rsz && section->uraw >> 31) || peinfo->sections[i].ursz >> 31) { cli_dbgmsg("cli_peheader: Found PE values with sign bit set\n"); - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } @@ -5302,7 +5235,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct if (section->urva != peinfo->hdr_size) { /* Bad first section RVA */ cli_dbgmsg("cli_peheader: First section doesn't start immediately after the header\n"); if (opts & CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS) { - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } } @@ -5313,7 +5246,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct if (section->urva - peinfo->sections[i - 1].urva != peinfo->sections[i - 1].vsz) { /* No holes, no overlapping, no virtual disorder */ cli_dbgmsg("cli_peheader: Virtually misplaced section (wrong order, overlapping, non contiguous)\n"); if (opts & CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS) { - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } } @@ -5340,7 +5273,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct // TODO Should this offset include peinfo->offset? if (!(peinfo->ep = cli_rawaddr(peinfo->vep, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size)) && err) { cli_dbgmsg("cli_peheader: Broken PE file - Can't map EntryPoint to a file offset\n"); - ret = CLI_PEHEADER_RET_BROKEN_PE; + ret = CL_EFORMAT; goto done; } @@ -5349,7 +5282,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct cli_jsonint(pe_json, "EntryPointOffset", peinfo->ep); if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS) { - ret = CLI_PEHEADER_RET_JSON_TIMEOUT; + ret = CL_ETIMEOUT; goto done; } } @@ -5552,7 +5485,7 @@ int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ct // Do final preperations for peinfo to be passed back peinfo->is_dll = is_dll; - ret = CLI_PEHEADER_RET_SUCCESS; + ret = CL_SUCCESS; done: /* In the fail case, peinfo will get destroyed by the caller */ @@ -5618,7 +5551,7 @@ cl_error_t cli_check_auth_header(cli_ctx *ctx, struct cli_exe_info *peinfo) peinfo = &_peinfo; cli_exe_info_init(peinfo, 0); - if (cli_peheader(ctx->fmap, peinfo, CLI_PEHEADER_OPT_NONE, NULL) != CLI_PEHEADER_RET_SUCCESS) { + if (CL_SUCCESS != cli_peheader(ctx->fmap, peinfo, CLI_PEHEADER_OPT_NONE, NULL)) { cli_exe_info_destroy(peinfo); return CL_EFORMAT; } @@ -5761,7 +5694,7 @@ cl_error_t cli_check_auth_header(cli_ctx *ctx, struct cli_exe_info *peinfo) // At this point we should compute the SHA1 authenticode hash to see // whether we've had any hashes added from external catalog files static const struct supported_hashes { - const enum CLI_HASH_TYPE hashtype; + const cli_hash_type_t hashtype; const char *hashctx_name; } supported_hashes[] = { {CLI_HASH_SHA1, "sha1"}, @@ -5769,8 +5702,8 @@ cl_error_t cli_check_auth_header(cli_ctx *ctx, struct cli_exe_info *peinfo) }; for (i = 0; i < (sizeof(supported_hashes) / sizeof(supported_hashes[0])); i++) { - const enum CLI_HASH_TYPE hashtype = supported_hashes[i].hashtype; - const char *hashctx_name = supported_hashes[i].hashctx_name; + const cli_hash_type_t hashtype = supported_hashes[i].hashtype; + const char *hashctx_name = supported_hashes[i].hashctx_name; if (!cli_hm_have_size(ctx->engine->hm_fp, hashtype, 2)) { continue; @@ -5872,7 +5805,7 @@ cl_error_t cli_genhash_pe(cli_ctx *ctx, unsigned int class, int type, stats_sect // if so, use that to avoid having to re-parse the header cli_exe_info_init(peinfo, 0); - if (cli_peheader(ctx->fmap, peinfo, CLI_PEHEADER_OPT_NONE, NULL) != CLI_PEHEADER_RET_SUCCESS) { + if (cli_peheader(ctx->fmap, peinfo, CLI_PEHEADER_OPT_NONE, NULL) != CL_SUCCESS) { cli_exe_info_destroy(peinfo); return CL_EFORMAT; } diff --git a/libclamav/pe.h b/libclamav/pe.h index 1b61e25479..b2f491a806 100644 --- a/libclamav/pe.h +++ b/libclamav/pe.h @@ -87,13 +87,8 @@ enum { #define CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS 0x8 #define CLI_PEHEADER_OPT_REMOVE_MISSING_SECTIONS 0x10 -#define CLI_PEHEADER_RET_SUCCESS 0 -#define CLI_PEHEADER_RET_GENERIC_ERROR -1 -#define CLI_PEHEADER_RET_BROKEN_PE -2 -#define CLI_PEHEADER_RET_JSON_TIMEOUT -3 - -int cli_pe_targetinfo(cli_ctx *ctx, struct cli_exe_info *peinfo); -int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ctx *ctx); +cl_error_t cli_pe_targetinfo(cli_ctx *ctx, struct cli_exe_info *peinfo); +cl_error_t cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ctx *ctx); cl_error_t cli_check_auth_header(cli_ctx *ctx, struct cli_exe_info *peinfo); cl_error_t cli_genhash_pe(cli_ctx *ctx, unsigned int class, int type, stats_section_t *hashes); diff --git a/libclamav/phishcheck.c b/libclamav/phishcheck.c index 0baaf6fe67..c51ba33b6a 100644 --- a/libclamav/phishcheck.c +++ b/libclamav/phishcheck.c @@ -732,9 +732,6 @@ cl_error_t phishingScan(cli_ctx* ctx, tag_arguments_t* hrefs) goto done; } - if (!ctx->found_possibly_unwanted && !SCAN_ALLMATCHES) - *ctx->virname = NULL; - for (i = 0; i < hrefs->count; i++) { struct url_check urls; enum phish_status phishing_verdict; @@ -776,32 +773,32 @@ cl_error_t phishingScan(cli_ctx* ctx, tag_arguments_t* hrefs) case CL_PHISH_CLEAN: continue; case CL_PHISH_NUMERIC_IP: - status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.NumericIP"); + status = cli_append_potentially_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.NumericIP"); break; case CL_PHISH_CLOAKED_NULL: - status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.Null"); /*fakesite%01%00@fake.example.com*/ + status = cli_append_potentially_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.Null"); /*fakesite%01%00@fake.example.com*/ break; case CL_PHISH_SSL_SPOOF: - status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.SSL-Spoof"); + status = cli_append_potentially_unwanted(ctx, "Heuristics.Phishing.Email.SSL-Spoof"); break; case CL_PHISH_CLOAKED_UIU: - status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.Username"); /*http://banksite@fake.example.com*/ + status = cli_append_potentially_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.Username"); /*http://banksite@fake.example.com*/ break; case CL_PHISH_HASH0: - status = cli_append_possibly_unwanted(ctx, "Heuristics.Safebrowsing.Suspected-malware_safebrowsing.clamav.net"); + status = cli_append_potentially_unwanted(ctx, "Heuristics.Safebrowsing.Suspected-malware_safebrowsing.clamav.net"); break; case CL_PHISH_HASH1: - status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.URL.Blocked"); + status = cli_append_potentially_unwanted(ctx, "Heuristics.Phishing.URL.Blocked"); break; case CL_PHISH_HASH2: - status = cli_append_possibly_unwanted(ctx, "Heuristics.Safebrowsing.Suspected-phishing_safebrowsing.clamav.net"); + status = cli_append_potentially_unwanted(ctx, "Heuristics.Safebrowsing.Suspected-phishing_safebrowsing.clamav.net"); break; case CL_PHISH_NOMATCH: default: - status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.SpoofedDomain"); + status = cli_append_potentially_unwanted(ctx, "Heuristics.Phishing.Email.SpoofedDomain"); break; } - if (CL_CLEAN != status && !SCAN_ALLMATCHES) { + if (CL_SUCCESS != status) { goto done; } } diff --git a/libclamav/png.c b/libclamav/png.c index ad5ae8de68..b72bcd8593 100644 --- a/libclamav/png.c +++ b/libclamav/png.c @@ -77,7 +77,7 @@ typedef struct __attribute__((packed)) { cl_error_t cli_parsepng(cli_ctx *ctx) { - cl_error_t status = CL_ERROR; + cl_error_t status = CL_SUCCESS; uint64_t chunk_data_length = 0; char chunk_type[5] = {'\0', '\0', '\0', '\0', '\0'}; @@ -114,8 +114,7 @@ cl_error_t cli_parsepng(cli_ctx *ctx) if (chunk_data_length > (uint64_t)0x7fffffff) { cli_dbgmsg("PNG: invalid chunk length (too large): 0x" STDx64 "\n", chunk_data_length); if (SCAN_HEURISTIC_BROKEN_MEDIA) { - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.PNG.InvalidChunkLength"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.PNG.InvalidChunkLength"); } goto scan_overlay; } @@ -123,8 +122,7 @@ cl_error_t cli_parsepng(cli_ctx *ctx) if (fmap_readn(map, chunk_type, offset, PNG_CHUNK_TYPE_SIZE) != PNG_CHUNK_TYPE_SIZE) { cli_dbgmsg("PNG: EOF while reading chunk type\n"); if (SCAN_HEURISTIC_BROKEN_MEDIA) { - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.PNG.EOFReadingChunkType"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.PNG.EOFReadingChunkType"); } goto scan_overlay; } @@ -141,8 +139,7 @@ cl_error_t cli_parsepng(cli_ctx *ctx) if (NULL == ptr) { cli_warnmsg("PNG: Unexpected early end-of-file.\n"); if (SCAN_HEURISTIC_BROKEN_MEDIA) { - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.PNG.EOFReadingChunk"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.PNG.EOFReadingChunk"); } goto scan_overlay; } @@ -263,8 +260,8 @@ cl_error_t cli_parsepng(cli_ctx *ctx) if (color_type == 3) { if ((chunk_data_length > 256 || chunk_data_length > num_palette_entries) && !have_PLTE) { - status = cli_append_virus(ctx, "Heuristics.PNG.CVE-2004-0597"); - goto done; + status = cli_append_potentially_unwanted(ctx, "Heuristics.PNG.CVE-2004-0597"); + goto done; // always, even if allmatch mode means status comes back 'success' instead of 'virus'. } } } @@ -272,8 +269,7 @@ cl_error_t cli_parsepng(cli_ctx *ctx) if (fmap_readn(map, &chunk_crc, offset, PNG_CHUNK_CRC_SIZE) != PNG_CHUNK_CRC_SIZE) { cli_dbgmsg("PNG: EOF while reading chunk crc\n"); if (SCAN_HEURISTIC_BROKEN_MEDIA) { - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.PNG.EOFReadingChunkCRC"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.PNG.EOFReadingChunkCRC"); } goto scan_overlay; } @@ -294,21 +290,16 @@ cl_error_t cli_parsepng(cli_ctx *ctx) } scan_overlay: - if (status == CL_EPARSE) { - /* We added with cli_append_possibly_unwanted so it will alert at the end if nothing else matches. */ - status = CL_CLEAN; - } - /* Check if there's an overlay, and scan it if one exists. */ - if (map->len > offset) { - cli_dbgmsg("PNG: Found " STDu64 " additional data after end of PNG! Scanning as a nested file.\n", map->len - offset); - status = cli_magic_scan_nested_fmap_type(map, (size_t)offset, map->len - offset, ctx, - CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); - goto done; + if (CL_SUCCESS == status) { + /* Check if there's an overlay, and scan it if one exists. */ + if (map->len > offset) { + cli_dbgmsg("PNG: Found " STDu64 " additional data after end of PNG! Scanning as a nested file.\n", map->len - offset); + status = cli_magic_scan_nested_fmap_type(map, (size_t)offset, map->len - offset, ctx, CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); + goto done; + } } - status = CL_CLEAN; - done: return status; diff --git a/libclamav/readdb.c b/libclamav/readdb.c index ea547b839d..344e531db4 100644 --- a/libclamav/readdb.c +++ b/libclamav/readdb.c @@ -127,7 +127,7 @@ char *cli_virname(const char *virname, unsigned int official) cl_error_t cli_sigopts_handler(struct cli_matcher *root, const char *virname, const char *hexsig, uint8_t sigopts, uint16_t rtype, uint16_t type, - const char *offset, uint8_t target, const uint32_t *lsigid, unsigned int options) + const char *offset, const uint32_t *lsigid, unsigned int options) { char *hexcpy, *start, *end, *mid; unsigned int i; @@ -191,7 +191,7 @@ cl_error_t cli_sigopts_handler(struct cli_matcher *root, const char *virname, co return CL_EMALFDB; } - ret = cli_add_content_match_pattern(root, virname, hexcpy, sigopts, rtype, type, offset, target, lsigid, options); + ret = cli_add_content_match_pattern(root, virname, hexcpy, sigopts, rtype, type, offset, lsigid, options); free(hexcpy); return ret; } @@ -203,7 +203,7 @@ cl_error_t cli_sigopts_handler(struct cli_matcher *root, const char *virname, co if (start != end && mid && (*(++mid) == '#' || !strncmp(mid, ">>", 2) || !strncmp(mid, "<<", 2) || !strncmp(mid, "0#", 2))) { /* TODO byte compare currently does not have support for sigopts, pass through */ - ret = cli_add_content_match_pattern(root, virname, hexcpy, sigopts, rtype, type, offset, target, lsigid, options); + ret = cli_add_content_match_pattern(root, virname, hexcpy, sigopts, rtype, type, offset, lsigid, options); free(hexcpy); return ret; } @@ -300,7 +300,7 @@ cl_error_t cli_sigopts_handler(struct cli_matcher *root, const char *virname, co } /* NOCASE sigopt is handled in cli_ac_addsig */ - ret = cli_add_content_match_pattern(root, virname, hexovr, sigopts, rtype, type, offset, target, lsigid, options); + ret = cli_add_content_match_pattern(root, virname, hexovr, sigopts, rtype, type, offset, lsigid, options); free(hexovr); if (ret != CL_SUCCESS || !(sigopts & ACPATT_OPTION_ASCII)) { free(hexcpy); @@ -312,7 +312,7 @@ cl_error_t cli_sigopts_handler(struct cli_matcher *root, const char *virname, co } /* ASCII sigopt; NOCASE sigopt is handled in cli_ac_addsig */ - ret = cli_add_content_match_pattern(root, virname, hexcpy, sigopts, rtype, type, offset, target, lsigid, options); + ret = cli_add_content_match_pattern(root, virname, hexcpy, sigopts, rtype, type, offset, lsigid, options); free(hexcpy); return ret; } @@ -426,7 +426,7 @@ static cl_error_t readdb_load_regex_subsignature(struct cli_matcher *root, const } cl_error_t readdb_parse_ldb_subsignature(struct cli_matcher *root, const char *virname, char *hexsig, - const char *offset, uint8_t target, const uint32_t *lsigid, unsigned int options, + const char *offset, const uint32_t *lsigid, unsigned int options, int current_subsig_index, int num_subsigs, struct cli_lsig_tdb *tdb) { cl_error_t status = CL_EPARSE; @@ -493,7 +493,7 @@ cl_error_t readdb_parse_ldb_subsignature(struct cli_matcher *root, const char *v goto done; } - if ((ret = cli_ac_addpatt(root, patt))) { + if (CL_SUCCESS != (ret = cli_ac_addpatt(root, patt))) { MPOOL_FREE(root->mempool, patt->pattern); free(patt); status = ret; @@ -615,9 +615,9 @@ cl_error_t readdb_parse_ldb_subsignature(struct cli_matcher *root, const char *v sig = (subtokens_count % 2) ? subtokens[0] : subtokens[1]; if (subsig_opts) { - ret = cli_sigopts_handler(root, virname, sig, subsig_opts, 0, 0, offset, target, lsigid, options); + ret = cli_sigopts_handler(root, virname, sig, subsig_opts, 0, 0, offset, lsigid, options); } else { - ret = cli_add_content_match_pattern(root, virname, sig, 0, 0, 0, offset, target, lsigid, options); + ret = cli_add_content_match_pattern(root, virname, sig, 0, 0, 0, offset, lsigid, options); } if (CL_SUCCESS != ret) { @@ -647,13 +647,12 @@ cl_error_t readdb_parse_ldb_subsignature(struct cli_matcher *root, const char *v * @param hexsig The string containing the regex * @param subsig_opts Content match pattern options. See ACPATT_* macros in matcher-ac.h. * @param offset The string offset where the pattern starts - * @param target The clamav target type. * @param lsigid An array of 2 uint32_t numbers: lsig_id and subsig_id. May be NULL for testing. * @param options Database options. See CL_DB_* macros in clamav.h. * @return cl_error_t */ static cl_error_t readdb_parse_yara_string(struct cli_matcher *root, const char *virname, char *hexsig, uint8_t subsig_opts, - const char *offset, uint8_t target, const uint32_t *lsigid, unsigned int options) + const char *offset, const uint32_t *lsigid, unsigned int options) { cl_error_t status = CL_EPARSE; cl_error_t ret; @@ -669,9 +668,9 @@ static cl_error_t readdb_parse_yara_string(struct cli_matcher *root, const char * Looks like an AC/BM content match subsignature. */ if (subsig_opts) { - ret = cli_sigopts_handler(root, virname, hexsig, subsig_opts, 0, 0, offset, target, lsigid, options); + ret = cli_sigopts_handler(root, virname, hexsig, subsig_opts, 0, 0, offset, lsigid, options); } else { - ret = cli_add_content_match_pattern(root, virname, hexsig, 0, 0, 0, offset, target, lsigid, options); + ret = cli_add_content_match_pattern(root, virname, hexsig, 0, 0, 0, offset, lsigid, options); } } @@ -700,14 +699,13 @@ static cl_error_t readdb_parse_yara_string(struct cli_matcher *root, const char * @param rtype * @param type * @param offset The string offset where the pattern starts - * @param target The clamav target type. * @param lsigid An array of 2 uint32_t numbers: lsig_id and subsig_id. May be NULL for testing. * @param options Database options. See CL_DB_* macros in clamav.h. * @return cl_error_t */ cl_error_t cli_add_content_match_pattern(struct cli_matcher *root, const char *virname, const char *hexsig, uint8_t sigopts, uint16_t rtype, uint16_t type, - const char *offset, uint8_t target, const uint32_t *lsigid, unsigned int options) + const char *offset, const uint32_t *lsigid, unsigned int options) { struct cli_bm_patt *bm_new; char *pt, *hexcpy, *n, l, r; @@ -747,7 +745,7 @@ cl_error_t cli_add_content_match_pattern(struct cli_matcher *root, const char *v } strcat(hexcpy, ++wild); - ret = cli_add_content_match_pattern(root, virname, hexcpy, sigopts, rtype, type, offset, target, lsigid, options); + ret = cli_add_content_match_pattern(root, virname, hexcpy, sigopts, rtype, type, offset, lsigid, options); free(hexcpy); return ret; @@ -820,7 +818,7 @@ cl_error_t cli_add_content_match_pattern(struct cli_matcher *root, const char *v *pt++ = 0; } - if ((ret = cli_ac_addsig(root, virname, start, sigopts, root->ac_partsigs, parts, i, rtype, type, mindist, maxdist, offset, lsigid, options))) { + if (CL_SUCCESS != (ret = cli_ac_addsig(root, virname, start, sigopts, root->ac_partsigs, parts, i, rtype, type, mindist, maxdist, offset, lsigid, options))) { cli_errmsg("cli_add_content_match_pattern: Problem adding signature (1).\n"); error = 1; break; @@ -921,7 +919,7 @@ cl_error_t cli_add_content_match_pattern(struct cli_matcher *root, const char *v return CL_EMALFDB; } - if ((ret = cli_ac_addsig(root, virname, pt, sigopts, root->ac_partsigs, parts, i, rtype, type, 0, 0, offset, lsigid, options))) { + if (CL_SUCCESS != (ret = cli_ac_addsig(root, virname, pt, sigopts, root->ac_partsigs, parts, i, rtype, type, 0, 0, offset, lsigid, options))) { cli_errmsg("cli_add_content_match_pattern: Problem adding signature (2).\n"); free(pt); return ret; @@ -1275,7 +1273,7 @@ static cl_error_t cli_loaddb(FILE *fs, struct cl_engine *engine, unsigned int *s if (*pt == '=') continue; - if (CL_SUCCESS != (ret = cli_add_content_match_pattern(root, start, pt, 0, 0, 0, "*", 0, NULL, options))) { + if (CL_SUCCESS != (ret = cli_add_content_match_pattern(root, start, pt, 0, 0, 0, "*", NULL, options))) { cli_dbgmsg("cli_loaddb: cli_add_content_match_pattern failed on line %d\n", line); ret = CL_EMALFDB; break; @@ -1584,7 +1582,7 @@ static int cli_loadndb(FILE *fs, struct cl_engine *engine, unsigned int *signo, const char *sig, *virname, *offset, *pt; struct cli_matcher *root; int line = 0, sigs = 0, ret = 0, tokens_count; - unsigned short target; + cli_target_t target; unsigned int phish = options & CL_DB_PHISHING; UNUSEDPARAM(dbname); @@ -1661,19 +1659,19 @@ static int cli_loadndb(FILE *fs, struct cl_engine *engine, unsigned int *signo, ret = CL_EMALFDB; break; } - target = (unsigned short)atoi(pt); + target = (cli_target_t)atoi(pt); - if (target >= CLI_MTARGETS) { - cli_dbgmsg("Not supported target type in signature for %s\n", virname); + if (target >= CLI_MTARGETS || target < 0) { + cli_dbgmsg("Not supported target type (%d) in signature for %s\n", (int)target, virname); continue; } - root = engine->root[target]; + root = engine->root[(size_t)target]; offset = tokens[2]; sig = tokens[3]; - if (CL_SUCCESS != (ret = cli_add_content_match_pattern(root, virname, sig, 0, 0, 0, offset, target, NULL, options))) { + if (CL_SUCCESS != (ret = cli_add_content_match_pattern(root, virname, sig, 0, 0, 0, offset, NULL, options))) { ret = CL_EMALFDB; break; } @@ -2018,13 +2016,13 @@ static inline int init_tdb(struct cli_lsig_tdb *tdb, struct cl_engine *engine, c return CL_BREAK; } - if ((tdb->icongrp1 || tdb->icongrp2) && tdb->target[0] != 1) { + if ((tdb->icongrp1 || tdb->icongrp2) && tdb->target[0] != TARGET_PE) { FREE_TDB_P(tdb); cli_errmsg("init_tdb: IconGroup is only supported in PE (target 1) signatures\n"); return CL_EMALFDB; } - if ((tdb->ep || tdb->nos) && tdb->target[0] != 1 && tdb->target[0] != 6 && tdb->target[0] != 9) { + if ((tdb->ep || tdb->nos) && tdb->target[0] != TARGET_PE && tdb->target[0] != TARGET_ELF && tdb->target[0] != TARGET_MACHO) { FREE_TDB_P(tdb); cli_errmsg("init_tdb: EntryPoint/NumberOfSections is only supported in PE/ELF/Mach-O signatures\n"); return CL_EMALFDB; @@ -2047,7 +2045,6 @@ static cl_error_t load_oneldb(char *buffer, int chkpua, struct cl_engine *engine struct cli_ac_lsig *lsig = NULL; char *tokens[LDB_TOKENS + 1]; int i, subsigs, tokens_count; - unsigned short target = 0; struct cli_matcher *root; struct cli_lsig_tdb tdb; uint32_t lsigid[2]; @@ -2186,7 +2183,7 @@ static cl_error_t load_oneldb(char *buffer, int chkpua, struct cl_engine *engine lsigid[1] = i; // handle each LDB subsig - ret = readdb_parse_ldb_subsignature(root, virname, tokens[3 + i], "*", target, lsigid, options, i, subsigs, &tdb); + ret = readdb_parse_ldb_subsignature(root, virname, tokens[3 + i], "*", lsigid, options, i, subsigs, &tdb); if (CL_SUCCESS != ret) { cli_errmsg("cli_loadldb: failed to parse subsignature %d in %s\n", i, virname); status = ret; @@ -2496,7 +2493,7 @@ static int cli_loadftm(FILE *fs, struct cl_engine *engine, unsigned int options, magictype = atoi(tokens[0]); if (magictype == 1) { /* A-C */ - if (CL_SUCCESS != (ret = cli_add_content_match_pattern(engine->root[0], tokens[3], tokens[2], 0, rtype, type, tokens[1], 0, NULL, options))) + if (CL_SUCCESS != (ret = cli_add_content_match_pattern(engine->root[0], tokens[3], tokens[2], 0, rtype, type, tokens[1], NULL, options))) break; } else if ((magictype == 0) || (magictype == 4)) { /* memcmp() */ @@ -3802,7 +3799,7 @@ static int yara_hexstr_verify(YR_STRING *string, const char *hexstr, uint32_t *l } /* Long Check: Attempt to load hexstr */ - if (CL_SUCCESS != (ret = cli_sigopts_handler(engine->test_root, "test-hex", hexstr, 0, 0, 0, "*", 0, lsigid, options))) { + if (CL_SUCCESS != (ret = cli_sigopts_handler(engine->test_root, "test-hex", hexstr, 0, 0, 0, "*", lsigid, options))) { if (ret == CL_EMALFDB) { cli_warnmsg("load_oneyara[verify]: recovered from database loading error\n"); /* TODO: if necessary, reset testing matcher if error occurs */ @@ -3831,7 +3828,6 @@ static int load_oneyara(YR_RULE *rule, int chkpua, struct cl_engine *engine, uns uint32_t lsigid[2]; struct cli_matcher *root; struct cli_ac_lsig **newtable, *lsig, *tsig = NULL; - unsigned short target = 0; char *logic = NULL, *target_str = NULL; char *newident = NULL; /* size_t lsize; */ // only used in commented out code @@ -4312,7 +4308,7 @@ static int load_oneyara(YR_RULE *rule, int chkpua, struct cl_engine *engine, uns (ytable.table[i]->sigopts & ACPATT_OPTION_ASCII) ? "a" : ""); ret = readdb_parse_yara_string(root, newident, ytable.table[i]->hexstr, ytable.table[i]->sigopts, - ytable.table[i]->offset, target, lsigid, options); + ytable.table[i]->offset, lsigid, options); if (CL_SUCCESS != ret) { root->ac_lsigs--; FREE_TDB(tdb); @@ -5304,7 +5300,7 @@ cl_error_t cl_load(const char *path, struct cl_engine *engine, unsigned int *sig cli_dbgmsg("Bytecode engine disabled\n"); } - if (!engine->cache && cli_cache_init(engine)) + if (!engine->cache && clean_cache_init(engine)) return CL_EMEM; engine->dboptions |= dboptions; @@ -5842,7 +5838,7 @@ cl_error_t cl_engine_free(struct cl_engine *engine) TASK_COMPLETE(); if (engine->cache) { - cli_cache_destroy(engine); + clean_cache_destroy(engine); } TASK_COMPLETE(); @@ -6062,7 +6058,7 @@ cl_error_t cl_engine_compile(struct cl_engine *engine) MPOOL_FLUSH(engine->mempool); /* Compile bytecode */ - if ((ret = cli_bytecode_prepare2(engine, &engine->bcs, engine->dconf->bytecode))) { + if (CL_SUCCESS != (ret = cli_bytecode_prepare2(engine, &engine->bcs, engine->dconf->bytecode))) { cli_errmsg("Unable to compile/load bytecode: %s\n", cl_strerror(ret)); return ret; } diff --git a/libclamav/readdb.h b/libclamav/readdb.h index f6f33efe25..d0b76877b6 100644 --- a/libclamav/readdb.h +++ b/libclamav/readdb.h @@ -140,14 +140,13 @@ char *cli_virname(const char *virname, unsigned int official); * @param rtype * @param type * @param offset - * @param target * @param lsigid * @param options * @return cl_error_t */ cl_error_t cli_sigopts_handler(struct cli_matcher *root, const char *virname, const char *hexsig, uint8_t sigopts, uint16_t rtype, uint16_t type, - const char *offset, uint8_t target, const uint32_t *lsigid, unsigned int options); + const char *offset, const uint32_t *lsigid, unsigned int options); /** * @brief Parse body-based patterns that DO NOT have subsignature modifiers. @@ -162,14 +161,13 @@ cl_error_t cli_sigopts_handler(struct cli_matcher *root, const char *virname, co * @param rtype * @param type * @param offset - * @param target * @param lsigid * @param options * @return cl_error_t */ cl_error_t cli_add_content_match_pattern(struct cli_matcher *root, const char *virname, const char *hexsig, uint8_t sigopts, uint16_t rtype, uint16_t type, - const char *offset, uint8_t target, const uint32_t *lsigid, unsigned int options); + const char *offset, const uint32_t *lsigid, unsigned int options); /** * @brief Parse a subsignature from a logical signature. @@ -188,7 +186,6 @@ cl_error_t cli_add_content_match_pattern(struct cli_matcher *root, const char *v * @param virname * @param hexsig * @param offset - * @param target * @param lsigid An array of 2 uint32_t numbers: lsig_id and subsig_id. May be NULL for testing. * @param options * @param current_subsig_index @@ -197,7 +194,7 @@ cl_error_t cli_add_content_match_pattern(struct cli_matcher *root, const char *v * @return cl_error_t */ cl_error_t readdb_parse_ldb_subsignature(struct cli_matcher *root, const char *virname, char *hexsig, - const char *offset, uint8_t target, const uint32_t *lsigid, unsigned int options, + const char *offset, const uint32_t *lsigid, unsigned int options, int current_subsig_index, int num_subsigs, struct cli_lsig_tdb *tdb); cl_error_t cli_load(const char *filename, struct cl_engine *engine, unsigned int *signo, unsigned int options, struct cli_dbio *dbio); diff --git a/libclamav/regex_list.c b/libclamav/regex_list.c index 6fae820b76..df689134e8 100644 --- a/libclamav/regex_list.c +++ b/libclamav/regex_list.c @@ -757,7 +757,6 @@ static cl_error_t add_pattern_suffix(void *cbdata, const char *suffix, size_t su struct regex_matcher *matcher = cbdata; struct regex_list *regex = NULL; const struct cli_element *el = NULL; - void *tmp_matcher = NULL; /* save original address if OOM occurs */ cl_error_t ret = CL_SUCCESS; if (NULL == matcher) { @@ -802,9 +801,8 @@ static cl_error_t add_pattern_suffix(void *cbdata, const char *suffix, size_t su list_add_tail(&matcher->suffix_regexes[(size_t)el->data], regex); } else { /* new suffix */ - size_t n = matcher->suffix_cnt; - el = cli_hashtab_insert(&matcher->suffix_hash, suffix, suffix_len, (cli_element_data)n); - tmp_matcher = matcher->suffix_regexes; /* save the current value before cli_realloc() */ + size_t n = matcher->suffix_cnt; + el = cli_hashtab_insert(&matcher->suffix_hash, suffix, suffix_len, (cli_element_data)n); CLI_REALLOC(matcher->suffix_regexes, (n + 1) * sizeof(*matcher->suffix_regexes), cli_errmsg("add_pattern_suffix: Unable to reallocate memory for matcher->suffix_regexes\n"); diff --git a/libclamav/regex_pcre.h b/libclamav/regex_pcre.h index d1f4127984..adb51e80f3 100644 --- a/libclamav/regex_pcre.h +++ b/libclamav/regex_pcre.h @@ -54,7 +54,7 @@ struct cli_pcre_data { }; struct cli_pcre_results { - int err; + cl_error_t err; uint32_t match[2]; /* populated by cli_pcre_match to be start (0) and end (1) offset of match */ pcre2_match_data *match_data; @@ -69,7 +69,7 @@ struct cli_pcre_data { }; struct cli_pcre_results { - int err; + cl_error_t err; uint32_t match[2]; /* populated by cli_pcre_match to be start (0) and end (1) offset of match */ int ovector[OVECCOUNT]; @@ -79,6 +79,18 @@ struct cli_pcre_results { cl_error_t cli_pcre_init_internal(); cl_error_t cli_pcre_addoptions(struct cli_pcre_data *pd, const char **opt, int errout); cl_error_t cli_pcre_compile(struct cli_pcre_data *pd, long long unsigned match_limit, long long unsigned match_limit_recursion, unsigned int options, int opt_override); + +/** + * @brief perform a pcre match on a string + * + * @param pd + * @param buffer + * @param buflen + * @param override_offset + * @param options + * @param results + * @return int greater than zero if a match. 0 if no match. A PCRE2_ERROR_* error code if something went wrong. + */ int cli_pcre_match(struct cli_pcre_data *pd, const unsigned char *buffer, size_t buflen, size_t override_offset, int options, struct cli_pcre_results *results); void cli_pcre_report(const struct cli_pcre_data *pd, const unsigned char *buffer, size_t buflen, int rc, struct cli_pcre_results *results); diff --git a/libclamav/regex_suffix.c b/libclamav/regex_suffix.c index 2eeb3cc919..b65d501e22 100644 --- a/libclamav/regex_suffix.c +++ b/libclamav/regex_suffix.c @@ -329,6 +329,7 @@ static struct node *parse_regex(const uint8_t *p, size_t *last) /* next char is escaped, advance pointer * and let fall-through handle it */ ++*last; + /* fall-through */ default: right = make_leaf(p[*last]); v = make_node(concat, v, right); @@ -479,7 +480,7 @@ cl_error_t cli_regex2suffix(const char *pattern, regex_t *preg, suffix_callback cli_errmsg("cli_regex2suffix: unable to strdup regex.pattern"); rc = REG_ESPACE); - n = parse_regex(pattern, &last); + n = parse_regex((const uint8_t *)pattern, &last); if (!n) { rc = REG_ESPACE; goto done; diff --git a/libclamav/rtf.c b/libclamav/rtf.c index 682835a2f5..21f022fd58 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 6666c63571..8db7f27e66 100644 --- a/libclamav/scanners.c +++ b/libclamav/scanners.c @@ -54,7 +54,6 @@ #include #include "clamav_rust.h" - #include "clamav.h" #include "others.h" #include "dconf.h" @@ -134,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))) { @@ -154,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; } } @@ -196,9 +184,6 @@ cl_error_t cli_magic_scan_dir(const char *dir, cli_ctx *ctx, uint32_t attributes free(fname); } - if (SCAN_ALLMATCHES && viruses_found) - status = CL_VIRUS; - return status; } @@ -234,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; @@ -268,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) { @@ -307,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; } } @@ -364,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; } @@ -465,17 +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) { - cli_dbgmsg("RAR: infected with %s\n", cli_get_last_virus(ctx)); - status = CL_VIRUS; - viruses_found++; + if (status != CL_SUCCESS) { + // Bail out if "virus" and also if exceeded scan maximums, etc. + goto done; } } } @@ -488,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... */ @@ -512,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) { @@ -551,23 +521,17 @@ static cl_error_t cli_scanrar_file(const char *filepath, int desc, cli_ctx *ctx) 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_virus(ctx, "Heuristics.Encrypted.RAR")) { + 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; } @@ -666,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; @@ -685,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; @@ -701,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) { @@ -753,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; } } @@ -811,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; @@ -857,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); @@ -923,10 +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) { - cli_dbgmsg("EGG: infected with %s\n", cli_get_last_virus(ctx)); - status = CL_VIRUS; - viruses_found++; + if (status != CL_SUCCESS) { + goto done; } if (NULL != filename_base) { @@ -951,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; @@ -973,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; @@ -1003,34 +964,26 @@ static cl_error_t cli_scanegg(cli_ctx *ctx) 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_virus(ctx, "Heuristics.Encrypted.EGG")) { + 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"); @@ -1056,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) { @@ -1080,30 +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) { - cli_dbgmsg("ARJ: infected with %s\n", cli_get_last_virus(ctx)); - 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; @@ -1111,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; } @@ -1175,22 +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))) { - cli_dbgmsg("GZip: Infected with %s\n", cli_get_last_virus(ctx)); + 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; } @@ -1277,8 +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))) { - cli_dbgmsg("GZip: Infected with %s\n", cli_get_last_virus(ctx)); + 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)) { @@ -1287,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; } @@ -1383,18 +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))) { - cli_dbgmsg("Bzip: Infected with %s\n", cli_get_last_virus(ctx)); + 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) @@ -1457,7 +1402,7 @@ static cl_error_t cli_scanxz(cli_ctx *ctx) rc = cli_XzDecode(&strm); if (XZ_RESULT_OK != rc && XZ_STREAM_END != rc) { if (rc == XZ_DIC_HEURISTIC) { - ret = cli_append_virus(ctx, "Heuristics.XZ.DicSizeLimit"); + ret = cli_append_potentially_unwanted(ctx, "Heuristics.XZ.DicSizeLimit"); goto xz_exit; } cli_errmsg("cli_scanxz: decompress error: %d\n", rc); @@ -1492,16 +1437,16 @@ static cl_error_t cli_scanxz(cli_ctx *ctx) } while (XZ_STREAM_END != rc); /* scan decompressed file */ - if (CL_VIRUS == (ret = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE))) { - cli_dbgmsg("cli_scanxz: Infected with %s\n", cli_get_last_virus(ctx)); - } + ret = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE); xz_exit: cli_XzShutdown(&strm); close(fd); - if (!ctx->engine->keeptmp) - if (cli_unlink(tmpname) && ret == CL_CLEAN) + if (!ctx->engine->keeptmp) { + if (cli_unlink(tmpname) && ret == CL_CLEAN) { ret = CL_EUNLINK; + } + } free(tmpname); free(buf); return ret; @@ -1544,23 +1489,23 @@ static cl_error_t cli_scanszdd(cli_ctx *ctx) static cl_error_t vba_scandata(const unsigned char *data, size_t len, cli_ctx *ctx) { - cl_error_t ret = CL_SUCCESS; - struct cli_matcher *groot = ctx->engine->root[0]; - struct cli_matcher *troot = ctx->engine->root[2]; + cl_error_t ret = CL_SUCCESS; + struct cli_matcher *generic_ac_root = ctx->engine->root[0]; + struct cli_matcher *target_ac_root = ctx->engine->root[2]; struct cli_ac_data gmdata, tmdata; 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; - if ((ret = cli_ac_initdata(&tmdata, troot->ac_partsigs, troot->ac_lsigs, troot->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN))) { + if ((ret = cli_ac_initdata(&tmdata, target_ac_root->ac_partsigs, target_ac_root->ac_lsigs, target_ac_root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN))) { goto done; } tmdata_initialized = true; - if ((ret = cli_ac_initdata(&gmdata, groot->ac_partsigs, groot->ac_lsigs, groot->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN))) { + if ((ret = cli_ac_initdata(&gmdata, generic_ac_root->ac_partsigs, generic_ac_root->ac_lsigs, generic_ac_root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN))) { goto done; } gmdata_initialized = true; @@ -1569,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, troot, &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, groot, &gmdata, NULL, NULL); - } + 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); + +done: + if (must_pop_stack) { (void)cli_recursion_stack_pop(ctx); /* Restore the parent fmap */ } -done: if (NULL != new_map) { funmap(new_map); } @@ -1615,9 +1561,6 @@ static cl_error_t vba_scandata(const unsigned char *data, size_t len, cli_ctx *c cli_ac_freedata(&gmdata); } - if (ret == CL_CLEAN && viruses_found) { - ret = CL_VIRUS; - } return ret; } @@ -1689,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); @@ -1729,12 +1671,9 @@ static cl_error_t cli_ole2_tempdir_scan_vba_new(const char *dir, cli_ctx *ctx, s } #endif if (SCAN_HEURISTIC_MACROS && *has_macros) { - ret = cli_append_virus(ctx, "Heuristics.OLE2.ContainsMacros.VBA"); + ret = cli_append_potentially_unwanted(ctx, "Heuristics.OLE2.ContainsMacros.VBA"); if (ret == CL_VIRUS) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - goto done; - } + goto done; } } @@ -1747,17 +1686,13 @@ static cl_error_t cli_ole2_tempdir_scan_vba_new(const char *dir, cli_ctx *ctx, s goto done; } - ret = cli_scan_desc(tempfd, ctx, CL_TYPE_SCRIPT, 0, NULL, AC_SCAN_VIR, NULL, NULL, LAYER_ATTRIBUTES_NONE); + 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--; @@ -1769,8 +1704,6 @@ static cl_error_t cli_ole2_tempdir_scan_vba_new(const char *dir, cli_ctx *ctx, s tempfd = -1; } - if (viruses_found > 0) - ret = CL_VIRUS; return ret; } @@ -1853,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))) { @@ -1863,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; @@ -1893,20 +1829,24 @@ static cl_error_t cli_ole2_tempdir_scan_embedded_ole10(const char *dir, cli_ctx 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) { @@ -1917,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'; @@ -1926,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; + + cli_dbgmsg("cli_ole2_tempdir_scan_vba: VBA project '%s_%u' dumped to %s\n", vba_project->name[i], j, proj_contents_fname); + + free(proj_contents_fname); + proj_contents_fname = NULL; } - 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 (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; + 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'; - 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; + } - 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 ((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; - } - } + if (!ctx->engine->keeptmp) { + cli_rmdirs(fullname); } - close(fd); - hashcnt--; + 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, "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; + 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'; + + fd = open(vbaname, O_RDONLY | O_BINARY); + if (fd == -1) { + hashcnt--; + continue; } - while (hashcnt) { - int fd = -1; - snprintf(vbaname, sizeof(vbaname), "%s" PATHSEP "%s_%u", dir, hash, hashcnt); - vbaname[sizeof(vbaname) - 1] = '\0'; + if (!(vba_project = (vba_project_t *)cli_wm_readdir(fd))) { + close(fd); + fd = -1; + hashcnt--; + continue; + } - fd = open(vbaname, O_RDONLY | O_BINARY); - if (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]); - if (!(vba_project = (vba_project_t *)cli_wm_readdir(fd))) { - close(fd); - hashcnt--; - continue; - } + 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); - 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); + if (ctx->scanned) { + *ctx->scanned += vba_project->length[i] / CL_COUNT_PRECISION; } - } - close(fd); - cli_free_vba_project(vba_project); - vba_project = NULL; + status = vba_scandata(data, vba_project->length[i], ctx); + if (CL_SUCCESS != status) { + goto done; + } - if (status == CL_VIRUS && !SCAN_ALLMATCHES) { - break; + free(data); + data = NULL; } - hashcnt--; } + + 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_virus(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 (proj_contents_fd >= 0) { + close(proj_contents_fd); + } + if (NULL != proj_contents_fname) { + free(proj_contents_fname); } - if (viruses_found > 0) { - status = CL_VIRUS; + 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))) { @@ -2117,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: @@ -2129,139 +2076,151 @@ 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, 0, 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, 0, NULL, AC_SCAN_VIR, - NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED))) { - viruses_found++; - } - - close(fd); - } - } - } + /* 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); - 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, 0, NULL, AC_SCAN_VIR, - NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED))) { - viruses_found++; - } + // notags.html file exists, so lets scan it. - if (ret == CL_CLEAN || (ret == CL_VIRUS && SCAN_ALLMATCHES)) { - if (CL_VIRUS == (ret = cli_scan_desc(fd, ctx, CL_TYPE_TEXT_ASCII, 0, NULL, AC_SCAN_VIR, - NULL, NULL, LAYER_ATTRIBUTES_NORMALIZED))) { - viruses_found++; - } + 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)) { - 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 *troot; + struct cli_matcher *target_ac_root; uint32_t maxpatlen, offset = 0; - struct cli_matcher *groot; + struct cli_matcher *generic_ac_root; struct cli_ac_data gmdata, tmdata; int gmdata_initialized = 0; int tmdata_initialized = 0; 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; if (!ctx || !ctx->engine->root) return CL_ENULLARG; - map = ctx->fmap; - curr_len = map->len; - groot = ctx->engine->root[0]; - troot = ctx->engine->root[7]; - maxpatlen = troot ? troot->maxpatlen : 0; + map = ctx->fmap; + curr_len = map->len; + generic_ac_root = ctx->engine->root[0]; + target_ac_root = ctx->engine->root[7]; + maxpatlen = target_ac_root ? target_ac_root->maxpatlen : 0; // Initialize info so it's safe to pass to destroy later cli_targetinfo_init(&info); @@ -2282,12 +2241,12 @@ static cl_error_t cli_scanscript(cli_ctx *ctx) } text_normalize_init(&state, normalized, SCANBUFF + maxpatlen); - if ((ret = cli_ac_initdata(&tmdata, troot ? troot->ac_partsigs : 0, troot ? troot->ac_lsigs : 0, troot ? troot->ac_reloff_num : 0, CLI_DEFAULT_AC_TRACKLEN))) { + if ((ret = cli_ac_initdata(&tmdata, target_ac_root ? target_ac_root->ac_partsigs : 0, target_ac_root ? target_ac_root->ac_lsigs : 0, target_ac_root ? target_ac_root->ac_reloff_num : 0, CLI_DEFAULT_AC_TRACKLEN))) { goto done; } tmdata_initialized = 1; - if ((ret = cli_ac_initdata(&gmdata, groot->ac_partsigs, groot->ac_lsigs, groot->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN))) { + if ((ret = cli_ac_initdata(&gmdata, generic_ac_root->ac_partsigs, generic_ac_root->ac_lsigs, generic_ac_root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN))) { goto done; } gmdata_initialized = 1; @@ -2295,7 +2254,7 @@ static cl_error_t cli_scanscript(cli_ctx *ctx) /* dump to disk only if explicitly asked to * or if necessary to check relative offsets, * otherwise we can process just in-memory */ - if (ctx->engine->keeptmp || (troot && (troot->ac_reloff_num > 0 || troot->linked_bcs))) { + if (ctx->engine->keeptmp || (target_ac_root && (target_ac_root->ac_reloff_num > 0 || target_ac_root->linked_bcs))) { if ((ret = cli_gentempfd(ctx->sub_tmpdir, &tmpname, &ofd))) { cli_dbgmsg("cli_scanscript: Can't generate temporary file/descriptor\n"); goto done; @@ -2307,8 +2266,8 @@ static cl_error_t cli_scanscript(cli_ctx *ctx) mdata[0] = &tmdata; mdata[1] = &gmdata; - /* If there's a relative offset in troot or triggered bytecodes, normalize to file.*/ - if (troot && (troot->ac_reloff_num > 0 || troot->linked_bcs)) { + /* If there's a relative offset in target_ac_root or triggered bytecodes, normalize to file.*/ + if (target_ac_root && (target_ac_root->ac_reloff_num > 0 || target_ac_root->linked_bcs)) { size_t map_off = 0; while (map_off < map->len) { size_t written; @@ -2339,19 +2298,21 @@ static cl_error_t cli_scanscript(cli_ctx *ctx) } /* scan map */ - ret = cli_scan_fmap(ctx, CL_TYPE_TEXT_ASCII, 0, NULL, AC_SCAN_VIR, NULL, NULL); - if (ret == CL_VIRUS) { - viruses_found++; - } + ret = cli_scan_fmap(ctx, CL_TYPE_TEXT_ASCII, false, NULL, AC_SCAN_VIR, NULL, NULL); (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. */ - if (troot) { + if (target_ac_root) { cli_targetinfo(&info, 7, ctx); - ret = cli_ac_caloff(troot, &tmdata, &info); + ret = cli_ac_caloff(target_ac_root, &tmdata, &info); if (ret) goto done; } @@ -2369,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); @@ -2394,12 +2353,14 @@ static cl_error_t cli_scanscript(cli_ctx *ctx) } } - if (ret != CL_VIRUS || SCAN_ALLMATCHES) { - if ((ret = cli_exp_eval(ctx, troot, &tmdata, NULL, NULL)) == CL_VIRUS) - viruses_found++; - if (ret != CL_VIRUS || SCAN_ALLMATCHES) - if ((ret = cli_exp_eval(ctx, groot, &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: @@ -2421,17 +2382,17 @@ static cl_error_t cli_scanscript(cli_ctx *ctx) cli_ac_freedata(&gmdata); } - if (ofd != -1) + if (ofd != -1) { close(ofd); + } + if (tmpname != NULL) { - if (!ctx->engine->keeptmp) - cli_unlink(tmpname); + if (!ctx->engine->keeptmp) { + (void)cli_unlink(tmpname); + } free(tmpname); } - if (viruses_found) - return CL_VIRUS; - return ret; } @@ -2498,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) { @@ -2535,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; @@ -2550,51 +2512,27 @@ 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; } } if (has_xlm) { if (SCAN_HEURISTIC_MACROS) { - status = cli_append_virus(ctx, "Heuristics.OLE2.ContainsMacros.XLM"); - if (status == CL_VIRUS) { - viruses_found++; - if (!SCAN_ALLMATCHES) { - goto done; - } + status = cli_append_potentially_unwanted(ctx, "Heuristics.OLE2.ContainsMacros.XLM"); + if (CL_SUCCESS != status) { + goto done; } } } @@ -2603,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; } } @@ -2659,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; } } @@ -2691,9 +2607,6 @@ static cl_error_t cli_ole2_scan_tempdir( free(subdirectory); } - if (viruses_found > 0) { - status = CL_VIRUS; - } return status; } @@ -2705,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"); @@ -2724,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) { /* @@ -2767,10 +2672,6 @@ static cl_error_t cli_scanole2(cli_ctx *ctx) free(dir); } - if (viruses_found > 0) { - ret = CL_VIRUS; - } - return ret; } @@ -2831,7 +2732,7 @@ static cl_error_t cli_scanriff(cli_ctx *ctx) cl_error_t ret = CL_CLEAN; if (cli_check_riff_exploit(ctx) == 2) - ret = cli_append_virus(ctx, "Heuristics.Exploit.W32.MS05-002"); + ret = cli_append_potentially_unwanted(ctx, "Heuristics.Exploit.W32.MS05-002"); return ret; } @@ -2882,15 +2783,17 @@ static cl_error_t cli_scancryptff(cli_ctx *ctx) cli_dbgmsg("CryptFF: Scanning decrypted data\n"); - if (CL_VIRUS == (ret = cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE))) - cli_dbgmsg("CryptFF: Infected with %s\n", cli_get_last_virus(ctx)); + ret = cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE); 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; @@ -2973,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; } @@ -3020,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; @@ -3067,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_virus(ctx, "Heuristics.Structured.CreditCardNumber")) { - if (SCAN_ALLMATCHES) { - viruses_found++; - } else { - return CL_VIRUS; - } + if (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Structured.CreditCardNumber")) { + 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_virus(ctx, "Heuristics.Structured.SSN")) { - if (SCAN_ALLMATCHES) { - viruses_found++; - } else { - return CL_VIRUS; - } + if (CL_VIRUS == cli_append_potentially_unwanted(ctx, "Heuristics.Structured.SSN")) { + return CL_VIRUS; } } - if (viruses_found) - return CL_VIRUS; return CL_CLEAN; } @@ -3161,12 +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) { - cli_dbgmsg("cli_scanembpe: Infected with %s\n", cli_get_last_virus(ctx)); + if (ret != CL_SUCCESS) { close(fd); if (!ctx->engine->keeptmp) { if (cli_unlink(tmpname)) { @@ -3175,7 +3068,7 @@ static cl_error_t cli_scanembpe(cli_ctx *ctx, off_t offset) } } free(tmpname); - return CL_VIRUS; + return ret; } close(fd); @@ -3187,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; } @@ -3375,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 @@ -3401,16 +3295,17 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi } perf_start(ctx, PERFT_RAW); - ret = cli_scan_fmap(ctx, type == CL_TYPE_TEXT_ASCII ? CL_TYPE_ANY : type, 0, &ftoffset, acmode, NULL, refhash); + 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) { @@ -3418,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; @@ -3598,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. */ @@ -3941,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; @@ -3959,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 || @@ -3996,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) { @@ -4010,9 +3911,6 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi free(fpt); } - if (ret == CL_VIRUS) - cli_dbgmsg("%s found\n", cli_get_last_virus(ctx)); - return ret; } @@ -4030,7 +3928,7 @@ void emax_reached(cli_ctx *ctx) fmap_t *map = ctx->recursion_stack[stack_index].fmap; if (NULL != map) { - map->dont_cache_flag = 1; + map->dont_cache_flag = true; } stack_index -= 1; @@ -4140,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; @@ -4209,18 +4106,116 @@ static cl_error_t calculate_fuzzy_image_hash(cli_ctx *ctx, cli_file_t type) 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) { + // ensure abort_scan is respected + halt_scan = true; + } + + 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 @@ -4243,7 +4238,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; } @@ -4394,21 +4389,23 @@ 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; } /* * Get the maphash */ - if (CL_SUCCESS != fmap_get_MD5(ctx->fmap, &hash)) { + 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; @@ -4427,22 +4424,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); - - if (!(SCAN_COLLECT_METADATA)) - res = cache_check(hash, ctx); - else - res = CL_VIRUS; + 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; @@ -4450,43 +4445,31 @@ 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__); + ret = CL_SUCCESS; goto early_ret; } 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; - } - - if (CL_VIRUS == (ret = cli_scan_fmap(ctx, CL_TYPE_ANY, 0, NULL, AC_SCAN_VIR, NULL, hash))) - cli_dbgmsg("cli_magic_scan: %s found in descriptor %d\n", cli_get_last_virus(ctx), fmap_fd(ctx->fmap)); - - 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; - } + 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; } @@ -4498,7 +4481,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; } @@ -4509,8 +4492,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; } } @@ -4881,79 +4866,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; + } } /* @@ -4967,83 +4908,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)) { - ret = cli_scan_fmap(ctx, CL_TYPE_MAIL, 0, NULL, AC_SCAN_VIR, NULL, NULL); + } + 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, 0, NULL, AC_SCAN_VIR, NULL, NULL); + 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 */ @@ -5055,53 +4975,70 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) ctx->wrkproperty = (struct json_object *)(parent_property); #endif - if (ret == CL_CLEAN && ctx->found_possibly_unwanted) { - 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 { - if (ret == CL_CLEAN && ctx->num_viruses != 0) - cb_retcode = 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; const char *virusname = NULL; - perf_start(ctx, PERFT_POSTCB); - 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); - switch (ctx->engine->cb_post_scan(fmap_fd(ctx->fmap), cb_retcode, virusname, ctx->cb_ctx)) { + } + + perf_start(ctx, PERFT_POSTCB); + 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) { case CL_BREAK: - cli_dbgmsg("cli_magic_scan_desc: file allowed by post_scan callback\n"); - perf_stop(ctx, PERFT_POSTCB); + 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"); - perf_stop(ctx, PERFT_POSTCB); - 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"); } - perf_stop(ctx, PERFT_POSTCB); } - if (cb_retcode == CL_CLEAN && cache_clean && !ctx->fmap->dont_cache_flag && !SCAN_COLLECT_METADATA) { + /* + * 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); - cache_add(hash, hashed_size, ctx); + 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)) { @@ -5143,7 +5080,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__); @@ -5377,9 +5314,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; @@ -5392,12 +5334,16 @@ static cl_error_t scan_common(cl_fmap_t *map, const char *filepath, const char * return CL_ENULLARG; } + size_t num_potentially_unwanted_indicators = 0; + + *virname = NULL; + ctx.engine = engine; - ctx.virname = virname; ctx.scanned = scanned; ctx.options = malloc(sizeof(struct cl_scan_options)); memcpy(ctx.options, scanoptions, sizeof(struct cl_scan_options)); - ctx.found_possibly_unwanted = 0; + + ctx.evidence = evidence_new(); ctx.dconf = (struct cli_dconf *)engine->dconf; ctx.cb_ctx = context; @@ -5505,6 +5451,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 @@ -5514,7 +5461,7 @@ static cl_error_t scan_common(cl_fmap_t *map, const char *filepath, const char * * bound to behave badly with large files. */ if (map->len > INT_MAX - 2) { if (scanoptions->heuristic & CL_SCAN_HEURISTIC_EXCEEDS_MAX) { - status = cli_append_virus(&ctx, "Heuristics.Limits.Exceeded.MaxFileSize"); + status = cli_append_potentially_unwanted(&ctx, "Heuristics.Limits.Exceeded.MaxFileSize"); } else { status = CL_CLEAN; } @@ -5523,8 +5470,55 @@ static cl_error_t scan_common(cl_fmap_t *map, const char *filepath, const char * status = cli_magic_scan(&ctx, CL_TYPE_ANY); - if (status == CL_CLEAN && ctx.found_possibly_unwanted) { - cli_virus_found_cb(&ctx); + // If any alerts occurred, set the output pointer to the "latest" alert signature name. + if (0 < evidence_num_alerts(ctx.evidence)) { + *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); + if (0 != num_potentially_unwanted_indicators) { + // We have "potentially unwanted" indicators that would not have been reported yet. + // We may wish to report them now, ... depending .... + + if (ctx.options->general & CL_SCAN_GENERAL_ALLMATCHES) { + // We're in allmatch mode, so report all "potentially unwanted" matches now. + + size_t i; + + for (i = 0; i < num_potentially_unwanted_indicators; i++) { + const char *pua_alert = evidence_get_indicator( + ctx.evidence, + IndicatorType_PotentiallyUnwanted, + i); + + if (NULL != pua_alert) { + // We don't know exactly which layer the alert happened at. + // There's a decent chance it wasn't at this layer, and in that case we wouldn't + // even have access to that file anymore (it's gone!). So we'll pass back -1 for the + // file descriptor rather than using `cli_virus_found_cb() which would pass back + // The top level file descriptor. + if (ctx.engine->cb_virus_found) { + ctx.engine->cb_virus_found( + -1, + pua_alert, + ctx.cb_ctx); + } + } + } + + } else { + // Not allmatch mode. Only want to report one thing... + if (0 == evidence_num_indicators_type(ctx.evidence, IndicatorType_Strong)) { + // And it looks like we haven't reported anything else, so report the last "potentially unwanted" one. + cli_virus_found_cb(&ctx, cli_get_last_virus(&ctx)); + } + } } #if HAVE_JSON @@ -5553,77 +5547,94 @@ 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); - } + )) { + 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; + /* + * 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; + /* + * 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); + 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 { - 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); + cli_dbgmsg("json written to: %s\n", tmpname); } - if (fd != -1) - close(fd); - if (NULL != tmpname) - free(tmpname); + } + if (fd != -1) { + close(fd); + } + if (NULL != tmpname) { + free(tmpname); } } - cli_json_delobj(ctx.properties); /* frees all json memory */ } #endif // HAVE_JSON - if (status == CL_CLEAN) { - if ((ctx.found_possibly_unwanted) || - ((ctx.num_viruses != 0) && - ((ctx.options->general & CL_SCAN_GENERAL_ALLMATCHES) || - (ctx.options->heuristic & CL_SCAN_HEURISTIC_EXCEEDS_MAX)))) { - status = CL_VIRUS; - } + 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; } - cli_logg_unsetup(); - done: + // Filter the result from the post-scan hooks and stuff, so we don't propagate non-fatal errors. + // And to convert CL_VERIFIED -> CL_CLEAN + (void)result_should_goto_done(&ctx, status, &status); + + 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); @@ -5655,6 +5666,10 @@ static cl_error_t scan_common(cl_fmap_t *map, const char *filepath, const char * free(ctx.options); } + if (NULL != ctx.evidence) { + evidence_free(ctx.evidence); + } + return status; } @@ -5730,28 +5745,6 @@ cl_error_t cl_scanmap_callback(cl_fmap_t *map, const char *filename, const char return scan_common(map, filename, virname, scanned, engine, scanoptions, context); } -cl_error_t cli_found_possibly_unwanted(cli_ctx *ctx) -{ - if (cli_get_last_virus(ctx)) { - cli_dbgmsg("found Possibly Unwanted: %s\n", cli_get_last_virus(ctx)); - if (SCAN_HEURISTIC_PRECEDENCE) { - /* we found a heuristic match, don't scan further, - * but consider it a virus. */ - cli_dbgmsg("cli_found_possibly_unwanted: CL_VIRUS\n"); - return CL_VIRUS; - } - /* heuristic scan isn't taking precedence, keep scanning. - * If this is part of an archive, and - * we find a real malware we report that instead of the - * heuristic match */ - ctx->found_possibly_unwanted = 1; - } else { - cli_warnmsg("cli_found_possibly_unwanted called, but virname is not set\n"); - } - emax_reached(ctx); - return CL_CLEAN; -} - cl_error_t cli_magic_scan_file(const char *filename, cli_ctx *ctx, const char *original_name, uint32_t attributes) { int fd = -1; diff --git a/libclamav/scanners.h b/libclamav/scanners.h index b5887b91cf..a63404bc0f 100644 --- a/libclamav/scanners.h +++ b/libclamav/scanners.h @@ -102,8 +102,6 @@ cl_error_t cli_magic_scan_nested_fmap_type(cl_fmap_t *map, size_t offset, size_t cl_error_t cli_magic_scan_buff(const void *buffer, size_t length, cli_ctx *ctx, const char *name, uint32_t attributes); -cl_error_t cli_found_possibly_unwanted(cli_ctx *ctx); - /** * @brief Internal-use version of cl_scanfile. * diff --git a/libclamav/sis.c b/libclamav/sis.c index 11a9c46b09..650a9ae4d3 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/special.c b/libclamav/special.c index fe3ba3efbb..943a969499 100644 --- a/libclamav/special.c +++ b/libclamav/special.c @@ -89,7 +89,7 @@ int cli_check_mydoom_log(cli_ctx *ctx) if ((~check) != key) return CL_CLEAN; - return cli_append_virus(ctx, "Heuristics.Worm.Mydoom.M.log"); + return cli_append_potentially_unwanted(ctx, "Heuristics.Worm.Mydoom.M.log"); } static uint32_t riff_endian_convert_32(uint32_t value, int big_endian) diff --git a/libclamav/swf.c b/libclamav/swf.c index 404092cdaf..b6bd133dc6 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/tiff.c b/libclamav/tiff.c index cf26a1b37d..7b95d46ff7 100644 --- a/libclamav/tiff.c +++ b/libclamav/tiff.c @@ -78,8 +78,7 @@ cl_error_t cli_parsetiff(cli_ctx *ctx) /* acquire offset of first IFD */ if (fmap_readn(map, &offset, offset, 4) != 4) { cli_dbgmsg("cli_parsetiff: Failed to acquire offset of first IFD, file appears to be truncated.\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingFirstIFDOffset"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingFirstIFDOffset"); goto done; } /* offset of the first IFD */ @@ -89,8 +88,7 @@ cl_error_t cli_parsetiff(cli_ctx *ctx) if (!offset) { cli_errmsg("cli_parsetiff: Invalid offset for first IFD\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.TIFF.InvalidIFDOffset"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.TIFF.InvalidIFDOffset"); goto done; } @@ -99,8 +97,7 @@ cl_error_t cli_parsetiff(cli_ctx *ctx) /* acquire number of directory entries in current IFD */ if (fmap_readn(map, &num_entries, offset, 2) != 2) { cli_dbgmsg("cli_parsetiff: Failed to acquire number of directory entries in current IFD, file appears to be truncated.\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingNumIFDDirectoryEntries"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingNumIFDDirectoryEntries"); goto done; } offset += 2; @@ -112,8 +109,7 @@ cl_error_t cli_parsetiff(cli_ctx *ctx) for (i = 0; i < num_entries; i++) { if (fmap_readn(map, &entry, offset, sizeof(entry)) != sizeof(entry)) { cli_dbgmsg("cli_parsetiff: Failed to read next IFD entry, file appears to be truncated.\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingIFDEntry"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingIFDEntry"); goto done; } offset += sizeof(entry); @@ -175,7 +171,7 @@ cl_error_t cli_parsetiff(cli_ctx *ctx) if (entry.value + value_size > map->len) { cli_warnmsg("cli_parsetiff: TFD entry field %u exceeds bounds of TIFF file [%llu > %llu]\n", i, (long long unsigned)(entry.value + value_size), (long long unsigned)map->len); - status = cli_append_virus(ctx, "Heuristics.Broken.Media.TIFF.OutOfBoundsAccess"); + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.TIFF.OutOfBoundsAccess"); goto done; } } @@ -188,8 +184,7 @@ cl_error_t cli_parsetiff(cli_ctx *ctx) /* acquire next IFD location, gets 0 if last IFD */ if (fmap_readn(map, &offset, offset, sizeof(offset)) != sizeof(offset)) { cli_dbgmsg("cli_parsetiff: Failed to aquire next IFD location, file appears to be truncated.\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingChunkCRC"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingChunkCRC"); goto done; } offset = tiff32_to_host(big_endian, offset); @@ -198,8 +193,7 @@ cl_error_t cli_parsetiff(cli_ctx *ctx) /*If the offsets are not in order, that is suspicious.*/ if (last_offset >= offset) { cli_dbgmsg("cli_parsetiff: Next offset is before current offset, file appears to be malformed.\n"); - cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.TIFF.OutOfOrderIFDOffset"); - status = CL_EPARSE; + status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.TIFF.OutOfOrderIFDOffset"); goto done; } } @@ -210,10 +204,6 @@ cl_error_t cli_parsetiff(cli_ctx *ctx) status = CL_CLEAN; done: - if (status == CL_EPARSE) { - /* We added with cli_append_possibly_unwanted so it will alert at the end if nothing else matches. */ - status = CL_CLEAN; - } return status; } diff --git a/libclamav/untar.c b/libclamav/untar.c index 6306500c1d..0dbdbc7236 100644 --- a/libclamav/untar.c +++ b/libclamav/untar.c @@ -138,7 +138,6 @@ cl_error_t cli_untar(const char *dir, unsigned int posix, cli_ctx *ctx) size_t pos = 0; size_t currsize = 0; char zero[BLOCKSIZE]; - unsigned int num_viruses = 0; cli_dbgmsg("In untar(%s)\n", dir); memset(zero, 0, sizeof(zero)); @@ -175,13 +174,13 @@ cl_error_t cli_untar(const char *dir, unsigned int posix, cli_ctx *ctx) lseek(fout, 0, SEEK_SET); ret = cli_magic_scan_desc(fout, fullname, ctx, name, LAYER_ATTRIBUTES_NONE); close(fout); - if (!ctx->engine->keeptmp) - if (cli_unlink(fullname)) return CL_EUNLINK; - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) - return CL_VIRUS; - else - num_viruses++; + if (!ctx->engine->keeptmp) { + if (cli_unlink(fullname)) { + return CL_EUNLINK; + } + } + if (ret != CL_SUCCESS) { + return ret; } fout = -1; } @@ -304,10 +303,7 @@ cl_error_t cli_untar(const char *dir, unsigned int posix, cli_ctx *ctx) strncpy(name, block, 100); name[100] = '\0'; if (cli_matchmeta(ctx, name, size, size, 0, files, 0, NULL) == CL_VIRUS) { - if (!SCAN_ALLMATCHES) - return CL_VIRUS; - else - num_viruses++; + return CL_VIRUS; } snprintf(fullname, sizeof(fullname) - 1, "%s" PATHSEP "tar%02u", dir, files); @@ -371,12 +367,15 @@ cl_error_t cli_untar(const char *dir, unsigned int posix, cli_ctx *ctx) lseek(fout, 0, SEEK_SET); ret = cli_magic_scan_desc(fout, fullname, ctx, name, LAYER_ATTRIBUTES_NONE); close(fout); - if (!ctx->engine->keeptmp) - if (cli_unlink(fullname)) return CL_EUNLINK; - if (ret == CL_VIRUS) - return CL_VIRUS; + if (!ctx->engine->keeptmp) { + if (cli_unlink(fullname)) { + return CL_EUNLINK; + } + } + if (ret != CL_SUCCESS) { + return ret; + } } - if (num_viruses) - return CL_VIRUS; + return CL_CLEAN; } diff --git a/libclamav/unzip.c b/libclamav/unzip.c index 37bed2992e..7bb08c97ce 100644 --- a/libclamav/unzip.c +++ b/libclamav/unzip.c @@ -622,7 +622,6 @@ static unsigned int parse_local_file_header( char name[256]; char *original_filename = NULL; uint32_t csize, usize; - int virus_found = 0; unsigned int size_of_fileheader_and_data = 0; if (!(local_header = fmap_need_off(map, loff, SIZEOF_LOCAL_HEADER))) { @@ -670,9 +669,7 @@ static unsigned int parse_local_file_header( /* Scan file header metadata. */ if (cli_matchmeta(ctx, name, LOCAL_HEADER_csize, LOCAL_HEADER_usize, (LOCAL_HEADER_flags & F_ENCR) != 0, file_count, LOCAL_HEADER_crc32, NULL) == CL_VIRUS) { *ret = CL_VIRUS; - if (!SCAN_ALLMATCHES) - goto done; - virus_found = 1; + goto done; } if (LOCAL_HEADER_flags & F_MSKED) { @@ -685,13 +682,12 @@ static unsigned int parse_local_file_header( if (detect_encrypted && (LOCAL_HEADER_flags & F_ENCR) && SCAN_HEURISTIC_ENCRYPTED_ARCHIVE) { cl_error_t fp_check; cli_dbgmsg("cli_unzip: Encrypted files found in archive.\n"); - fp_check = cli_append_virus(ctx, "Heuristics.Encrypted.Zip"); - if ((fp_check == CL_VIRUS && !SCAN_ALLMATCHES) || fp_check != CL_CLEAN) { + fp_check = cli_append_potentially_unwanted(ctx, "Heuristics.Encrypted.Zip"); + if (fp_check != CL_SUCCESS) { *ret = fp_check; fmap_unneed_off(map, loff, SIZEOF_LOCAL_HEADER); goto done; } - virus_found = 1; } if (LOCAL_HEADER_flags & F_USEDD) { @@ -782,9 +778,6 @@ static unsigned int parse_local_file_header( free(original_filename); } - if ((NULL != ret) && (0 != virus_found)) - *ret = CL_VIRUS; - return size_of_fileheader_and_data; } @@ -821,10 +814,16 @@ parse_central_directory_file_header( char name[256]; int last = 0; const uint8_t *central_header = NULL; - int virus_found = 0; *ret = CL_EPARSE; + if (cli_checktimelimit(ctx) != CL_SUCCESS) { + cli_dbgmsg("cli_unzip: central header - Time limit reached (max: %u)\n", ctx->engine->maxscantime); + last = 1; + *ret = CL_ETIMEOUT; + goto done; + } + if (!(central_header = fmap_need_off(map, coff, SIZEOF_CENTRAL_HEADER)) || CENTRAL_HEADER_magic != ZIP_MAGIC_CENTRAL_DIRECTORY_RECORD_BEGIN) { if (central_header) { fmap_unneed_ptr(map, central_header, SIZEOF_CENTRAL_HEADER); @@ -859,12 +858,9 @@ parse_central_directory_file_header( /* requests do not supply a ctx; also prevent multiple scans */ if (ctx && (CL_VIRUS == cli_matchmeta(ctx, name, CENTRAL_HEADER_csize, CENTRAL_HEADER_usize, (CENTRAL_HEADER_flags & F_ENCR) != 0, file_count, CENTRAL_HEADER_crc32, NULL))) { - virus_found = 1; - - if (!SCAN_ALLMATCHES) { - last = 1; - goto done; - } + last = 1; + *ret = CL_VIRUS; + goto done; } if (zsize - coff <= CENTRAL_HEADER_extra_len && !last) { @@ -916,9 +912,6 @@ parse_central_directory_file_header( } done: - if (virus_found == 1) - *ret = CL_VIRUS; - if (NULL != central_header) { fmap_unneed_ptr(map, central_header, SIZEOF_CENTRAL_HEADER); } @@ -991,7 +984,6 @@ cl_error_t index_the_central_directory( struct zip_record *curr_record = NULL; struct zip_record *prev_record = NULL; uint32_t num_overlapping_files = 0; - int virus_found = 0; bool exceeded_max_files = false; if (NULL == catalogue || NULL == num_records) { @@ -1035,12 +1027,8 @@ cl_error_t index_the_central_directory( } if (ret == CL_VIRUS) { - if (SCAN_ALLMATCHES) - virus_found = 1; - else { - status = CL_VIRUS; - goto done; - } + status = CL_VIRUS; + goto done; } index++; @@ -1054,7 +1042,7 @@ cl_error_t index_the_central_directory( /* stop checking file entries if we'll exceed maxfiles */ if (ctx->engine->maxfiles && records_count >= ctx->engine->maxfiles) { cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles); - cli_append_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); + cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); exceeded_max_files = true; // Set a bool so we can return the correct status code later. // We still need to scan the files we found while reviewing the file records up to this limit. break; @@ -1088,12 +1076,8 @@ cl_error_t index_the_central_directory( } while (1); if (ret == CL_VIRUS) { - if (SCAN_ALLMATCHES) - virus_found = 1; - else { - status = CL_VIRUS; - goto done; - } + status = CL_VIRUS; + goto done; } if (records_count > 1) { @@ -1141,7 +1125,7 @@ cl_error_t index_the_central_directory( if (ZIP_MAX_NUM_OVERLAPPING_FILES < num_overlapping_files) { if (SCAN_HEURISTICS) { - status = cli_append_virus(ctx, "Heuristics.Zip.OverlappingFiles"); + status = cli_append_potentially_unwanted(ctx, "Heuristics.Zip.OverlappingFiles"); } else { status = CL_EFORMAT; } @@ -1176,12 +1160,11 @@ cl_error_t index_the_central_directory( free(zip_catalogue); zip_catalogue = NULL; } - } - if (virus_found) - status = CL_VIRUS; - else if (exceeded_max_files) - status = CL_EMAXFILES; + if (exceeded_max_files) { + status = CL_EMAXFILES; + } + } return status; } @@ -1194,7 +1177,6 @@ cl_error_t cli_unzip(cli_ctx *ctx) fmap_t *map = ctx->fmap; char *tmpd = NULL; const char *ptr; - int virus_found = 0; #if HAVE_JSON int toval = 0; #endif @@ -1240,11 +1222,7 @@ cl_error_t cli_unzip(cli_ctx *ctx) &zip_catalogue, &records_count); if (CL_SUCCESS != ret) { - if (CL_VIRUS == ret && SCAN_ALLMATCHES) - virus_found = 1; - else { - goto done; - } + goto done; } /* @@ -1302,13 +1280,14 @@ cl_error_t cli_unzip(cli_ctx *ctx) // so we will also check and update the limits for the actual number of scanned // files inside cli_magic_scan() cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles); - cli_append_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); + cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); ret = CL_EMAXFILES; } if (cli_checktimelimit(ctx) != CL_SUCCESS) { cli_dbgmsg("cli_unzip: Time limit reached (max: %u)\n", ctx->engine->maxscantime); ret = CL_ETIMEOUT; + goto done; } #if HAVE_JSON @@ -1316,22 +1295,21 @@ cl_error_t cli_unzip(cli_ctx *ctx) ret = CL_ETIMEOUT; } #endif - if (ret != CL_CLEAN) { - if (ret == CL_VIRUS && SCAN_ALLMATCHES) { - ret = CL_CLEAN; - virus_found = 1; - } else { - break; - } + if (ret != CL_SUCCESS) { + break; } } } else { cli_dbgmsg("cli_unzip: central not found, using localhdrs\n"); } - if (virus_found == 1) { - ret = CL_VIRUS; + if (CL_SUCCESS != ret) { + // goto done right away if there was a timeout, an alert, etc. + // This is slightly redundant since the while loop will only happen + // if ret == CL_SUCCESS but it's more explicit. + goto done; } + if (0 < num_files_unzipped && num_files_unzipped <= (file_count / 4)) { /* FIXME: make up a sane ratio or remove the whole logic */ file_count = 0; while ((ret == CL_CLEAN) && @@ -1350,10 +1328,7 @@ cl_error_t cli_unzip(cli_ctx *ctx) NULL)))) { file_count++; lhoff += coff; - if (SCAN_ALLMATCHES && ret == CL_VIRUS) { - ret = CL_CLEAN; - virus_found = 1; - } + if (ctx->engine->maxfiles && num_files_unzipped >= ctx->engine->maxfiles) { // Note: this check piggybacks on the MaxFiles setting, but is not actually // scanning these files or incrementing the ctx->scannedfiles count @@ -1361,7 +1336,7 @@ cl_error_t cli_unzip(cli_ctx *ctx) // so we will also check and update the limits for the actual number of scanned // files inside cli_magic_scan() cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles); - cli_append_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); + cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); ret = CL_EMAXFILES; } #if HAVE_JSON @@ -1393,9 +1368,6 @@ cl_error_t cli_unzip(cli_ctx *ctx) free(tmpd); } - if (ret == CL_CLEAN && virus_found) - ret = CL_VIRUS; - return ret; } @@ -1522,7 +1494,7 @@ cl_error_t unzip_search(cli_ctx *ctx, fmap_t *map, struct zip_requests *requests // Note: this check piggybacks on the MaxFiles setting, but is not actually // scanning these files or incrementing the ctx->scannedfiles count cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles); - cli_append_virus_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); + cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); ret = CL_EMAXFILES; } #if HAVE_JSON diff --git a/libclamav/vba_extract.c b/libclamav/vba_extract.c index 3e2bf67fcb..40f14acba9 100644 --- a/libclamav/vba_extract.c +++ b/libclamav/vba_extract.c @@ -1720,15 +1720,25 @@ int cli_scan_ole10(int fd, cli_ctx *ctx) free(fullname); return CL_ECREAT; } + cli_dbgmsg("cli_decode_ole_object: decoding to %s\n", fullname); + ole_copy_file_data(fd, ofd, object_size); + lseek(ofd, 0, SEEK_SET); + ret = cli_magic_scan_desc(ofd, fullname, ctx, NULL, LAYER_ATTRIBUTES_NONE); + close(ofd); - if (ctx && !ctx->engine->keeptmp) - if (cli_unlink(fullname)) - ret = CL_EUNLINK; + + if (ctx && !ctx->engine->keeptmp) { + if (cli_unlink(fullname)) { + cli_dbgmsg("cli_decode_ole_object: Failed to remove temp file: %s\n", fullname); + } + } + free(fullname); + return ret; } diff --git a/libclamav/xar.c b/libclamav/xar.c index a5de297370..ac63be1bc9 100644 --- a/libclamav/xar.c +++ b/libclamav/xar.c @@ -309,8 +309,6 @@ static int xar_scan_subdocuments(xmlTextReaderPtr reader, cli_ctx *ctx) subdoc_len = xmlStrlen(subdoc); cli_dbgmsg("cli_scanxar: in-memory scan of xml subdocument, len %i.\n", subdoc_len); rc = cli_magic_scan_buff(subdoc, subdoc_len, ctx, NULL, LAYER_ATTRIBUTES_NONE); - if (rc == CL_VIRUS && SCAN_ALLMATCHES) - rc = CL_SUCCESS; /* make a file to leave if --leave-temps in effect */ if (ctx->engine->keeptmp) { @@ -519,8 +517,7 @@ int cli_scanxar(cli_ctx *ctx) cli_dbgmsg("cli_scanxar: scanning xar TOC xml in memory.\n"); rc = cli_magic_scan_buff(toc, hdr.toc_length_decompressed, ctx, NULL, LAYER_ATTRIBUTES_NONE); if (rc != CL_SUCCESS) { - if (rc != CL_VIRUS || !SCAN_ALLMATCHES) - goto exit_toc; + goto exit_toc; } /* make a file to leave if --leave-temps in effect */ @@ -848,14 +845,7 @@ int cli_scanxar(cli_ctx *ctx) rc = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE); /// TODO: collect file names in xar_get_toc_data_values() if (rc != CL_SUCCESS) { - if (rc == CL_VIRUS) { - cli_dbgmsg("cli_scanxar: Infected with %s\n", cli_get_last_virus(ctx)); - if (!SCAN_ALLMATCHES) - goto exit_tmpfile; - } else if (rc != CL_BREAK) { - cli_dbgmsg("cli_scanxar: cli_magic_scan_desc error %i\n", rc); - goto exit_tmpfile; - } + goto exit_tmpfile; } } diff --git a/libclamav/xdp.c b/libclamav/xdp.c index 6536d9ce6b..f0e2fdde24 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/libclamav/xlm_extract.c b/libclamav/xlm_extract.c index e242ed894d..9159f29606 100644 --- a/libclamav/xlm_extract.c +++ b/libclamav/xlm_extract.c @@ -4215,7 +4215,6 @@ cl_error_t process_blip_record(struct OfficeArtRecordHeader_Unpacked *rh, const { cl_error_t status = CL_EARG; cl_error_t ret; - bool virus_found = false; char *extracted_image_filepath = NULL; int extracted_image_tempfd = -1; @@ -4334,11 +4333,11 @@ cl_error_t process_blip_record(struct OfficeArtRecordHeader_Unpacked *rh, const if (ctx->engine->keeptmp) { /* Drop a temp file and scan that */ - if ((ret = cli_gentempfd_with_prefix( - ctx->sub_tmpdir, - extracted_image_type, - &extracted_image_filepath, - &extracted_image_tempfd)) != CL_SUCCESS) { + if (CL_SUCCESS != (ret = cli_gentempfd_with_prefix( + ctx->sub_tmpdir, + extracted_image_type, + &extracted_image_filepath, + &extracted_image_tempfd))) { cli_warnmsg("Failed to create temp file for extracted %s file\n", extracted_image_type); status = CL_EOPEN; goto done; @@ -4356,12 +4355,9 @@ cl_error_t process_blip_record(struct OfficeArtRecordHeader_Unpacked *rh, const /* Scan the buffer */ ret = cli_magic_scan_buff(start_of_image, size_of_image, ctx, NULL, LAYER_ATTRIBUTES_NONE); } - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - status = CL_VIRUS; - goto done; - } - virus_found = true; + if (CL_SUCCESS != ret) { + status = ret; + goto done; } } @@ -4382,9 +4378,6 @@ cl_error_t process_blip_record(struct OfficeArtRecordHeader_Unpacked *rh, const free(extracted_image_filepath); } - if (virus_found) { - status = CL_VIRUS; - } return status; } @@ -4399,8 +4392,6 @@ cl_error_t process_blip_record(struct OfficeArtRecordHeader_Unpacked *rh, const cl_error_t process_blip_store_container(const unsigned char *blip_store_container, size_t blip_store_container_len, cli_ctx *ctx) { cl_error_t status = CL_EARG; - cl_error_t ret; - bool virus_found = false; struct OfficeArtRecordHeader_Unpacked rh; const unsigned char *index = blip_store_container; @@ -4479,13 +4470,9 @@ cl_error_t process_blip_store_container(const unsigned char *blip_store_containe cli_dbgmsg("process_blip_store_container: Failed to get header\n"); goto done; } - ret = process_blip_record(&embeddedBlip_rh, embeddedBlip, embeddedBlip_size, ctx); - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - status = CL_VIRUS; - goto done; - } - virus_found = true; + status = process_blip_record(&embeddedBlip_rh, embeddedBlip, embeddedBlip_size, ctx); + if (CL_SUCCESS != status) { + goto done; } } } @@ -4493,13 +4480,9 @@ cl_error_t process_blip_store_container(const unsigned char *blip_store_containe } else if ((0xF018 <= rh.recType) && (0xF117 >= rh.recType)) { /* it's an OfficeArtBlip record */ cli_dbgmsg("process_blip_store_container: Found a Blip record\n"); - ret = process_blip_record(&rh, index, remaining, ctx); - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - status = CL_VIRUS; - goto done; - } - virus_found = true; + status = process_blip_record(&rh, index, remaining, ctx); + if (CL_SUCCESS != status) { + goto done; } } else { @@ -4519,17 +4502,12 @@ cl_error_t process_blip_store_container(const unsigned char *blip_store_containe done: - if (virus_found) { - status = CL_VIRUS; - } return status; } cl_error_t cli_extract_images_from_drawing_group(const unsigned char *drawinggroup, size_t drawinggroup_len, cli_ctx *ctx) { cl_error_t status = CL_EARG; - cl_error_t ret; - bool virus_found = false; struct OfficeArtRecordHeader_Unpacked rh; const unsigned char *index = drawinggroup; @@ -4605,13 +4583,9 @@ cl_error_t cli_extract_images_from_drawing_group(const unsigned char *drawinggro blip_store_container_len = rh.recLen; } - ret = process_blip_store_container(start_of_blip_store_container, blip_store_container_len, ctx); - if (ret == CL_VIRUS) { - if (!SCAN_ALLMATCHES) { - status = CL_VIRUS; - goto done; - } - virus_found = true; + status = process_blip_store_container(start_of_blip_store_container, blip_store_container_len, ctx); + if (CL_SUCCESS != status) { + goto done; } } @@ -4626,9 +4600,7 @@ cl_error_t cli_extract_images_from_drawing_group(const unsigned char *drawinggro status = CL_SUCCESS; done: - if (virus_found) { - status = CL_VIRUS; - } + return status; } @@ -4977,7 +4949,7 @@ cl_error_t cli_extract_xlm_macros_and_images(const char *dir, cli_ctx *ctx, char goto done; } - if (CL_VIRUS == cli_scan_desc(out_fd, ctx, CL_TYPE_SCRIPT, 0, NULL, AC_SCAN_VIR, + if (CL_VIRUS == cli_scan_desc(out_fd, ctx, CL_TYPE_SCRIPT, false, NULL, AC_SCAN_VIR, NULL, NULL, LAYER_ATTRIBUTES_NONE)) { status = CL_VIRUS; goto done; @@ -4996,8 +4968,8 @@ cl_error_t cli_extract_xlm_macros_and_images(const char *dir, cli_ctx *ctx, char * If we fail to extract images, that's fine. */ ret = cli_extract_images_from_drawing_group(drawinggroup, drawinggroup_len, ctx); - if (ret == CL_VIRUS) { - status = CL_VIRUS; + if (CL_SUCCESS != ret) { + status = ret; goto done; } } diff --git a/libclamav/yc.c b/libclamav/yc.c index 5ade2b79ce..e95491edab 100644 --- a/libclamav/yc.c +++ b/libclamav/yc.c @@ -46,7 +46,7 @@ static int yc_bounds_check(cli_ctx *ctx, char *base, unsigned int filesize, char if ((unsigned int)((offset + bound) - base) > filesize) { cli_dbgmsg("yC: Bounds check assertion.\n"); #if DO_HEURISTIC - cli_append_virus(ctx, "Heuristics.BoundsCheck"); + cli_append_potentially_unwanted(ctx, "Heuristics.BoundsCheck"); #endif return 1; } diff --git a/libclamav_rust/cbindgen.toml b/libclamav_rust/cbindgen.toml index c2406af7b3..e3c0b806ed 100644 --- a/libclamav_rust/cbindgen.toml +++ b/libclamav_rust/cbindgen.toml @@ -28,6 +28,14 @@ include = [ "frs_error::ffierror_fmt", "frs_error::ffierror_free", "logging::clrs_eprint", + "evidence::evidence_new", + "evidence::evidence_free", + "evidence::evidence_render_verdict", + "evidence::evidence_get_last_alert", + "evidence::evidence_num_alerts", + "evidence::evidence_num_indicators_type", + "evidence::evidence_add_indicator", + "evidence::IndicatorType", ] # prefix = "CAPI_" @@ -48,3 +56,6 @@ all_features = false crates = [] default_features = true features = [] + +[enum] +prefix_with_name = true diff --git a/libclamav_rust/src/evidence.rs b/libclamav_rust/src/evidence.rs new file mode 100644 index 0000000000..6b76329f55 --- /dev/null +++ b/libclamav_rust/src/evidence.rs @@ -0,0 +1,275 @@ +/* + * Functions and structures for recording, reporting evidence towards a scan verdict. + * + * Copyright (C) 2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Authors: Micah Snyder + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +use std::{collections::HashMap, ffi::CStr, mem::ManuallyDrop, os::raw::c_char}; + +use log::{debug, error, warn}; +use thiserror::Error; + +use crate::{ffi_util::FFIError, rrf_call, sys, validate_str_param}; + +/// CdiffError enumerates all possible errors returned by this library. +#[derive(Error, Debug)] +pub enum EvidenceError { + #[error("Invalid format")] + Format, + + #[error("Invalid parameter: {0}")] + InvalidParameter(String), + + #[error("{0} parmeter is NULL")] + NullParam(&'static str), +} + +#[repr(C)] +pub enum IndicatorType { + /// For hash-based indicators. + Strong, + /// For potentially unwanted applications/programs that are not malicious but may be used maliciously. + PotentiallyUnwanted, + + #[cfg(feature = "not_ready")] + /// Weak indicators that together with other indicators can be used to form a stronger indicator. + /// This type of indicator should NEVER alert the user on its own. + Weak, +} + +#[derive(Debug, Default, Clone)] +pub struct Evidence { + strong: HashMap>, + pua: HashMap>, + #[cfg(feature = "not_ready")] + weak: HashMap>, +} + +#[derive(Debug, Clone)] +pub struct IndicatorMeta { + /// The original string pointer for the "virname", to pass back. + static_virname: *const c_char, +} + +/// Initialize a match vector +#[no_mangle] +pub extern "C" fn evidence_new() -> sys::evidence_t { + Box::into_raw(Box::new(Evidence::default())) as sys::evidence_t +} + +/// 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 { + let _ = unsafe { Box::from_raw(evidence as *mut Evidence) }; + } +} + +/// C interface for Evidence::render_verdict(). +/// Handles all the unsafe ffi stuff. +/// +/// Render a verdict based on the evidence, depending on the severity of the +/// indicators found and the scan configuration. +/// +/// The individual alerting-indicators would have already been printed at this point. +/// +/// # Safety +/// +/// No parameters may be NULL +#[export_name = "evidence_render_verdict"] +pub unsafe extern "C" fn _evidence_render_verdict(evidence: sys::evidence_t) -> bool { + let evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence)); + + evidence.render_verdict() +} + +/// C interface to get a string name for one of the alerts. +/// Will first check for one from the strong indicators, then pua. +/// +/// # Safety +/// +/// Returns a string that is either static, or allocated when reading the database. +/// So the lifetime of the string is good at least until you reload or unload the databases. +/// +/// No parameters may be NULL +#[export_name = "evidence_get_last_alert"] +pub unsafe extern "C" fn _evidence_get_last_alert(evidence: sys::evidence_t) -> *const c_char { + let evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence)); + + if let Some(meta) = evidence.strong.values().last() { + meta.last().unwrap().static_virname as *const c_char + } else if let Some(meta) = evidence.pua.values().last() { + meta.last().unwrap().static_virname as *const c_char + } else { + // no alerts, return NULL + std::ptr::null() + } +} + +/// C interface to get a string name for one of the alerts. +/// Will first check for one from the strong indicators, then pua. +/// +/// # Safety +/// +/// Returns a string that is either static, or allocated when reading the database. +/// So the lifetime of the string is good at least until you reload or unload the databases. +/// +/// No parameters may be NULL +#[export_name = "evidence_get_indicator"] +pub unsafe extern "C" fn _evidence_get_indicator( + evidence: sys::evidence_t, + indicator_type: IndicatorType, + index: usize, +) -> *const c_char { + let evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence)); + + match indicator_type { + IndicatorType::Strong => { + if let Some(meta) = evidence.strong.values().nth(index) { + return meta.last().unwrap().static_virname as *const c_char; + } else { + // no alert at that index. return NULL + return std::ptr::null(); + } + } + IndicatorType::PotentiallyUnwanted => { + if let Some(meta) = evidence.pua.values().nth(index) { + return meta.last().unwrap().static_virname as *const c_char; + } else { + // no alert at that index. return NULL + return std::ptr::null(); + } + } + } +} + +/// C interface to check number of alerting indicators in evidence. +/// +/// # Safety +/// +/// No parameters may be NULL +#[export_name = "evidence_num_alerts"] +pub unsafe extern "C" fn _evidence_num_alerts(evidence: sys::evidence_t) -> usize { + let evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence)); + + evidence.strong.len() + evidence.pua.len() +} + +/// C interface to check number of indicators in evidence. +/// Handles all the unsafe ffi stuff. +/// +/// # Safety +/// +/// No parameters may be NULL +#[export_name = "evidence_num_indicators_type"] +pub unsafe extern "C" fn _evidence_num_indicators_type( + evidence: sys::evidence_t, + indicator_type: IndicatorType, +) -> usize { + let evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence)); + + match indicator_type { + IndicatorType::Strong => evidence.strong.len(), + IndicatorType::PotentiallyUnwanted => evidence.pua.len(), + #[cfg(feature = "not_ready")] + // TODO: Implement a way to record, report number of indicators in the tree (you know, after making this a tree). + IndicatorType::Weak => evidence.weak.len(), + } +} + +/// C interface for Evidence::add_indicator(). +/// Handles all the unsafe ffi stuff. +/// +/// Add an indicator to the evidence. +/// +/// # Safety +/// +/// `hexsig` and `err` must not be NULL +#[export_name = "evidence_add_indicator"] +pub unsafe extern "C" fn _evidence_add_indicator( + evidence: sys::evidence_t, + name: *const c_char, + indicator_type: IndicatorType, + err: *mut *mut FFIError, +) -> bool { + let name_str = validate_str_param!(name); + + let mut evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence)); + + rrf_call!( + err = err, + evidence.add_indicator(name_str, name, indicator_type) + ) +} + +impl Evidence { + /// Check if we have any indicators that should alert the user. + pub fn render_verdict(&self) -> bool { + debug!("Checking verdict..."); + + let num_alerting_indicators = self.strong.len() + self.pua.len(); + + if num_alerting_indicators > 0 { + debug!("Found {} alerting indicators", num_alerting_indicators); + return true; + } + false + } + + /// Add an indicator to the evidence. + pub fn add_indicator( + &mut self, + name: &str, + static_virname: *const c_char, + indicator_type: IndicatorType, + ) -> Result<(), EvidenceError> { + let meta: IndicatorMeta = IndicatorMeta { static_virname }; + + match indicator_type { + IndicatorType::Strong => { + self.strong + .entry(name.to_string()) + .or_insert_with(Vec::new) + .push(meta); + } + + IndicatorType::PotentiallyUnwanted => { + self.pua + .entry(name.to_string()) + .or_insert_with(Vec::new) + .push(meta); + } + + #[cfg(feature = "not_ready")] + // TODO: Implement a tree structure for recording weak indicators, to + // match the archive/extraction level at which each was found. + // This will be required for alerting signatures to depend on weak-indicators for embedded content. + IndicatorType::Weak => { + self.weak + .entry(name.to_string()) + .or_insert_with(Vec::new) + .push(meta); + } + } + + Ok(()) + } +} diff --git a/libclamav_rust/src/fuzzy_hash.rs b/libclamav_rust/src/fuzzy_hash.rs index 1754db1ae6..63c0b51cfa 100644 --- a/libclamav_rust/src/fuzzy_hash.rs +++ b/libclamav_rust/src/fuzzy_hash.rs @@ -132,7 +132,7 @@ pub extern "C" fn fuzzy_hash_free_hashmap(fuzzy_hashmap: sys::fuzzyhashmap_t) { } } -/// C interface for fuzzy_hash_check(). +/// C interface for FuzzyHashMap::check(). /// Handles all the unsafe ffi stuff. /// /// # Safety @@ -162,7 +162,7 @@ pub unsafe extern "C" fn _fuzzy_hash_check( true } -/// C interface for fuzzy_hash_load_subsignature(). +/// C interface for FuzzyHashMap::load_subsignature(). /// Handles all the unsafe ffi stuff. /// /// # Safety diff --git a/libclamav_rust/src/lib.rs b/libclamav_rust/src/lib.rs index 574d6fe280..6b861f3f56 100644 --- a/libclamav_rust/src/lib.rs +++ b/libclamav_rust/src/lib.rs @@ -24,6 +24,7 @@ pub mod sys; pub mod cdiff; +pub mod evidence; pub mod ffi_util; pub mod fuzzy_hash; pub mod logging; diff --git a/libclamav_rust/src/sys.rs b/libclamav_rust/src/sys.rs index a7f5fb8afe..bffdaefb7b 100644 --- a/libclamav_rust/src/sys.rs +++ b/libclamav_rust/src/sys.rs @@ -435,7 +435,7 @@ pub struct cl_fmap { pub pgsz: u64, pub paged: u64, pub aging: u16, - pub dont_cache_flag: u16, + pub dont_cache_flag: bool, #[doc = " indicates if we should not cache scan results for this fmap. Used if limits exceeded"] pub handle_is_fd: u16, #[doc = " non-zero if map->handle is an fd."] @@ -473,8 +473,12 @@ pub struct cl_fmap { >, pub unneed_off: ::std::option::Option, - pub have_maphash: bool, - pub maphash: [::std::os::raw::c_uchar; 16usize], + pub have_md5: bool, + pub md5: [::std::os::raw::c_uchar; 16usize], + pub have_sha1: bool, + pub sha1: [::std::os::raw::c_uchar; 20usize], + pub have_sha256: bool, + pub sha256: [::std::os::raw::c_uchar; 32usize], pub bitmap: *mut u64, pub name: *mut ::std::os::raw::c_char, } @@ -778,24 +782,20 @@ pub struct recursion_level_tag { pub calculated_image_fuzzy_hash: bool, } pub type recursion_level_t = recursion_level_tag; +pub type evidence_t = *mut ::std::os::raw::c_void; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct cli_ctx_tag { - #[doc = "< (optional) The filepath of the original scan target."] pub target_filepath: *mut ::std::os::raw::c_char, - #[doc = "< (optional) The filepath of the current file being parsed. May be a temp file."] pub sub_filepath: *const ::std::os::raw::c_char, - #[doc = "< The directory to store tmp files at this recursion depth."] pub sub_tmpdir: *mut ::std::os::raw::c_char, - pub virname: *mut *const ::std::os::raw::c_char, - pub num_viruses: ::std::os::raw::c_uint, + pub evidence: evidence_t, pub scanned: *mut ::std::os::raw::c_ulong, pub root: *const cli_matcher, pub engine: *const cl_engine, pub scansize: u64, pub options: *mut cl_scan_options, pub scannedfiles: ::std::os::raw::c_uint, - pub found_possibly_unwanted: ::std::os::raw::c_uint, pub corrupted_input: ::std::os::raw::c_uint, pub recursion_stack: *mut recursion_level_t, pub recursion_stack_size: u32, diff --git a/libfreshclam/libfreshclam_internal.c b/libfreshclam/libfreshclam_internal.c index bc687b5e2c..8af13feb3c 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/sigtool/sigtool.c b/sigtool/sigtool.c index 4d241f61d0..a414592d61 100644 --- a/sigtool/sigtool.c +++ b/sigtool/sigtool.c @@ -54,12 +54,12 @@ #include "others.h" #include "pe.h" #include "entconv.h" +#include "clamav_rust.h" // common #include "output.h" #include "optparser.h" #include "misc.h" -#include "clamav_rust.h" #include "tar.h" #include "vba.h" @@ -195,7 +195,7 @@ static int hashpe(const char *filename, unsigned int class, int type) goto done; } - if (cli_add_content_match_pattern(engine->root[0], "test", "deadbeef", 0, 0, 0, "*", 0, NULL, 0) != CL_SUCCESS) { + if (cli_add_content_match_pattern(engine->root[0], "test", "deadbeef", 0, 0, 0, "*", NULL, 0) != CL_SUCCESS) { mprintf(LOGG_ERROR, "hashpe: Can't parse signature\n"); goto done; } @@ -206,7 +206,10 @@ static int hashpe(const char *filename, unsigned int class, int type) } /* prepare context */ - ctx.engine = engine; + ctx.engine = engine; + + ctx.evidence = evidence_new(); + ctx.options = &options; ctx.options->parse = ~0; ctx.dconf = (struct cli_dconf *)engine->dconf; @@ -273,6 +276,9 @@ static int hashpe(const char *filename, unsigned int class, int type) if (NULL != ctx.recursion_stack) { free(ctx.recursion_stack); } + if (NULL != ctx.evidence) { + evidence_free(ctx.evidence); + } if (NULL != engine) { cl_engine_free(engine); } @@ -358,7 +364,7 @@ static int fuzzy_img_file(char *filename) bytes_read = read(target_fd, mem, (size_t)st.st_size); if (bytes_read == -1) { char err[128]; - mprintf(LOGG_ERROR, "%s: Failed to read file.\n", basename(filename), cli_strerror(errno, err, sizeof(err))); + mprintf(LOGG_ERROR, "%s: Failed to read file: %s\n", basename(filename), cli_strerror(errno, err, sizeof(err))); goto done; } if (bytes_read < (ssize_t)st.st_size) { @@ -2090,7 +2096,7 @@ static void matchsig(char *sig, const char *offset, int fd) goto done; } - if (readdb_parse_ldb_subsignature(engine->root[0], "test", sig, "*", 0, NULL, 0, 0, 1, &tdb) != CL_SUCCESS) { + if (readdb_parse_ldb_subsignature(engine->root[0], "test", sig, "*", NULL, 0, 0, 1, &tdb) != CL_SUCCESS) { mprintf(LOGG_ERROR, "matchsig: Can't parse signature\n"); goto done; } @@ -2100,7 +2106,10 @@ static void matchsig(char *sig, const char *offset, int fd) goto done; } - ctx.engine = engine; + ctx.engine = engine; + + ctx.evidence = evidence_new(); + ctx.options = &options; ctx.options->parse = ~0; ctx.dconf = (struct cli_dconf *)engine->dconf; @@ -2118,7 +2127,7 @@ static void matchsig(char *sig, const char *offset, int fd) ctx.fmap = ctx.recursion_stack[ctx.recursion_level].fmap; - (void)cli_scan_fmap(&ctx, 0, 0, NULL, AC_SCAN_VIR, &acres, NULL); + (void)cli_scan_fmap(&ctx, CL_TYPE_ANY, false, NULL, AC_SCAN_VIR, &acres, NULL); res = acres; while (res) { @@ -2152,6 +2161,9 @@ static void matchsig(char *sig, const char *offset, int fd) if (NULL != ctx.recursion_stack) { free(ctx.recursion_stack); } + if (NULL != ctx.evidence) { + evidence_free(ctx.evidence); + } if (NULL != engine) { cl_engine_free(engine); } @@ -3301,7 +3313,7 @@ static int dumpcerts(const struct optstruct *opts) goto done; } - if (cli_add_content_match_pattern(engine->root[0], "test", "deadbeef", 0, 0, 0, "*", 0, NULL, 0) != CL_SUCCESS) { + if (cli_add_content_match_pattern(engine->root[0], "test", "deadbeef", 0, 0, 0, "*", NULL, 0) != CL_SUCCESS) { mprintf(LOGG_ERROR, "dumpcerts: Can't parse signature\n"); goto done; } @@ -3315,7 +3327,10 @@ static int dumpcerts(const struct optstruct *opts) cl_debug(); /* prepare context */ - ctx.engine = engine; + ctx.engine = engine; + + ctx.evidence = evidence_new(); + ctx.options = &options; ctx.options->parse = ~0; ctx.dconf = (struct cli_dconf *)engine->dconf; @@ -3365,6 +3380,9 @@ static int dumpcerts(const struct optstruct *opts) if (NULL != ctx.recursion_stack) { free(ctx.recursion_stack); } + if (NULL != ctx.evidence) { + evidence_free(ctx.evidence); + } if (NULL != engine) { cl_engine_free(engine); } diff --git a/sigtool/vba.c b/sigtool/vba.c index d526f1ddf1..e9852a7a5d 100644 --- a/sigtool/vba.c +++ b/sigtool/vba.c @@ -38,6 +38,7 @@ #include "vba_extract.h" #include "ole2_extract.h" #include "readdb.h" +#include "clamav_rust.h" // common #include "output.h" @@ -76,7 +77,7 @@ cli_ctx *convenience_ctx(int fd) goto done; } - if (cli_add_content_match_pattern(engine->root[0], "test", "deadbeef", 0, 0, 0, "*", 0, NULL, 0) != CL_SUCCESS) { + if (cli_add_content_match_pattern(engine->root[0], "test", "deadbeef", 0, 0, 0, "*", NULL, 0) != CL_SUCCESS) { printf("convenience_ctx: Can't parse signature\n"); goto done; } @@ -102,6 +103,8 @@ cli_ctx *convenience_ctx(int fd) ctx->engine = (const struct cl_engine *)engine; + ctx->evidence = evidence_new(); + ctx->dconf = (struct cli_dconf *)engine->dconf; ctx->recursion_stack_size = ctx->engine->max_recursion_level; @@ -182,6 +185,10 @@ void destroy_ctx(cli_ctx *ctx) ctx->options = NULL; } + if (NULL != ctx->evidence) { + evidence_free(ctx->evidence); + } + free(ctx); } } diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index 3f6965a6af..452a73ece9 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -354,11 +354,11 @@ add_rust_test(NAME libclamav_rust set_property(TEST libclamav_rust PROPERTY ENVIRONMENT ${ENVIRONMENT}) if(ENABLE_APP) - add_test(NAME clamscan COMMAND ${PythonTest_COMMAND};clamscan_test.py + add_test(NAME clamscan COMMAND ${PythonTest_COMMAND};clamscan WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) set_property(TEST clamscan PROPERTY ENVIRONMENT ${ENVIRONMENT}) if(Valgrind_FOUND) - add_test(NAME clamscan_valgrind COMMAND ${PythonTest_COMMAND};clamscan_test.py + add_test(NAME clamscan_valgrind COMMAND ${PythonTest_COMMAND};clamscan WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) set_property(TEST clamscan_valgrind PROPERTY ENVIRONMENT ${ENVIRONMENT} VALGRIND=${Valgrind_EXECUTABLE}) endif() diff --git a/unit_tests/check_bytecode.c b/unit_tests/check_bytecode.c index 4e14173f00..f3acebf484 100644 --- a/unit_tests/check_bytecode.c +++ b/unit_tests/check_bytecode.c @@ -40,6 +40,7 @@ #include "dconf.h" #include "bytecode_priv.h" #include "pe.h" +#include "clamav_rust.h" #include "checks.h" @@ -72,12 +73,13 @@ static void runtest(const char *file, uint64_t expected, int fail, int nojit, cctx.options = &options; cctx.options->general |= CL_SCAN_GENERAL_ALLMATCHES; - cctx.virname = &virname; cctx.engine = engine = cl_engine_new(); ck_assert_msg(!!cctx.engine, "cannot create engine"); rc = cl_engine_compile(engine); ck_assert_msg(!rc, "cannot compile engine"); + cctx.evidence = evidence_new(); + cctx.dconf = cctx.engine->dconf; cctx.recursion_stack_size = cctx.engine->max_recursion_level; @@ -160,6 +162,7 @@ static void runtest(const char *file, uint64_t expected, int fail, int nojit, cli_bytecode_done(&bcs); free(cctx.recursion_stack); cl_engine_free(engine); + evidence_free(cctx.evidence); if (fdin >= 0) close(fdin); } diff --git a/unit_tests/check_clamav.c b/unit_tests/check_clamav.c index 74cc8e5b16..045591ed66 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_matchers.c b/unit_tests/check_matchers.c index a8e5f49a8a..e0595f81a1 100644 --- a/unit_tests/check_matchers.c +++ b/unit_tests/check_matchers.c @@ -35,6 +35,7 @@ #include "matcher-pcre.h" #include "others.h" #include "default.h" +#include "clamav_rust.h" #include "checks.h" @@ -169,8 +170,9 @@ static void setup(void) memset(&options, 0, sizeof(struct cl_scan_options)); ctx.options = &options; - ctx.virname = &virname; - ctx.engine = cl_engine_new(); + ctx.evidence = evidence_new(); + + ctx.engine = cl_engine_new(); ck_assert_msg(!!ctx.engine, "cl_engine_new() failed"); ctx.dconf = ctx.engine->dconf; @@ -197,6 +199,7 @@ static void teardown(void) { cl_engine_free((struct cl_engine *)ctx.engine); free(ctx.recursion_stack); + evidence_free(ctx.evidence); } START_TEST(test_ac_scanbuff) @@ -217,7 +220,7 @@ START_TEST(test_ac_scanbuff) ck_assert_msg(ret == CL_SUCCESS, "cli_ac_init() failed"); for (i = 0; ac_testdata[i].data; i++) { - ret = cli_add_content_match_pattern(root, ac_testdata[i].virname, ac_testdata[i].hexsig, 0, 0, 0, "*", 0, NULL, 0); + ret = cli_add_content_match_pattern(root, ac_testdata[i].virname, ac_testdata[i].hexsig, 0, 0, 0, "*", NULL, 0); ck_assert_msg(ret == CL_SUCCESS, "cli_add_content_match_pattern failed"); } @@ -260,7 +263,7 @@ START_TEST(test_ac_scanbuff_allscan) ck_assert_msg(ret == CL_SUCCESS, "cli_ac_init() failed"); for (i = 0; ac_testdata[i].data; i++) { - ret = cli_add_content_match_pattern(root, ac_testdata[i].virname, ac_testdata[i].hexsig, 0, 0, 0, "*", 0, NULL, 0); + ret = cli_add_content_match_pattern(root, ac_testdata[i].virname, ac_testdata[i].hexsig, 0, 0, 0, "*", NULL, 0); ck_assert_msg(ret == CL_SUCCESS, "cli_add_content_match_pattern failed"); } @@ -277,10 +280,17 @@ START_TEST(test_ac_scanbuff_allscan) ck_assert_msg(!strncmp(virname, ac_testdata[i].virname, strlen(ac_testdata[i].virname)), "Dataset %u matched with %s", i, virname); ret = cli_scan_buff((const unsigned char *)ac_testdata[i].data, strlen(ac_testdata[i].data), 0, &ctx, 0, NULL); - ck_assert_msg(ret == CL_VIRUS, "cli_scan_buff() failed for %s", ac_testdata[i].virname); + ck_assert_msg(ret == CL_SUCCESS, "cli_scan_buff() failed for %s", ac_testdata[i].virname); + + // phishingScan() doesn't check the number of alerts. When using CL_SCAN_GENERAL_ALLMATCHES + // or if using `CL_SCAN_GENERAL_HEURISTIC_PRECEDENCE` and `cli_append_potentially_unwanted()` + // we need to count the number of alerts manually to determine the verdict. + ck_assert_msg(0 < evidence_num_alerts(ctx.evidence), "cli_scan_buff() failed for %s", ac_testdata[i].virname); ck_assert_msg(!strncmp(virname, ac_testdata[i].virname, strlen(ac_testdata[i].virname)), "Dataset %u matched with %s", i, virname); - if (ctx.num_viruses) - ctx.num_viruses = 0; + if (evidence_num_alerts(ctx.evidence) > 0) { + evidence_free(ctx.evidence); + ctx.evidence = evidence_new(); + } } cli_ac_freedata(&mdata); @@ -305,7 +315,7 @@ START_TEST(test_ac_scanbuff_ex) ck_assert_msg(ret == CL_SUCCESS, "[ac_ex] cli_ac_init() failed"); for (i = 0; ac_sigopts_testdata[i].data; i++) { - ret = cli_sigopts_handler(root, ac_sigopts_testdata[i].virname, ac_sigopts_testdata[i].hexsig, ac_sigopts_testdata[i].sigopts, 0, 0, ac_sigopts_testdata[i].offset, 0, NULL, 0); + ret = cli_sigopts_handler(root, ac_sigopts_testdata[i].virname, ac_sigopts_testdata[i].hexsig, ac_sigopts_testdata[i].sigopts, 0, 0, ac_sigopts_testdata[i].offset, NULL, 0); ck_assert_msg(ret == CL_SUCCESS, "[ac_ex] cli_sigopts_handler() failed"); } @@ -348,7 +358,7 @@ START_TEST(test_ac_scanbuff_allscan_ex) ck_assert_msg(ret == CL_SUCCESS, "[ac_ex] cli_ac_init() failed"); for (i = 0; ac_sigopts_testdata[i].data; i++) { - ret = cli_sigopts_handler(root, ac_sigopts_testdata[i].virname, ac_sigopts_testdata[i].hexsig, ac_sigopts_testdata[i].sigopts, 0, 0, ac_sigopts_testdata[i].offset, 0, NULL, 0); + ret = cli_sigopts_handler(root, ac_sigopts_testdata[i].virname, ac_sigopts_testdata[i].hexsig, ac_sigopts_testdata[i].sigopts, 0, 0, ac_sigopts_testdata[i].offset, NULL, 0); ck_assert_msg(ret == CL_SUCCESS, "[ac_ex] cli_sigopts_handler() failed"); } @@ -360,15 +370,28 @@ START_TEST(test_ac_scanbuff_allscan_ex) ctx.options->general |= CL_SCAN_GENERAL_ALLMATCHES; /* enable all-match */ for (i = 0; ac_sigopts_testdata[i].data; i++) { + cl_error_t verdict = CL_CLEAN; + ret = cli_ac_scanbuff((const unsigned char *)ac_sigopts_testdata[i].data, ac_sigopts_testdata[i].dlength, &virname, NULL, NULL, root, &mdata, 0, 0, NULL, AC_SCAN_VIR, NULL); ck_assert_msg(ret == ac_sigopts_testdata[i].expected_result, "[ac_ex] cli_ac_scanbuff() failed for %s (%d != %d)", ac_sigopts_testdata[i].virname, ret, ac_sigopts_testdata[i].expected_result); if (ac_sigopts_testdata[i].expected_result == CL_VIRUS) ck_assert_msg(!strncmp(virname, ac_sigopts_testdata[i].virname, strlen(ac_sigopts_testdata[i].virname)), "[ac_ex] Dataset %u matched with %s", i, virname); ret = cli_scan_buff((const unsigned char *)ac_sigopts_testdata[i].data, ac_sigopts_testdata[i].dlength, 0, &ctx, 0, NULL); - ck_assert_msg(ret == ac_sigopts_testdata[i].expected_result, "[ac_ex] cli_ac_scanbuff() failed for %s (%d != %d)", ac_sigopts_testdata[i].virname, ret, ac_sigopts_testdata[i].expected_result); - if (ctx.num_viruses) - ctx.num_viruses = 0; + ck_assert_msg(ret == CL_SUCCESS, "[ac_ex] cli_ac_scanbuff() failed for %s (%d != %d)", ac_sigopts_testdata[i].virname, ret, ac_sigopts_testdata[i].expected_result); + + // phishingScan() doesn't check the number of alerts. When using CL_SCAN_GENERAL_ALLMATCHES + // or if using `CL_SCAN_GENERAL_HEURISTIC_PRECEDENCE` and `cli_append_potentially_unwanted()` + // we need to count the number of alerts manually to determine the verdict. + if (0 < evidence_num_alerts(ctx.evidence)) { + verdict = CL_VIRUS; + } + + ck_assert_msg(verdict == ac_sigopts_testdata[i].expected_result, "[ac_ex] cli_ac_scanbuff() failed for %s (%d != %d)", ac_sigopts_testdata[i].virname, verdict, ac_sigopts_testdata[i].expected_result); + if (evidence_num_alerts(ctx.evidence) > 0) { + evidence_free(ctx.evidence); + ctx.evidence = evidence_new(); + } } cli_ac_freedata(&mdata); @@ -390,11 +413,11 @@ START_TEST(test_bm_scanbuff) ret = cli_bm_init(root); ck_assert_msg(ret == CL_SUCCESS, "cli_bm_init() failed"); - ret = cli_add_content_match_pattern(root, "Sig1", "deadbabe", 0, 0, 0, "*", 0, NULL, 0); + ret = cli_add_content_match_pattern(root, "Sig1", "deadbabe", 0, 0, 0, "*", NULL, 0); ck_assert_msg(ret == CL_SUCCESS, "cli_add_content_match_pattern failed"); - ret = cli_add_content_match_pattern(root, "Sig2", "deadbeef", 0, 0, 0, "*", 0, NULL, 0); + ret = cli_add_content_match_pattern(root, "Sig2", "deadbeef", 0, 0, 0, "*", NULL, 0); ck_assert_msg(ret == CL_SUCCESS, "cli_add_content_match_pattern failed"); - ret = cli_add_content_match_pattern(root, "Sig3", "babedead", 0, 0, 0, "*", 0, NULL, 0); + ret = cli_add_content_match_pattern(root, "Sig3", "babedead", 0, 0, 0, "*", NULL, 0); ck_assert_msg(ret == CL_SUCCESS, "cli_add_content_match_pattern failed"); ctx.options->general &= ~CL_SCAN_GENERAL_ALLMATCHES; /* make sure all-match is disabled */ @@ -419,11 +442,11 @@ START_TEST(test_bm_scanbuff_allscan) ret = cli_bm_init(root); ck_assert_msg(ret == CL_SUCCESS, "cli_bm_init() failed"); - ret = cli_add_content_match_pattern(root, "Sig1", "deadbabe", 0, 0, 0, "*", 0, NULL, 0); + ret = cli_add_content_match_pattern(root, "Sig1", "deadbabe", 0, 0, 0, "*", NULL, 0); ck_assert_msg(ret == CL_SUCCESS, "cli_add_content_match_pattern failed"); - ret = cli_add_content_match_pattern(root, "Sig2", "deadbeef", 0, 0, 0, "*", 0, NULL, 0); + ret = cli_add_content_match_pattern(root, "Sig2", "deadbeef", 0, 0, 0, "*", NULL, 0); ck_assert_msg(ret == CL_SUCCESS, "cli_add_content_match_pattern failed"); - ret = cli_add_content_match_pattern(root, "Sig3", "babedead", 0, 0, 0, "*", 0, NULL, 0); + ret = cli_add_content_match_pattern(root, "Sig3", "babedead", 0, 0, 0, "*", NULL, 0); ck_assert_msg(ret == CL_SUCCESS, "cli_add_content_match_pattern failed"); ctx.options->general |= CL_SCAN_GENERAL_ALLMATCHES; /* enable all-match */ @@ -461,7 +484,7 @@ START_TEST(test_pcre_scanbuff) strncat(hexsig, PCRE_BYPASS, hexlen); strncat(hexsig, pcre_testdata[i].hexsig, hexlen); - ret = readdb_parse_ldb_subsignature(root, pcre_testdata[i].virname, hexsig, pcre_testdata[i].offset, 0, NULL, 0, 0, 0, NULL); + ret = readdb_parse_ldb_subsignature(root, pcre_testdata[i].virname, hexsig, pcre_testdata[i].offset, NULL, 0, 0, 0, NULL); ck_assert_msg(ret == CL_SUCCESS, "[pcre] readdb_parse_ldb_subsignature failed"); free(hexsig); } @@ -515,7 +538,7 @@ START_TEST(test_pcre_scanbuff_allscan) strncat(hexsig, PCRE_BYPASS, hexlen); strncat(hexsig, pcre_testdata[i].hexsig, hexlen); - ret = readdb_parse_ldb_subsignature(root, pcre_testdata[i].virname, hexsig, pcre_testdata[i].offset, 0, NULL, 0, 0, 1, NULL); + ret = readdb_parse_ldb_subsignature(root, pcre_testdata[i].virname, hexsig, pcre_testdata[i].offset, NULL, 0, 0, 1, NULL); ck_assert_msg(ret == CL_SUCCESS, "[pcre] readdb_parse_ldb_subsignature failed"); free(hexsig); } @@ -530,16 +553,28 @@ START_TEST(test_pcre_scanbuff_allscan) ctx.options->general |= CL_SCAN_GENERAL_ALLMATCHES; /* enable all-match */ for (i = 0; pcre_testdata[i].data; i++) { + cl_error_t verdict = CL_CLEAN; + ret = cli_pcre_scanbuf((const unsigned char *)pcre_testdata[i].data, strlen(pcre_testdata[i].data), &virname, NULL, root, NULL, NULL, NULL); ck_assert_msg(ret == pcre_testdata[i].expected_result, "[pcre] cli_pcre_scanbuff() failed for %s (%d != %d)", pcre_testdata[i].virname, ret, pcre_testdata[i].expected_result); // we cannot check if the virname matches because we didn't load a whole logical signature, and virnames are stored in the lsig structure, now. ret = cli_scan_buff((const unsigned char *)pcre_testdata[i].data, strlen(pcre_testdata[i].data), 0, &ctx, 0, NULL); - ck_assert_msg(ret == pcre_testdata[i].expected_result, "[pcre] cli_scan_buff() failed for %s", pcre_testdata[i].virname); + + // cli_scan_buff() doesn't check the number of alerts. When using CL_SCAN_GENERAL_ALLMATCHES + // or if using `CL_SCAN_GENERAL_HEURISTIC_PRECEDENCE` and `cli_append_potentially_unwanted()` + // we need to count the number of alerts manually to determine the verdict. + if (0 < evidence_num_alerts(ctx.evidence)) { + verdict = CL_VIRUS; + } + + ck_assert_msg(verdict == pcre_testdata[i].expected_result, "[pcre] cli_scan_buff() failed for %s", pcre_testdata[i].virname); /* num_virus field add to test case struct */ - if (ctx.num_viruses) - ctx.num_viruses = 0; + if (evidence_num_alerts(ctx.evidence) > 0) { + evidence_free(ctx.evidence); + ctx.evidence = evidence_new(); + } } cli_ac_freedata(&mdata); diff --git a/unit_tests/check_regex.c b/unit_tests/check_regex.c index 6e4159b2c6..c1f17a43e8 100644 --- a/unit_tests/check_regex.c +++ b/unit_tests/check_regex.c @@ -43,6 +43,8 @@ #include "phish_domaincheck_db.h" #include "phish_allow_list.h" +#include "clamav_rust.h" + #include "checks.h" static size_t cb_called = 0; @@ -405,8 +407,8 @@ static void do_phishing_test(const struct rtest *rtest) hrefs.tag[0] = (unsigned char *)cli_strdup("href"); hrefs.contents[0] = (unsigned char *)cli_strdup(rtest->displayurl); - ctx.engine = engine; - ctx.virname = &virname; + ctx.engine = engine; + ctx.evidence = evidence_new(); rc = phishingScan(&ctx, &hrefs); @@ -414,30 +416,34 @@ static void do_phishing_test(const struct rtest *rtest) ck_assert_msg(rc == CL_CLEAN, "phishingScan"); switch (rtest->result) { case RTR_PHISH: - ck_assert_msg(ctx.found_possibly_unwanted, + ck_assert_msg(evidence_num_indicators_type(ctx.evidence, IndicatorType_PotentiallyUnwanted), "this should be phishing, realURL: %s, displayURL: %s", rtest->realurl, rtest->displayurl); break; case RTR_ALLOWED: - ck_assert_msg(!ctx.found_possibly_unwanted, + ck_assert_msg(!evidence_num_indicators_type(ctx.evidence, IndicatorType_PotentiallyUnwanted), "this should be allowed, realURL: %s, displayURL: %s", rtest->realurl, rtest->displayurl); break; case RTR_CLEAN: - ck_assert_msg(!ctx.found_possibly_unwanted, + ck_assert_msg(!evidence_num_indicators_type(ctx.evidence, IndicatorType_PotentiallyUnwanted), "this should be clean, realURL: %s, displayURL: %s", rtest->realurl, rtest->displayurl); break; case RTR_BLOCKED: if (!loaded_2) - ck_assert_msg(!ctx.found_possibly_unwanted, + ck_assert_msg(!evidence_num_indicators_type(ctx.evidence, IndicatorType_PotentiallyUnwanted), "this should be clean, realURL: %s, displayURL: %s", rtest->realurl, rtest->displayurl); else { - ck_assert_msg(ctx.found_possibly_unwanted, + const char *viruname = NULL; + + ck_assert_msg(evidence_num_indicators_type(ctx.evidence, IndicatorType_PotentiallyUnwanted), "this should be blocked, realURL: %s, displayURL: %s", rtest->realurl, rtest->displayurl); - if (*ctx.virname) { + + virname = cli_get_last_virus_str(&ctx); + if (NULL != virname) { char *phishingFound = NULL; char *detectionName = NULL; if (strstr(rtest->realurl, "malware-test")) { @@ -447,8 +453,8 @@ static void do_phishing_test(const struct rtest *rtest) detectionName = "Heuristics.Safebrowsing.Suspected-phishing_safebrowsing.clamav.net"; } ck_assert_msg(detectionName != NULL, "\n\t Block list test case error - malware-test or phishing-test not found in: %s\n", rtest->realurl); - phishingFound = strstr((const char *)*ctx.virname, detectionName); - ck_assert_msg(phishingFound != NULL, "\n\t should be: %s,\n\t but is: %s\n", detectionName, *ctx.virname); + phishingFound = strstr((const char *)virname, detectionName); + ck_assert_msg(phishingFound != NULL, "\n\t should be: %s,\n\t but is: %s\n", detectionName, virname); } } break; @@ -456,6 +462,8 @@ static void do_phishing_test(const struct rtest *rtest) /* don't worry about it, this was tested in regex_list_match_test() */ break; } + + evidence_free(ctx.evidence); } static void do_phishing_test_allscan(const struct rtest *rtest) @@ -465,6 +473,7 @@ static void do_phishing_test_allscan(const struct rtest *rtest) const char *virname = NULL; tag_arguments_t hrefs; cl_error_t rc; + cl_error_t verdict = CL_CLEAN; struct cl_scan_options options; memset(&ctx, 0, sizeof(ctx)); @@ -485,50 +494,63 @@ static void do_phishing_test_allscan(const struct rtest *rtest) hrefs.tag[0] = (unsigned char *)cli_strdup("href"); hrefs.contents[0] = (unsigned char *)cli_strdup(rtest->displayurl); - ctx.engine = engine; - ctx.virname = &virname; - ctx.options->general |= CL_SCAN_GENERAL_ALLMATCHES; + ctx.engine = engine; + ctx.evidence = evidence_new(); rc = phishingScan(&ctx, &hrefs); + ck_assert_msg(rc == CL_SUCCESS || rc == CL_VIRUS, "phishingScan failed with error code: %s (%u)", + cl_strerror(rc), + rc); + + // phishingScan() doesn't check the number of alerts. When using CL_SCAN_GENERAL_ALLMATCHES + // or if using `CL_SCAN_GENERAL_HEURISTIC_PRECEDENCE` and `cli_append_potentially_unwanted()` + // we need to count the number of alerts manually to determine the verdict. + if (0 < evidence_num_alerts(ctx.evidence)) { + verdict = CL_VIRUS; + } - html_tag_arg_free(&hrefs); if (rtest->result == RTR_PHISH || (loaded_2 != 0 && rtest->result == RTR_BLOCKED)) { - ck_assert_msg(rc == CL_VIRUS, "phishingScan returned \"%s\", expected \"%s\". \n\trealURL: %s \n\tdisplayURL: %s", - cl_strerror(rc), + ck_assert_msg(verdict == CL_VIRUS, "phishingScan returned \"%s\", expected \"%s\". \n\trealURL: %s \n\tdisplayURL: %s", + cl_strerror(verdict), cl_strerror(CL_VIRUS), rtest->realurl, rtest->displayurl); } else { - ck_assert_msg(rc == CL_CLEAN, "phishingScan returned \"%s\", expected \"%s\". \n\trealURL: %s \n\tdisplayURL: %s", - cl_strerror(rc), + ck_assert_msg(verdict == CL_CLEAN, "phishingScan returned \"%s\", expected \"%s\". \n\trealURL: %s \n\tdisplayURL: %s", + cl_strerror(verdict), cl_strerror(CL_CLEAN), rtest->realurl, rtest->displayurl); } + switch (rtest->result) { case RTR_PHISH: - ck_assert_msg(ctx.num_viruses, + ck_assert_msg(evidence_num_alerts(ctx.evidence), "this should be phishing, realURL: %s, displayURL: %s", rtest->realurl, rtest->displayurl); break; case RTR_ALLOWED: - ck_assert_msg(!ctx.num_viruses, + ck_assert_msg(!evidence_num_alerts(ctx.evidence), "this should be allowed, realURL: %s, displayURL: %s", rtest->realurl, rtest->displayurl); break; case RTR_CLEAN: - ck_assert_msg(!ctx.num_viruses, + ck_assert_msg(!evidence_num_alerts(ctx.evidence), "this should be clean, realURL: %s, displayURL: %s", rtest->realurl, rtest->displayurl); break; case RTR_BLOCKED: - if (!loaded_2) - ck_assert_msg(!ctx.num_viruses, + if (!loaded_2) { + ck_assert_msg(!evidence_num_alerts(ctx.evidence), "this should be clean, realURL: %s, displayURL: %s", rtest->realurl, rtest->displayurl); - else { - ck_assert_msg(ctx.num_viruses, + } else { + const char *viruname = NULL; + + ck_assert_msg(evidence_num_alerts(ctx.evidence), "this should be blocked, realURL: %s, displayURL: %s", rtest->realurl, rtest->displayurl); - if (*ctx.virname) { + + virname = cli_get_last_virus_str(&ctx); + if (NULL != virname) { char *phishingFound = NULL; char *detectionName = NULL; if (strstr(rtest->realurl, "malware-test")) { @@ -538,8 +560,8 @@ static void do_phishing_test_allscan(const struct rtest *rtest) detectionName = "Heuristics.Safebrowsing.Suspected-phishing_safebrowsing.clamav.net"; } ck_assert_msg(detectionName != NULL, "\n\t Block list test case error - malware-test or phishing-test not found in: %s\n", rtest->realurl); - phishingFound = strstr((const char *)*ctx.virname, detectionName); - ck_assert_msg(phishingFound != NULL, "\n\t should be: %s,\n\t but is: %s\n", detectionName, *ctx.virname); + phishingFound = strstr(virname, detectionName); + ck_assert_msg(phishingFound != NULL, "\n\t should be: %s,\n\t but is: %s\n", detectionName, virname); } } break; @@ -547,6 +569,10 @@ static void do_phishing_test_allscan(const struct rtest *rtest) /* don't worry about it, this was tested in regex_list_match_test() */ break; } + + html_tag_arg_free(&hrefs); + + evidence_free(ctx.evidence); } START_TEST(phishingScan_test) diff --git a/unit_tests/check_str.c b/unit_tests/check_str.c index 611b198206..501039c442 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 diff --git a/unit_tests/clamscan/_basic_test.py b/unit_tests/clamscan/_basic_test.py new file mode 100644 index 0000000000..aeb0a31b7d --- /dev/null +++ b/unit_tests/clamscan/_basic_test.py @@ -0,0 +1,97 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +import shutil +import sys + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + TC.testpaths = list(TC.path_build.glob('unit_tests/input/clamav_hdb_scanfiles/clam*')) # A list of Path()'s of each of our generated test files + + # Prepare a directory to store our test databases + TC.path_db = TC.path_tmp / 'database' + TC.path_db.mkdir(parents=True) + + shutil.copy( + str(TC.path_build / 'unit_tests' / 'input' / 'clamav.hdb'), + str(TC.path_db), + ) + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_00_version(self): + self.step_name('clamscan version test') + + command = '{valgrind} {valgrind_args} {clamscan} -V'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan + ) + output = self.execute_command(command) + + assert output.ec == 0 # success + + expected_results = [ + 'ClamAV {}'.format(TC.version), + ] + self.verify_output(output.out, expected=expected_results) + + def test_01_all_testfiles(self): + self.step_name('Test that clamscan alerts on all test files') + + testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, + clamscan=TC.clamscan, + path_db=TC.path_db / 'clamav.hdb', + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = ['{}: ClamAV-Test-File.UNOFFICIAL FOUND'.format(testpath.name) for testpath in TC.testpaths] + expected_results.append('Scanned files: {}'.format(len(TC.testpaths))) + expected_results.append('Infected files: {}'.format(len(TC.testpaths))) + self.verify_output(output.out, expected=expected_results) + + def test_02_all_testfiles_ign2(self): + self.step_name('Test that clamscan ignores ClamAV-Test-File alerts') + + # Drop an ignore db into the test database directory + # in our scan, we'll just use the whole directory, which should load the ignore db *first*. + (TC.path_db / 'clamav.ign2').write_text('ClamAV-Test-File\n') + + testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, + clamscan=TC.clamscan, + path_db=TC.path_db, + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 0 # virus found + + expected_results = ['Scanned files: {}'.format(len(TC.testpaths))] + expected_results.append('Infected files: 0') + unexpected_results = ['{}: ClamAV-Test-File.UNOFFICIAL FOUND'.format(testpath.name) for testpath in TC.testpaths] + self.verify_output(output.out, expected=expected_results, unexpected=unexpected_results) + diff --git a/unit_tests/clamscan/allmatch_test.py b/unit_tests/clamscan/allmatch_test.py new file mode 100644 index 0000000000..13e429da0b --- /dev/null +++ b/unit_tests/clamscan/allmatch_test.py @@ -0,0 +1,315 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +import os +from zipfile import ZIP_DEFLATED, ZipFile +import sys + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + # Prepare a directory to store our test databases + TC.path_db = TC.path_tmp / 'database' + TC.path_db.mkdir(parents=True) + + (TC.path_db / 'clam.ndb').write_text( + "Test.NDB:0:*:4b45524e454c33322e444c4c00004578\n" + ) + (TC.path_db / 'clam.ldb').write_text( + "Test.LDB;Engine:52-255,Target:1;0;4B45524E454C33322E444C4C00004578697450726F63657373005553455233322E444C4C00434C414D657373616765426F7841\n" + ) + (TC.path_db / 'clam.hdb').write_text( + "aa15bcf478d165efd2065190eb473bcb:544:Test.MD5.Hash:73\n" + "aa15bcf478d165efd2065190eb473bcb:*:Test.MD5.Hash.NoSize:73\n" + ) + (TC.path_db / 'clam.hsb').write_text( + "71e7b604d18aefd839e51a39c88df8383bb4c071dc31f87f00a2b5df580d4495:544:Test.Sha256.Hash:73\n" + "71e7b604d18aefd839e51a39c88df8383bb4c071dc31f87f00a2b5df580d4495:*:Test.Sha256.Hash.NoSize:73\n" + "62dd70f5e7530e0239901ac186f1f9ae39292561:544:Test.Sha1.Hash:73\n" + "62dd70f5e7530e0239901ac186f1f9ae39292561:*:Test.Sha1.NoSize:73\n" + ) + (TC.path_db / 'clam.imp').write_text( + "98c88d882f01a3f6ac1e5f7dfd761624:39:Test.Import.Hash\n" + "98c88d882f01a3f6ac1e5f7dfd761624:*:Test.Import.Hash.NoSize\n" + ) + (TC.path_db / 'clam.mdb').write_text( + "512:23db1dd3f77fae25610b6a32701313ae:Test.PESection.Hash:73\n" + "*:23db1dd3f77fae25610b6a32701313ae:Test.PESection.Hash.NoSize:73\n" + ) + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_many_sigs(self): + self.step_name('Test that each type of sig alerts in all-match mode') + + testfiles = TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe' + + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=TC.path_db, + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = [ + 'Test.LDB.UNOFFICIAL FOUND', + 'Test.NDB.UNOFFICIAL FOUND', + 'Test.MD5.Hash.UNOFFICIAL FOUND', + 'Test.MD5.Hash.NoSize.UNOFFICIAL FOUND', + 'Test.Sha1.Hash.UNOFFICIAL FOUND', + 'Test.Sha1.NoSize.UNOFFICIAL FOUND', + 'Test.Sha256.Hash.UNOFFICIAL FOUND', + 'Test.Sha256.Hash.NoSize.UNOFFICIAL FOUND', + 'Test.PESection.Hash.UNOFFICIAL FOUND', + 'Test.PESection.Hash.NoSize.UNOFFICIAL FOUND', + 'Test.Import.Hash.UNOFFICIAL FOUND', + 'Test.Import.Hash.NoSize.UNOFFICIAL FOUND', + ] + self.verify_output(output.out, expected=expected_results) + + def test_many_sigs_no_allmatch(self): + self.step_name('Test that only one sig alerts when not using all-match mode') + + testfiles = TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe' + + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=TC.path_db, + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + assert output.out.count('FOUND') == 1 # only finds one of these (order not guaranteeds afaik, so don't care which) + + def test_regression_imphash_nosize(self): + self.step_name('Test an import hash with wildcard size when all-match mode is disabled.') + + db_dir = TC.path_db / 'allmatch-regression-test-sigs' + + os.mkdir(str(db_dir)) + + (db_dir / 'clam.imp').write_text( + "98c88d882f01a3f6ac1e5f7dfd761624:*:Test.Import.Hash.NoSize\n" + ) + + testfiles = TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe' + + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=db_dir / 'clam.imp', + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = [ + 'Test.Import.Hash.NoSize.UNOFFICIAL FOUND', + ] + self.verify_output(output.out, expected=expected_results) + + def test_regression_cbc_and_ndb(self): + self.step_name('Test that bytecode rules will run after content match alerts in all-match mode.') + + # Source for ClamAV-Unit-Test_Signature.cbc + # ```c + # VIRUSNAME_PREFIX("BC.Clamav-Unit-Test-Signature") + # VIRUSNAMES("") + # TARGET(0) + + # FUNCTIONALITY_LEVEL_MIN(FUNC_LEVEL_096_4) + + # SIGNATURES_DECL_BEGIN + # DECLARE_SIGNATURE(test_string) + # SIGNATURES_DECL_END + + # SIGNATURES_DEF_BEGIN + # /* matches "CLAMAV-TEST-STRING-NOT-EICAR" */ + # DEFINE_SIGNATURE(test_string, "0:434c414d41562d544553542d535452494e472d4e4f542d4549434152") + # SIGNATURES_DEF_END + + # bool logical_trigger() + # { + # return matches(Signatures.test_string); + # } + + # int entrypoint(void) + # { + # foundVirus(""); + # return 0; + # } + # ``` + + testfile = TC.path_tmp / 'CLAMAV-TEST-STRING-NOT-EICAR' + + (testfile).write_text( + "CLAMAV-TEST-STRING-NOT-EICAR" + ) + + command = '{valgrind} {valgrind_args} {clamscan} -d {cbc_db} -d {ndb_db} --bytecode-unsigned --allmatch {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + cbc_db=TC.path_source / 'unit_tests' / 'input' / 'bytecode_sigs' / 'Clamav-Unit-Test-Signature.cbc', + ndb_db=TC.path_source / 'unit_tests' / 'input' / 'other_sigs' / 'Clamav-Unit-Test-Signature.ndb', + testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = [ + 'BC.Clamav-Unit-Test-Signature FOUND', # <-- ".UNOFFICIAL" is not added for bytecode signatures + 'NDB.Clamav-Unit-Test-Signature.UNOFFICIAL FOUND', + ] + self.verify_output(output.out, expected=expected_results) + + def test_txt_plus_clam_zipsfx(self): + self.step_name('Test that clam will detect a string in text file, plus identify, extract, and alert on concatenated clam.zip containing clam.exe with a hash sig.') + + testfile = TC.path_tmp / 'test-string-cat-clam.exe.txt' + + clamzip = TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.zip' + + testfile.write_bytes(b"CLAMAV-TEST-STRING-NOT-EICAR" + clamzip.read_bytes()) + + command = '{valgrind} {valgrind_args} {clamscan} -d {clam_exe_db} -d {not_eicar_db} --allmatch {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + clam_exe_db=TC.path_source / 'unit_tests' / 'input' / 'clamav.hdb', + not_eicar_db=TC.path_source / 'unit_tests' / 'input' / 'other_sigs' / 'Clamav-Unit-Test-Signature.ndb', + testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = [ + 'ClamAV-Test-File.UNOFFICIAL FOUND', + 'NDB.Clamav-Unit-Test-Signature.UNOFFICIAL FOUND', + ] + self.verify_output(output.out, expected=expected_results) + + def test_exe_imphash_plus_zipsfx(self): + self.step_name('Test that clam will detect a string in text file, plus identify, extract, and alert on concatenated clam.zip containing clam.exe with an imp-hash sig.') + + # We can't use the hash sig for this clam.exe program because the hash goes out the window when we concatenate on the zip. + (TC.path_tmp / 'clam.imp').write_text( + "98c88d882f01a3f6ac1e5f7dfd761624:39:Test.Import.Hash\n" + ) + + # Build a file that is the clam.exe program with a zip concatinated on that contains the not_eicar test string file. + clam_exe = TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe' + + not_eicar_zip = TC.path_tmp / 'not-eicar.zip' + with ZipFile(str(not_eicar_zip), 'w', ZIP_DEFLATED) as zf: + zf.writestr('not-eicar.txt', b"CLAMAV-TEST-STRING-NOT-EICAR") + + testfile = TC.path_tmp / 'clam.exe.not_eicar.zipsfx' + testfile.write_bytes(clam_exe.read_bytes() + not_eicar_zip.read_bytes()) + + command = '{valgrind} {valgrind_args} {clamscan} -d {clam_exe_db} -d {not_eicar_db} --allmatch {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + clam_exe_db=TC.path_tmp / 'clam.imp', + not_eicar_db=TC.path_source / 'unit_tests' / 'input' / 'other_sigs' / 'Clamav-Unit-Test-Signature.ndb', + testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = [ + 'Test.Import.Hash.UNOFFICIAL FOUND', + 'NDB.Clamav-Unit-Test-Signature.UNOFFICIAL FOUND', + ] + self.verify_output(output.out, expected=expected_results) + + def test_exe_pattern_plus_zipsfx(self): + self.step_name('Test that clam will detect a string in text file, plus identify, extract, and alert on concatenated clam.zip containing clam.exe with a pattern-match sig.') + # This tests a regression where clam will fail to extract the embedded zip file if the pattern-match sig matches before the embedded file type sig. + + # Build a file that is the clam.exe program with a zip concatinated on that contains the not_eicar test string file. + clam_exe = TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe' + + not_eicar_zip = TC.path_tmp / 'not-eicar.zip' + with ZipFile(str(not_eicar_zip), 'w', ZIP_DEFLATED) as zf: + zf.writestr('not-eicar.txt', b"CLAMAV-TEST-STRING-NOT-EICAR") + + testfile = TC.path_tmp / 'clam.exe.not_eicar.zipsfx' + testfile.write_bytes(clam_exe.read_bytes() + not_eicar_zip.read_bytes()) + + command = '{valgrind} {valgrind_args} {clamscan} -d {clam_exe_db} -d {not_eicar_db} --allmatch {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + # We can't use the hash sig for this clam.exe program because the hash goes out the window when we concatenate on the zip. + clam_exe_db=TC.path_db / 'clam.ndb', + not_eicar_db=TC.path_source / 'unit_tests' / 'input' / 'other_sigs' / 'Clamav-Unit-Test-Signature.ndb', + testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = [ + 'Test.NDB.UNOFFICIAL FOUND', + 'NDB.Clamav-Unit-Test-Signature.UNOFFICIAL FOUND', + ] + self.verify_output(output.out, expected=expected_results) + + def test_pe_allmatch(self): + self.step_name('Test that clam will detect a string in test.exe with a wide variety of signatures written or generated for the file.') + + # The sig set and test.exe for test set was written by one of our threat researchers to test the allmatch option. + # Overall, it's much more thorough than previous tests, but some of the tests are duplicates of the previous tests. + + # TODO: The section signatures are not working as written, hence the "broken_dbs" directory. + # There is a known issue with relative offset signatures when using the Boyer-Moore matcher. The sigs work if using the Aho-Corasick matcher. + # When we fix section signatures, we can move them to the alerting sigs directory and update this test. + + test_path = TC.path_source / 'unit_tests' / 'input' / 'pe_allmatch' + test_exe = test_path / 'test.exe' + + command = '{valgrind} {valgrind_args} {clamscan} \ + -d {alerting_dbs} \ + -d {weak_dbs} \ + -d {broken_dbs} \ + --allmatch --bytecode-unsigned {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + alerting_dbs=test_path / 'alert-sigs', + weak_dbs=test_path / 'weak-sigs', + broken_dbs=test_path / 'broken-sigs', + testfiles=test_exe, + ) + output = self.execute_command(command) + + assert output.ec == 1 + + # The alert sig files are all given the signature name, so we can verify that the correct sigs were found. + # We need only to trim off the extension and say "FOUND" for the alerting sigs. + # Note: Some of these have ".UNOFFICIAL" in the name because not all of them have that ".UNOFFICIAL" suffix when reported. + # I think this is a minor bug. So if we change that, we'll need to update this test. + expected_results = ['{sig} FOUND'.format(sig=f.stem) for f in (test_path / 'alert-sigs').iterdir()] + + # The broken sig files are all given the signature name, so we can verify that the correct sigs were found. + # TODO: When we fix section signatures, we can move them to the alerting sigs directory and get rid of this line. + unexpected_results = ['{sig} FOUND'.format(sig=f.stem) for f in (test_path / 'broken-sigs').iterdir()] + + self.verify_output(output.out, expected=expected_results, unexpected=unexpected_results) diff --git a/unit_tests/clamscan/assorted_test.py b/unit_tests/clamscan/assorted_test.py new file mode 100644 index 0000000000..3217e05cf1 --- /dev/null +++ b/unit_tests/clamscan/assorted_test.py @@ -0,0 +1,124 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +import unittest +import sys + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + TC.testpaths = list(TC.path_build.glob('unit_tests/input/clamav_hdb_scanfiles/clam*')) # A list of Path()'s of each of our generated test files + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_00_version(self): + self.step_name('clamscan version test') + + command = '{valgrind} {valgrind_args} {clamscan} -V'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan + ) + output = self.execute_command(command) + + assert output.ec == 0 # success + + expected_results = [ + 'ClamAV {}'.format(TC.version), + ] + self.verify_output(output.out, expected=expected_results) + + def test_weak_indicator_icon(self): + self.step_name('Test icon (.ldb + .idb) weak indicator matching signatures') + + (TC.path_tmp / 'icon.idb').write_text( + "EA0X-32x32x8:ea0x-grp1:ea0x-grp2:2046f030a42a07153f4120a0031600007000005e1617ef0000d21100cb090674150f880313970b0e7716116d01136216022500002f0a173700081a004a0e\n" + "IScab-16x16x8:iscab-grp1:iscab-grp2:107b3000168306015c20a0105b07060be0a0b11c050bea0706cb0a0bbb060b6f00017c06018301068109086b03046705081b000a270a002a000039002b17\n" + ) + (TC.path_tmp / 'icon.ldb').write_text( + "ClamAV-Test-Icon-EA0X;Engine:52-1000,Target:1,IconGroup1:ea0x-grp1,IconGroup2:*;(0);0:4d5a\n" + "ClamAV-Test-Icon-IScab;Engine:52-1000,Target:1,IconGroup2:iscab-grp2;(0);0:4d5a\n" + ) + + testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_ldb} -d {path_idb} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, + clamscan=TC.clamscan, + path_ldb=TC.path_tmp / 'icon.ldb', + path_idb=TC.path_tmp / 'icon.idb', + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + # Use check_fpu_endian to determine expected results + command = '{}'.format(TC.check_fpu_endian) + fpu_endian_output = self.execute_command(command) + + expected_results = [ + 'clam_IScab_ext.exe: ClamAV-Test-Icon-IScab.UNOFFICIAL FOUND', + 'clam_IScab_int.exe: ClamAV-Test-Icon-IScab.UNOFFICIAL FOUND', + ] + if fpu_endian_output.ec == 3: + expected_num_infected = 3 + else: + expected_results.append('clam.ea06.exe: ClamAV-Test-Icon-EA0X.UNOFFICIAL FOUND') + expected_num_infected = 4 + expected_results.append('Infected files: {}'.format(expected_num_infected)) + self.verify_output(output.out, expected=expected_results) + + @unittest.expectedFailure + def test_pe_cert_trust(self): + self.step_name('Test that clam can trust an EXE based on an authenticode certificate check.') + + # TODO: This feature was added in 0.105, but was also broken during that release cycle when we upgraded TomsFastMath. + # So instead of trusting the certificate, prints this out and the certificate is not trusted so the matches may still happen: + # LibClamAV Warning: crtmgr_rsa_verify: verification failed: fp_exptmod failed with 1 + # We need to fix this, and then update this test. + + test_path = TC.path_source / 'unit_tests' / 'input' / 'pe_allmatch' + test_exe = test_path / 'test.exe' + + command = '{valgrind} {valgrind_args} {clamscan} \ + -d {alerting_dbs} \ + -d {weak_dbs} \ + -d {broken_dbs} \ + -d {trust_dbs} \ + --allmatch --bytecode-unsigned {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + alerting_dbs=test_path / 'alert-sigs', + weak_dbs=test_path / 'weak-sigs', + broken_dbs=test_path / 'broken-sigs', + trust_dbs=test_path / 'trust-sigs', + testfiles=test_exe, + ) + output = self.execute_command(command) + + assert output.ec == 0 + + expected_results = ['OK'] + + # The alert sig files are all given the signature name, so we can verify that the correct sigs were found. + # We need only to trim off the extension and say "FOUND" for the alerting sigs. + # Note: Some of these have ".UNOFFICIAL" in the name because not all of them have that ".UNOFFICIAL" suffix when reported. + # I think this is a minor bug. So if we change that, we'll need to update this test. + unexpected_results = ['{sig} FOUND'.format(sig=f.stem) for f in (test_path / 'alert-sigs').iterdir()] + + self.verify_output(output.out, expected=expected_results, unexpected=unexpected_results) diff --git a/unit_tests/clamscan/bytecode_test.py b/unit_tests/clamscan/bytecode_test.py new file mode 100644 index 0000000000..7b65e097fa --- /dev/null +++ b/unit_tests/clamscan/bytecode_test.py @@ -0,0 +1,45 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +import sys + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_pdf_hook(self): + self.step_name('Test that pdf bytecode hooks trigger') + + testfiles = TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.pdf' + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --bytecode-unsigned'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=TC.path_source / 'unit_tests' / 'input' / 'bytecode_sigs' / 'pdf-hook.cbc', + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = [ + 'Test.Case.BC.PDF.hook FOUND', + ] + self.verify_output(output.out, expected=expected_results) diff --git a/unit_tests/clamscan/container_sigs_test.py b/unit_tests/clamscan/container_sigs_test.py new file mode 100644 index 0000000000..b93b30388f --- /dev/null +++ b/unit_tests/clamscan/container_sigs_test.py @@ -0,0 +1,81 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +import sys + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_container(self): + self.step_name('Test that clamav can successfully alert on jpeg image extracted from XLS documents') + # Note: we aren't testing PNG because the attached PNG is not properly fuzzy-hashed by clamav, yet. + + (TC.path_tmp / '7z_zip_container.ldb').write_text( + "7z_zip_container_good;Engine:81-255,Container:CL_TYPE_7Z,Target:0;0;0:7631727573\n" + "7z_zip_container_bad;Engine:81-255,Container:CL_TYPE_ZIP,Target:0;0;0:7631727573\n" + ) + + testfiles = TC.path_source / 'unit_tests' / 'input' / 'other_scanfiles' / 'v1rusv1rus.7z.zip' + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=TC.path_tmp / '7z_zip_container.ldb', + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # no virus, no failures + + expected_stdout = [ + 'v1rusv1rus.7z.zip: 7z_zip_container_good.UNOFFICIAL FOUND', + ] + unexpected_stdout = [ + 'v1rusv1rus.7z.zip: 7z_zip_container_bad.UNOFFICIAL FOUND', + ] + self.verify_output(output.out, expected=expected_stdout, unexpected=unexpected_stdout) + + def test_intermediates(self): + self.step_name('Test that clamav can successfully alert on jpeg image extracted from XLS documents') + # Note: we aren't testing PNG because the attached PNG is not properly fuzzy-hashed by clamav, yet. + + (TC.path_tmp / '7z_zip_intermediates.ldb').write_text( + "7z_zip_intermediates_good;Engine:81-255,Intermediates:CL_TYPE_ZIP>CL_TYPE_7Z,Target:0;0;0:7631727573\n" + "7z_zip_intermediates;Engine:81-255,Intermediates:CL_TYPE_7Z>CL_TYPE_TEXT_ASCII,Target:0;0;0:7631727573\n" + ) + + testfiles = TC.path_source / 'unit_tests' / 'input' / 'other_scanfiles' / 'v1rusv1rus.7z.zip' + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=TC.path_tmp / '7z_zip_intermediates.ldb', + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # no virus, no failures + + expected_stdout = [ + 'v1rusv1rus.7z.zip: 7z_zip_intermediates_good.UNOFFICIAL FOUND', + ] + unexpected_stdout = [ + 'v1rusv1rus.7z.zip: 7z_zip_intermediates_bad.UNOFFICIAL FOUND', + ] + self.verify_output(output.out, expected=expected_stdout, unexpected=unexpected_stdout) diff --git a/unit_tests/clamscan/fp_check_test.py b/unit_tests/clamscan/fp_check_test.py new file mode 100644 index 0000000000..5838931c69 --- /dev/null +++ b/unit_tests/clamscan/fp_check_test.py @@ -0,0 +1,221 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run {valgrind} {valgrind_args} {clamscan} tests. +""" + +import unittest +import hashlib +from zipfile import ZIP_DEFLATED, ZipFile +import sys + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + TC.test_file = TC.path_tmp / "test_file" + with open(TC.test_file, "wb") as testfile: + testfile.write( + b"""sfasfasf +?> +""") + + TC.normalized_match_sig = TC.path_tmp / "normalized.ndb" + TC.normalized_match_sig.write_text(r"Malicious.PHP.normalized:0:*:69676e6f72655f757365725f61626f7274286173646629") + + TC.original_hash_fp = TC.path_tmp / "original_hash.fp" + TC.original_hash_fp.write_text(r"a4b3c39134fa424beb9f84ffe5f175a3:190:original_hash") + + TC.original_hash_wild_fp = TC.path_tmp / "original_hash.wild.fp" + TC.original_hash_wild_fp.write_text(r"a4b3c39134fa424beb9f84ffe5f175a3:*:original_hash.wild:73") + + # The normalized hash is this for now. Changes to clamav normalization logic may require + # changes to this hash. + TC.normalized_hash_fp = TC.path_tmp / "normalized_hash.fp" + TC.normalized_hash_fp.write_text(r"0e32a3ab501afb50daedc04764f8dc16:188:normalized_hash") + + TC.normalized_hash_wild_fp = TC.path_tmp / "normalized_hash.wild.fp" + TC.normalized_hash_wild_fp.write_text(r"0e32a3ab501afb50daedc04764f8dc16:*:normalized_hash.wild:73") + + TC.test_file_zipped = TC.path_tmp / 'test_file.zip' + with ZipFile(str(TC.test_file_zipped), 'w', ZIP_DEFLATED) as zf: + # Add truncted PNG file that will alert with --alert-broken-media + with open(TC.path_source / 'logo.png', 'br') as logo_png: + zf.writestr('test_file', b"""sfasfasf +?> +""") + + # Generate hash of the zipped file. + # Since we generated the zip in python, we don't know the hash in advance. + hash_md5 = hashlib.md5() + with TC.test_file_zipped.open("rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + hash_md5 = hash_md5.hexdigest() + + TC.test_file_zipped_hash_fp = TC.path_tmp / 'test_file.zip.hash.fp' + TC.test_file_zipped_hash_fp.write_text('{hash}:{size}:test_file.zip'.format( + hash=hash_md5, + size=TC.test_file_zipped.stat().st_size)) + + TC.test_file_zipped_hash_wild_fp = TC.path_tmp / 'test_file.zip.hash.wild.fp' + TC.test_file_zipped_hash_wild_fp.write_text('{hash}:*:test_file.zip.wild:73'.format( + hash=hash_md5)) + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_alerts_on_normalized(self): + """ + This test expects that the normalized pattern match sig without the .fp sig will in fact alert. + """ + self.step_name("Test file detection with pattern from normalized HTML") + + output = self.execute_command( + "{valgrind} {valgrind_args} {clamscan} {testfiles} -d {db1}".format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + testfiles=TC.test_file, + db1=TC.normalized_match_sig, + ) + ) + self.verify_output(output.out, expected=["Malicious.PHP.normalized.UNOFFICIAL FOUND"], unexpected=[]) + + def test_alerts_on_zip(self): + """ + This test expects that the OG sig without the .fp sig will in fact alert. + """ + self.step_name("Test file detection with pattern from normalized HTML inside a ZIP file") + + output = self.execute_command( + "{valgrind} {valgrind_args} {clamscan} {testfiles} -d {db1}".format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + testfiles=TC.test_file_zipped, + db1=TC.normalized_match_sig, + ) + ) + self.verify_output(output.out, expected=["Malicious.PHP.normalized.UNOFFICIAL FOUND"], unexpected=[]) + + def test_fp_for_normalized(self): + """ + This test expects that FP sigs for normalized HTML hashes will work, + because hashes are now created when an fmap is created and all embedded + file content to be scanned now gets its own fmap. + """ + self.step_name("Test file trusted with fixed-size hash of the normalized HTML") + + output = self.execute_command( + "{valgrind} {valgrind_args} {clamscan} {testfiles} -d {db1} -d {db2} ".format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + testfiles=TC.test_file, + db1=TC.normalized_match_sig, + db2=TC.normalized_hash_fp, + ) + ) + self.verify_output(output.out, expected=["OK"], unexpected=[]) + + def test_fp_for_normalized_wild(self): + """ + This test expects that wildcard FP sigs for normalized HTML hashes will work, + because hashes are now created when an fmap is created and all embedded + file content to be scanned now gets its own fmap. + """ + self.step_name("Test file trusted with wild-card hash of the normalized HTML") + + output = self.execute_command( + "{valgrind} {valgrind_args} {clamscan} {testfiles} -d {db1} -d {db2} ".format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + testfiles=TC.test_file, + db1=TC.normalized_match_sig, + db2=TC.normalized_hash_wild_fp, + ) + ) + self.verify_output(output.out, expected=["OK"], unexpected=[]) + + def test_fp_for_nonnormalized(self): + """ + This test expects that FP sigs for non-normalized HTML hashes will work, + because we now check each hash in the fmap recursion list. + """ + self.step_name("Test file trusted with the original non-normalized fixed-size hash") + + output = self.execute_command( + "{valgrind} {valgrind_args} {clamscan} {testfiles} -d {db1} -d {db2}".format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + testfiles=TC.test_file, + db1=TC.normalized_match_sig, + db2=TC.original_hash_fp, + ) + ) + self.verify_output(output.out, expected=["OK"], unexpected=[]) + + def test_fp_for_nonnormalized_wild(self): + """ + This test expects that FP sigs for non-normalized HTML hashes will work, + because we now check each hash in the fmap recursion list. + """ + self.step_name("Test file trusted with the original non-normalized wild-card hash") + + output = self.execute_command( + "{valgrind} {valgrind_args} {clamscan} {testfiles} -d {db1} -d {db2}".format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + testfiles=TC.test_file, + db1=TC.normalized_match_sig, + db2=TC.original_hash_wild_fp, + ) + ) + self.verify_output(output.out, expected=["OK"], unexpected=[]) + + def test_fp_for_zipped_file(self): + """ + This test expects that FP sigs for a zip containing the test file will work. + """ + self.step_name("Test file trusted with fixed-size hash of zip containing test file") + + output = self.execute_command( + "{valgrind} {valgrind_args} {clamscan} {testfiles} -d {db1} -d {db2}".format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + testfiles=TC.test_file_zipped, + db1=TC.normalized_match_sig, + db2=TC.test_file_zipped_hash_fp, + ) + ) + self.verify_output(output.out, expected=["OK"], unexpected=[]) + + def test_fp_for_zipped_file_wild(self): + """ + This test expects that FP sigs for a zip containing the test file will work. + """ + self.step_name("Test file trusted with wildcard hash of zip containing test file") + + output = self.execute_command( + "{valgrind} {valgrind_args} {clamscan} {testfiles} -d {db1} -d {db2}".format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + testfiles=TC.test_file_zipped, + db1=TC.normalized_match_sig, + db2=TC.test_file_zipped_hash_wild_fp, + ) + ) + self.verify_output(output.out, expected=["OK"], unexpected=[]) diff --git a/unit_tests/clamscan/fuzzy_img_hash_test.py b/unit_tests/clamscan/fuzzy_img_hash_test.py new file mode 100644 index 0000000000..1e86f11716 --- /dev/null +++ b/unit_tests/clamscan/fuzzy_img_hash_test.py @@ -0,0 +1,142 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +import sys + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + # Testing with our logo png file. + TC.testfiles = TC.path_source / 'logo.png' + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + # + # First check with the good database in all-match mode. + # + + def test_sigs_good_allmatch(self): + self.step_name('Test with a good database in all-match mode.') + + (TC.path_tmp / 'good.ldb').write_text( + "logo.png.good;Engine:150-255,Target:0;0;fuzzy_img#af2ad01ed42993c7#0\n" + "logo.png.bad.with.second.subsig;Engine:150-255,Target:0;0&1;deadbeef;fuzzy_img#af2ad01ed42993c7#0\n" + "logo.png.good.with.second.subsig;Engine:150-255,Target:0;0&1;49484452;fuzzy_img#af2ad01ed42993c7#0\n" + ) + + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=TC.path_tmp / 'good.ldb', + testfiles=TC.testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_stdout = [ + 'logo.png.good.UNOFFICIAL FOUND', + 'logo.png.good.with.second.subsig.UNOFFICIAL FOUND', + ] + unexpected_stdout = [ + 'logo.png.bad.with.second.subsig.UNOFFICIAL FOUND', + ] + self.verify_output(output.out, expected=expected_stdout) + + # + # Next check with the bad signatures + # + + def test_sigs_bad_hash(self): + self.step_name('Test Invalid hash') + + # Invalid hash + (TC.path_tmp / 'invalid-hash.ldb').write_text( + "logo.png.bad;Engine:150-255,Target:0;0;fuzzy_img#abcdef#0\n" + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=TC.path_tmp / 'invalid-hash.ldb', + testfiles=TC.testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 2 # error + + expected_stderr = [ + 'LibClamAV Error: Failed to load', + 'Invalid hash: Image fuzzy hash must be 16 characters in length: abcdef', + ] + unexpected_stdout = [ + 'logo.png.bad.UNOFFICIAL FOUND', + ] + self.verify_output(output.err, expected=expected_stderr) + self.verify_output(output.out, unexpected=unexpected_stdout) + + def test_sigs_bad_hamming(self): + self.step_name('Test Unsupported hamming distancee') + + # Unsupported hamming distance + (TC.path_tmp / 'invalid-ham.ldb').write_text( + "logo.png.bad;Engine:150-255,Target:0;0;fuzzy_img#af2ad01ed42993c7#1\n" + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=TC.path_tmp / 'invalid-ham.ldb', + testfiles=TC.testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 2 # error + + expected_stderr = [ + 'LibClamAV Error: Failed to load', + 'Invalid hamming distance: 1', + ] + unexpected_stdout = [ + 'logo.png.bad.UNOFFICIAL FOUND', + ] + self.verify_output(output.err, expected=expected_stderr) + self.verify_output(output.out, unexpected=unexpected_stdout) + + def test_sigs_bad_algorithm(self): + self.step_name('Test invalid fuzzy image hash algorithm') + + # invalid algorithm + (TC.path_tmp / 'invalid-alg.ldb').write_text( + "logo.png.bad;Engine:150-255,Target:0;0;fuzzy_imgy#af2ad01ed42993c7#0\n" + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=TC.path_tmp / 'invalid-alg.ldb', + testfiles=TC.testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 2 # error + + expected_stderr = [ + 'cli_loadldb: failed to parse subsignature 0 in logo.png', + ] + unexpected_stdout = [ + 'logo.png.bad.UNOFFICIAL FOUND', + ] + self.verify_output(output.err, expected=expected_stderr) + self.verify_output(output.out, unexpected=unexpected_stdout) diff --git a/unit_tests/clamscan/heuristics_test.py b/unit_tests/clamscan/heuristics_test.py new file mode 100644 index 0000000000..a7ef351073 --- /dev/null +++ b/unit_tests/clamscan/heuristics_test.py @@ -0,0 +1,196 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +from zipfile import ZIP_DEFLATED, ZipFile +import sys + +sys.path.append('../unit_tests') +import testcase +from testcase import STRICT_ORDER + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + (TC.path_tmp / 'clam.ndb').write_text( + "Test.NDB:0:*:4b45524e454c33322e444c4c00004578\n" + ) + + # Create a ZIP that has two things: + # 1. malformed file that will alert with --alert-broken-media + # 2. the clam.exe file that will alert normally. + # The idea is that since the malformed file is first, the heuristic alert will be encountered first. + # The heuristic alert must behave as intended, depending on whether we use --allmatch, --heuristic-scan-precedence, etc. + TC.heuristics_testfile = TC.path_tmp / 'heuristics-test.zip' + with ZipFile(str(TC.heuristics_testfile), 'w', ZIP_DEFLATED) as zf: + # Add truncted PNG file that will alert with --alert-broken-media + with open(TC.path_source / 'logo.png', 'br') as logo_png: + zf.writestr('logo.png.truncated', logo_png.read(6378)) + + # Add clam.exe which will alert normally + clam_exe = TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe' + zf.writestr('clam.exe', clam_exe.read_bytes()) + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_hidden_by_strong_indicator(self): + ''' + This test uses a ZIP that has two things: + 1. malformed file that will alert with --alert-broken-media + 2. the clam.exe file that will alert normally. + The idea is that since the malformed file is first, the heuristic alert will be encountered first. + + In this test the heuristic alert must not alert because neither allmatch is specified, nor --heuristic-scan-precedence + ''' + self.step_name('Test that a clam heuristic not alert because regular sig alerts first.') + + testfile = TC.heuristics_testfile + + command = '{valgrind} {valgrind_args} {clamscan} -d {clam_exe_db} {testfiles} --alert-broken-media'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + clam_exe_db=TC.path_tmp / 'clam.ndb', + testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = ['Test.NDB.UNOFFICIAL FOUND'] + unexpected_results = ['Heuristics.Broken.Media.PNG.EOFReadingChunk FOUND'] + self.verify_output(output.out, expected=expected_results, unexpected=unexpected_results) + + def test_only_heur(self): + ''' + This test uses a ZIP that has two things: + 1. malformed file that will alert with --alert-broken-media + 2. the clam.exe file that will alert normally. + The idea is that since the malformed file is first, the heuristic alert will be encountered first. + + In this test the heuristic alert must alert because we don't use the sig for the other file. + ''' + self.step_name('Test that a clam heuristic will alert, because it is the only detection.') + + testfile = TC.heuristics_testfile + + # Add an empty NDB file, because we need to pass in some sort of database. + (TC.path_tmp / 'empty.ndb').write_text( + "# Just a comment\n" + ) + + command = '{valgrind} {valgrind_args} {clamscan} -d {clam_exe_db} {testfiles} --alert-broken-media'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + clam_exe_db=TC.path_tmp / 'empty.ndb', + testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = ['Heuristics.Broken.Media.PNG.EOFReadingChunk FOUND'] + unexpected_results = ['Test.NDB.UNOFFICIAL FOUND'] + self.verify_output(output.out, expected=expected_results, unexpected=unexpected_results) + + def test_precedence(self): + ''' + This test uses a ZIP that has two things: + 1. malformed file that will alert with --alert-broken-media + 2. the clam.exe file that will alert normally. + The idea is that since the malformed file is first, the heuristic alert will be encountered first. + + In this test the heuristic alert must alert first because --heuristic-scan-precedence is enabled. + We won't see the other alert because it's not allmatch mode. + ''' + self.step_name('Test that a heuristic-precendence will cause the heuristic alert to happen first, with no other alerts because not allmatch.') + + testfile = TC.heuristics_testfile + + command = '{valgrind} {valgrind_args} {clamscan} -d {clam_exe_db} {testfiles} --alert-broken-media \ + --heuristic-scan-precedence'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + clam_exe_db=TC.path_tmp / 'clam.ndb', + testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = ['Heuristics.Broken.Media.PNG.EOFReadingChunk FOUND'] + unexpected_results = ['Test.NDB.UNOFFICIAL FOUND'] + self.verify_output(output.out, expected=expected_results, unexpected=unexpected_results) + + def test_allmatch(self): + ''' + This test uses a ZIP that has two things: + 1. malformed file that will alert with --alert-broken-media + 2. the clam.exe file that will alert normally. + The idea is that since the malformed file is first, the heuristic alert will be encountered first. + + In this test we use --allmatch but we don't use --heuristic-scan-precedence. + That means the NDB sig should alert first, even though the heuristic is encountered first. + Note the verify_output() uses STRICT_ORDER. + ''' + self.step_name('Test that a clam heuristic alert will alert LAST in allmatch mode without heuristic-precedence.') + + testfile = TC.heuristics_testfile + + command = '{valgrind} {valgrind_args} {clamscan} -d {clam_exe_db} {testfiles} --alert-broken-media \ + --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + clam_exe_db=TC.path_tmp / 'clam.ndb', + testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = [ + 'Test.NDB.UNOFFICIAL FOUND', + 'Heuristics.Broken.Media.PNG.EOFReadingChunk FOUND', + ] + self.verify_output(output.out, expected=expected_results, order=STRICT_ORDER) + + def test_allmatch_precedence(self): + ''' + This test uses a ZIP that has two things: + 1. malformed file that will alert with --alert-broken-media + 2. the clam.exe file that will alert normally. + The idea is that since the malformed file is first, the heuristic alert will be encountered first. + + In this test we use --allmatch AND we use --heuristic-scan-precedence. + That means the heuristic is encountered first and should be treated equally, so it should alert first. + Note the verify_output() uses STRICT_ORDER. + ''' + self.step_name('Test that a clam heuristic alert will alert FIRST in allmatch mode with heuristic-precedence.') + + testfile = TC.heuristics_testfile + + command = '{valgrind} {valgrind_args} {clamscan} -d {clam_exe_db} {testfiles} --alert-broken-media \ + --allmatch \ + --heuristic-scan-precedence'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + clam_exe_db=TC.path_tmp / 'clam.ndb', + testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus + + expected_results = [ + 'Heuristics.Broken.Media.PNG.EOFReadingChunk FOUND', + 'Test.NDB.UNOFFICIAL FOUND', + ] + self.verify_output(output.out, expected=expected_results, order=STRICT_ORDER) diff --git a/unit_tests/clamscan/image_extraction_test.py b/unit_tests/clamscan/image_extraction_test.py new file mode 100644 index 0000000000..e800eca30d --- /dev/null +++ b/unit_tests/clamscan/image_extraction_test.py @@ -0,0 +1,85 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +import os +import sys + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_xls_jpeg_png(self): + self.step_name('Test that clamav can successfully extract jpeg and png images from XLS documents') + # Note: we aren't testing BMP, TIFF, or GIF because excel converts them to PNG when you try to insert them. + + testfiles = TC.path_source / 'unit_tests' / 'input' / 'other_scanfiles' / 'has_png_and_jpeg.xls' + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=TC.path_build / 'unit_tests' / 'input' / 'clamav.hdb', + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 0 # no virus, no failures + + expected_results = [ + 'Recognized PNG file', + 'Recognized JPEG file', + '"FileMD5":"41e64a9ddb49690f0b6fbbd71362b1b3"', + '"FileMD5":"5341e0efde53a50c416b2352263e7693"', + ] + self.verify_output(output.err, expected=expected_results) + + def test_xls_with_detection(self): + self.step_name('Test that clamav can successfully alert on PNG image extracted from XLS documents') + # This tests a regression wherein extracted images weren't properly scanned, or the scan result recorded. + # Note: we aren't testing the JPEG detection because the JPEG attached to the sample XLS is not properly fuzzy-hashed by clamav, yet. + # TODO: Once it is working, add the JPEG detection test. + + os.mkdir(str(TC.path_tmp / 'xls-jpeg-detection-sigs')) + + (TC.path_tmp / 'logo.png.ldb').write_text( + "logo.png.good;Engine:150-255,Target:0;0;fuzzy_img#ea0f85d0de719887#0\n" + ) + + testfiles = TC.path_source / 'unit_tests' / 'input' / 'other_scanfiles' / 'has_png_and_jpeg.xls' + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=TC.path_tmp / 'logo.png.ldb', + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # no virus, no failures + + expected_stderr = [ + 'Recognized PNG file', + 'Recognized JPEG file', + '"FileMD5":"41e64a9ddb49690f0b6fbbd71362b1b3"', + '"FileMD5":"5341e0efde53a50c416b2352263e7693"', + ] + self.verify_output(output.err, expected=expected_stderr) + + expected_stdout = [ + 'has_png_and_jpeg.xls: logo.png.good.UNOFFICIAL FOUND', + ] + self.verify_output(output.out, expected=expected_stdout) diff --git a/unit_tests/clamscan/match_offsets_test.py b/unit_tests/clamscan/match_offsets_test.py new file mode 100644 index 0000000000..6300006764 --- /dev/null +++ b/unit_tests/clamscan/match_offsets_test.py @@ -0,0 +1,94 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +import shutil +import sys + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + TC.testpaths = list(TC.path_build.glob('unit_tests/input/clamav_hdb_scanfiles/clam*')) # A list of Path()'s of each of our generated test files + + (TC.path_tmp / 'Clam-VI.ldb').write_text( + "Clam-VI-Test:Target;Engine:52-255,Target:1;(0&1);VI:43006f006d00700061006e0079004e0061006d0065000000000063006f006d00700061006e007900;VI:500072006f0064007500630074004e0061006d0065000000000063006c0061006d00\n" + ) + (TC.path_tmp / 'yara-at-offset.yara').write_text( + "rule yara_at_offset {strings: $tar_magic = { 75 73 74 61 72 } condition: $tar_magic at 257}\n" + ) + (TC.path_tmp / 'yara-in-range.yara').write_text( + "rule yara_in_range {strings: $tar_magic = { 75 73 74 61 72 } condition: $tar_magic in (200..300)}\n" + ) + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_LDB_VI(self): + self.step_name('Test LDB VI feature') + + testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=TC.path_tmp / 'Clam-VI.ldb', testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = [ + 'clam_ISmsi_ext.exe: Clam-VI-Test:Target.UNOFFICIAL FOUND', + 'clam_ISmsi_int.exe: Clam-VI-Test:Target.UNOFFICIAL FOUND', + 'Infected files: 2', + ] + self.verify_output(output.out, expected=expected_results) + + def test_yara_at_offset(self): + self.step_name('Test yara signature - detect TAR file magic at an offset') + + testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=TC.path_tmp / 'yara-at-offset.yara', testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = [ + 'clam.tar.gz: YARA.yara_at_offset.UNOFFICIAL FOUND', + 'clam_cache_emax.tgz: YARA.yara_at_offset.UNOFFICIAL FOUND', + 'Infected files: 3', + ] + self.verify_output(output.out, expected=expected_results) + + def test_yara_in_range(self): + self.step_name('Test yara signature - detect TAR file magic in a range') + + testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=TC.path_tmp / 'yara-in-range.yara', testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = [ + 'clam.tar.gz: YARA.yara_in_range.UNOFFICIAL FOUND', + 'clam_cache_emax.tgz: YARA.yara_in_range.UNOFFICIAL FOUND', + 'Infected files: 3', + ] + self.verify_output(output.out, expected=expected_results) diff --git a/unit_tests/clamscan/phishing_test.py b/unit_tests/clamscan/phishing_test.py new file mode 100644 index 0000000000..a07dd9a844 --- /dev/null +++ b/unit_tests/clamscan/phishing_test.py @@ -0,0 +1,75 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +import sys + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + (TC.path_tmp / 'monitor-example-com.pdb').write_text('H:example.com\n') + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_not_enabled(self): + self.step_name('Test that clamscan will load the phishing sigs w/out issue') + + testpaths = list(TC.path_source.glob('unit_tests/input/other_scanfiles/phish-test-*')) + + testfiles = ' '.join([str(testpath) for testpath in testpaths]) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, + clamscan=TC.clamscan, + path_db=TC.path_tmp / 'monitor-example-com.pdb', + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 0 # virus NOT found + + expected_results = [ + 'Scanned files: 3', + 'Infected files: 0', + ] + self.verify_output(output.out, expected=expected_results) + + def test_ssl_and_cloak(self): + self.step_name('Test clamscan --alert-phishing-ssl --alert-phishing-cloak') + + testpaths = list(TC.path_source.glob('unit_tests/input/other_scanfiles/phish-test-*')) + + testfiles = ' '.join([str(testpath) for testpath in testpaths]) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} --alert-phishing-ssl --alert-phishing-cloak {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, + clamscan=TC.clamscan, + path_db=TC.path_tmp / 'monitor-example-com.pdb', + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = [ + 'phish-test-ssl: Heuristics.Phishing.Email.SSL-Spoof FOUND', + 'phish-test-cloak: Heuristics.Phishing.Email.Cloaked.Null FOUND', + 'Scanned files: 3', + 'Infected files: 2', # there's a clean one + ] + self.verify_output(output.out, expected=expected_results) diff --git a/unit_tests/clamscan/regex_test.py b/unit_tests/clamscan/regex_test.py new file mode 100644 index 0000000000..c6525a6b2c --- /dev/null +++ b/unit_tests/clamscan/regex_test.py @@ -0,0 +1,286 @@ +# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +import sys + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + TC.testpaths = list(TC.path_build.glob('unit_tests/input/clamav_hdb_scanfiles/clam*')) # A list of Path()'s of each of our generated test files + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_yara(self): + self.step_name('Test yara signature - detect TAR file magic in a range') + + db = TC.path_tmp / 'regex.yara' + db.write_text( + r''' +rule regex +{ + meta: + author = "Micah" + date = "2022/03/12" + description = "Just a test" + strings: + $a = "/+eat/" /* <-- not a regex */ + $b = /\$protein+=\([a-z]+\)/ /* <-- is a regex */ + condition: + all of them +} + ''' + ) + testfile = TC.path_tmp / 'regex.sample' + testfile.write_text('var $protein=(slugs); /+eat/ $protein') + + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=db, testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = [ + 'regex.sample: YARA.regex.UNOFFICIAL FOUND', + 'Infected files: 1', + ] + self.verify_output(output.out, expected=expected_results) + + def test_slash_colon(self): + self.step_name('Test LDB and Yara regex rules with / and : in the string work') + # This is a regression test for a bug where :'s in a PCRE regex would act + # as delimiters if there was also a / in the regex before the : + + testfile = TC.path_tmp / 'regex-slash-colon.sample' + testfile.write_text('hello blee/blah: bleh') + + # First test with LDB PCRE rule + # + yara_db = TC.path_tmp / 'regex-slash-colon.ldb' + yara_db.write_text( + r'regex;Engine:81-255,Target:0;1;68656c6c6f20;0/hello blee\/blah: bleh/' + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = [ + 'regex-slash-colon.sample: regex.UNOFFICIAL FOUND', + 'Infected files: 1', + ] + self.verify_output(output.out, expected=expected_results) + + # Second test with YARA regex rule + # + yara_db = TC.path_tmp / 'regex-slash-colon.yara' + yara_db.write_text( + r''' +rule regex +{ + meta: + author = "Micah" + date = "2022/07/25" + description = "Just a test" + strings: + $b = /hello blee\/blah: bleh/ + condition: + all of them +} + ''' + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = [ + 'regex-slash-colon.sample: YARA.regex.UNOFFICIAL FOUND', + 'Infected files: 1', + ] + self.verify_output(output.out, expected=expected_results) + + def test_ldb_offset_pcre(self): + self.step_name('Test LDB regex rules with an offset') + # The offset feature starts the match some # of bytes after start of the pattern match + # The offset is EXACT, meaning it's no longer wildcard. + # The match must occur exactly that number of bytes from the start of the file. + + # using 'MZ' prefix so it is detected as MSEXE and not TEXT. This is to avoid normalization. + testfile = TC.path_tmp / 'ldb_offset_pcre' + testfile.write_text('MZ hello blee') + + # First without the offset, make sure it matches + yara_db = TC.path_tmp / 'ldb_pcre_no_offset.ldb' + yara_db.write_text( + r'ldb_pcre_no_offset;Engine:81-255,Target:0;0&1;68656c6c6f20;0/hello blee/' + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = [ + 'ldb_offset_pcre: ldb_pcre_no_offset.UNOFFICIAL FOUND', + 'Infected files: 1', + ] + + # Next, with the offset, but it won't match, because the regex pattern is "hello blee" + # and with the offset of 5 (from start of file) means it should start the pcre matching at "llo blee" + yara_db = TC.path_tmp / 'ldb_pcre_offset_no_match.ldb' + yara_db.write_text( + r'ldb_pcre_offset_no_match;Engine:81-255,Target:0;0&1;68656c6c6f20;5:0/hello blee/' + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 0 # virus NOT found + + expected_results = [ + 'ldb_offset_pcre: OK', + ] + + # Next, with the offset, and it SHOULD match, because the regex pattern is "llo blee" + # and with the offset of 5 (from start of file) means it should start the pcre matching at "llo blee" + yara_db = TC.path_tmp / 'ldb_pcre_offset_match.ldb' + yara_db.write_text( + r'ldb_pcre_offset_match;Engine:81-255,Target:0;0&1;68656c6c6f20;5:0/llo blee/' + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = [ + 'ldb_offset_pcre: ldb_pcre_offset_match.UNOFFICIAL FOUND', + 'Infected files: 1', + ] + + def test_pcre_flag(self): + self.step_name('Test LDB regex rules with case insensitive flag') + # This test validates that the flags field is, and more specifically the case-insensitive flag is working. + + # using 'MZ' prefix so it is detected as MSEXE and not TEXT. This is to avoid normalization. + testfile = TC.path_tmp / 'ldb_pcre_flag' + testfile.write_text('MZ hello blee / BlAh') + + # First test withOUT the case-insensitive flag. It should NOT match. + yara_db = TC.path_tmp / 'ldb_pcre_case.ldb' + yara_db.write_text( + r'ldb_pcre_case;Engine:81-255,Target:0;0&1;68656c6c6f20;0/blah/' + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 0 # virus NOT found + + expected_results = [ + 'ldb_pcre_flag: OK', + ] + + # First test WITH the case-insensitive flag. It SHOULD match. + yara_db = TC.path_tmp / 'ldb_pcre_nocase.ldb' + yara_db.write_text( + r'ldb_pcre_nocase;Engine:81-255,Target:0;0&1;68656c6c6f20;0/blah/i' + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = [ + 'ldb_pcre_flag: ldb_pcre_nocase.UNOFFICIAL FOUND', + 'Infected files: 1', + ] + + def test_ldb_multi_pcre(self): + self.step_name('Test LDB and Yara regex rules with / and : in the string work') + # This is a regression test for a bug where :'s in a PCRE regex would act + # as delimiters if there was also a / in the regex before the : + + # using 'MZ' prefix so it is detected as MSEXE and not TEXT. This is to avoid normalization. + testfile = TC.path_tmp / 'ldb_multi_pcre' + testfile.write_text('MZ hello blee / BlAh') + + # Verify first with two subsigs that should match, that the alert has found. + yara_db = TC.path_tmp / 'ldb_multi_pcre.ldb' + yara_db.write_text( + r'ldb_multi_pcre;Engine:81-255,Target:0;0&1&2;68656c6c6f20;0/hello blee/;0/blah/i' + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 1 # virus found + + expected_results = [ + 'ldb_multi_pcre: ldb_multi_pcre.UNOFFICIAL FOUND', + 'Infected files: 1', + ] + + # Verify next that if one of the two subsigs do not match, the whole thing does not match. + yara_db = TC.path_tmp / 'ldb_multi_pcre.ldb' + yara_db.write_text( + r'ldb_multi_pcre;Engine:81-255,Target:0;0&1&2;68656c6c6f20;0/hello blee/;0/bloh/i' + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 0 # virus NOT found + + expected_results = [ + 'ldb_multi_pcre: OK', + 'Infected files: 0', + ] + + # Verify next that if the other of the two subsigs do not match, the whole thing does not match. + yara_db = TC.path_tmp / 'ldb_multi_pcre.ldb' + yara_db.write_text( + r'ldb_multi_pcre;Engine:81-255,Target:0;0&1&2;68656c6c6f20;0/hella blee/;0/blah/i' + ) + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, + ) + output = self.execute_command(command) + + assert output.ec == 0 # virus NOT found + + expected_results = [ + 'ldb_multi_pcre: OK', + 'Infected files: 0', + ] diff --git a/unit_tests/clamscan_test.py b/unit_tests/clamscan_test.py deleted file mode 100644 index b77aabd9f4..0000000000 --- a/unit_tests/clamscan_test.py +++ /dev/null @@ -1,781 +0,0 @@ -# Copyright (C) 2020-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - -""" -Run clamscan tests. -""" - -import os -from pathlib import Path -import platform -import shutil -import subprocess -import sys -import time -import unittest - -import testcase - - -os_platform = platform.platform() -operating_system = os_platform.split('-')[0].lower() - - -class TC(testcase.TestCase): - @classmethod - def setUpClass(cls): - super(TC, cls).setUpClass() - - TC.testpaths = list(TC.path_build.glob('unit_tests/input/clamav_hdb_scanfiles/clam*')) # A list of Path()'s of each of our generated test files - - # Prepare a directory to store our test databases - TC.path_db = TC.path_tmp / 'database' - TC.path_db.mkdir(parents=True) - - shutil.copy( - str(TC.path_build / 'unit_tests' / 'input' / 'clamav.hdb'), - str(TC.path_db), - ) - - (TC.path_db / 'clamav.ign2').write_text('ClamAV-Test-File\n') - - (TC.path_db / 'phish.pdb').write_text('H:example.com\n') - - (TC.path_db / 'icon.idb').write_text( - "EA0X-32x32x8:ea0x-grp1:ea0x-grp2:2046f030a42a07153f4120a0031600007000005e1617ef0000d21100cb090674150f880313970b0e7716116d01136216022500002f0a173700081a004a0e\n" - "IScab-16x16x8:iscab-grp1:iscab-grp2:107b3000168306015c20a0105b07060be0a0b11c050bea0706cb0a0bbb060b6f00017c06018301068109086b03046705081b000a270a002a000039002b17\n" - ) - (TC.path_db / 'icon.ldb').write_text( - "ClamAV-Test-Icon-EA0X;Engine:52-1000,Target:1,IconGroup1:ea0x-grp1,IconGroup2:*;(0);0:4d5a\n" - "ClamAV-Test-Icon-IScab;Engine:52-1000,Target:1,IconGroup2:iscab-grp2;(0);0:4d5a\n" - ) - (TC.path_db / 'Clam-VI.ldb').write_text( - "Clam-VI-Test:Target;Engine:52-255,Target:1;(0&1);VI:43006f006d00700061006e0079004e0061006d0065000000000063006f006d00700061006e007900;VI:500072006f0064007500630074004e0061006d0065000000000063006c0061006d00\n" - ) - (TC.path_db / 'yara-at-offset.yara').write_text( - "rule yara_at_offset {strings: $tar_magic = { 75 73 74 61 72 } condition: $tar_magic at 257}\n" - ) - (TC.path_db / 'yara-in-range.yara').write_text( - "rule yara_in_range {strings: $tar_magic = { 75 73 74 61 72 } condition: $tar_magic in (200..300)}\n" - ) - - @classmethod - def tearDownClass(cls): - super(TC, cls).tearDownClass() - - def setUp(self): - super(TC, self).setUp() - - def tearDown(self): - super(TC, self).tearDown() - self.verify_valgrind_log() - - def test_clamscan_00_version(self): - self.step_name('clamscan version test') - - command = '{valgrind} {valgrind_args} {clamscan} -V'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan - ) - output = self.execute_command(command) - - assert output.ec == 0 # success - - expected_results = [ - 'ClamAV {}'.format(TC.version), - ] - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_01_all_testfiles(self): - self.step_name('Test that clamscan alerts on all test files') - - testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=TC.path_db / "clamav.hdb", testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = ['{}: ClamAV-Test-File.UNOFFICIAL FOUND'.format(testpath.name) for testpath in TC.testpaths] - expected_results.append('Scanned files: {}'.format(len(TC.testpaths))) - expected_results.append('Infected files: {}'.format(len(TC.testpaths))) - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_02_all_testfiles_ign2(self): - self.step_name('Test that clamscan ignores ClamAV-Test-File alerts') - - testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} -d {path_ign_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=TC.path_db / "clamav.hdb", path_ign_db=TC.path_db / "clamav.ign2", testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = ['{}: ClamAV-Test-File.UNOFFICIAL FOUND'.format(testpath.name) for testpath in TC.testpaths] - expected_results.append('Scanned files: {}'.format(len(TC.testpaths))) - expected_results.append('Infected files: {}'.format(len(TC.testpaths))) - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_03_phish_test_not_enabled(self): - self.step_name('Test that clamscan will load the phishing sigs w/out issue') - - testpaths = list(TC.path_source.glob('unit_tests/input/other_scanfiles/phish-test-*')) - - testfiles = ' '.join([str(testpath) for testpath in testpaths]) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=TC.path_db / "phish.pdb", path_ign_db=TC.path_db / "clamav.ign2", testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 0 # virus NOT found - - expected_results = [ - 'Scanned files: 3', - 'Infected files: 0', - ] - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_04_phish_test_alert_phishing_ssl_alert_phishing_cloak(self): - self.step_name('Test clamscan --alert-phishing-ssl --alert-phishing-cloak') - - testpaths = list(TC.path_source.glob('unit_tests/input/other_scanfiles/phish-test-*')) - - testfiles = ' '.join([str(testpath) for testpath in testpaths]) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} --alert-phishing-ssl --alert-phishing-cloak {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=TC.path_db / "phish.pdb", testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = [ - 'phish-test-ssl: Heuristics.Phishing.Email.SSL-Spoof FOUND', - 'phish-test-cloak: Heuristics.Phishing.Email.Cloaked.Null FOUND', - 'Scanned files: 3', - 'Infected files: 2', # there's a clean one - ] - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_05_icon(self): - self.step_name('Test icon (.ldb + .idb) signatures') - - testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_ldb} -d {path_idb} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_ldb=TC.path_db / "icon.ldb", path_idb=TC.path_db / "icon.idb", testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - # Use check_fpu_endian to determine expected results - command = '{}'.format(TC.check_fpu_endian) - fpu_endian_output = self.execute_command(command) - - expected_results = [ - 'clam_IScab_ext.exe: ClamAV-Test-Icon-IScab.UNOFFICIAL FOUND', - 'clam_IScab_int.exe: ClamAV-Test-Icon-IScab.UNOFFICIAL FOUND', - ] - if fpu_endian_output.ec == 3: - expected_num_infected = 3 - else: - expected_results.append('clam.ea06.exe: ClamAV-Test-Icon-EA0X.UNOFFICIAL FOUND') - expected_num_infected = 4 - expected_results.append('Infected files: {}'.format(expected_num_infected)) - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_06_LDB_VI(self): - self.step_name('Test LDB VI feature') - - testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=TC.path_db / "Clam-VI.ldb", testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = [ - 'clam_ISmsi_ext.exe: Clam-VI-Test:Target.UNOFFICIAL FOUND', - 'clam_ISmsi_int.exe: Clam-VI-Test:Target.UNOFFICIAL FOUND', - 'Infected files: 2', - ] - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_07_yara_at_offset(self): - self.step_name('Test yara signature - detect TAR file magic at an offset') - - testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=TC.path_db / "yara-at-offset.yara", testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = [ - 'clam.tar.gz: YARA.yara_at_offset.UNOFFICIAL FOUND', - 'clam_cache_emax.tgz: YARA.yara_at_offset.UNOFFICIAL FOUND', - 'Infected files: 3', - ] - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_08_yara_in_range(self): - self.step_name('Test yara signature - detect TAR file magic in a range') - - testfiles = ' '.join([str(testpath) for testpath in TC.testpaths]) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=TC.path_db / "yara-in-range.yara", testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = [ - 'clam.tar.gz: YARA.yara_in_range.UNOFFICIAL FOUND', - 'clam_cache_emax.tgz: YARA.yara_in_range.UNOFFICIAL FOUND', - 'Infected files: 3', - ] - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_09_xls_jpeg_png_extraction(self): - self.step_name('Test that clamav can successfully extract jpeg and png images from XLS documents') - # Note: we aren't testing BMP, TIFF, or GIF because excel converts them to PNG when you try to insert them. - - testfiles = TC.path_source / 'unit_tests' / 'input' / 'other_scanfiles' / 'has_png_and_jpeg.xls' - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, - path_db=TC.path_build / 'unit_tests' / 'input' / 'clamav.hdb', - testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 0 # no virus, no failures - - expected_results = [ - 'Recognized PNG file', - 'Recognized JPEG file', - '"FileMD5":"41e64a9ddb49690f0b6fbbd71362b1b3"', - '"FileMD5":"5341e0efde53a50c416b2352263e7693"', - ] - self.verify_output(output.err, expected=expected_results) - - def test_clamscan_10_bytecode_pdf_hook(self): - self.step_name('Test that pdf bytecode hooks trigger') - - testfiles = TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.pdf' - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --bytecode-unsigned'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, - path_db=TC.path_source / 'unit_tests' / 'input' / 'bytecode_sigs' / 'pdf-hook.cbc', - testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus - - expected_results = [ - 'Test.Case.BC.PDF.hook FOUND', - ] - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_11_allmatch_hash_sigs(self): - self.step_name('Test that each type of hash sig is detected in all-match mode') - - os.mkdir(str(TC.path_db / 'allmatch-test-sigs')) - - (TC.path_db / 'allmatch-test-sigs' / 'clam.hdb').write_text( - "aa15bcf478d165efd2065190eb473bcb:544:Test.MD5.Hash:73\n" - "aa15bcf478d165efd2065190eb473bcb:*:Test.MD5.Hash.NoSize:73\n" - ) - (TC.path_db / 'allmatch-test-sigs' / 'clam.hsb').write_text( - "71e7b604d18aefd839e51a39c88df8383bb4c071dc31f87f00a2b5df580d4495:544:Test.Sha256.Hash:73\n" - "71e7b604d18aefd839e51a39c88df8383bb4c071dc31f87f00a2b5df580d4495:*:Test.Sha256.Hash.NoSize:73\n" - "62dd70f5e7530e0239901ac186f1f9ae39292561:544:Test.Sha1.Hash:73\n" - "62dd70f5e7530e0239901ac186f1f9ae39292561:*:Test.Sha1.NoSize:73\n" - ) - (TC.path_db / 'allmatch-test-sigs' / 'clam.imp').write_text( - "98c88d882f01a3f6ac1e5f7dfd761624:39:Test.Import.Hash\n" - "98c88d882f01a3f6ac1e5f7dfd761624:*:Test.Import.Hash.NoSize\n" - ) - (TC.path_db / 'allmatch-test-sigs' / 'clam.mdb').write_text( - "512:23db1dd3f77fae25610b6a32701313ae:Test.PESection.Hash:73\n" - "*:23db1dd3f77fae25610b6a32701313ae:Test.PESection.Hash.NoSize:73\n" - ) - - testfiles = TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe' - - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --allmatch'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, - path_db=TC.path_db / 'allmatch-test-sigs', - testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus - - expected_results = [ - 'Test.MD5.Hash.UNOFFICIAL FOUND', - 'Test.MD5.Hash.NoSize.UNOFFICIAL FOUND', - 'Test.Sha1.Hash.UNOFFICIAL FOUND', - 'Test.Sha1.NoSize.UNOFFICIAL FOUND', - 'Test.Sha256.Hash.UNOFFICIAL FOUND', - 'Test.Sha256.Hash.NoSize.UNOFFICIAL FOUND', - 'Test.PESection.Hash.UNOFFICIAL FOUND', - 'Test.PESection.Hash.NoSize.UNOFFICIAL FOUND', - 'Test.Import.Hash.UNOFFICIAL FOUND', - 'Test.Import.Hash.NoSize.UNOFFICIAL FOUND', - ] - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_12_image_fuzzy_hash_sigs(self): - self.step_name('Test that each type of hash sig is detected in all-match mode') - - os.mkdir(str(TC.path_db / 'image-fuzzy-hash-test-sigs')) - - (TC.path_db / 'image-fuzzy-hash-test-sigs' / 'good.ldb').write_text( - "logo.png.good;Engine:150-255,Target:0;0;fuzzy_img#af2ad01ed42993c7#0\n" - "logo.png.bad.with.second.subsig;Engine:150-255,Target:0;0&1;deadbeef;fuzzy_img#af2ad01ed42993c7#0\n" - "logo.png.good.with.second.subsig;Engine:150-255,Target:0;0&1;49484452;fuzzy_img#af2ad01ed42993c7#0\n" - ) - - testfiles = TC.path_source / 'logo.png' - - # - # First check with the good database in all-match mode. - # - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --allmatch'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, - path_db=TC.path_db / 'image-fuzzy-hash-test-sigs' / 'good.ldb', - testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus - - expected_stdout = [ - 'logo.png.good.UNOFFICIAL FOUND', - 'logo.png.good.with.second.subsig.UNOFFICIAL FOUND', - ] - unexpected_stdout = [ - 'logo.png.bad.with.second.subsig.UNOFFICIAL FOUND', - ] - self.verify_output(output.out, expected=expected_stdout) - - # - # Next check with the bad signatures - # - - # Invalid hash - (TC.path_db / 'image-fuzzy-hash-test-sigs' / 'invalid-hash.ldb').write_text( - "logo.png.bad;Engine:150-255,Target:0;0;fuzzy_img#abcdef#0\n" - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --allmatch'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, - path_db=TC.path_db / 'image-fuzzy-hash-test-sigs' / 'invalid-hash.ldb', - testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 2 # error - - expected_stderr = [ - 'LibClamAV Error: Failed to load', - 'Invalid hash: Image fuzzy hash must be 16 characters in length: abcdef', - ] - unexpected_stdout = [ - 'logo.png.bad.UNOFFICIAL FOUND', - ] - self.verify_output(output.err, expected=expected_stderr) - self.verify_output(output.out, unexpected=unexpected_stdout) - - # Unsupported hamming distance - (TC.path_db / 'image-fuzzy-hash-test-sigs' / 'invalid-ham.ldb').write_text( - "logo.png.bad;Engine:150-255,Target:0;0;fuzzy_img#af2ad01ed42993c7#1\n" - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --allmatch'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, - path_db=TC.path_db / 'image-fuzzy-hash-test-sigs' / 'invalid-ham.ldb', - testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 2 # error - - expected_stderr = [ - 'LibClamAV Error: Failed to load', - 'Invalid hamming distance: 1', - ] - unexpected_stdout = [ - 'logo.png.bad.UNOFFICIAL FOUND', - ] - self.verify_output(output.err, expected=expected_stderr) - self.verify_output(output.out, unexpected=unexpected_stdout) - - # invalid algorithm - (TC.path_db / 'image-fuzzy-hash-test-sigs' / 'invalid-alg.ldb').write_text( - "logo.png.bad;Engine:150-255,Target:0;0;fuzzy_imgy#af2ad01ed42993c7#0\n" - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --allmatch'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, - path_db=TC.path_db / 'image-fuzzy-hash-test-sigs' / 'invalid-alg.ldb', - testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 2 # error - - expected_stderr = [ - 'cli_loadldb: failed to parse subsignature 0 in logo.png', - ] - unexpected_stdout = [ - 'logo.png.bad.UNOFFICIAL FOUND', - ] - self.verify_output(output.err, expected=expected_stderr) - self.verify_output(output.out, unexpected=unexpected_stdout) - - def test_clamscan_13_yara_regex(self): - self.step_name('Test yara signature - detect TAR file magic in a range') - - db = TC.path_tmp / 'regex.yara' - db.write_text( - r''' -rule regex -{ - meta: - author = "Micah" - date = "2022/03/12" - description = "Just a test" - strings: - $a = "/+eat/" /* <-- not a regex */ - $b = /\$protein+=\([a-z]+\)/ /* <-- is a regex */ - condition: - all of them -} - ''' - ) - testfile = TC.path_tmp / 'regex.sample' - testfile.write_text('var $protein=(slugs); /+eat/ $protein') - - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=db, testfiles=testfile, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = [ - 'regex.sample: YARA.regex.UNOFFICIAL FOUND', - 'Infected files: 1', - ] - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_14_xls_jpeg_detection(self): - self.step_name('Test that clamav can successfully alert on jpeg image extracted from XLS documents') - # Note: we aren't testing PNG because the attached PNG is not properly fuzzy-hashed by clamav, yet. - - os.mkdir(str(TC.path_db / 'xls-jpeg-detection-sigs')) - - (TC.path_db / 'image-fuzzy-hash-test-sigs' / 'good.ldb').write_text( - "logo.png.good;Engine:150-255,Target:0;0;fuzzy_img#ea0f85d0de719887#0\n" - ) - - testfiles = TC.path_source / 'unit_tests' / 'input' / 'other_scanfiles' / 'has_png_and_jpeg.xls' - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug --allmatch'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, - path_db=TC.path_db / 'image-fuzzy-hash-test-sigs' / 'good.ldb', - testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # no virus, no failures - - expected_stderr = [ - 'Recognized PNG file', - 'Recognized JPEG file', - '"FileMD5":"41e64a9ddb49690f0b6fbbd71362b1b3"', - '"FileMD5":"5341e0efde53a50c416b2352263e7693"', - ] - self.verify_output(output.err, expected=expected_stderr) - - expected_stdout = [ - 'has_png_and_jpeg.xls: logo.png.good.UNOFFICIAL FOUND', - ] - self.verify_output(output.out, expected=expected_stdout) - - def test_clamscan_15_container(self): - self.step_name('Test that clamav can successfully alert on jpeg image extracted from XLS documents') - # Note: we aren't testing PNG because the attached PNG is not properly fuzzy-hashed by clamav, yet. - - os.mkdir(str(TC.path_db / '7z_zip_container')) - - (TC.path_db / '7z_zip_container' / 'test.ldb').write_text( - "7z_zip_container_good;Engine:81-255,Container:CL_TYPE_7Z,Target:0;0;0:7631727573\n" - "7z_zip_container_bad;Engine:81-255,Container:CL_TYPE_ZIP,Target:0;0;0:7631727573\n" - ) - - testfiles = TC.path_source / 'unit_tests' / 'input' / 'other_scanfiles' / 'v1rusv1rus.7z.zip' - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug --allmatch'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, - path_db=TC.path_db / '7z_zip_container' / 'test.ldb', - testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # no virus, no failures - - expected_stdout = [ - 'v1rusv1rus.7z.zip: 7z_zip_container_good.UNOFFICIAL FOUND', - ] - unexpected_stdout = [ - 'v1rusv1rus.7z.zip: 7z_zip_container_bad.UNOFFICIAL FOUND', - ] - self.verify_output(output.out, expected=expected_stdout, unexpected=unexpected_stdout) - - def test_clamscan_16_intermediates(self): - self.step_name('Test that clamav can successfully alert on jpeg image extracted from XLS documents') - # Note: we aren't testing PNG because the attached PNG is not properly fuzzy-hashed by clamav, yet. - - os.mkdir(str(TC.path_db / '7z_zip_intermediates')) - - (TC.path_db / '7z_zip_intermediates' / 'test.ldb').write_text( - "7z_zip_intermediates_good;Engine:81-255,Intermediates:CL_TYPE_ZIP>CL_TYPE_7Z,Target:0;0;0:7631727573\n" - "7z_zip_intermediates;Engine:81-255,Intermediates:CL_TYPE_7Z>CL_TYPE_TEXT_ASCII,Target:0;0;0:7631727573\n" - ) - - testfiles = TC.path_source / 'unit_tests' / 'input' / 'other_scanfiles' / 'v1rusv1rus.7z.zip' - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug --allmatch'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, - path_db=TC.path_db / '7z_zip_intermediates' / 'test.ldb', - testfiles=testfiles, - ) - output = self.execute_command(command) - - assert output.ec == 1 # no virus, no failures - - expected_stdout = [ - 'v1rusv1rus.7z.zip: 7z_zip_intermediates_good.UNOFFICIAL FOUND', - ] - unexpected_stdout = [ - 'v1rusv1rus.7z.zip: 7z_zip_intermediates_bad.UNOFFICIAL FOUND', - ] - self.verify_output(output.out, expected=expected_stdout, unexpected=unexpected_stdout) - - def test_clamscan_17_pcre_slash_colon(self): - self.step_name('Test LDB and Yara regex rules with / and : in the string work') - # This is a regression test for a bug where :'s in a PCRE regex would act - # as delimiters if there was also a / in the regex before the : - - testfile = TC.path_tmp / 'regex-slash-colon.sample' - testfile.write_text('hello blee/blah: bleh') - - # First test with LDB PCRE rule - # - yara_db = TC.path_tmp / 'regex-slash-colon.ldb' - yara_db.write_text( - r'regex;Engine:81-255,Target:0;1;68656c6c6f20;0/hello blee\/blah: bleh/' - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = [ - 'regex-slash-colon.sample: regex.UNOFFICIAL FOUND', - 'Infected files: 1', - ] - self.verify_output(output.out, expected=expected_results) - - # Second test with YARA regex rule - # - yara_db = TC.path_tmp / 'regex-slash-colon.yara' - yara_db.write_text( - r''' -rule regex -{ - meta: - author = "Micah" - date = "2022/07/25" - description = "Just a test" - strings: - $b = /hello blee\/blah: bleh/ - condition: - all of them -} - ''' - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = [ - 'regex-slash-colon.sample: YARA.regex.UNOFFICIAL FOUND', - 'Infected files: 1', - ] - self.verify_output(output.out, expected=expected_results) - - def test_clamscan_18_ldb_offset_pcre(self): - self.step_name('Test LDB regex rules with an offset') - # The offset feature starts the match some # of bytes after start of the pattern match - # The offset is EXACT, meaning it's no longer wildcard. - # The match must occur exactly that number of bytes from the start of the file. - - # using 'MZ' prefix so it is detected as MSEXE and not TEXT. This is to avoid normalization. - testfile = TC.path_tmp / 'ldb_offset_pcre' - testfile.write_text('MZ hello blee') - - # First without the offset, make sure it matches - yara_db = TC.path_tmp / 'ldb_pcre_no_offset.ldb' - yara_db.write_text( - r'ldb_pcre_no_offset;Engine:81-255,Target:0;0&1;68656c6c6f20;0/hello blee/' - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = [ - 'ldb_offset_pcre: ldb_pcre_no_offset.UNOFFICIAL FOUND', - 'Infected files: 1', - ] - - # Next, with the offset, but it won't match, because the regex pattern is "hello blee" - # and with the offset of 5 (from start of file) means it should start the pcre matching at "llo blee" - yara_db = TC.path_tmp / 'ldb_pcre_offset_no_match.ldb' - yara_db.write_text( - r'ldb_pcre_offset_no_match;Engine:81-255,Target:0;0&1;68656c6c6f20;5:0/hello blee/' - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, - ) - output = self.execute_command(command) - - assert output.ec == 0 # virus NOT found - - expected_results = [ - 'ldb_offset_pcre: OK', - ] - - # Next, with the offset, and it SHOULD match, because the regex pattern is "llo blee" - # and with the offset of 5 (from start of file) means it should start the pcre matching at "llo blee" - yara_db = TC.path_tmp / 'ldb_pcre_offset_match.ldb' - yara_db.write_text( - r'ldb_pcre_offset_match;Engine:81-255,Target:0;0&1;68656c6c6f20;5:0/llo blee/' - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = [ - 'ldb_offset_pcre: ldb_pcre_offset_match.UNOFFICIAL FOUND', - 'Infected files: 1', - ] - - def test_clamscan_18_ldb_pcre_flag(self): - self.step_name('Test LDB regex rules with case insensitive flag') - # This test validates that the flags field is, and more specifically the case-insensitive flag is working. - - # using 'MZ' prefix so it is detected as MSEXE and not TEXT. This is to avoid normalization. - testfile = TC.path_tmp / 'ldb_pcre_flag' - testfile.write_text('MZ hello blee / BlAh') - - # First test withOUT the case-insensitive flag. It should NOT match. - yara_db = TC.path_tmp / 'ldb_pcre_case.ldb' - yara_db.write_text( - r'ldb_pcre_case;Engine:81-255,Target:0;0&1;68656c6c6f20;0/blah/' - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, - ) - output = self.execute_command(command) - - assert output.ec == 0 # virus NOT found - - expected_results = [ - 'ldb_pcre_flag: OK', - ] - - # First test WITH the case-insensitive flag. It SHOULD match. - yara_db = TC.path_tmp / 'ldb_pcre_nocase.ldb' - yara_db.write_text( - r'ldb_pcre_nocase;Engine:81-255,Target:0;0&1;68656c6c6f20;0/blah/i' - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = [ - 'ldb_pcre_flag: ldb_pcre_nocase.UNOFFICIAL FOUND', - 'Infected files: 1', - ] - - def test_clamscan_18_ldb_multi_pcre(self): - self.step_name('Test LDB and Yara regex rules with / and : in the string work') - # This is a regression test for a bug where :'s in a PCRE regex would act - # as delimiters if there was also a / in the regex before the : - - # using 'MZ' prefix so it is detected as MSEXE and not TEXT. This is to avoid normalization. - testfile = TC.path_tmp / 'ldb_multi_pcre' - testfile.write_text('MZ hello blee / BlAh') - - # Verify first with two subsigs that should match, that the alert has found. - yara_db = TC.path_tmp / 'ldb_multi_pcre.ldb' - yara_db.write_text( - r'ldb_multi_pcre;Engine:81-255,Target:0;0&1&2;68656c6c6f20;0/hello blee/;0/blah/i' - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, - ) - output = self.execute_command(command) - - assert output.ec == 1 # virus found - - expected_results = [ - 'ldb_multi_pcre: ldb_multi_pcre.UNOFFICIAL FOUND', - 'Infected files: 1', - ] - - # Verify next that if one of the two subsigs do not match, the whole thing does not match. - yara_db = TC.path_tmp / 'ldb_multi_pcre.ldb' - yara_db.write_text( - r'ldb_multi_pcre;Engine:81-255,Target:0;0&1&2;68656c6c6f20;0/hello blee/;0/bloh/i' - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, - ) - output = self.execute_command(command) - - assert output.ec == 0 # virus NOT found - - expected_results = [ - 'ldb_multi_pcre: OK', - 'Infected files: 0', - ] - - # Verify next that if the other of the two subsigs do not match, the whole thing does not match. - yara_db = TC.path_tmp / 'ldb_multi_pcre.ldb' - yara_db.write_text( - r'ldb_multi_pcre;Engine:81-255,Target:0;0&1&2;68656c6c6f20;0/hella blee/;0/blah/i' - ) - command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles}'.format( - valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, path_db=yara_db, testfiles=testfile, - ) - output = self.execute_command(command) - - assert output.ec == 0 # virus NOT found - - expected_results = [ - 'ldb_multi_pcre: OK', - 'Infected files: 0', - ] diff --git a/unit_tests/input/bytecode_sigs/Clamav-Unit-Test-Signature.cbc b/unit_tests/input/bytecode_sigs/Clamav-Unit-Test-Signature.cbc new file mode 100644 index 0000000000..bc199effd8 --- /dev/null +++ b/unit_tests/input/bytecode_sigs/Clamav-Unit-Test-Signature.cbc @@ -0,0 +1,13 @@ +ClamBCaghocbcnlbf|aimfifcfafcgnfigdf```c``abhc``|ah`cnbac`cecnb`c``b`aaap`clamcoincidencejb:4096 +BC.Clamav-Unit-Test-Signature.{};Engine:56-255,Target:0;0;0:434c414d41562d544553542d535452494e472d4e4f542d4549434152 +Teddaaahdabahdacahdadahdaeahdafahdagahebodebadaacb`bbadb`bdbnaah +Eaeaaaebnd|amcgefdgfgifbgegcgnfafmfef`` +G`ad`@`bodBbdBcdBnbBcdBlfBafBmfBafBfgBmbBeeBnfBifBdgBmbBdeBefBcgBdgBmbBceBifBgfBnfBafBdgBegBbgBef@`bad@Aa`bad@Aa` +A`b`bLaab`b`Fabaa +Bb`b`abbaeAc`BmadTcab`b@dE +Sfeidbeeecendadmdedoe`ebeedfdidhehbbbbdcdnbcdlfafmfaffgmbeenfifdgmbdeefcgdgmbceifgfnfafdgegbgefbbibSfeidbeeecendadmdedcehbbbbbibSdeadbegdeddehb`cibSSfdeendcddeidodndadldiddeieoeldedfeedldoemdidndhbfdeendcdoeldedfeedldoe`cicfcoedcib +SceidgdndaddeeebeedceoeddedcdldoebdedgdidndSddedcdldadbeedoeceidgdndaddeeebeedhbdgefcgdgoecgdgbgifnfgfibSceidgdndaddeeebeedceoeddedcdldoeedndddSSceidgdndaddeeebeedceoeddedfdoebdedgdidnd +objb`b`b`bmfafdgcfhfefcg`bbbcdldadmdadfembdeedcedembcedebeidndgdmbndoddembedidcdadbebb`bjbobSddedfdidndedoeceidgdndaddeeebeedhbdgefcgdgoecgdgbgifnfgflb`bbb`cjcdcccdccfdcacdcdfdcacecfcbcdfecdcdcececccecdcbcdfecccecdcecbcdcicdcefdcgcbcdfdcefdcffecdcbcdfdcecdcicdcccdcacecbcbbib +ceidgdndaddeeebeedceoeddedfdoeedndddSSbfofoflf`blfofgfifcfaflfoedgbgifgfgfefbghbibSkgSobjbjbjbgeiflflf`bbgefdgegbgnf`bdgbgegef`bifff`bcgifgfnfafdgegbgef`bmfafdgcfhfefcg`bjbjbjbob +bgefdgegbgnf`bmfafdgcfhfefcghbceifgfnfafdgegbgefcgnbdgefcgdgoecgdgbgifnfgfibkcSmgSSobjbjbjbbfigdgefcfofdfef`bffegnfcfdgifofnf`bdghfafdg`befhgefcfegdgefcg`bifff`bdghfef`blfofgfifcfaflf`bcgifgfnfafdgegbgef`bmfafdgcfhfefdf`bjbjbjbob +ifnfdg`befnfdgbgig`gofifnfdghbfgofifdfibSkgSffofegnfdffeifbgegcghbbbbbibkcSbgefdgegbgnf`b`ckcSmgS diff --git a/unit_tests/input/other_sigs/Clamav-Unit-Test-Signature.ndb b/unit_tests/input/other_sigs/Clamav-Unit-Test-Signature.ndb new file mode 100755 index 0000000000..987f76c821 --- /dev/null +++ b/unit_tests/input/other_sigs/Clamav-Unit-Test-Signature.ndb @@ -0,0 +1 @@ +NDB.Clamav-Unit-Test-Signature:0:0:434c414d41562d544553542d535452494e472d4e4f542d4549434152 diff --git a/unit_tests/input/pe_allmatch/README.md b/unit_tests/input/pe_allmatch/README.md new file mode 100644 index 0000000000..beab848325 --- /dev/null +++ b/unit_tests/input/pe_allmatch/README.md @@ -0,0 +1,95 @@ +## Overview + +This aims to provide many different types of ClamAV rules for the given `test.exe` x86 Windows executable binary in order to test ClamAV's ability to report multiple signature matches (`-z` or `--allmatch` in `clamscan`). + +- `src` - contains the code necessary to rebuild the `test.exe` program. +- `alert-sigs` are a combination of signatures generated by this tool, and those that were manually created. + - As the name implies, these signatures are expected to alert on the `text.exe` program. + - Some of these signatures depend on signatures in `weak-sigs`. +- `weak-sigs` are a combination of signatures generated by this tool, and those that were manually created. + - As the name implies, these signatures support the `alert-sigs`, but do not alert on their own. + +The tools necessary to (re)generate those sigs that were generated are not included here. + +## Requirements + +These are the requirements to build the `test.exe` program. +The requirements, features needed to generate those of the sig test set that weren't hand-written are not provided here. + +- `python3` +- `mingw-w64` + - Just `sudo apt install mingw-w64` on ubuntu 20.04 +- `osslsigncode` + - The ubuntu package is dated, but it's pretty easy to install from https://github.com/mtrojnar/osslsigncode + - Follow the directions for those prereqs. + - Note: you can use these `cmake` commands: + ```bash + mkdir build && cd build + cmake .. && cmake --build . + sudo cmake --install . --prefix /usr/local/bin + ``` + +## Steps to regenerate the `test.exe` program + +1. Build the binary with: `./build.py` + +2. Generate the signatures. First run: + ```sh + mkdir gen + ``` + Then run: + ```sh + python generate.py build/test.exe + ``` + +## Testing: + - Example invocation: +``` +$ clamscan -z -d gen/ -d manual/ build/test.exe --bytecode-unsigned --no-summary | sort -n +test.exe: Test.GenSig.HSB_01of06_MD5_FIXED.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_01of90_MD5_FIXED_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_02of90_SHA1_FIXED_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_03of90_SHA256_FIXED_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_04of90_MD5_STAR_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_05of90_SHA1_STAR_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_06of90_SHA256_STAR_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_01of08_legalcopyright.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_02of08_internalname.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_03of08_fileversion.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_04of08_companyname.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_05of08_productname.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_06of08_productversion.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_07of08_filedescription.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_08of08_originalfilename.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_01of16_PE_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_02of16_PE_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_03of16_ANY_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_04of16_ANY_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_05of16_PE_EP_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_06of16_PE_EP_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_07of16_PE_SE1_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_09of16_PE_S1_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_10of16_PE_S1_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_11of16_PE_PCRE_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_12of16_PE_PCRE_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_13of16_ANY_PCRE_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_14of16_ANY_PCRE_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_15of16_PE_ICON_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_16of16_PE_ICON_2.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_01of10_PE_1.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_02of10_PE_2.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_03of10_ANY_1.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_04of10_ANY_2.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_05of10_PE_EP_1.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_06of10_PE_EP_2.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_07of10_PE_SE2_1.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_09of10_PE_S1_1.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_10of10_PE_S1_2.UNOFFICIAL FOUND +test.exe: YARA.Test_Sig_YARA_1of1_strings.UNOFFICIAL FOUND +``` + +## Steps to generate the .ico file: +``` +head -c 245760 /dev/urandom | convert -depth 8 -size 320x256 RGB:- test.png +convert -background transparent test.png -define icon:auto-resize=16,32,48,64,256 test.ico +``` diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_1of6_ANY_testexe_0.matched.cbc b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_1of6_ANY_testexe_0.matched.cbc new file mode 100644 index 0000000000..1c364c4ed5 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_1of6_ANY_testexe_0.matched.cbc @@ -0,0 +1,12 @@ +ClamBCafhijhbcnbf|aimfifcfafcgnfigdf```c``a```|ah`cnbac`cccnbac``b`aaap`clamcoincidencejb:4096 +Test.GenSig.BC_1of6_ANY_testexe_0.{matched};Engine:56-255,Target:0;0;434c414d41565f544553545f5052494e54465f535452494e475f666661385f333939345f313738385f65386236 +Teddaaahdabahdacahdadahdaeahdafahdagahebodebadaacb`bbadb`bdbjbah +Eaeaaaebnd|amcgefdgfgifbgegcgnfafmfef`` +G`ac`@`bodBdeBefBcgBdgBnbBgdBefBnfBceBifBgfBnbBbdBcdBoeBacBofBffBfcBoeBadBndBieBoeBdgBefBcgBdgBefBhgBefBoeB`cBnbBmfBafBdgBcfBhfBefBdf@`bad@Aa` +A`b`bLaab`b`Fabaa +Bb`b`abbaeAb`BibdTcab`b@dE +SSfeidbeeecendadmdedoe`ebeedfdidhehbbbdeefcgdgnbgdefnfceifgfnbbdcdoeacoffffcoeadndieoedgefcgdgefhgefoe`cbbibSfeidbeeecendadmdedcehbbbmfafdgcfhfefdfbbibSdeadbegdeddehb`cib +SceidgdndaddeeebeedceoeddedcdldoebdedgdidndSobob`bldofofkfcg`bffofbgjc`bcdldadmdadfeoedeedcedeoe`ebeidnddefdoecedebeidndgdoeffffafhcoeccicicdcoeacgchchcoeefhcbffc +ddedcdldadbeedoeceidgdndaddeeebeedhbdgefcgdgibSceidgdndaddeeebeedceoeddedcdldoeedndddSSceidgdndaddeeebeedceoeddedfdoebdedgdidndSddedfdidndedoeceidgdndaddeeebeedhbdgefcgdglb`bbbdcccdccfdcacdcdfdcacecfcecffecdcdcececccecdcecffec`cecbcdcicdcefecdcdcfcecffecccecdcecbcdcicdcefdcgcecfffcfcfcfcfcaccchcecffccccccicccicccdcecffccacccgccchccchcecfffceccchcfcbcccfcbbib +ceidgdndaddeeebeedceoeddedfdoeedndddSSbfofoflf`blfofgfifcfaflfoedgbgifgfgfefbghbibSkgSbgefdgegbgnf`bmfafdgcfhfefcghbceifgfnfafdgegbgefcgnbdgefcgdgibkcSmgSSSSifnfdg`befnfdgbgig`gofifnfdghbfgofifdfib +kgSffofegnfdffeifbgegcghbbbmfafdgcfhfefdfbbibkcSbgefdgegbgnf`b`ckcSmgS diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_2of6_ANY_testexe_1.matched.cbc b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_2of6_ANY_testexe_1.matched.cbc new file mode 100644 index 0000000000..b2f05f4d89 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_2of6_ANY_testexe_1.matched.cbc @@ -0,0 +1,12 @@ +ClamBCafhjjhbcnbf|aimfifcfafcgnfigdf```c``a```|ah`cnbac`cccnbac``b`aaap`clamcoincidencejb:4096 +Test.GenSig.BC_2of6_ANY_testexe_1.{matched};Engine:56-255,Target:0;0;434c414d41565f544553545f5052494e54465f535452494e475f653464375f376234335f333135355f30343061 +Teddaaahdabahdacahdadahdaeahdafahdagahebodebadaacb`bbadb`bdbjbah +Eaeaaaebnd|amcgefdgfgifbgegcgnfafmfef`` +G`ac`@`bodBdeBefBcgBdgBnbBgdBefBnfBceBifBgfBnbBbdBcdBoeBbcBofBffBfcBoeBadBndBieBoeBdgBefBcgBdgBefBhgBefBoeBacBnbBmfBafBdgBcfBhfBefBdf@`bad@Aa` +A`b`bLaab`b`Fabaa +Bb`b`abbaeAb`BibdTcab`b@dE +SSfeidbeeecendadmdedoe`ebeedfdidhehbbbdeefcgdgnbgdefnfceifgfnbbdcdoebcoffffcoeadndieoedgefcgdgefhgefoeacbbibSfeidbeeecendadmdedcehbbbmfafdgcfhfefdfbbibSdeadbegdeddehb`cib +SceidgdndaddeeebeedceoeddedcdldoebdedgdidndSobob`bldofofkfcg`bffofbgjc`bcdldadmdadfeoedeedcedeoe`ebeidnddefdoecedebeidndgdoeefdcdfgcoegcbfdcccoeccacececoe`cdc`caf +ddedcdldadbeedoeceidgdndaddeeebeedhbdgefcgdgibSceidgdndaddeeebeedceoeddedcdldoeedndddSSceidgdndaddeeebeedceoeddedfdoebdedgdidndSddedfdidndedoeceidgdndaddeeebeedhbdgefcgdglb`bbbdcccdccfdcacdcdfdcacecfcecffecdcdcececccecdcecffec`cecbcdcicdcefecdcdcfcecffecccecdcecbcdcicdcefdcgcecfffcecccdcfcdcccgcecffccgcfcbcccdcccccecffccccccacccecccececffcc`cccdccc`cfcacbbib +ceidgdndaddeeebeedceoeddedfdoeedndddSSbfofoflf`blfofgfifcfaflfoedgbgifgfgfefbghbibSkgSbgefdgegbgnf`bmfafdgcfhfefcghbceifgfnfafdgegbgefcgnbdgefcgdgibkcSmgSSSSifnfdg`befnfdgbgig`gofifnfdghbfgofifdfib +kgSffofegnfdffeifbgegcghbbbmfafdgcfhfefdfbbibkcSbgefdgegbgnf`b`ckcSmgS diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_3of6_PE_REG_testexe_0.matched.cbc b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_3of6_PE_REG_testexe_0.matched.cbc new file mode 100644 index 0000000000..75620a9191 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_3of6_PE_REG_testexe_0.matched.cbc @@ -0,0 +1,12 @@ +ClamBCafhjjhbcnbf|aimfifcfafcgnfigdf```c``a```|ah`cnbac`cccnbac``b`aaap`clamcoincidencejb:4096 +Test.GenSig.BC_3of6_PE_REG_testexe_0.{matched};Engine:56-255,Target:1;0;434c414d41565f544553545f5052494e54465f535452494e475f666661385f333939345f313738385f65386236 +Teddaaahdabahdacahdadahdaeahdafahdagahebodebadaacb`bbadb`bdbmbah +Eaeaaaebnd|amcgefdgfgifbgegcgnfafmfef`` +G`ac`@`bodBdeBefBcgBdgBnbBgdBefBnfBceBifBgfBnbBbdBcdBoeBccBofBffBfcBoeB`eBedBoeBbeBedBgdBoeBdgBefBcgBdgBefBhgBefBoeB`cBnbBmfBafBdgBcfBhfBefBdf@`bad@Aa` +A`b`bLaab`b`Fabaa +Bb`b`abbaeAb`BlbdTcab`b@dE +SSfeidbeeecendadmdedoe`ebeedfdidhehbbbdeefcgdgnbgdefnfceifgfnbbdcdoeccoffffcoe`eedoebeedgdoedgefcgdgefhgefoe`cbbibSfeidbeeecendadmdedcehbbbmfafdgcfhfefdfbbibSdeadbegdeddehbacib +SceidgdndaddeeebeedceoeddedcdldoebdedgdidndSobob`bldofofkfcg`bffofbgjc`bcdldadmdadfeoedeedcedeoe`ebeidnddefdoecedebeidndgdoeffffafhcoeccicicdcoeacgchchcoeefhcbffc +ddedcdldadbeedoeceidgdndaddeeebeedhbdgefcgdgibSceidgdndaddeeebeedceoeddedcdldoeedndddSSceidgdndaddeeebeedceoeddedfdoebdedgdidndSddedfdidndedoeceidgdndaddeeebeedhbdgefcgdglb`bbbdcccdccfdcacdcdfdcacecfcecffecdcdcececccecdcecffec`cecbcdcicdcefecdcdcfcecffecccecdcecbcdcicdcefdcgcecfffcfcfcfcfcaccchcecffccccccicccicccdcecffccacccgccchccchcecfffceccchcfcbcccfcbbib +ceidgdndaddeeebeedceoeddedfdoeedndddSSbfofoflf`blfofgfifcfaflfoedgbgifgfgfefbghbibSkgSbgefdgegbgnf`bmfafdgcfhfefcghbceifgfnfafdgegbgefcgnbdgefcgdgibkcSmgSSSSifnfdg`befnfdgbgig`gofifnfdghbfgofifdfib +kgSffofegnfdffeifbgegcghbbbmfafdgcfhfefdfbbibkcSbgefdgegbgnf`b`ckcSmgS diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_4of6_PE_REG_testexe_1.matched.cbc b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_4of6_PE_REG_testexe_1.matched.cbc new file mode 100644 index 0000000000..28e502904e --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_4of6_PE_REG_testexe_1.matched.cbc @@ -0,0 +1,12 @@ +ClamBCafhjjhbcnbf|aimfifcfafcgnfigdf```c``a```|ah`cnbac`cccnbac``b`aaap`clamcoincidencejb:4096 +Test.GenSig.BC_4of6_PE_REG_testexe_1.{matched};Engine:56-255,Target:1;0;434c414d41565f544553545f5052494e54465f535452494e475f653464375f376234335f333135355f30343061 +Teddaaahdabahdacahdadahdaeahdafahdagahebodebadaacb`bbadb`bdbmbah +Eaeaaaebnd|amcgefdgfgifbgegcgnfafmfef`` +G`ac`@`bodBdeBefBcgBdgBnbBgdBefBnfBceBifBgfBnbBbdBcdBoeBdcBofBffBfcBoeB`eBedBoeBbeBedBgdBoeBdgBefBcgBdgBefBhgBefBoeBacBnbBmfBafBdgBcfBhfBefBdf@`bad@Aa` +A`b`bLaab`b`Fabaa +Bb`b`abbaeAb`BlbdTcab`b@dE +SSfeidbeeecendadmdedoe`ebeedfdidhehbbbdeefcgdgnbgdefnfceifgfnbbdcdoedcoffffcoe`eedoebeedgdoedgefcgdgefhgefoeacbbibSfeidbeeecendadmdedcehbbbmfafdgcfhfefdfbbibSdeadbegdeddehbacib +SceidgdndaddeeebeedceoeddedcdldoebdedgdidndSobob`bldofofkfcg`bffofbgjc`bcdldadmdadfeoedeedcedeoe`ebeidnddefdoecedebeidndgdoeefdcdfgcoegcbfdcccoeccacececoe`cdc`caf +ddedcdldadbeedoeceidgdndaddeeebeedhbdgefcgdgibSceidgdndaddeeebeedceoeddedcdldoeedndddSSceidgdndaddeeebeedceoeddedfdoebdedgdidndSddedfdidndedoeceidgdndaddeeebeedhbdgefcgdglb`bbbdcccdccfdcacdcdfdcacecfcecffecdcdcececccecdcecffec`cecbcdcicdcefecdcdcfcecffecccecdcecbcdcicdcefdcgcecfffcecccdcfcdcccgcecffccgcfcbcccdcccccecffccccccacccecccececffcc`cccdccc`cfcacbbib +ceidgdndaddeeebeedceoeddedfdoeedndddSSbfofoflf`blfofgfifcfaflfoedgbgifgfgfefbghbibSkgSbgefdgegbgnf`bmfafdgcfhfefcghbceifgfnfafdgegbgefcgnbdgefcgdgibkcSmgSSSSifnfdg`befnfdgbgig`gofifnfdghbfgofifdfib +kgSffofegnfdffeifbgegcghbbbmfafdgcfhfefdfbbibkcSbgefdgegbgnf`b`ckcSmgS diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_5of6_PE_UNPCKR_testexe_0.matched.cbc b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_5of6_PE_UNPCKR_testexe_0.matched.cbc new file mode 100644 index 0000000000..2714eadb36 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_5of6_PE_UNPCKR_testexe_0.matched.cbc @@ -0,0 +1,12 @@ +ClamBCafhjjhbcnbf|aimfifcfafcgnfigdf```ca`a```|ah`cnbac`cccnbac``b`aaap`clamcoincidencejb:4096 +Test.GenSig.BC_5of6_PE_UNPCKR_testexe_0.{matched};Engine:56-255,Target:1;0;434c414d41565f544553545f5052494e54465f535452494e475f666661385f333939345f313738385f65386236 +Teddaaahdabahdacahdadahdaeahdafahdagahebodebadaacb`bbadb`bdb`cah +Eaeaaaebnd|amcgefdgfgifbgegcgnfafmfef`` +G`ac`@`bodBdeBefBcgBdgBnbBgdBefBnfBceBifBgfBnbBbdBcdBoeBecBofBffBfcBoeB`eBedBoeBeeBndB`eBcdBkdBbeBoeBdgBefBcgBdgBefBhgBefBoeB`cBnbBmfBafBdgBcfBhfBefBdf@`bad@Aa` +A`b`bLaab`b`Fabaa +Bb`b`abbaeAb`BobdTcab`b@dE +SSfeidbeeecendadmdedoe`ebeedfdidhehbbbdeefcgdgnbgdefnfceifgfnbbdcdoeecoffffcoe`eedoeeend`ecdkdbeoedgefcgdgefhgefoe`cbbibSfeidbeeecendadmdedcehbbbmfafdgcfhfefdfbbib +deadbegdeddehbacibSSceidgdndaddeeebeedceoeddedcdldoebdedgdidndSobob`bldofofkfcg`bffofbgjc`bcdldadmdadfeoedeedcedeoe`ebeidnddefdoecedebeidndgdoeffffafhcoeccicicdcoeacgchchcoeefhcbffc +ddedcdldadbeedoeceidgdndaddeeebeedhbdgefcgdgibSceidgdndaddeeebeedceoeddedcdldoeedndddSSceidgdndaddeeebeedceoeddedfdoebdedgdidndSddedfdidndedoeceidgdndaddeeebeedhbdgefcgdglb`bbbdcccdccfdcacdcdfdcacecfcecffecdcdcececccecdcecffec`cecbcdcicdcefecdcdcfcecffecccecdcecbcdcicdcefdcgcecfffcfcfcfcfcaccchcecffccccccicccicccdcecffccacccgccchccchcecfffceccchcfcbcccfcbbib +ceidgdndaddeeebeedceoeddedfdoeedndddSSbfofoflf`blfofgfifcfaflfoedgbgifgfgfefbghbibSkgSbgefdgegbgnf`bmfafdgcfhfefcghbceifgfnfafdgegbgefcgnbdgefcgdgibkcSmgSS`eedoeeend`eadcdkdedbeoeddedcdldadbeed +Sifnfdg`befnfdgbgig`gofifnfdghbfgofifdfibSkgSffofegnfdffeifbgegcghbbbmfafdgcfhfefdfbbibkcSbgefdgegbgnf`b`ckcSmgS diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_6of6_PE_UNPCKR_testexe_1.matched.cbc b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_6of6_PE_UNPCKR_testexe_1.matched.cbc new file mode 100644 index 0000000000..1c19ec4424 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.BC_6of6_PE_UNPCKR_testexe_1.matched.cbc @@ -0,0 +1,12 @@ +ClamBCafhjjhbcnbf|aimfifcfafcgnfigdf```ca`a```|ah`cnbac`cccnbac``b`aaap`clamcoincidencejb:4096 +Test.GenSig.BC_6of6_PE_UNPCKR_testexe_1.{matched};Engine:56-255,Target:1;0;434c414d41565f544553545f5052494e54465f535452494e475f653464375f376234335f333135355f30343061 +Teddaaahdabahdacahdadahdaeahdafahdagahebodebadaacb`bbadb`bdb`cah +Eaeaaaebnd|amcgefdgfgifbgegcgnfafmfef`` +G`ac`@`bodBdeBefBcgBdgBnbBgdBefBnfBceBifBgfBnbBbdBcdBoeBfcBofBffBfcBoeB`eBedBoeBeeBndB`eBcdBkdBbeBoeBdgBefBcgBdgBefBhgBefBoeBacBnbBmfBafBdgBcfBhfBefBdf@`bad@Aa` +A`b`bLaab`b`Fabaa +Bb`b`abbaeAb`BobdTcab`b@dE +SSfeidbeeecendadmdedoe`ebeedfdidhehbbbdeefcgdgnbgdefnfceifgfnbbdcdoefcoffffcoe`eedoeeend`ecdkdbeoedgefcgdgefhgefoeacbbibSfeidbeeecendadmdedcehbbbmfafdgcfhfefdfbbib +deadbegdeddehbacibSSceidgdndaddeeebeedceoeddedcdldoebdedgdidndSobob`bldofofkfcg`bffofbgjc`bcdldadmdadfeoedeedcedeoe`ebeidnddefdoecedebeidndgdoeefdcdfgcoegcbfdcccoeccacececoe`cdc`caf +ddedcdldadbeedoeceidgdndaddeeebeedhbdgefcgdgibSceidgdndaddeeebeedceoeddedcdldoeedndddSSceidgdndaddeeebeedceoeddedfdoebdedgdidndSddedfdidndedoeceidgdndaddeeebeedhbdgefcgdglb`bbbdcccdccfdcacdcdfdcacecfcecffecdcdcececccecdcecffec`cecbcdcicdcefecdcdcfcecffecccecdcecbcdcicdcefdcgcecfffcecccdcfcdcccgcecffccgcfcbcccdcccccecffccccccacccecccececffcc`cccdccc`cfcacbbib +ceidgdndaddeeebeedceoeddedfdoeedndddSSbfofoflf`blfofgfifcfaflfoedgbgifgfgfefbghbibSkgSbgefdgegbgnf`bmfafdgcfhfefcghbceifgfnfafdgegbgefcgnbdgefcgdgibkcSmgSS`eedoeeend`eadcdkdedbeoeddedcdldadbeed +Sifnfdg`befnfdgbgig`gofifnfdghbfgofifdfibSkgSffofegnfdffeifbgegcghbbbmfafdgcfhfefdfbbibkcSbgefdgegbgnf`b`ckcSmgS diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HDB_1of2_MD5_FIXED_testexe.UNOFFICIAL.hdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HDB_1of2_MD5_FIXED_testexe.UNOFFICIAL.hdb new file mode 100644 index 0000000000..94fe32aec2 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HDB_1of2_MD5_FIXED_testexe.UNOFFICIAL.hdb @@ -0,0 +1 @@ +05fcb14bd4dbad8617251d4e22708367:1447976:Test.GenSig.HDB_1of2_MD5_FIXED_testexe diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HDB_2of2_MD5_STAR_testexe.UNOFFICIAL.hdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HDB_2of2_MD5_STAR_testexe.UNOFFICIAL.hdb new file mode 100644 index 0000000000..261053d1d8 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HDB_2of2_MD5_STAR_testexe.UNOFFICIAL.hdb @@ -0,0 +1 @@ +05fcb14bd4dbad8617251d4e22708367:*:Test.GenSig.HDB_2of2_MD5_STAR_testexe:73 \ No newline at end of file diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_1of4_SHA1_FIXED_testexe.UNOFFICIAL.hsb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_1of4_SHA1_FIXED_testexe.UNOFFICIAL.hsb new file mode 100644 index 0000000000..7a3d422c7a --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_1of4_SHA1_FIXED_testexe.UNOFFICIAL.hsb @@ -0,0 +1 @@ +2ba31b0352bae4f57c1c9144f64ac7a57c010876:1447976:Test.GenSig.HSB_1of4_SHA1_FIXED_testexe diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_2of4_SHA1_STAR_testexe.UNOFFICIAL.hsb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_2of4_SHA1_STAR_testexe.UNOFFICIAL.hsb new file mode 100644 index 0000000000..9d05bc2da2 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_2of4_SHA1_STAR_testexe.UNOFFICIAL.hsb @@ -0,0 +1 @@ +2ba31b0352bae4f57c1c9144f64ac7a57c010876:*:Test.GenSig.HSB_2of4_SHA1_STAR_testexe:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_3of4_SHA256_FIXED_testexe.UNOFFICIAL.hsb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_3of4_SHA256_FIXED_testexe.UNOFFICIAL.hsb new file mode 100644 index 0000000000..60300a3689 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_3of4_SHA256_FIXED_testexe.UNOFFICIAL.hsb @@ -0,0 +1 @@ +4f713f2f0d3269d5ea24bf58c8acff9ad67d53044c07f028ae825cacffb6e82e:1447976:Test.GenSig.HSB_3of4_SHA256_FIXED_testexe diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_4of4_SHA256_STAR_testexe.UNOFFICIAL.hsb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_4of4_SHA256_STAR_testexe.UNOFFICIAL.hsb new file mode 100644 index 0000000000..1558769a64 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.HSB_4of4_SHA256_STAR_testexe.UNOFFICIAL.hsb @@ -0,0 +1 @@ +4f713f2f0d3269d5ea24bf58c8acff9ad67d53044c07f028ae825cacffb6e82e:*:Test.GenSig.HSB_4of4_SHA256_STAR_testexe:73 \ No newline at end of file diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_1of6_MD5_FIXED.UNOFFICIAL.imp b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_1of6_MD5_FIXED.UNOFFICIAL.imp new file mode 100644 index 0000000000..5b77fead8e --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_1of6_MD5_FIXED.UNOFFICIAL.imp @@ -0,0 +1 @@ +ddeba1ab394d6c43225d83208b2d8ee3:1351:Test.GenSig.IMP_1of6_MD5_FIXED diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_2of6_MD5_STAR.UNOFFICIAL.imp b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_2of6_MD5_STAR.UNOFFICIAL.imp new file mode 100644 index 0000000000..c8e8bb66ad --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_2of6_MD5_STAR.UNOFFICIAL.imp @@ -0,0 +1 @@ +ddeba1ab394d6c43225d83208b2d8ee3:*:Test.GenSig.IMP_2of6_MD5_STAR:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_3of6_SHA1_FIXED.UNOFFICIAL.imp b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_3of6_SHA1_FIXED.UNOFFICIAL.imp new file mode 100644 index 0000000000..1ff7ad2190 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_3of6_SHA1_FIXED.UNOFFICIAL.imp @@ -0,0 +1 @@ +0fd72fd48bcf571bdfb7399fe285618aaec7b089:1351:Test.GenSig.IMP_3of6_SHA1_FIXED diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_4of6_SHA1_STAR.UNOFFICIAL.imp b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_4of6_SHA1_STAR.UNOFFICIAL.imp new file mode 100644 index 0000000000..692ee67bbd --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_4of6_SHA1_STAR.UNOFFICIAL.imp @@ -0,0 +1 @@ +0fd72fd48bcf571bdfb7399fe285618aaec7b089:*:Test.GenSig.IMP_4of6_SHA1_STAR:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_5of6_SHA256_FIXED.UNOFFICIAL.imp b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_5of6_SHA256_FIXED.UNOFFICIAL.imp new file mode 100644 index 0000000000..9847e67cb7 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_5of6_SHA256_FIXED.UNOFFICIAL.imp @@ -0,0 +1 @@ +f4b75231012ecd2164685ce91557c4c68c7473efeddc521bb7279ad13c362205:1351:Test.GenSig.IMP_5of6_SHA256_FIXED diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_6of6_SHA256_STAR.UNOFFICIAL.imp b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_6of6_SHA256_STAR.UNOFFICIAL.imp new file mode 100644 index 0000000000..ca5d53cbde --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.IMP_6of6_SHA256_STAR.UNOFFICIAL.imp @@ -0,0 +1 @@ +f4b75231012ecd2164685ce91557c4c68c7473efeddc521bb7279ad13c362205:*:Test.GenSig.IMP_6of6_SHA256_STAR:73 \ No newline at end of file diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_01of16_MD5_FIXED_text.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_01of16_MD5_FIXED_text.UNOFFICIAL.mdb new file mode 100644 index 0000000000..60ea617381 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_01of16_MD5_FIXED_text.UNOFFICIAL.mdb @@ -0,0 +1 @@ +34304:c2cf3afc85a94f96246ebc2d10427b99:Test.GenSig.MDB_01of16_MD5_FIXED_text diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_02of16_MD5_STAR_text.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_02of16_MD5_STAR_text.UNOFFICIAL.mdb new file mode 100644 index 0000000000..a5fa019c22 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_02of16_MD5_STAR_text.UNOFFICIAL.mdb @@ -0,0 +1 @@ +*:c2cf3afc85a94f96246ebc2d10427b99:Test.GenSig.MDB_02of16_MD5_STAR_text:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_03of16_MD5_FIXED_data.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_03of16_MD5_FIXED_data.UNOFFICIAL.mdb new file mode 100644 index 0000000000..da1edc59fc --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_03of16_MD5_FIXED_data.UNOFFICIAL.mdb @@ -0,0 +1 @@ +1168896:6623c7640384c88d74cc4d7701a02627:Test.GenSig.MDB_03of16_MD5_FIXED_data diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_04of16_MD5_STAR_data.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_04of16_MD5_STAR_data.UNOFFICIAL.mdb new file mode 100644 index 0000000000..7fbd3d852b --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_04of16_MD5_STAR_data.UNOFFICIAL.mdb @@ -0,0 +1 @@ +*:6623c7640384c88d74cc4d7701a02627:Test.GenSig.MDB_04of16_MD5_STAR_data:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_05of16_MD5_FIXED_rdata.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_05of16_MD5_FIXED_rdata.UNOFFICIAL.mdb new file mode 100644 index 0000000000..e269ed8501 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_05of16_MD5_FIXED_rdata.UNOFFICIAL.mdb @@ -0,0 +1 @@ +3584:4ddc472eca4b9be7039758dda4737e8c:Test.GenSig.MDB_05of16_MD5_FIXED_rdata diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_06of16_MD5_STAR_rdata.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_06of16_MD5_STAR_rdata.UNOFFICIAL.mdb new file mode 100644 index 0000000000..fa20ff93a7 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_06of16_MD5_STAR_rdata.UNOFFICIAL.mdb @@ -0,0 +1 @@ +*:4ddc472eca4b9be7039758dda4737e8c:Test.GenSig.MDB_06of16_MD5_STAR_rdata:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_09of16_MD5_FIXED_idata.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_09of16_MD5_FIXED_idata.UNOFFICIAL.mdb new file mode 100644 index 0000000000..3e76f56dd2 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_09of16_MD5_FIXED_idata.UNOFFICIAL.mdb @@ -0,0 +1 @@ +2048:e5ec1d4f047f50cec79e2a0503509f27:Test.GenSig.MDB_09of16_MD5_FIXED_idata diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_10of16_MD5_STAR_idata.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_10of16_MD5_STAR_idata.UNOFFICIAL.mdb new file mode 100644 index 0000000000..efad9d2fe6 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_10of16_MD5_STAR_idata.UNOFFICIAL.mdb @@ -0,0 +1 @@ +*:e5ec1d4f047f50cec79e2a0503509f27:Test.GenSig.MDB_10of16_MD5_STAR_idata:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_11of16_MD5_FIXED_CRT.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_11of16_MD5_FIXED_CRT.UNOFFICIAL.mdb new file mode 100644 index 0000000000..0b08dfbd82 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_11of16_MD5_FIXED_CRT.UNOFFICIAL.mdb @@ -0,0 +1 @@ +512:1e10c60d92bb19eefe6f3e1e4d3d78ec:Test.GenSig.MDB_11of16_MD5_FIXED_CRT diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_12of16_MD5_STAR_CRT.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_12of16_MD5_STAR_CRT.UNOFFICIAL.mdb new file mode 100644 index 0000000000..0b6d0193a0 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_12of16_MD5_STAR_CRT.UNOFFICIAL.mdb @@ -0,0 +1 @@ +*:1e10c60d92bb19eefe6f3e1e4d3d78ec:Test.GenSig.MDB_12of16_MD5_STAR_CRT:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_13of16_MD5_FIXED_tls.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_13of16_MD5_FIXED_tls.UNOFFICIAL.mdb new file mode 100644 index 0000000000..4d7e5f3247 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_13of16_MD5_FIXED_tls.UNOFFICIAL.mdb @@ -0,0 +1 @@ +512:bf619eac0cdf3f68d496ea9344137e8b:Test.GenSig.MDB_13of16_MD5_FIXED_tls diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_14of16_MD5_STAR_tls.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_14of16_MD5_STAR_tls.UNOFFICIAL.mdb new file mode 100644 index 0000000000..7bb00262fb --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_14of16_MD5_STAR_tls.UNOFFICIAL.mdb @@ -0,0 +1 @@ +*:bf619eac0cdf3f68d496ea9344137e8b:Test.GenSig.MDB_14of16_MD5_STAR_tls:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_15of16_MD5_FIXED_rsrc.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_15of16_MD5_FIXED_rsrc.UNOFFICIAL.mdb new file mode 100644 index 0000000000..3dd2da66d8 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_15of16_MD5_FIXED_rsrc.UNOFFICIAL.mdb @@ -0,0 +1 @@ +230912:2eed75bfc8ec3ff1e5ed3161b6fd11df:Test.GenSig.MDB_15of16_MD5_FIXED_rsrc diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_16of16_MD5_STAR_rsrc.UNOFFICIAL.mdb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_16of16_MD5_STAR_rsrc.UNOFFICIAL.mdb new file mode 100644 index 0000000000..d895ab8e83 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MDB_16of16_MD5_STAR_rsrc.UNOFFICIAL.mdb @@ -0,0 +1 @@ +*:2eed75bfc8ec3ff1e5ed3161b6fd11df:Test.GenSig.MDB_16of16_MD5_STAR_rsrc:73 \ No newline at end of file diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_01of32_SHA1_FIXED_text.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_01of32_SHA1_FIXED_text.UNOFFICIAL.msb new file mode 100644 index 0000000000..26fb2ba711 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_01of32_SHA1_FIXED_text.UNOFFICIAL.msb @@ -0,0 +1 @@ +34304:7bcc8fbbab4b38c28cb9a571fa7004d8ff47b09d:Test.GenSig.MSB_01of32_SHA1_FIXED_text diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_02of32_SHA1_STAR_text.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_02of32_SHA1_STAR_text.UNOFFICIAL.msb new file mode 100644 index 0000000000..b614ef8fa0 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_02of32_SHA1_STAR_text.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:7bcc8fbbab4b38c28cb9a571fa7004d8ff47b09d:Test.GenSig.MSB_02of32_SHA1_STAR_text:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_03of32_SHA1_FIXED_data.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_03of32_SHA1_FIXED_data.UNOFFICIAL.msb new file mode 100644 index 0000000000..d7bca8539a --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_03of32_SHA1_FIXED_data.UNOFFICIAL.msb @@ -0,0 +1 @@ +1168896:dae420693dde3530da0ad06f593148c9647a66b3:Test.GenSig.MSB_03of32_SHA1_FIXED_data diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_04of32_SHA1_STAR_data.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_04of32_SHA1_STAR_data.UNOFFICIAL.msb new file mode 100644 index 0000000000..8762992c3e --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_04of32_SHA1_STAR_data.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:dae420693dde3530da0ad06f593148c9647a66b3:Test.GenSig.MSB_04of32_SHA1_STAR_data:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_05of32_SHA1_FIXED_rdata.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_05of32_SHA1_FIXED_rdata.UNOFFICIAL.msb new file mode 100644 index 0000000000..7bf702d895 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_05of32_SHA1_FIXED_rdata.UNOFFICIAL.msb @@ -0,0 +1 @@ +3584:ff14bc18e10021c1e30f9f8cdead7a6a8d017da1:Test.GenSig.MSB_05of32_SHA1_FIXED_rdata diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_06of32_SHA1_STAR_rdata.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_06of32_SHA1_STAR_rdata.UNOFFICIAL.msb new file mode 100644 index 0000000000..7a91124a8c --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_06of32_SHA1_STAR_rdata.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:ff14bc18e10021c1e30f9f8cdead7a6a8d017da1:Test.GenSig.MSB_06of32_SHA1_STAR_rdata:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_09of32_SHA1_FIXED_idata.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_09of32_SHA1_FIXED_idata.UNOFFICIAL.msb new file mode 100644 index 0000000000..71fdb37bc7 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_09of32_SHA1_FIXED_idata.UNOFFICIAL.msb @@ -0,0 +1 @@ +2048:4f9ec5efe49dd45cc3e39f8e553c5b22b9d6d820:Test.GenSig.MSB_09of32_SHA1_FIXED_idata diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_10of32_SHA1_STAR_idata.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_10of32_SHA1_STAR_idata.UNOFFICIAL.msb new file mode 100644 index 0000000000..a5eda1eed8 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_10of32_SHA1_STAR_idata.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:4f9ec5efe49dd45cc3e39f8e553c5b22b9d6d820:Test.GenSig.MSB_10of32_SHA1_STAR_idata:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_11of32_SHA1_FIXED_CRT.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_11of32_SHA1_FIXED_CRT.UNOFFICIAL.msb new file mode 100644 index 0000000000..071c049fe1 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_11of32_SHA1_FIXED_CRT.UNOFFICIAL.msb @@ -0,0 +1 @@ +512:8c0957f3d183375d014b1c920cfb3111cdb045e1:Test.GenSig.MSB_11of32_SHA1_FIXED_CRT diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_12of32_SHA1_STAR_CRT.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_12of32_SHA1_STAR_CRT.UNOFFICIAL.msb new file mode 100644 index 0000000000..3cae6f96cd --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_12of32_SHA1_STAR_CRT.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:8c0957f3d183375d014b1c920cfb3111cdb045e1:Test.GenSig.MSB_12of32_SHA1_STAR_CRT:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_13of32_SHA1_FIXED_tls.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_13of32_SHA1_FIXED_tls.UNOFFICIAL.msb new file mode 100644 index 0000000000..67a7ffe824 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_13of32_SHA1_FIXED_tls.UNOFFICIAL.msb @@ -0,0 +1 @@ +512:5c3eb80066420002bc3dcc7ca4ab6efad7ed4ae5:Test.GenSig.MSB_13of32_SHA1_FIXED_tls diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_14of32_SHA1_STAR_tls.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_14of32_SHA1_STAR_tls.UNOFFICIAL.msb new file mode 100644 index 0000000000..f1ba4dc539 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_14of32_SHA1_STAR_tls.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:5c3eb80066420002bc3dcc7ca4ab6efad7ed4ae5:Test.GenSig.MSB_14of32_SHA1_STAR_tls:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_15of32_SHA1_FIXED_rsrc.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_15of32_SHA1_FIXED_rsrc.UNOFFICIAL.msb new file mode 100644 index 0000000000..2c75a10158 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_15of32_SHA1_FIXED_rsrc.UNOFFICIAL.msb @@ -0,0 +1 @@ +230912:c86503c04dc7dc934c4bda3630667c29d0df0b02:Test.GenSig.MSB_15of32_SHA1_FIXED_rsrc diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_16of32_SHA1_STAR_rsrc.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_16of32_SHA1_STAR_rsrc.UNOFFICIAL.msb new file mode 100644 index 0000000000..cfce1cc6b9 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_16of32_SHA1_STAR_rsrc.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:c86503c04dc7dc934c4bda3630667c29d0df0b02:Test.GenSig.MSB_16of32_SHA1_STAR_rsrc:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_17of32_SHA256_FIXED_text.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_17of32_SHA256_FIXED_text.UNOFFICIAL.msb new file mode 100644 index 0000000000..c23e1d313a --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_17of32_SHA256_FIXED_text.UNOFFICIAL.msb @@ -0,0 +1 @@ +34304:a0174c8dfab8cd480495fede811c9fcd16ec40db6d9dbe69e9e5f32907be3a1a:Test.GenSig.MSB_17of32_SHA256_FIXED_text diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_18of32_SHA256_STAR_text.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_18of32_SHA256_STAR_text.UNOFFICIAL.msb new file mode 100644 index 0000000000..920ef139e6 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_18of32_SHA256_STAR_text.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:a0174c8dfab8cd480495fede811c9fcd16ec40db6d9dbe69e9e5f32907be3a1a:Test.GenSig.MSB_18of32_SHA256_STAR_text:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_19of32_SHA256_FIXED_data.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_19of32_SHA256_FIXED_data.UNOFFICIAL.msb new file mode 100644 index 0000000000..aefc9430e9 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_19of32_SHA256_FIXED_data.UNOFFICIAL.msb @@ -0,0 +1 @@ +1168896:96559752f87084cc488e3163b615d13eac1816580375facd2f872a3e4d808789:Test.GenSig.MSB_19of32_SHA256_FIXED_data diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_20of32_SHA256_STAR_data.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_20of32_SHA256_STAR_data.UNOFFICIAL.msb new file mode 100644 index 0000000000..236bec5bbc --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_20of32_SHA256_STAR_data.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:96559752f87084cc488e3163b615d13eac1816580375facd2f872a3e4d808789:Test.GenSig.MSB_20of32_SHA256_STAR_data:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_21of32_SHA256_FIXED_rdata.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_21of32_SHA256_FIXED_rdata.UNOFFICIAL.msb new file mode 100644 index 0000000000..a493114145 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_21of32_SHA256_FIXED_rdata.UNOFFICIAL.msb @@ -0,0 +1 @@ +3584:758066b48be8aa110b1169906eee6556ee1eaccde3cbc2d2815ec0c9514b5199:Test.GenSig.MSB_21of32_SHA256_FIXED_rdata diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_22of32_SHA256_STAR_rdata.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_22of32_SHA256_STAR_rdata.UNOFFICIAL.msb new file mode 100644 index 0000000000..7aac76afe5 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_22of32_SHA256_STAR_rdata.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:758066b48be8aa110b1169906eee6556ee1eaccde3cbc2d2815ec0c9514b5199:Test.GenSig.MSB_22of32_SHA256_STAR_rdata:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_25of32_SHA256_FIXED_idata.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_25of32_SHA256_FIXED_idata.UNOFFICIAL.msb new file mode 100644 index 0000000000..768364b5a6 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_25of32_SHA256_FIXED_idata.UNOFFICIAL.msb @@ -0,0 +1 @@ +2048:52a7e01eefcc019ac8a1fe696fe9fd4b68a2a637c86971711b8d827ff56f6ea1:Test.GenSig.MSB_25of32_SHA256_FIXED_idata diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_26of32_SHA256_STAR_idata.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_26of32_SHA256_STAR_idata.UNOFFICIAL.msb new file mode 100644 index 0000000000..a6a40b66e7 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_26of32_SHA256_STAR_idata.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:52a7e01eefcc019ac8a1fe696fe9fd4b68a2a637c86971711b8d827ff56f6ea1:Test.GenSig.MSB_26of32_SHA256_STAR_idata:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_27of32_SHA256_FIXED_CRT.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_27of32_SHA256_FIXED_CRT.UNOFFICIAL.msb new file mode 100644 index 0000000000..baf4d3a65c --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_27of32_SHA256_FIXED_CRT.UNOFFICIAL.msb @@ -0,0 +1 @@ +512:629b57a90ca0c6338e5eb18ecfe55ea8695236b94833dc34ea0f9f302a0c3cad:Test.GenSig.MSB_27of32_SHA256_FIXED_CRT diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_28of32_SHA256_STAR_CRT.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_28of32_SHA256_STAR_CRT.UNOFFICIAL.msb new file mode 100644 index 0000000000..ff6d3c589b --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_28of32_SHA256_STAR_CRT.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:629b57a90ca0c6338e5eb18ecfe55ea8695236b94833dc34ea0f9f302a0c3cad:Test.GenSig.MSB_28of32_SHA256_STAR_CRT:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_29of32_SHA256_FIXED_tls.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_29of32_SHA256_FIXED_tls.UNOFFICIAL.msb new file mode 100644 index 0000000000..49f7d29ec4 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_29of32_SHA256_FIXED_tls.UNOFFICIAL.msb @@ -0,0 +1 @@ +512:076a27c79e5ace2a3d47f9dd2e83e4ff6ea8872b3c2218f66c92b89b55f36560:Test.GenSig.MSB_29of32_SHA256_FIXED_tls diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_30of32_SHA256_STAR_tls.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_30of32_SHA256_STAR_tls.UNOFFICIAL.msb new file mode 100644 index 0000000000..4ce81c820e --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_30of32_SHA256_STAR_tls.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:076a27c79e5ace2a3d47f9dd2e83e4ff6ea8872b3c2218f66c92b89b55f36560:Test.GenSig.MSB_30of32_SHA256_STAR_tls:73 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_31of32_SHA256_FIXED_rsrc.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_31of32_SHA256_FIXED_rsrc.UNOFFICIAL.msb new file mode 100644 index 0000000000..c10a90ca80 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_31of32_SHA256_FIXED_rsrc.UNOFFICIAL.msb @@ -0,0 +1 @@ +230912:d5d3481e5ca17ad4b5e940d9af88a5950d5a363080a73e04b2f0692cdde0eb44:Test.GenSig.MSB_31of32_SHA256_FIXED_rsrc diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_32of32_SHA256_STAR_rsrc.UNOFFICIAL.msb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_32of32_SHA256_STAR_rsrc.UNOFFICIAL.msb new file mode 100644 index 0000000000..508998cd4f --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.MSB_32of32_SHA256_STAR_rsrc.UNOFFICIAL.msb @@ -0,0 +1 @@ +*:d5d3481e5ca17ad4b5e940d9af88a5950d5a363080a73e04b2f0692cdde0eb44:Test.GenSig.MSB_32of32_SHA256_STAR_rsrc:73 \ No newline at end of file diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_01of22_companyname.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_01of22_companyname.UNOFFICIAL.ndb new file mode 100644 index 0000000000..ec60a9668b --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_01of22_companyname.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_01of22_companyname:1:VI:43006f006d00700061006e0079004e0061006d006500000000005400650073007400200043006f006d00700061006e0079004e0061006d00650000000000 \ No newline at end of file diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_02of22_filedescription.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_02of22_filedescription.UNOFFICIAL.ndb new file mode 100644 index 0000000000..63190c9f3c --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_02of22_filedescription.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_02of22_filedescription:1:VI:460069006c0065004400650073006300720069007000740069006f006e000000000054006500730074002000460069006c0065004400650073006300720069007000740069006f006e0000000000 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_03of22_fileversion.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_03of22_fileversion.UNOFFICIAL.ndb new file mode 100644 index 0000000000..c6127c9341 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_03of22_fileversion.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_03of22_fileversion:1:VI:460069006c006500560065007200730069006f006e000000000054006500730074002000460069006c006500560065007200730069006f006e0000000000 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_04of22_internalname.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_04of22_internalname.UNOFFICIAL.ndb new file mode 100644 index 0000000000..353950f2da --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_04of22_internalname.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_04of22_internalname:1:VI:49006e007400650072006e0061006c004e0061006d00650000005400650073007400200049006e007400650072006e0061006c004e0061006d0065000000 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_05of22_legalcopyright.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_05of22_legalcopyright.UNOFFICIAL.ndb new file mode 100644 index 0000000000..01898ec166 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_05of22_legalcopyright.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_05of22_legalcopyright:1:VI:4c006500670061006c0043006f0070007900720069006700680074000000540065007300740020004c006500670061006c0043006f0070007900720069006700680074000000 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_06of22_originalfilename.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_06of22_originalfilename.UNOFFICIAL.ndb new file mode 100644 index 0000000000..31443055a3 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_06of22_originalfilename.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_06of22_originalfilename:1:VI:4f0072006900670069006e0061006c00460069006c0065006e0061006d0065000000540065007300740020004f0072006900670069006e0061006c00460069006c0065006e0061006d0065000000 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_07of22_productname.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_07of22_productname.UNOFFICIAL.ndb new file mode 100644 index 0000000000..66a9b88c7d --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_07of22_productname.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_07of22_productname:1:VI:500072006f0064007500630074004e0061006d0065000000000054006500730074002000500072006f0064007500630074004e0061006d00650000000000 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_08of22_productversion.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_08of22_productversion.UNOFFICIAL.ndb new file mode 100644 index 0000000000..eff3e221a3 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_08of22_productversion.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_08of22_productversion:1:VI:500072006f006400750063007400560065007200730069006f006e00000054006500730074002000500072006f006400750063007400560065007200730069006f006e000000 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_09of22_ANY_testexe_0.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_09of22_ANY_testexe_0.UNOFFICIAL.ndb new file mode 100644 index 0000000000..b2059de9be --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_09of22_ANY_testexe_0.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_09of22_ANY_testexe_0:0:*:434c414d41565f544553545f5052494e54465f535452494e475f666661385f333939345f313738385f65386236 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_10of22_PE_testexe_0.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_10of22_PE_testexe_0.UNOFFICIAL.ndb new file mode 100644 index 0000000000..85567a1757 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_10of22_PE_testexe_0.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_10of22_PE_testexe_0:1:*:434c414d41565f544553545f5052494e54465f535452494e475f666661385f333939345f313738385f65386236 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_11of22_PE_EP_testexe_0.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_11of22_PE_EP_testexe_0.UNOFFICIAL.ndb new file mode 100644 index 0000000000..9d69e5a06e --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_11of22_PE_EP_testexe_0.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_11of22_PE_EP_testexe_0:1:EP+69320,69365:434c414d41565f544553545f5052494e54465f535452494e475f666661385f333939345f313738385f65386236 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_12of22_PE_EP_testexe_0.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_12of22_PE_EP_testexe_0.UNOFFICIAL.ndb new file mode 100644 index 0000000000..e807938fb4 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_12of22_PE_EP_testexe_0.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_12of22_PE_EP_testexe_0:1:EP+0,134217728:434c414d41565f544553545f5052494e54465f535452494e475f666661385f333939345f313738385f65386236 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_13of22_PE_S1_EXACT_testexe_0.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_13of22_PE_S1_EXACT_testexe_0.UNOFFICIAL.ndb new file mode 100644 index 0000000000..03f454eff0 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_13of22_PE_S1_EXACT_testexe_0.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_13of22_PE_S1_EXACT_testexe_0:1:S1+36232,36277:434c414d41565f544553545f5052494e54465f535452494e475f666661385f333939345f313738385f65386236 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_14of22_PE_S1_BROAD_testexe_0.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_14of22_PE_S1_BROAD_testexe_0.UNOFFICIAL.ndb new file mode 100644 index 0000000000..ac620d2f64 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_14of22_PE_S1_BROAD_testexe_0.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_14of22_PE_S1_BROAD_testexe_0:1:S1+0,134217728:434c414d41565f544553545f5052494e54465f535452494e475f666661385f333939345f313738385f65386236 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_16of22_ANY_testexe_1.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_16of22_ANY_testexe_1.UNOFFICIAL.ndb new file mode 100644 index 0000000000..f546a80ea4 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_16of22_ANY_testexe_1.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_16of22_ANY_testexe_1:0:*:434c414d41565f544553545f5052494e54465f535452494e475f653464375f376234335f333135355f30343061 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_17of22_PE_testexe_1.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_17of22_PE_testexe_1.UNOFFICIAL.ndb new file mode 100644 index 0000000000..350859e2d5 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_17of22_PE_testexe_1.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_17of22_PE_testexe_1:1:*:434c414d41565f544553545f5052494e54465f535452494e475f653464375f376234335f333135355f30343061 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_18of22_PE_EP_testexe_1.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_18of22_PE_EP_testexe_1.UNOFFICIAL.ndb new file mode 100644 index 0000000000..99acd8881c --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_18of22_PE_EP_testexe_1.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_18of22_PE_EP_testexe_1:1:EP+69370,69415:434c414d41565f544553545f5052494e54465f535452494e475f653464375f376234335f333135355f30343061 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_19of22_PE_EP_testexe_1.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_19of22_PE_EP_testexe_1.UNOFFICIAL.ndb new file mode 100644 index 0000000000..a27854fe44 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_19of22_PE_EP_testexe_1.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_19of22_PE_EP_testexe_1:1:EP+0,134217728:434c414d41565f544553545f5052494e54465f535452494e475f653464375f376234335f333135355f30343061 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_20of22_PE_S1_EXACT_testexe_1.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_20of22_PE_S1_EXACT_testexe_1.UNOFFICIAL.ndb new file mode 100644 index 0000000000..8c9c9afb42 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_20of22_PE_S1_EXACT_testexe_1.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_20of22_PE_S1_EXACT_testexe_1:1:S1+36282,36327:434c414d41565f544553545f5052494e54465f535452494e475f653464375f376234335f333135355f30343061 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_21of22_PE_S1_BROAD_testexe_1.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_21of22_PE_S1_BROAD_testexe_1.UNOFFICIAL.ndb new file mode 100644 index 0000000000..100d9bdbac --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.GenSig.NDB_21of22_PE_S1_BROAD_testexe_1.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_21of22_PE_S1_BROAD_testexe_1:1:S1+0,134217728:434c414d41565f544553545f5052494e54465f535452494e475f653464375f376234335f333135355f30343061 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.Sig.CRB.BlockCert.crb b/unit_tests/input/pe_allmatch/alert-sigs/Test.Sig.CRB.BlockCert.crb new file mode 100644 index 0000000000..08083f0d69 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.Sig.CRB.BlockCert.crb @@ -0,0 +1 @@ +Test.Sig.CRB.BlockCert;0;8b166a274bfaa700a912edd57e8e41365beea576;d2a8ea878c4bba243788488f59354835a005baae;E709F7C042C0DFE60CDEF79BFEC8723468DE92B027E4BC31C2D2B8224DE04B6A23C49C8FEBFAD35274651AA5DAA91FD392E0336B31140F90C125E43E4DEBD3276659BBC639425595F4713C4CAC1892D5D136F76263EDE02DF4EBB849A508B492C7BD3FE295617FC5FF1C482543C938F389D521D8E758D59183C7986A5729E16B5BC3081CF3A749447E23106D170E5835BA137821202B100124EDAD00F7508C19F8103B774E9FA19989058EC52776934690E2CAD67B99E93A9AD50C470E0DF4C48F9F78DBFCEF812730A3A458A310A913CCA7E0B10699A4A441C8900A59193FFDC7376162DA6DB805E4BD9AD9463717B6EEDACAD53AEA9E7FDBB2826588FBF8E45F390B4A44A6A01787DC8110581DD1DC00407C3868F3534241BB340AED7CC9CAB56D27F7E6B645F7CC7BA7B0D1BFB27036F09B9FB25B396575C16B0BF3177FE052F7B5C8BB97F72E69DA7971EAFA643C68E36B5F156BAD46F3E3A580A7CE56BA92ACB972143DDA4B20867A45081262DF2E7A1F80A9D3588C60D48010F10461A8AE675CF8EE47E66425A5A6A0D95F1076AFEC6246C3AA635C13AC1E9CEA760316FD89AE2C19FDF696A106BC20B5A2F8E14613A4633726ADC92FD67B6E219CE1A419FE7F397F153C2591547EE08B19C54C8F04B0F6824EABD2572AF9115486479567A77853DDA31CB84609154BA3A043AD0548204875FF365047BC4B7382FD03AF;010001;0;1;0;;Generated with details from sigtool --print-certs \ No newline at end of file diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.Sig.LDB_1of2_PE_ICON_1.UNOFFICIAL.ldb b/unit_tests/input/pe_allmatch/alert-sigs/Test.Sig.LDB_1of2_PE_ICON_1.UNOFFICIAL.ldb new file mode 100644 index 0000000000..64b0262f31 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.Sig.LDB_1of2_PE_ICON_1.UNOFFICIAL.ldb @@ -0,0 +1 @@ +Test.Sig.LDB_1of2_PE_ICON_1;Engine:51-255,Target:1,IconGroup1:TEST_ICON_GROUP_1;0;434c414d41565f544553545f5052494e54465f535452494e475f diff --git a/unit_tests/input/pe_allmatch/alert-sigs/Test.Sig.LDB_2of2_PE_ICON_2.UNOFFICIAL.ldb b/unit_tests/input/pe_allmatch/alert-sigs/Test.Sig.LDB_2of2_PE_ICON_2.UNOFFICIAL.ldb new file mode 100644 index 0000000000..ea8dc3cfa0 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/Test.Sig.LDB_2of2_PE_ICON_2.UNOFFICIAL.ldb @@ -0,0 +1 @@ +Test.Sig.LDB_2of2_PE_ICON_2;Engine:51-255,Target:1,IconGroup2:TEST_ICON_GROUP_2;0;434c414d41565f544553545f5052494e54465f535452494e47 diff --git a/unit_tests/input/pe_allmatch/alert-sigs/YARA.Test_GenSig_YARA_1of2_BASIC_0.UNOFFICIAL.yar b/unit_tests/input/pe_allmatch/alert-sigs/YARA.Test_GenSig_YARA_1of2_BASIC_0.UNOFFICIAL.yar new file mode 100644 index 0000000000..69eebec561 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/YARA.Test_GenSig_YARA_1of2_BASIC_0.UNOFFICIAL.yar @@ -0,0 +1,8 @@ + +rule Test_GenSig_YARA_1of2_BASIC_0 +{ + strings: + $s1 = "CLAMAV_TEST_PRINTF_STRING_ffa8_3994_1788_e8b6" + condition: + $s1 +} diff --git a/unit_tests/input/pe_allmatch/alert-sigs/YARA.Test_GenSig_YARA_2of2_BASIC_1.UNOFFICIAL.yar b/unit_tests/input/pe_allmatch/alert-sigs/YARA.Test_GenSig_YARA_2of2_BASIC_1.UNOFFICIAL.yar new file mode 100644 index 0000000000..595de8ddd7 --- /dev/null +++ b/unit_tests/input/pe_allmatch/alert-sigs/YARA.Test_GenSig_YARA_2of2_BASIC_1.UNOFFICIAL.yar @@ -0,0 +1,8 @@ + +rule Test_GenSig_YARA_2of2_BASIC_1 +{ + strings: + $s1 = "CLAMAV_TEST_PRINTF_STRING_e4d7_7b43_3155_040a" + condition: + $s1 +} diff --git a/unit_tests/input/pe_allmatch/broken-sigs/Test.GenSig.NDB_15of22_PE_SE1_testexe_0.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/broken-sigs/Test.GenSig.NDB_15of22_PE_SE1_testexe_0.UNOFFICIAL.ndb new file mode 100644 index 0000000000..33b033e402 --- /dev/null +++ b/unit_tests/input/pe_allmatch/broken-sigs/Test.GenSig.NDB_15of22_PE_SE1_testexe_0.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_15of22_PE_SE1_testexe_0:1:SE1:434c414d41565f544553545f5052494e54465f535452494e475f666661385f333939345f313738385f65386236 diff --git a/unit_tests/input/pe_allmatch/broken-sigs/Test.GenSig.NDB_22of22_PE_SE1_testexe_1.UNOFFICIAL.ndb b/unit_tests/input/pe_allmatch/broken-sigs/Test.GenSig.NDB_22of22_PE_SE1_testexe_1.UNOFFICIAL.ndb new file mode 100644 index 0000000000..5c7f2349ef --- /dev/null +++ b/unit_tests/input/pe_allmatch/broken-sigs/Test.GenSig.NDB_22of22_PE_SE1_testexe_1.UNOFFICIAL.ndb @@ -0,0 +1 @@ +Test.GenSig.NDB_22of22_PE_SE1_testexe_1:1:SE1:434c414d41565f544553545f5052494e54465f535452494e475f653464375f376234335f333135355f30343061 \ No newline at end of file diff --git a/unit_tests/input/pe_allmatch/test-exe-src/README.md b/unit_tests/input/pe_allmatch/test-exe-src/README.md new file mode 100644 index 0000000000..24a4817c0c --- /dev/null +++ b/unit_tests/input/pe_allmatch/test-exe-src/README.md @@ -0,0 +1,92 @@ +## Overview + +This aims to provide many different types of ClamAV rules for the given `test.exe` x86 Windows executable binary in order to test ClamAV's ability to report multiple signature matches (`-z` or `--allmatch` in `clamscan`). + +- `src` - contains the code necessary to rebuild the `test.exe` program. +- `alert-sigs` are a combination of signatures generated by this tool, and those that were manually created. + - As the name implies, these signatures are expected to alert on the `text.exe` program. + - Some of these signatures depend on signatures in `weak-sigs`. +- `weak-sigs` are a combination of signatures generated by this tool, and those that were manually created. + - As the name implies, these signatures support the `alert-sigs`, but do not alert on their own. + +The tools necessary to (re)generate those sigs that were generated are not included here. + +## Requirements + +- `python3` +- `mingw-w64` + - Just `sudo apt install mingw-w64` on ubuntu 20.04 +- `osslsigncode` + - The ubuntu package is dated, but it's pretty easy to install from https://github.com/mtrojnar/osslsigncode + - Follow the directions for those prereqs. + - Note: you can use these `cmake` commands: + ```bash + mkdir build && cd build + cmake .. && cmake --build . + sudo cmake --install . --prefix /usr/local/bin + ``` + +## Steps to regenerate the `test.exe` program + +1. Build the binary with: `./build.py` + +2. Generate the signatures. First run: + ```sh + mkdir gen + ``` + Then run: + ```sh + python generate.py build/test.exe + ``` + +## Testing: + - Example invocation: +``` +$ clamscan -z -d gen/ -d manual/ build/test.exe --bytecode-unsigned --no-summary | sort -n +test.exe: Test.GenSig.HSB_01of06_MD5_FIXED.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_01of90_MD5_FIXED_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_02of90_SHA1_FIXED_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_03of90_SHA256_FIXED_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_04of90_MD5_STAR_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_05of90_SHA1_STAR_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.MSB_06of90_SHA256_STAR_dottext.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_01of08_legalcopyright.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_02of08_internalname.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_03of08_fileversion.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_04of08_companyname.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_05of08_productname.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_06of08_productversion.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_07of08_filedescription.UNOFFICIAL FOUND +test.exe: Test.GenSig.NDB_08of08_originalfilename.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_01of16_PE_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_02of16_PE_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_03of16_ANY_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_04of16_ANY_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_05of16_PE_EP_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_06of16_PE_EP_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_07of16_PE_SE1_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_09of16_PE_S1_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_10of16_PE_S1_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_11of16_PE_PCRE_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_12of16_PE_PCRE_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_13of16_ANY_PCRE_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_14of16_ANY_PCRE_2.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_15of16_PE_ICON_1.UNOFFICIAL FOUND +test.exe: Test.Sig.LDB_16of16_PE_ICON_2.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_01of10_PE_1.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_02of10_PE_2.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_03of10_ANY_1.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_04of10_ANY_2.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_05of10_PE_EP_1.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_06of10_PE_EP_2.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_07of10_PE_SE2_1.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_09of10_PE_S1_1.UNOFFICIAL FOUND +test.exe: Test.Sig.NDB_10of10_PE_S1_2.UNOFFICIAL FOUND +test.exe: YARA.Test_Sig_YARA_1of1_strings.UNOFFICIAL FOUND +``` + +## Steps to generate the .ico file: +``` +head -c 245760 /dev/urandom | convert -depth 8 -size 320x256 RGB:- test.png +convert -background transparent test.png -define icon:auto-resize=16,32,48,64,256 test.ico +``` diff --git a/unit_tests/input/pe_allmatch/test-exe-src/build.py b/unit_tests/input/pe_allmatch/test-exe-src/build.py new file mode 100755 index 0000000000..45e63abf3f --- /dev/null +++ b/unit_tests/input/pe_allmatch/test-exe-src/build.py @@ -0,0 +1,224 @@ +#!/bin/env python3 + +# Copyright (C) 2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. +# +# Authors: Andrew Williams +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + +''' +Build a `test.exe` program with distinctive features that may be easily detected by clamscan. +This program is also signed with an authenticode certificate to test authenticode certificate trust features. +''' + +import errno +import os +import argparse +import random + +parser = argparse.ArgumentParser() +parser.add_argument("os", choices=['windows', 'linux', 'win32', 'win64'], nargs='?', default='windows') +parser.add_argument("--no-sign", action='store_true', default=False) +parser.add_argument("--no-strip", action='store_true', default=False) +parser.add_argument("--no-cleanup", action='store_true', default=False) +args = parser.parse_args() + +do_cleanup = not args.no_cleanup + +if args.os == 'windows' or args.os == 'win32': + prefix = 'i686-w64-mingw32-' + do_res = True + do_sign = not args.no_sign + do_strip = not args.no_strip + # __USE_MINGW_ANSI_STDIO lets %xd be used correctly when doing printf + cc_flags = '-D__USE_MINGW_ANSI_STDIO=1' + ext = ".exe" + +elif args.os == 'win64': + prefix = 'x86_64-w64-mingw32-' + do_res = True + do_sign = not args.no_sign + do_strip = not args.no_strip + cc_flags = '-D__USE_MINGW_ANSI_STDIO=1' + ext = ".exe" + +else: # linux + prefix = '' + do_res = False + do_sign = False + do_strip = False + cc_flags = '' + ext = '' + +windres = '%swindres' % (prefix) +gcc = '%sgcc' % (prefix) +ld = '%sld' % (prefix) +strip = '%sstrip' % (prefix) + +indicators = [ + # Indicators for the outer binary + ('22df_62ba_15c8_f482', '1a0d_301b_228d_56c8'), + + # Indicators for up to 24 inner binaries + ('ffa8_3994_1788_e8b6', 'e4d7_7b43_3155_040a'), + ('3a28_3628_9dd8_9c32', '3da0_c7bc_1948_39bd'), + ('d251_d598_8e5c_6f9d', 'ee01_7f34_e9c9_1ec1'), + ('6761_4a90_365b_b4c0', 'b8b0_d765_3df3_e550'), + ('7939_5658_544a_f991', '768b_f5e5_f5e4_1a3b'), + ('d765_7e8b_90b9_4a2f', '6cb8_1966_8bc3_9874'), + ('cef1_e295_6e46_f429', '0ef3_77f1_e811_2753'), + ('8f8a_f7f2_cdd1_9d0b', '99aa_e67d_afb6_3735'), + ('d183_4b76_bde9_f5fb', 'f5e2_acf2_ff78_88f7'), + ('3bce_e84b_be85_77bc', 'b155_ac2c_22f6_cb9e'), + ('6efd_bf13_3679_9d30', '00de_ce35_6606_114c'), + ('6ef7_8f1c_19f8_9746', '38d9_f9c7_45de_9931'), + ('54b5_a0a4_b852_d3ba', 'cc78_0d79_8e9d_6f8a'), + ('7468_e227_c130_b48d', '0c57_4d6a_e113_e03a'), + ('8272_e8e2_a133_2971', 'e113_d12a_266c_3253'), + ('b552_7c09_8b0f_01c7', '01c3_a69a_899f_0764'), + ('04a3_b125_45ed_2a8b', '58f8_6bf7_b1bb_c7b6'), + ('5749_2c8c_9df1_4c7e', 'c0a0_3f27_99c5_51f4'), + ('89df_0268_6c60_29fb', 'db58_4b5e_f2df_9b65'), + ('c2cd_3a28_5180_dc7a', '2c70_5359_0a46_06fa'), + ('6764_76f5_0e92_faa6', 'd07a_b1af_e36f_8894'), + ('4e45_18c0_40da_e74d', '6a1d_d382_068a_7e71'), + ('01c4_23f0_0e32_fad1', 'a2f0_7a46_5cf1_2e0d'), + ('1cec_e7c0_daac_517f', '448a_2336_facf_e5e7'), +] + +if len(indicators)-1 > 32: + raise Exception("More embedded binaries than extract.h can handle") + +try: + os.mkdir('build') +except OSError as exc: + if exc.errno != errno.EEXIST: + raise + pass + +def run_cmd(cmd): + print(cmd) + if os.system(cmd): + raise Exception("Command Failed: %s" % cmd) + +def gen_ca_cert(): + run_cmd('openssl genrsa -out build/ca.key 4096') + + # TODO Explore making this cert have attributes that look more like + # a real CA cert (ex: restrict its uses) + subj = "/C=US/ST=Maryland/L=Fulton/O=Cisco Talos/OU=ClamAV Test CA %016x/emailAddress=rfc2606@example.net" % (random.randint(1,0xFFFFFFFFFFFFFFFF)) + cmd = 'openssl req -new -x509 -days 3650 -key build/ca.key -out build/ca.crt -subj "%s"' % (subj) + run_cmd(cmd) + +# https://blog.didierstevens.com/2008/12/30/howto-make-your-own-cert-with-openssl/ +def gen_cs_cert(name, ext): + key_name = 'build/%s%s.key' % (name, ext) + csr_name = 'build/%s%s.csr' % (name, ext) + crt_name = 'build/%s%s.crt' % (name, ext) + + run_cmd('openssl genrsa -out %s 4096' % (key_name)) + + # TODO Explore making this cert have attributes that look more like + # a real CS cert (ex: restrict its uses) + subj = "/C=US/ST=Maryland/L=Fulton/O=Cisco Talos/OU=ClamAV Test %016x/emailAddress=rfc2606@example.net" % (random.randint(1,0xFFFFFFFFFFFFFFFF)) + cmd = 'openssl req -new -key %s -out %s -subj "%s"' % (key_name, csr_name, subj) + run_cmd(cmd) + + cmd = 'openssl x509 -req -days 730 -in %s -CA build/ca.crt -CAkey build/ca.key -out %s -set_serial %012d -extfile ./cs.extfile.cfg' % (csr_name, crt_name, random.randint(100000000000,999999999999)) + run_cmd(cmd) + + return (key_name, crt_name) + +# https://blog.didierstevens.com/2018/09/24/quickpost-signing-windows-executables-on-kali/ +def sign_exe(name, ext, cert_info): + key_name = cert_info[0] + crt_name = cert_info[1] + orig_path = "build/%s%s" % (name, ext) + signed_path = "build/%s-signed%s" % (name, ext) + cmd = 'osslsigncode sign -certs %s -key %s -ts http://sha256timestamp.ws.symantec.com/sha256/timestamp -in %s -out %s' % (crt_name, key_name, orig_path, signed_path) + run_cmd(cmd) + os.unlink(orig_path) + os.rename(signed_path, orig_path) + +def build_exe(name, ext, index, cc_flags, do_res, do_strip, sources=['./test.c']): + + if do_res: + # TODO Generate a new icon and new version information per exe. The + # icon can be generated with: + # + # head -c 245760 /dev/urandom | convert -depth 8 -size 320x256 RGB:- test.png + # convert -background transparent test.png -define icon:auto-resize=16,32,48,64,256 test.ico + # + # NOTE ^^^ 245760 is 320x256x3 + res_path = 'build/%s%s.res' % (name, ext) + cmd = '%s ./test.rc -O coff -o %s' % (windres, res_path) + run_cmd(cmd) + sources.append(res_path) + + cmd = '%s -Os %s %s -DINDICATOR1=\\"%s\\" -DINDICATOR2=\\"%s\\" -DINDEX=%d -o build/%s%s' % (gcc, cc_flags, ' '.join([x for x in sources]), indicators[index][0], indicators[index][1], index, name, ext) + run_cmd(cmd) + + if do_strip: + cmd = '%s --strip-all build/%s%s' %(strip, name, ext) + run_cmd(cmd) + +def package_exe(name, ext): + # The name of the export symbols depends on the path, so we have to be in + # the same directory as the exe we are packaging for the symbol name the + # code expects to be generated. + os.chdir('build') + cmd = '%s -r -b binary %s%s -o %s%s.o' %(ld, name, ext, name, ext) + run_cmd(cmd) + os.chdir('..') + +# Generate a new CA cert for code-signing +if do_sign: + gen_ca_cert() + +outer_exe_sources = ['./test.c'] +# Build the inner exes. They MUST be named 'exe#', where # is the index. They +# can't have a file extension or else it breaks linking. +for i in range(1, len(indicators)): + name = 'exe%d' % (i) + inner_exe_ext = ".exe" + build_exe(name, inner_exe_ext, i, cc_flags, False, do_strip); + if do_sign: + cert_info = gen_cs_cert(name, inner_exe_ext) + sign_exe(name, inner_exe_ext, cert_info) + package_exe(name, inner_exe_ext) + outer_exe_sources.append('build/%s%s.o' % (name, inner_exe_ext)) + +# Build the outer exe +name = 'test' +build_exe(name, ext, 0, cc_flags, do_res, do_strip, sources=outer_exe_sources) +if do_sign: + cert_info = gen_cs_cert(name, ext) + sign_exe(name, ext, cert_info) + +# Delete unneeded artifacts +if do_cleanup: + cleanups = ['build/*.o'] + if do_sign: + cleanups += ['build/*.csr', 'build/*.key'] + + if do_res: + cleanups += ['build/test.exe.res'] + + for cleanup in cleanups: + cmd = 'rm %s' % (cleanup) + try: + run_cmd(cmd) + except: + pass diff --git a/unit_tests/input/pe_allmatch/test-exe-src/cs.extfile.cfg b/unit_tests/input/pe_allmatch/test-exe-src/cs.extfile.cfg new file mode 100644 index 0000000000..be14e3795d --- /dev/null +++ b/unit_tests/input/pe_allmatch/test-exe-src/cs.extfile.cfg @@ -0,0 +1,3 @@ +basicConstraints=CA:FALSE +keyUsage=digitalSignature +extendedKeyUsage=codeSigning diff --git a/unit_tests/input/pe_allmatch/test-exe-src/extract.h b/unit_tests/input/pe_allmatch/test-exe-src/extract.h new file mode 100644 index 0000000000..cc6f0c7776 --- /dev/null +++ b/unit_tests/input/pe_allmatch/test-exe-src/extract.h @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#define O_BINARY 0 +#include +#endif + +#define DO1(X) X(1) +#define DO2(X) DO1(X) X(2) +#define DO3(X) DO2(X) X(3) +#define DO4(X) DO3(X) X(4) +#define DO5(X) DO4(X) X(5) +#define DO6(X) DO5(X) X(6) +#define DO7(X) DO6(X) X(7) +#define DO8(X) DO7(X) X(8) +#define DO9(X) DO8(X) X(9) +#define DO10(X) DO9(X) X(10) +#define DO11(X) DO10(X) X(11) +#define DO12(X) DO11(X) X(12) +#define DO13(X) DO12(X) X(13) +#define DO14(X) DO13(X) X(14) +#define DO15(X) DO14(X) X(15) +#define DO16(X) DO15(X) X(16) +#define DO17(X) DO16(X) X(17) +#define DO18(X) DO17(X) X(18) +#define DO19(X) DO18(X) X(19) +#define DO20(X) DO19(X) X(20) +#define DO21(X) DO20(X) X(21) +#define DO22(X) DO21(X) X(22) +#define DO23(X) DO22(X) X(23) +#define DO24(X) DO23(X) X(24) +#define DO25(X) DO24(X) X(25) +#define DO26(X) DO25(X) X(26) +#define DO27(X) DO26(X) X(27) +#define DO28(X) DO27(X) X(28) +#define DO29(X) DO28(X) X(29) +#define DO30(X) DO29(X) X(30) +#define DO31(X) DO30(X) X(31) +#define DO32(X) DO31(X) X(32) + +#define DO(NUMBER, X) \ + DO##NUMBER(X) + +// The following works for GCC producing ELFs, but not with mingw-w64 +// producing Windows PEs: +// extern const char __attribute__((weak)) _binary_exe ## index ## _start[0]; +// Luckily using weakref instead works for both, albeit with the difference in +// whether an underscore is prepended. +// +// These symbols will correspond with embedded EXE object files that we create +// with `ld -r -b binary` and then link with. + +#ifdef __MINGW32__ +#define DEFINE(index) \ + static const char __attribute__((weakref, alias("binary_exe" #index "_start"))) exe##index##_start[0]; \ + static const char __attribute__((weakref, alias("binary_exe" #index "_end"))) exe##index##_end[0]; +#else +#define DEFINE(index) \ + static const char __attribute__((weakref, alias("_binary_exe" #index "_start"))) exe##index##_start[0]; \ + static const char __attribute__((weakref, alias("_binary_exe" #index "_end"))) exe##index##_end[0]; +#endif + +DO(32, DEFINE) + +void exec_in_new_process(const char *filename) +{ + +#ifdef _WIN32 + PROCESS_INFORMATION pi = {0}; + STARTUPINFO si = {0}; + si.cb = sizeof(si); + BOOL result = CreateProcess(NULL, (char *)filename, NULL, NULL, TRUE, + NORMAL_PRIORITY_CLASS, NULL, NULL, + &si, &pi); + if (result) { + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } +#else + pid_t pid = fork(); + if (0 == pid) { + char *const args[2] = {(char *)filename, NULL}; + execv(filename, args); + exit(1); + } else { + waitpid(pid, NULL, 0); + } +#endif +} + +#define EXTRACT_EXECUTE_DELETE(index) \ + do { \ + size_t size = exe##index##_end - exe##index##_start; \ + if (!size) break; \ + printf("Extracting file with size %zd\n", size); \ + /* Linux doesn't care if there is a file extension, but Windows does */ \ + const char *filename = "exe" #index ".exe"; \ + int fd = open(filename, O_WRONLY | O_CREAT | O_BINARY, S_IRWXU | S_IRWXG | S_IRWXO); \ + if (-1 == fd) { \ + perror("open"); \ + break; \ + } \ + int pos = 0; \ + while (pos < size) { \ + int written = write(fd, exe##index##_start + pos, size - pos); \ + if (-1 == written) { \ + perror("write"); \ + break; \ + } \ + pos += written; \ + } \ + close(fd); \ + if (pos == size) { \ + exec_in_new_process(filename); \ + } \ + unlink(filename); \ + printf("Finished extracting and executing\n"); \ + } while (0); + +#define DROP_AND_EXECUTE() \ + DO(32, EXTRACT_EXECUTE_DELETE) diff --git a/unit_tests/input/pe_allmatch/test-exe-src/test.c b/unit_tests/input/pe_allmatch/test-exe-src/test.c new file mode 100644 index 0000000000..c1de1814ab --- /dev/null +++ b/unit_tests/input/pe_allmatch/test-exe-src/test.c @@ -0,0 +1,73 @@ +#include +#include "extract.h" +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +int main(int argc, char **argv) +{ + DROP_AND_EXECUTE() + + printf("%s\n", "CLAMAV_TEST_PRINTF_STRING_" INDICATOR1); + printf("%s\n", "CLAMAV_TEST_PRINTF_STRING_" INDICATOR2); + + // Do some random stuff to change the .imp hashes and .text MDB/MSB hashes + // To change the .imp hash (Windows-specific) we actually need to make + // the exe import new functions, so we need to call interesting APIs. + // + // On Linux, we just need to make sure the assembly in the .text section + // is different +#if INDEX == 1 +#ifdef _WIN32 + + printf("Enumerating Modules via CreateToolhelp32Snapshot\n"); + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()); + if (INVALID_HANDLE_VALUE == h) { + return 1; + } + MODULEENTRY32 m; + if (!Module32First(h, &m)) { + CloseHandle(h); + return 1; + } + do { + printf(" - %s\n", m.szModule); + } while (Module32Next(h, &m)); + CloseHandle(h); + +#else + + printf("Listing program name\n - %s\n", argv[0]); + +#endif + +#elif INDEX == 2 +#ifdef _WIN32 + + printf("Enumerating Keys in HKEY_CURRENT_USER\n"); + char key[256]; + int index = 0; + if (ERROR_SUCCESS != RegEnumKeyA(HKEY_CURRENT_USER, index++, (char *)&key, sizeof(key))) { + return 1; + } + do { + printf(" - %s\n", key); + } while (ERROR_SUCCESS == RegEnumKeyA(HKEY_CURRENT_USER, index++, (char *)&key, sizeof(key))); + +#else + + printf("Listing current time\n - %d\n", (int)time(NULL)); + +#endif + +#else + + printf("Nothing to do!\n"); + +#endif + + return 0; +} diff --git a/unit_tests/input/pe_allmatch/test-exe-src/test.ico b/unit_tests/input/pe_allmatch/test-exe-src/test.ico new file mode 100644 index 0000000000..e906fb6f62 Binary files /dev/null and b/unit_tests/input/pe_allmatch/test-exe-src/test.ico differ diff --git a/unit_tests/input/pe_allmatch/test-exe-src/test.png b/unit_tests/input/pe_allmatch/test-exe-src/test.png new file mode 100644 index 0000000000..a3a2078470 Binary files /dev/null and b/unit_tests/input/pe_allmatch/test-exe-src/test.png differ diff --git a/unit_tests/input/pe_allmatch/test-exe-src/test.rc b/unit_tests/input/pe_allmatch/test-exe-src/test.rc new file mode 100644 index 0000000000..98bfca8144 --- /dev/null +++ b/unit_tests/input/pe_allmatch/test-exe-src/test.rc @@ -0,0 +1,24 @@ +id ICON "test.ico" +1 VERSIONINFO +FILEVERSION 1,0,0,0 +PRODUCTVERSION 1,0,0,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", "Test CompanyName" + VALUE "FileDescription", "Test FileDescription" + VALUE "FileVersion", "Test FileVersion" + VALUE "InternalName", "Test InternalName" + VALUE "LegalCopyright", "Test LegalCopyright" + VALUE "OriginalFilename", "Test OriginalFilename" + VALUE "ProductName", "Test ProductName" + VALUE "ProductVersion", "Test ProductVersion" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/unit_tests/input/pe_allmatch/test-exe-src/test.res b/unit_tests/input/pe_allmatch/test-exe-src/test.res new file mode 100644 index 0000000000..14cc62703c Binary files /dev/null and b/unit_tests/input/pe_allmatch/test-exe-src/test.res differ diff --git a/unit_tests/input/pe_allmatch/test.exe b/unit_tests/input/pe_allmatch/test.exe new file mode 100644 index 0000000000..3f0272eb0d Binary files /dev/null and b/unit_tests/input/pe_allmatch/test.exe differ diff --git a/unit_tests/input/pe_allmatch/trust-sigs/Test.Sig.CRB.TrustCert.crb b/unit_tests/input/pe_allmatch/trust-sigs/Test.Sig.CRB.TrustCert.crb new file mode 100644 index 0000000000..dbb59b5185 --- /dev/null +++ b/unit_tests/input/pe_allmatch/trust-sigs/Test.Sig.CRB.TrustCert.crb @@ -0,0 +1 @@ +Test.Sig.CRB.TrustCert;1;8b166a274bfaa700a912edd57e8e41365beea576;d2a8ea878c4bba243788488f59354835a005baae;E709F7C042C0DFE60CDEF79BFEC8723468DE92B027E4BC31C2D2B8224DE04B6A23C49C8FEBFAD35274651AA5DAA91FD392E0336B31140F90C125E43E4DEBD3276659BBC639425595F4713C4CAC1892D5D136F76263EDE02DF4EBB849A508B492C7BD3FE295617FC5FF1C482543C938F389D521D8E758D59183C7986A5729E16B5BC3081CF3A749447E23106D170E5835BA137821202B100124EDAD00F7508C19F8103B774E9FA19989058EC52776934690E2CAD67B99E93A9AD50C470E0DF4C48F9F78DBFCEF812730A3A458A310A913CCA7E0B10699A4A441C8900A59193FFDC7376162DA6DB805E4BD9AD9463717B6EEDACAD53AEA9E7FDBB2826588FBF8E45F390B4A44A6A01787DC8110581DD1DC00407C3868F3534241BB340AED7CC9CAB56D27F7E6B645F7CC7BA7B0D1BFB27036F09B9FB25B396575C16B0BF3177FE052F7B5C8BB97F72E69DA7971EAFA643C68E36B5F156BAD46F3E3A580A7CE56BA92ACB972143DDA4B20867A45081262DF2E7A1F80A9D3588C60D48010F10461A8AE675CF8EE47E66425A5A6A0D95F1076AFEC6246C3AA635C13AC1E9CEA760316FD89AE2C19FDF696A106BC20B5A2F8E14613A4633726ADC92FD67B6E219CE1A419FE7F397F153C2591547EE08B19C54C8F04B0F6824EABD2572AF9115486479567A77853DDA31CB84609154BA3A043AD0548204875FF365047BC4B7382FD03AF;010001;1;0;0;;Generated with details from sigtool --print-certs \ No newline at end of file diff --git a/unit_tests/input/pe_allmatch/weak-sigs/sig00.idb b/unit_tests/input/pe_allmatch/weak-sigs/sig00.idb new file mode 100644 index 0000000000..b20f2700bd --- /dev/null +++ b/unit_tests/input/pe_allmatch/weak-sigs/sig00.idb @@ -0,0 +1 @@ +Test.Sig.IDB_16x16x32:TEST_ICON_GROUP_1:TEST_ICON_GROUP_2:100620a0b05b0307055060b0350a0b0480307051060b8407008402058308058000008104098109096d020b6b0a046306031800001f040035080000000000 diff --git a/unit_tests/input/pe_allmatch/weak-sigs/sig01.idb b/unit_tests/input/pe_allmatch/weak-sigs/sig01.idb new file mode 100644 index 0000000000..de76424bb1 --- /dev/null +++ b/unit_tests/input/pe_allmatch/weak-sigs/sig01.idb @@ -0,0 +1 @@ +Test.Sig.IDB_32x32x32:TEST_ICON_GROUP_1:TEST_ICON_GROUP_2:20050161704f041604e0d13040161704104160410d13870f0087141787050a82000084070f85111365150d640808561503340000390a003c001700000000 diff --git a/unit_tests/input/pe_allmatch/weak-sigs/sig02.idb b/unit_tests/input/pe_allmatch/weak-sigs/sig02.idb new file mode 100644 index 0000000000..6bcf15608d --- /dev/null +++ b/unit_tests/input/pe_allmatch/weak-sigs/sig02.idb @@ -0,0 +1 @@ +Test.Sig.IDB_48x48x32:TEST_ICON_GROUP_1:TEST_ICON_GROUP_2:180510e100370f00000000003c0e100410f000410000860b02850211850b1181000083090a83051060050c5e10065605012e0000320a0037100000000000 diff --git a/unit_tests/input/pe_allmatch/weak-sigs/sig03.idb b/unit_tests/input/pe_allmatch/weak-sigs/sig03.idb new file mode 100644 index 0000000000..d9c710292d --- /dev/null +++ b/unit_tests/input/pe_allmatch/weak-sigs/sig03.idb @@ -0,0 +1 @@ +Test.Sig.IDB_64x64x32:TEST_ICON_GROUP_1:TEST_ICON_GROUP_2:20056150304f161704f020104015030411617043020187050a870f0087011783000084070e85111364150c6308095b0417380000390a003f160000000000