/* * Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. * Copyright (C) 2007-2013 Sourcefire, Inc. * * Authors: Tomasz Kojm, Trog * * 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. * */ #if HAVE_CONFIG_H #include "clamav-config.h" #endif #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #ifndef _WIN32 #include #include #endif #include #include #ifdef HAVE_PWD_H #include #endif #include #include "target.h" #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_MALLOC_H #include #endif #ifdef CL_THREAD_SAFE #include #endif #ifdef HAVE_LIBXML2 #include #endif #ifndef _WIN32 #include #endif #include "clamav.h" #include "others.h" #include "regex/regex.h" #include "matcher-ac.h" #include "matcher-pcre.h" #include "default.h" #include "scanners.h" #include "bytecode.h" #include "bytecode_api_impl.h" #include "cache.h" #include "readdb.h" #include "stats.h" #include "json_api.h" #include "clamav_rust.h" cl_unrar_error_t (*cli_unrar_open)(const char *filename, void **hArchive, char **comment, uint32_t *comment_size, uint8_t debug_flag); cl_unrar_error_t (*cli_unrar_peek_file_header)(void *hArchive, unrar_metadata_t *file_metadata); cl_unrar_error_t (*cli_unrar_extract_file)(void *hArchive, const char *destPath, char *outputBuffer); cl_unrar_error_t (*cli_unrar_skip_file)(void *hArchive); void (*cli_unrar_close)(void *hArchive); int have_rar = 0; static int is_rar_inited = 0; #define PASTE2(a, b) a #b #define PASTE(a, b) PASTE2(a, b) static void *load_module(const char *name, const char *featurename) { #ifdef _WIN32 static const char *suffixes[] = {LT_MODULE_EXT}; #else static const char *suffixes[] = { LT_MODULE_EXT "." LIBCLAMAV_FULLVER, PASTE(LT_MODULE_EXT ".", LIBCLAMAV_MAJORVER), LT_MODULE_EXT, "." LT_LIBEXT}; #endif const char *searchpath; char modulename[128]; size_t i; #ifdef _WIN32 HMODULE rhandle = NULL; #else void *rhandle; #endif #ifdef _WIN32 /* * First try a standard LoadLibraryA() without specifying a full path. * For more information on the DLL search order, see: * https://docs.microsoft.com/en-us/windows/desktop/Dlls/dynamic-link-library-search-order */ cli_dbgmsg("searching for %s\n", featurename); #else /* * First search using the provided SEARCH_LIBDIR (e.g. "/lib") * Known issue: If an older clamav version is already installed, the clamav * unit tests using this function will load the older library version from * the install path first. */ searchpath = SEARCH_LIBDIR; cli_dbgmsg("searching for %s, user-searchpath: %s\n", featurename, searchpath); #endif for (i = 0; i < sizeof(suffixes) / sizeof(suffixes[0]); i++) { #ifdef _WIN32 snprintf(modulename, sizeof(modulename), "%s%s", name, suffixes[i]); rhandle = LoadLibraryA(modulename); #else // !_WIN32 snprintf(modulename, sizeof(modulename), "%s" PATHSEP "%s%s", searchpath, name, suffixes[i]); rhandle = dlopen(modulename, RTLD_NOW); #endif // !_WIN32 if (rhandle) { break; } cli_dbgmsg("searching for %s: %s not found\n", featurename, modulename); } if (NULL == rhandle) { char *ld_library_path = NULL; /* * library not found. * Try again using LD_LIBRARY_PATH environment variable for the path. */ ld_library_path = getenv("LD_LIBRARY_PATH"); if (NULL != ld_library_path) { #define MAX_LIBRARY_PATHS 10 size_t token_index; size_t tokens_count; const char *tokens[MAX_LIBRARY_PATHS]; char *tokenized_library_path = NULL; tokenized_library_path = strdup(ld_library_path); tokens_count = cli_strtokenize(tokenized_library_path, ':', MAX_LIBRARY_PATHS, tokens); for (token_index = 0; token_index < tokens_count; token_index++) { cli_dbgmsg("searching for %s, LD_LIBRARY_PATH: %s\n", featurename, tokens[token_index]); for (i = 0; i < sizeof(suffixes) / sizeof(suffixes[0]); i++) { snprintf(modulename, sizeof(modulename), "%s" PATHSEP "%s%s", tokens[token_index], name, suffixes[i]); #ifdef _WIN32 rhandle = LoadLibraryA(modulename); #else // !_WIN32 rhandle = dlopen(modulename, RTLD_NOW); #endif // !_WIN32 if (rhandle) { break; } cli_dbgmsg("searching for %s: %s not found\n", featurename, modulename); } if (rhandle) { break; } } free(tokenized_library_path); } } if (NULL == rhandle) { #ifdef _WIN32 char *err = NULL; DWORD lasterr = GetLastError(); if (0 < lasterr) { FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, lasterr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&err, 0, NULL); } #else // !_WIN32 const char *err = dlerror(); #endif // !_WIN32 #ifdef WARN_DLOPEN_FAIL if (NULL == err) { cli_warnmsg("Cannot dlopen %s: Unknown error - %s support unavailable\n", name, featurename); } else { cli_warnmsg("Cannot dlopen %s: %s - %s support unavailable\n", name, err, featurename); } #else if (NULL == err) { cli_dbgmsg("Cannot dlopen %s: Unknown error - %s support unavailable\n", name, featurename); } else { cli_dbgmsg("Cannot dlopen %s: %s - %s support unavailable\n", name, err, featurename); } #endif #ifdef _WIN32 if (NULL != err) { LocalFree(err); } #endif return rhandle; } cli_dbgmsg("%s support loaded from %s\n", featurename, modulename); return (void *)rhandle; } #ifdef _WIN32 static void *get_module_function(HMODULE handle, const char *name) { void *procAddress = NULL; procAddress = GetProcAddress(handle, name); if (NULL == procAddress) { char *err = NULL; DWORD lasterr = GetLastError(); if (0 < lasterr) { FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, lasterr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPCSTR)&err, 0, NULL); } if (NULL == err) { cli_warnmsg("Failed to get function \"%s\": Unknown error.\n", name); } else { cli_warnmsg("Failed to get function \"%s\": %s\n", name, err); LocalFree(err); } } return procAddress; } #else // !_WIN32 static void *get_module_function(void *handle, const char *name) { void *procAddress = NULL; procAddress = dlsym(handle, name); if (NULL == procAddress) { const char *err = dlerror(); if (NULL == err) { cli_warnmsg("Failed to get function \"%s\": Unknown error.\n", name); } else { cli_warnmsg("Failed to get function \"%s\": %s\n", name, err); } } return procAddress; } #endif // !_WIN32 static void rarload(void) { #ifndef UNRAR_LINKED #ifdef _WIN32 HMODULE rhandle = NULL; #else void *rhandle = NULL; #endif #endif if (is_rar_inited) return; is_rar_inited = 1; if (have_rar) return; #ifdef UNRAR_LINKED cli_unrar_open = unrar_open; cli_unrar_peek_file_header = unrar_peek_file_header; cli_unrar_extract_file = unrar_extract_file; cli_unrar_skip_file = unrar_skip_file; cli_unrar_close = unrar_close; #else rhandle = load_module("libclamunrar_iface", "unrar"); if (NULL == rhandle) return; if ((NULL == (cli_unrar_open = (cl_unrar_error_t(*)(const char *, void **, char **, uint32_t *, uint8_t))get_module_function(rhandle, "libclamunrar_iface_LTX_unrar_open"))) || (NULL == (cli_unrar_peek_file_header = (cl_unrar_error_t(*)(void *, unrar_metadata_t *))get_module_function(rhandle, "libclamunrar_iface_LTX_unrar_peek_file_header"))) || (NULL == (cli_unrar_extract_file = (cl_unrar_error_t(*)(void *, const char *, char *))get_module_function(rhandle, "libclamunrar_iface_LTX_unrar_extract_file"))) || (NULL == (cli_unrar_skip_file = (cl_unrar_error_t(*)(void *))get_module_function(rhandle, "libclamunrar_iface_LTX_unrar_skip_file"))) || (NULL == (cli_unrar_close = (void (*)(void *))get_module_function(rhandle, "libclamunrar_iface_LTX_unrar_close")))) { cli_warnmsg("Failed to load function from UnRAR module\n"); cli_warnmsg("Version mismatch?\n"); cli_warnmsg("UnRAR support unavailable\n"); return; } #endif have_rar = 1; } void cl_debug(void) { cli_debug_flag = 1; } void cl_always_gen_section_hash(void) { cli_always_gen_section_hash = 1; } unsigned int cl_retflevel(void) { return CL_FLEVEL; } const char *cl_strerror(cl_error_t clerror) { switch (clerror) { /* libclamav specific codes */ case CL_CLEAN: return "No viruses detected"; case CL_VIRUS: return "Virus(es) detected"; case CL_ENULLARG: return "Null argument passed to function"; case CL_EARG: return "Invalid argument passed to function"; case CL_EMALFDB: return "Malformed database"; case CL_ECVD: return "Broken or not a CVD file"; case CL_EVERIFY: return "Can't verify database integrity"; case CL_EUNPACK: return "Can't unpack some data"; case CL_EPARSE: /* like CL_EFORMAT but reported outside magicscan() */ return "Can't parse data"; /* I/O and memory errors */ case CL_EOPEN: return "Can't open file or directory"; case CL_ECREAT: return "Can't create new file"; case CL_EUNLINK: return "Can't unlink file"; case CL_ESTAT: return "Can't get file status"; case CL_EREAD: return "Can't read file"; case CL_ESEEK: return "Can't set file offset"; case CL_EWRITE: return "Can't write to file"; case CL_EDUP: return "Can't duplicate file descriptor"; case CL_EACCES: return "Can't access file"; case CL_ETMPFILE: return "Can't create temporary file"; case CL_ETMPDIR: return "Can't create temporary directory"; case CL_EMAP: return "Can't map file into memory"; case CL_EMEM: return "Can't allocate memory"; case CL_ETIMEOUT: return "Exceeded time limit"; /* internal (needed for debug messages) */ case CL_EMAXREC: return "Exceeded max recursion depth"; case CL_EMAXSIZE: return "Exceeded max scan size"; case CL_EMAXFILES: return "Exceeded max scan files"; case CL_EFORMAT: return "Bad format or broken data"; case CL_EBYTECODE: return "Error during bytecode execution"; case CL_EBYTECODE_TESTFAIL: return "Failure in bytecode testmode"; case CL_ELOCK: return "Mutex lock failed"; case CL_EBUSY: return "Scanner still active"; case CL_ESTATE: return "Bad state (engine not initialized, or already initialized)"; case CL_ERROR: return "Unspecified error"; case CL_VERIFIED: return "The scanned object was verified and deemed trusted"; default: return "Unknown error code"; } } cl_error_t cl_init(unsigned int initoptions) { cl_error_t rc; struct timeval tv; unsigned int pid = (unsigned int)getpid(); UNUSEDPARAM(initoptions); /* Rust logging initialization */ if (!clrs_log_init()) { cli_dbgmsg("Unexpected problem occurred while setting up rust logging... continuing without rust logging. \ Please submit an issue to https://github.com/Cisco-Talos/clamav"); } cl_initialize_crypto(); rarload(); gettimeofday(&tv, (struct timezone *)0); srand(pid + tv.tv_usec * (pid + 1) + clock()); rc = bytecode_init(); if (rc) return rc; #ifdef HAVE_LIBXML2 xmlInitParser(); #endif return CL_SUCCESS; } struct cl_engine *cl_engine_new(void) { struct cl_engine *new; cli_intel_t *intel; new = (struct cl_engine *)cli_calloc(1, sizeof(struct cl_engine)); if (!new) { cli_errmsg("cl_engine_new: Can't allocate memory for cl_engine\n"); return NULL; } /* Setup default limits */ new->maxscantime = CLI_DEFAULT_TIMELIMIT; new->maxscansize = CLI_DEFAULT_MAXSCANSIZE; new->maxfilesize = CLI_DEFAULT_MAXFILESIZE; new->max_recursion_level = CLI_DEFAULT_MAXRECLEVEL; new->maxfiles = CLI_DEFAULT_MAXFILES; new->min_cc_count = CLI_DEFAULT_MIN_CC_COUNT; new->min_ssn_count = CLI_DEFAULT_MIN_SSN_COUNT; /* Engine Max sizes */ new->maxembeddedpe = CLI_DEFAULT_MAXEMBEDDEDPE; new->maxhtmlnormalize = CLI_DEFAULT_MAXHTMLNORMALIZE; new->maxhtmlnotags = CLI_DEFAULT_MAXHTMLNOTAGS; new->maxscriptnormalize = CLI_DEFAULT_MAXSCRIPTNORMALIZE; new->maxziptypercg = CLI_DEFAULT_MAXZIPTYPERCG; new->bytecode_security = CL_BYTECODE_TRUST_SIGNED; /* 5 seconds timeout */ new->bytecode_timeout = 60000; new->bytecode_mode = CL_BYTECODE_MODE_AUTO; new->refcount = 1; new->ac_only = 0; new->ac_mindepth = CLI_DEFAULT_AC_MINDEPTH; new->ac_maxdepth = CLI_DEFAULT_AC_MAXDEPTH; #ifdef USE_MPOOL if (!(new->mempool = mpool_create())) { cli_errmsg("cl_engine_new: Can't allocate memory for memory pool\n"); free(new); return NULL; } #endif new->root = MPOOL_CALLOC(new->mempool, CLI_MTARGETS, sizeof(struct cli_matcher *)); if (!new->root) { cli_errmsg("cl_engine_new: Can't allocate memory for roots\n"); #ifdef USE_MPOOL mpool_destroy(new->mempool); #endif free(new); return NULL; } new->dconf = cli_mpool_dconf_init(new->mempool); if (!new->dconf) { cli_errmsg("cl_engine_new: Can't initialize dynamic configuration\n"); MPOOL_FREE(new->mempool, new->root); #ifdef USE_MPOOL mpool_destroy(new->mempool); #endif free(new); return NULL; } new->pwdbs = MPOOL_CALLOC(new->mempool, CLI_PWDB_COUNT, sizeof(struct cli_pwdb *)); if (!new->pwdbs) { cli_errmsg("cl_engine_new: Can't initialize password databases\n"); MPOOL_FREE(new->mempool, new->dconf); MPOOL_FREE(new->mempool, new->root); #ifdef USE_MPOOL mpool_destroy(new->mempool); #endif free(new); return NULL; } crtmgr_init(&(new->cmgr)); if (crtmgr_add_roots(new, &(new->cmgr), 0)) { cli_errmsg("cl_engine_new: Can't initialize root certificates\n"); MPOOL_FREE(new->mempool, new->pwdbs); MPOOL_FREE(new->mempool, new->dconf); MPOOL_FREE(new->mempool, new->root); #ifdef USE_MPOOL mpool_destroy(new->mempool); #endif free(new); return NULL; } /* Set up default stats/intel gathering callbacks */ intel = cli_calloc(1, sizeof(cli_intel_t)); if ((intel)) { #ifdef CL_THREAD_SAFE if (pthread_mutex_init(&(intel->mutex), NULL)) { cli_errmsg("cli_engine_new: Cannot initialize stats gathering mutex\n"); MPOOL_FREE(new->mempool, new->pwdbs); MPOOL_FREE(new->mempool, new->dconf); MPOOL_FREE(new->mempool, new->root); #ifdef USE_MPOOL mpool_destroy(new->mempool); #endif free(new); free(intel); return NULL; } #endif intel->engine = new; intel->maxsamples = STATS_MAX_SAMPLES; intel->maxmem = STATS_MAX_MEM; intel->timeout = 10; new->stats_data = intel; } else { new->stats_data = NULL; } new->cb_stats_add_sample = NULL; new->cb_stats_submit = NULL; new->cb_stats_flush = clamav_stats_flush; new->cb_stats_remove_sample = clamav_stats_remove_sample; new->cb_stats_decrement_count = clamav_stats_decrement_count; new->cb_stats_get_num = clamav_stats_get_num; new->cb_stats_get_size = clamav_stats_get_size; new->cb_stats_get_hostid = clamav_stats_get_hostid; /* Setup raw disk image max settings */ new->maxpartitions = CLI_DEFAULT_MAXPARTITIONS; /* Engine max settings */ new->maxiconspe = CLI_DEFAULT_MAXICONSPE; new->maxrechwp3 = CLI_DEFAULT_MAXRECHWP3; /* PCRE matching limitations */ #if HAVE_PCRE cli_pcre_init(); #endif new->pcre_match_limit = CLI_DEFAULT_PCRE_MATCH_LIMIT; new->pcre_recmatch_limit = CLI_DEFAULT_PCRE_RECMATCH_LIMIT; new->pcre_max_filesize = CLI_DEFAULT_PCRE_MAX_FILESIZE; #ifdef HAVE_YARA /* YARA */ if (cli_yara_init(new) != CL_SUCCESS) { cli_errmsg("cli_engine_new: failed to initialize YARA\n"); MPOOL_FREE(new->mempool, new->pwdbs); MPOOL_FREE(new->mempool, new->dconf); MPOOL_FREE(new->mempool, new->root); #ifdef USE_MPOOL mpool_destroy(new->mempool); #endif free(new); free(intel); return NULL; } #endif cli_dbgmsg("Initialized %s engine\n", cl_retver()); return new; } cl_error_t cl_engine_set_num(struct cl_engine *engine, enum cl_engine_field field, long long num) { if (!engine) return CL_ENULLARG; /* TODO: consider adding checks and warn/errs when num overflows the * destination type */ switch (field) { case CL_ENGINE_MAX_SCANSIZE: engine->maxscansize = num; break; case CL_ENGINE_MAX_FILESIZE: engine->maxfilesize = num; break; case CL_ENGINE_MAX_RECURSION: if (!num) { cli_warnmsg("MaxRecursion: the value of 0 is not allowed, using default: %u\n", CLI_DEFAULT_MAXRECLEVEL); engine->max_recursion_level = CLI_DEFAULT_MAXRECLEVEL; } else engine->max_recursion_level = num; break; case CL_ENGINE_MAX_FILES: engine->maxfiles = num; break; case CL_ENGINE_MAX_EMBEDDEDPE: if (num < 0) { cli_warnmsg("MaxEmbeddedPE: negative values are not allowed, using default: %u\n", CLI_DEFAULT_MAXEMBEDDEDPE); engine->maxembeddedpe = CLI_DEFAULT_MAXEMBEDDEDPE; } else engine->maxembeddedpe = num; break; case CL_ENGINE_MAX_HTMLNORMALIZE: if (num < 0) { cli_warnmsg("MaxHTMLNormalize: negative values are not allowed, using default: %u\n", CLI_DEFAULT_MAXHTMLNORMALIZE); engine->maxhtmlnormalize = CLI_DEFAULT_MAXHTMLNORMALIZE; } else engine->maxhtmlnormalize = num; break; case CL_ENGINE_MAX_HTMLNOTAGS: if (num < 0) { cli_warnmsg("MaxHTMLNoTags: negative values are not allowed, using default: %u\n", CLI_DEFAULT_MAXHTMLNOTAGS); engine->maxhtmlnotags = CLI_DEFAULT_MAXHTMLNOTAGS; } else engine->maxhtmlnotags = num; break; case CL_ENGINE_MAX_SCRIPTNORMALIZE: if (num < 0) { cli_warnmsg("MaxScriptNormalize: negative values are not allowed, using default: %u\n", CLI_DEFAULT_MAXSCRIPTNORMALIZE); engine->maxscriptnormalize = CLI_DEFAULT_MAXSCRIPTNORMALIZE; } else engine->maxscriptnormalize = num; break; case CL_ENGINE_MAX_ZIPTYPERCG: if (num < 0) { cli_warnmsg("MaxZipTypeRcg: negative values are not allowed, using default: %u\n", CLI_DEFAULT_MAXZIPTYPERCG); engine->maxziptypercg = CLI_DEFAULT_MAXZIPTYPERCG; } else engine->maxziptypercg = num; break; case CL_ENGINE_MIN_CC_COUNT: engine->min_cc_count = num; break; case CL_ENGINE_MIN_SSN_COUNT: engine->min_ssn_count = num; break; case CL_ENGINE_DB_OPTIONS: case CL_ENGINE_DB_VERSION: case CL_ENGINE_DB_TIME: cli_warnmsg("cl_engine_set_num: The field is read only\n"); return CL_EARG; case CL_ENGINE_AC_ONLY: engine->ac_only = num; break; case CL_ENGINE_AC_MINDEPTH: engine->ac_mindepth = num; break; case CL_ENGINE_AC_MAXDEPTH: engine->ac_maxdepth = num; break; case CL_ENGINE_KEEPTMP: engine->keeptmp = num; break; case CL_ENGINE_FORCETODISK: if (num) engine->engine_options |= ENGINE_OPTIONS_FORCE_TO_DISK; else engine->engine_options &= ~(ENGINE_OPTIONS_FORCE_TO_DISK); break; case CL_ENGINE_BYTECODE_SECURITY: if (engine->dboptions & CL_DB_COMPILED) { cli_errmsg("cl_engine_set_num: CL_ENGINE_BYTECODE_SECURITY cannot be set after engine was compiled\n"); return CL_EARG; } engine->bytecode_security = num; break; case CL_ENGINE_BYTECODE_TIMEOUT: engine->bytecode_timeout = num; break; case CL_ENGINE_BYTECODE_MODE: if (engine->dboptions & CL_DB_COMPILED) { cli_errmsg("cl_engine_set_num: CL_ENGINE_BYTECODE_MODE cannot be set after engine was compiled\n"); return CL_EARG; } if (num == CL_BYTECODE_MODE_OFF) { cli_errmsg("cl_engine_set_num: CL_BYTECODE_MODE_OFF is not settable, use dboptions to turn off!\n"); return CL_EARG; } engine->bytecode_mode = num; if (num == CL_BYTECODE_MODE_TEST) cli_infomsg(NULL, "bytecode engine in test mode\n"); break; case CL_ENGINE_DISABLE_CACHE: if (num) { engine->engine_options |= ENGINE_OPTIONS_DISABLE_CACHE; } else { engine->engine_options &= ~(ENGINE_OPTIONS_DISABLE_CACHE); if (!(engine->cache)) clean_cache_init(engine); } break; case CL_ENGINE_DISABLE_PE_STATS: if (num) { engine->engine_options |= ENGINE_OPTIONS_DISABLE_PE_STATS; } else { engine->engine_options &= ~(ENGINE_OPTIONS_DISABLE_PE_STATS); } break; case CL_ENGINE_STATS_TIMEOUT: if ((engine->stats_data)) { cli_intel_t *intel = (cli_intel_t *)(engine->stats_data); intel->timeout = (uint32_t)num; } break; case CL_ENGINE_MAX_PARTITIONS: engine->maxpartitions = (uint32_t)num; break; case CL_ENGINE_MAX_ICONSPE: engine->maxiconspe = (uint32_t)num; break; case CL_ENGINE_MAX_RECHWP3: engine->maxrechwp3 = (uint32_t)num; break; case CL_ENGINE_MAX_SCANTIME: engine->maxscantime = (uint32_t)num; break; case CL_ENGINE_PCRE_MATCH_LIMIT: engine->pcre_match_limit = (uint64_t)num; break; case CL_ENGINE_PCRE_RECMATCH_LIMIT: engine->pcre_recmatch_limit = (uint64_t)num; break; case CL_ENGINE_PCRE_MAX_FILESIZE: engine->pcre_max_filesize = (uint64_t)num; break; case CL_ENGINE_DISABLE_PE_CERTS: if (num) { engine->engine_options |= ENGINE_OPTIONS_DISABLE_PE_CERTS; } else { engine->engine_options &= ~(ENGINE_OPTIONS_DISABLE_PE_CERTS); } break; case CL_ENGINE_PE_DUMPCERTS: if (num) { engine->engine_options |= ENGINE_OPTIONS_PE_DUMPCERTS; } else { engine->engine_options &= ~(ENGINE_OPTIONS_PE_DUMPCERTS); } break; default: cli_errmsg("cl_engine_set_num: Incorrect field number\n"); return CL_EARG; } return CL_SUCCESS; } long long cl_engine_get_num(const struct cl_engine *engine, enum cl_engine_field field, int *err) { if (!engine) { cli_errmsg("cl_engine_get_num: engine == NULL\n"); if (err) *err = CL_ENULLARG; return -1; } if (err) *err = CL_SUCCESS; switch (field) { case CL_ENGINE_DB_OPTIONS: return engine->dboptions; case CL_ENGINE_MAX_SCANSIZE: return engine->maxscansize; case CL_ENGINE_MAX_FILESIZE: return engine->maxfilesize; case CL_ENGINE_MAX_RECURSION: return engine->max_recursion_level; case CL_ENGINE_MAX_FILES: return engine->maxfiles; case CL_ENGINE_MAX_EMBEDDEDPE: return engine->maxembeddedpe; case CL_ENGINE_MAX_HTMLNORMALIZE: return engine->maxhtmlnormalize; case CL_ENGINE_MAX_HTMLNOTAGS: return engine->maxhtmlnotags; case CL_ENGINE_MAX_SCRIPTNORMALIZE: return engine->maxscriptnormalize; case CL_ENGINE_MAX_ZIPTYPERCG: return engine->maxziptypercg; case CL_ENGINE_MIN_CC_COUNT: return engine->min_cc_count; case CL_ENGINE_MIN_SSN_COUNT: return engine->min_ssn_count; case CL_ENGINE_DB_VERSION: return engine->dbversion[0]; case CL_ENGINE_DB_TIME: return engine->dbversion[1]; case CL_ENGINE_AC_ONLY: return engine->ac_only; case CL_ENGINE_AC_MINDEPTH: return engine->ac_mindepth; case CL_ENGINE_AC_MAXDEPTH: return engine->ac_maxdepth; case CL_ENGINE_KEEPTMP: return engine->keeptmp; case CL_ENGINE_FORCETODISK: return engine->engine_options & ENGINE_OPTIONS_FORCE_TO_DISK; case CL_ENGINE_BYTECODE_SECURITY: return engine->bytecode_security; case CL_ENGINE_BYTECODE_TIMEOUT: return engine->bytecode_timeout; case CL_ENGINE_BYTECODE_MODE: return engine->bytecode_mode; case CL_ENGINE_DISABLE_CACHE: return engine->engine_options & ENGINE_OPTIONS_DISABLE_CACHE; case CL_ENGINE_STATS_TIMEOUT: return ((cli_intel_t *)(engine->stats_data))->timeout; case CL_ENGINE_MAX_PARTITIONS: return engine->maxpartitions; case CL_ENGINE_MAX_ICONSPE: return engine->maxiconspe; case CL_ENGINE_MAX_RECHWP3: return engine->maxrechwp3; case CL_ENGINE_MAX_SCANTIME: return engine->maxscantime; case CL_ENGINE_PCRE_MATCH_LIMIT: return engine->pcre_match_limit; case CL_ENGINE_PCRE_RECMATCH_LIMIT: return engine->pcre_recmatch_limit; case CL_ENGINE_PCRE_MAX_FILESIZE: return engine->pcre_max_filesize; default: cli_errmsg("cl_engine_get: Incorrect field number\n"); if (err) *err = CL_EARG; return -1; } } cl_error_t cl_engine_set_str(struct cl_engine *engine, enum cl_engine_field field, const char *str) { if (!engine) return CL_ENULLARG; switch (field) { case CL_ENGINE_PUA_CATEGORIES: if (NULL != engine->pua_cats) { MPOOL_FREE(engine->mempool, engine->pua_cats); engine->pua_cats = NULL; } engine->pua_cats = CLI_MPOOL_STRDUP(engine->mempool, str); if (NULL == engine->pua_cats) return CL_EMEM; break; case CL_ENGINE_TMPDIR: if (NULL != engine->tmpdir) { MPOOL_FREE(engine->mempool, engine->tmpdir); engine->tmpdir = NULL; } engine->tmpdir = CLI_MPOOL_STRDUP(engine->mempool, str); if (NULL == engine->tmpdir) return CL_EMEM; break; default: cli_errmsg("cl_engine_set_num: Incorrect field number\n"); return CL_EARG; } return CL_SUCCESS; } const char *cl_engine_get_str(const struct cl_engine *engine, enum cl_engine_field field, int *err) { if (!engine) { cli_errmsg("cl_engine_get_str: engine == NULL\n"); if (err) *err = CL_ENULLARG; return NULL; } if (err) *err = CL_SUCCESS; switch (field) { case CL_ENGINE_PUA_CATEGORIES: return engine->pua_cats; case CL_ENGINE_TMPDIR: return engine->tmpdir; default: cli_errmsg("cl_engine_get: Incorrect field number\n"); if (err) *err = CL_EARG; return NULL; } } struct cl_settings *cl_engine_settings_copy(const struct cl_engine *engine) { struct cl_settings *settings; settings = (struct cl_settings *)malloc(sizeof(struct cl_settings)); if (!settings) { cli_errmsg("cl_engine_settings_copy: Unable to allocate memory for settings %llu\n", (long long unsigned)sizeof(struct cl_settings)); return NULL; } settings->ac_only = engine->ac_only; settings->ac_mindepth = engine->ac_mindepth; settings->ac_maxdepth = engine->ac_maxdepth; settings->tmpdir = engine->tmpdir ? strdup(engine->tmpdir) : NULL; settings->keeptmp = engine->keeptmp; settings->maxscantime = engine->maxscantime; settings->maxscansize = engine->maxscansize; settings->maxfilesize = engine->maxfilesize; settings->max_recursion_level = engine->max_recursion_level; settings->maxfiles = engine->maxfiles; settings->maxembeddedpe = engine->maxembeddedpe; settings->maxhtmlnormalize = engine->maxhtmlnormalize; settings->maxhtmlnotags = engine->maxhtmlnotags; settings->maxscriptnormalize = engine->maxscriptnormalize; settings->maxziptypercg = engine->maxziptypercg; settings->min_cc_count = engine->min_cc_count; settings->min_ssn_count = engine->min_ssn_count; settings->bytecode_security = engine->bytecode_security; settings->bytecode_timeout = engine->bytecode_timeout; settings->bytecode_mode = engine->bytecode_mode; settings->pua_cats = engine->pua_cats ? strdup(engine->pua_cats) : NULL; settings->cb_pre_cache = engine->cb_pre_cache; settings->cb_pre_scan = engine->cb_pre_scan; settings->cb_post_scan = engine->cb_post_scan; settings->cb_virus_found = engine->cb_virus_found; settings->cb_sigload = engine->cb_sigload; settings->cb_sigload_ctx = engine->cb_sigload_ctx; settings->cb_sigload_progress = engine->cb_sigload_progress; settings->cb_sigload_progress_ctx = engine->cb_sigload_progress_ctx; settings->cb_engine_compile_progress = engine->cb_engine_compile_progress; settings->cb_engine_compile_progress_ctx = engine->cb_engine_compile_progress_ctx; settings->cb_engine_free_progress = engine->cb_engine_free_progress; settings->cb_engine_free_progress_ctx = engine->cb_engine_free_progress_ctx; settings->cb_hash = engine->cb_hash; settings->cb_meta = engine->cb_meta; settings->cb_file_props = engine->cb_file_props; settings->engine_options = engine->engine_options; settings->cb_stats_add_sample = engine->cb_stats_add_sample; settings->cb_stats_remove_sample = engine->cb_stats_remove_sample; settings->cb_stats_decrement_count = engine->cb_stats_decrement_count; settings->cb_stats_submit = engine->cb_stats_submit; settings->cb_stats_flush = engine->cb_stats_flush; settings->cb_stats_get_num = engine->cb_stats_get_num; settings->cb_stats_get_size = engine->cb_stats_get_size; settings->cb_stats_get_hostid = engine->cb_stats_get_hostid; settings->maxpartitions = engine->maxpartitions; settings->maxiconspe = engine->maxiconspe; settings->maxrechwp3 = engine->maxrechwp3; settings->pcre_match_limit = engine->pcre_match_limit; settings->pcre_recmatch_limit = engine->pcre_recmatch_limit; settings->pcre_max_filesize = engine->pcre_max_filesize; return settings; } cl_error_t cl_engine_settings_apply(struct cl_engine *engine, const struct cl_settings *settings) { engine->ac_only = settings->ac_only; engine->ac_mindepth = settings->ac_mindepth; engine->ac_maxdepth = settings->ac_maxdepth; engine->keeptmp = settings->keeptmp; engine->maxscantime = settings->maxscantime; engine->maxscansize = settings->maxscansize; engine->maxfilesize = settings->maxfilesize; engine->max_recursion_level = settings->max_recursion_level; engine->maxfiles = settings->maxfiles; engine->maxembeddedpe = settings->maxembeddedpe; engine->maxhtmlnormalize = settings->maxhtmlnormalize; engine->maxhtmlnotags = settings->maxhtmlnotags; engine->maxscriptnormalize = settings->maxscriptnormalize; engine->maxziptypercg = settings->maxziptypercg; engine->min_cc_count = settings->min_cc_count; engine->min_ssn_count = settings->min_ssn_count; engine->bytecode_security = settings->bytecode_security; engine->bytecode_timeout = settings->bytecode_timeout; engine->bytecode_mode = settings->bytecode_mode; engine->engine_options = settings->engine_options; if (engine->tmpdir) MPOOL_FREE(engine->mempool, engine->tmpdir); if (settings->tmpdir) { engine->tmpdir = CLI_MPOOL_STRDUP(engine->mempool, settings->tmpdir); if (!engine->tmpdir) return CL_EMEM; } else { engine->tmpdir = NULL; } if (engine->pua_cats) MPOOL_FREE(engine->mempool, engine->pua_cats); if (settings->pua_cats) { engine->pua_cats = CLI_MPOOL_STRDUP(engine->mempool, settings->pua_cats); if (!engine->pua_cats) return CL_EMEM; } else { engine->pua_cats = NULL; } engine->cb_pre_cache = settings->cb_pre_cache; engine->cb_pre_scan = settings->cb_pre_scan; engine->cb_post_scan = settings->cb_post_scan; engine->cb_virus_found = settings->cb_virus_found; engine->cb_sigload = settings->cb_sigload; engine->cb_sigload_ctx = settings->cb_sigload_ctx; engine->cb_sigload_progress = settings->cb_sigload_progress; engine->cb_sigload_progress_ctx = settings->cb_sigload_progress_ctx; engine->cb_engine_compile_progress = settings->cb_engine_compile_progress; engine->cb_engine_compile_progress_ctx = settings->cb_engine_compile_progress_ctx; engine->cb_engine_free_progress = settings->cb_engine_free_progress; engine->cb_engine_free_progress_ctx = settings->cb_engine_free_progress_ctx; engine->cb_hash = settings->cb_hash; engine->cb_meta = settings->cb_meta; engine->cb_file_props = settings->cb_file_props; engine->cb_stats_add_sample = settings->cb_stats_add_sample; engine->cb_stats_remove_sample = settings->cb_stats_remove_sample; engine->cb_stats_decrement_count = settings->cb_stats_decrement_count; engine->cb_stats_submit = settings->cb_stats_submit; engine->cb_stats_flush = settings->cb_stats_flush; engine->cb_stats_get_num = settings->cb_stats_get_num; engine->cb_stats_get_size = settings->cb_stats_get_size; engine->cb_stats_get_hostid = settings->cb_stats_get_hostid; engine->maxpartitions = settings->maxpartitions; engine->maxiconspe = settings->maxiconspe; engine->maxrechwp3 = settings->maxrechwp3; engine->pcre_match_limit = settings->pcre_match_limit; engine->pcre_recmatch_limit = settings->pcre_recmatch_limit; engine->pcre_max_filesize = settings->pcre_max_filesize; return CL_SUCCESS; } cl_error_t cl_engine_settings_free(struct cl_settings *settings) { if (!settings) return CL_ENULLARG; free(settings->tmpdir); free(settings->pua_cats); free(settings); return CL_SUCCESS; } 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_potentially_unwanted(ctx, vname); cli_dbgmsg("%s: scanning may be incomplete and additional analysis needed for this file.\n", vname); } #if HAVE_JSON /* Also record the event in the scan metadata, under "ParseErrors" */ if (SCAN_COLLECT_METADATA && ctx->wrkproperty) { cli_json_parse_error(ctx->wrkproperty, vname); } #endif } } cl_error_t cli_checklimits(const char *who, cli_ctx *ctx, unsigned long need1, unsigned long need2, unsigned long need3) { cl_error_t ret = CL_SUCCESS; unsigned long needed; if (!ctx) { /* if called without limits, go on, unpack, scan */ goto done; } needed = (need1 > need2) ? need1 : need2; needed = (needed > need3) ? needed : need3; /* 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 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 limit enabled */ if (needed && (ctx->engine->maxfilesize != 0) && (ctx->engine->maxfilesize < needed)) { /* The size needed is greater than that limit ... Skip this file. */ cli_dbgmsg("%s: filesize exceeded (allowed: %lu, needed: %lu)\n", who, (unsigned long int)ctx->engine->maxfilesize, needed); ret = CL_EMAXSIZE; cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFileSize"); goto done; } /* Enforce limit on number of embedded files, if limit enabled */ if ((ctx->engine->maxfiles != 0) && (ctx->scannedfiles >= ctx->engine->maxfiles)) { /* This file would exceed the max # of files ... Skip this file. */ cli_dbgmsg("%s: files limit reached (max: %u)\n", who, ctx->engine->maxfiles); ret = CL_EMAXFILES; cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); // 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; } cl_error_t cli_updatelimits(cli_ctx *ctx, size_t needed) { cl_error_t ret = cli_checklimits("cli_updatelimits", ctx, needed, 0, 0); if (ret != CL_SUCCESS) { return ret; } ctx->scannedfiles++; ctx->scansize += needed; if (ctx->scansize > ctx->engine->maxscansize) ctx->scansize = ctx->engine->maxscansize; return CL_SUCCESS; } /** * @brief Check if we've exceeded the time limit. * If ctx is NULL, there can be no timelimit so just return success. * * @param ctx The scanning context. * @return cl_error_t CL_SUCCESS if has not exceeded, CL_ETIMEOUT if has exceeded. */ cl_error_t cli_checktimelimit(cli_ctx *ctx) { cl_error_t ret = CL_SUCCESS; if (NULL == ctx) { goto done; } if (ctx->time_limit.tv_sec != 0) { struct timeval now; if (gettimeofday(&now, NULL) == 0) { 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; } } } if (CL_ETIMEOUT == ret) { cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxScanTime"); // abort_scan flag is set so that in cli_magic_scan() we *will* stop scanning, even if we lose the status code. ctx->abort_scan = true; } done: return ret; } /* * Type: 1 = MD5, 2 = SHA1, 3 = SHA256 */ char *cli_hashstream(FILE *fs, unsigned char *digcpy, int type) { unsigned char digest[32]; char buff[FILEBUFF]; char *hashstr, *pt; const char *alg = NULL; int i, bytes, size; void *ctx; switch (type) { case 1: alg = "md5"; size = 16; break; case 2: alg = "sha1"; size = 20; break; default: alg = "sha256"; size = 32; break; } ctx = cl_hash_init(alg); if (!(ctx)) return NULL; while ((bytes = fread(buff, 1, FILEBUFF, fs))) cl_update_hash(ctx, buff, bytes); cl_finish_hash(ctx, digest); if (!(hashstr = (char *)cli_calloc(size * 2 + 1, sizeof(char)))) return NULL; pt = hashstr; for (i = 0; i < size; i++) { sprintf(pt, "%02x", digest[i]); pt += 2; } if (digcpy) memcpy(digcpy, digest, size); return hashstr; } char *cli_hashfile(const char *filename, int type) { FILE *fs; char *hashstr; if ((fs = fopen(filename, "rb")) == NULL) { cli_errmsg("cli_hashfile(): Can't open file %s\n", filename); return NULL; } hashstr = cli_hashstream(fs, NULL, type); fclose(fs); return hashstr; } /* Function: unlink unlink() with error checking */ cl_error_t cli_unlink(const char *pathname) { if (unlink(pathname) == -1) { #ifdef _WIN32 /* Windows may fail to unlink a file if it is marked read-only, * 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 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 for %s - %s\n", pathname, cli_strerror(errno, err, sizeof(err))); return CL_EUNLINK; } return CL_SUCCESS; #else char err[128]; cli_warnmsg("cli_unlink: unlink failure for %s - %s\n", pathname, cli_strerror(errno, err, sizeof(err))); return CL_EUNLINK; #endif } return CL_SUCCESS; } 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), virname, ctx->cb_ctx); } } /** * @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) { cl_error_t status = CL_ERROR; FFIError *add_indicator_error = NULL; bool add_successful; 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))) { // FP signature found for one of the layers. Ignore indicator. status = CL_SUCCESS; goto done; } 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; } 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) { json_object *arrobj, *virobj; if (!json_object_object_get_ex(ctx->wrkproperty, "Viruses", &arrobj)) { arrobj = json_object_new_array(); if (NULL == arrobj) { cli_errmsg("cli_append_virus: no memory for json virus array\n"); 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"); status = CL_EMEM; goto done; } json_object_array_add(arrobj, virobj); } #endif 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->evidence) { return NULL; } return evidence_get_last_alert(ctx->evidence); } const char *cli_get_last_virus_str(const cli_ctx *ctx) { const char *ret; if (NULL != (ret = cli_get_last_virus(ctx))) { return ret; } return ""; } cl_error_t cli_recursion_stack_push(cli_ctx *ctx, cl_fmap_t *map, cli_file_t type, bool is_new_buffer, uint32_t attributes) { cl_error_t status = CL_SUCCESS; recursion_level_t *current_container = NULL; recursion_level_t *new_container = NULL; // Check the regular limits 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; } // Check the recursion limit if (ctx->recursion_level == ctx->recursion_stack_size - 1) { 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_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxRecursion"); status = CL_EMAXREC; goto done; } current_container = &ctx->recursion_stack[ctx->recursion_level]; ctx->recursion_level++; new_container = &ctx->recursion_stack[ctx->recursion_level]; memset(new_container, 0, sizeof(recursion_level_t)); new_container->fmap = map; new_container->type = type; new_container->size = map->len; if (is_new_buffer) { new_container->recursion_level_buffer = current_container->recursion_level_buffer + 1; new_container->recursion_level_buffer_fmap = 0; } else { new_container->recursion_level_buffer_fmap = current_container->recursion_level_buffer_fmap + 1; } // Apply the requested next-layer attributes. // // Note that this is how we also keep track of normalized layers. // Normalized layers should be ignored when using the get_type() and get_intermediate_type() // functions so that signatures that specify the container or intermediates need not account // for normalized layers "contained in" HTML / Javascript / etc. new_container->attributes = attributes; // If the current layer is marked "decrypted", all child-layers are also marked "decrypted". if (current_container->attributes & LAYER_ATTRIBUTES_DECRYPTED) { new_container->attributes |= LAYER_ATTRIBUTES_DECRYPTED; } ctx->fmap = new_container->fmap; done: return status; } cl_fmap_t *cli_recursion_stack_pop(cli_ctx *ctx) { cl_fmap_t *popped_map = NULL; if (0 == ctx->recursion_level) { cli_dbgmsg("cli_recursion_stack_pop: recursion_level == 0, cannot pop off more layers!\n"); goto done; } /* save off the fmap to return it to the caller, in case they need it */ popped_map = ctx->recursion_stack[ctx->recursion_level].fmap; /* We're done with this layer, clear it */ memset(&ctx->recursion_stack[ctx->recursion_level], 0, sizeof(recursion_level_t)); ctx->recursion_level--; /* Set the ctx->fmap convenience pointer to the current layer's fmap */ ctx->fmap = ctx->recursion_stack[ctx->recursion_level].fmap; done: return popped_map; } void cli_recursion_stack_change_type(cli_ctx *ctx, cli_file_t type) { ctx->recursion_stack[ctx->recursion_level].type = type; } /** * @brief Convert the desired index into the recursion stack to an actual index, excluding normalized layers. * * Accepts negative indexes, which is in fact the primary use case. * * For index: * 0 == the outermost (bottom) layer of the stack. * 1 == the first layer (probably never explicitly used). * -1 == the present innermost (top) layer of the stack. * -2 == the parent layer (or "container"). That is, the second from the top of the stack. * * @param ctx The scanning context. * @param index The index (probably negative) of the layer we think we want. * @return int -1 if layer doesn't exist, else the index of the desired layer in the recursion_stack */ static int recursion_stack_get(cli_ctx *ctx, int index) { int desired_layer; int current_layer = (int)ctx->recursion_level; if (index < 0) { desired_layer = ctx->recursion_level + index + 1; // The +1 is so that -1 == the current layer // and -2 == the parent layer (the container) } else { desired_layer = index; } if (desired_layer > current_layer) { desired_layer = ctx->recursion_level + 1; // layer doesn't exist goto done; } while (current_layer >= desired_layer && current_layer > 0) { if (ctx->recursion_stack[current_layer].attributes & LAYER_ATTRIBUTES_NORMALIZED) { // The current layer is normalized, so we should step back an extra layer // It's okay if desired_layer goes negative. desired_layer--; } current_layer--; } done: return desired_layer; } cli_file_t cli_recursion_stack_get_type(cli_ctx *ctx, int index) { int index_ignoring_normalized_layers; // translate requested index into index of non-normalized layer index_ignoring_normalized_layers = recursion_stack_get(ctx, index); if (0 > index_ignoring_normalized_layers) { // Layer too low, does not exist. // Most likely we're at the top layer and there is no container. That's okay. return CL_TYPE_ANY; } else if (ctx->recursion_level < (uint32_t)index_ignoring_normalized_layers) { // layer too high, does not exist. This should never happen! return CL_TYPE_IGNORED; } return ctx->recursion_stack[index_ignoring_normalized_layers].type; } size_t cli_recursion_stack_get_size(cli_ctx *ctx, int index) { int index_ignoring_normalized_layers; // translate requested index into index of non-normalized layer index_ignoring_normalized_layers = recursion_stack_get(ctx, index); if (0 > index_ignoring_normalized_layers) { // Layer too low, does not exist. // Most likely we're at the top layer and there is no container. That's okay. return ctx->recursion_stack[0].size; } else if (ctx->recursion_level < (uint32_t)index_ignoring_normalized_layers) { // layer too high, does not exist. This should never happen! return 0; } return ctx->recursion_stack[index_ignoring_normalized_layers].size; } #ifdef C_WINDOWS /* * Windows doesn't allow you to delete a directory while it is still open */ int cli_rmdirs(const char *name) { int rc; STATBUF statb; DIR *dd; struct dirent *dent; char err[128]; if (CLAMSTAT(name, &statb) < 0) { cli_warnmsg("cli_rmdirs: Can't locate %s: %s\n", name, cli_strerror(errno, err, sizeof(err))); return -1; } if (!S_ISDIR(statb.st_mode)) { if (cli_unlink(name)) return -1; return 0; } if ((dd = opendir(name)) == NULL) return -1; rc = 0; while ((dent = readdir(dd)) != NULL) { char *path; if (strcmp(dent->d_name, ".") == 0) continue; if (strcmp(dent->d_name, "..") == 0) continue; path = cli_malloc(strlen(name) + strlen(dent->d_name) + 2); if (path == NULL) { cli_errmsg("cli_rmdirs: Unable to allocate memory for path %u\n", strlen(name) + strlen(dent->d_name) + 2); closedir(dd); return -1; } sprintf(path, "%s\\%s", name, dent->d_name); rc = cli_rmdirs(path); free(path); if (rc != 0) break; } closedir(dd); if (rmdir(name) < 0) { cli_errmsg("cli_rmdirs: Can't remove temporary directory %s: %s\n", name, cli_strerror(errno, err, sizeof(err))); return -1; } return rc; } #else int cli_rmdirs(const char *dirname) { DIR *dd; struct dirent *dent; STATBUF maind, statbuf; char *path; char err[128]; chmod(dirname, 0700); if ((dd = opendir(dirname)) != NULL) { while (CLAMSTAT(dirname, &maind) != -1) { if (!rmdir(dirname)) break; if (errno != ENOTEMPTY && errno != EEXIST && errno != EBADF) { cli_errmsg("cli_rmdirs: Can't remove temporary directory %s: %s\n", dirname, cli_strerror(errno, err, sizeof(err))); closedir(dd); return -1; } while ((dent = readdir(dd))) { if (dent->d_ino) { if (strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")) { path = cli_malloc(strlen(dirname) + strlen(dent->d_name) + 2); if (!path) { cli_errmsg("cli_rmdirs: Unable to allocate memory for path %llu\n", (long long unsigned)(strlen(dirname) + strlen(dent->d_name) + 2)); closedir(dd); return -1; } sprintf(path, "%s" PATHSEP "%s", dirname, dent->d_name); /* stat the file */ if (LSTAT(path, &statbuf) != -1) { if (S_ISDIR(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)) { if (rmdir(path) == -1) { /* can't be deleted */ if (errno == EACCES) { cli_errmsg("cli_rmdirs: Can't remove some temporary directories due to access problem.\n"); closedir(dd); free(path); return -1; } if (cli_rmdirs(path)) { cli_warnmsg("cli_rmdirs: Can't remove nested directory %s\n", path); free(path); closedir(dd); return -1; } } } else { if (cli_unlink(path)) { free(path); closedir(dd); return -1; } } } free(path); } } } rewinddir(dd); } } else { return -1; } closedir(dd); return 0; } #endif /* Implement a generic bitset, trog@clamav.net */ #define BITS_PER_CHAR (8) #define BITSET_DEFAULT_SIZE (1024) static unsigned long nearest_power(unsigned long num) { unsigned long n = BITSET_DEFAULT_SIZE; while (n < num) { n <<= 1; if (n == 0) { return num; } } return n; } bitset_t *cli_bitset_init(void) { bitset_t *bs; bs = cli_malloc(sizeof(bitset_t)); if (!bs) { cli_errmsg("cli_bitset_init: Unable to allocate memory for bs %llu\n", (long long unsigned)sizeof(bitset_t)); return NULL; } bs->length = BITSET_DEFAULT_SIZE; bs->bitset = cli_calloc(BITSET_DEFAULT_SIZE, 1); if (!bs->bitset) { cli_errmsg("cli_bitset_init: Unable to allocate memory for bs->bitset %u\n", BITSET_DEFAULT_SIZE); free(bs); return NULL; } return bs; } void cli_bitset_free(bitset_t *bs) { if (!bs) { return; } if (bs->bitset) { free(bs->bitset); } free(bs); } static bitset_t *bitset_realloc(bitset_t *bs, unsigned long min_size) { unsigned long new_length; unsigned char *new_bitset; new_length = nearest_power(min_size); new_bitset = (unsigned char *)cli_realloc(bs->bitset, new_length); if (!new_bitset) { return NULL; } bs->bitset = new_bitset; memset(bs->bitset + bs->length, 0, new_length - bs->length); bs->length = new_length; return bs; } int cli_bitset_set(bitset_t *bs, unsigned long bit_offset) { unsigned long char_offset; char_offset = bit_offset / BITS_PER_CHAR; bit_offset = bit_offset % BITS_PER_CHAR; if (char_offset >= bs->length) { bs = bitset_realloc(bs, char_offset + 1); if (!bs) { return FALSE; } } bs->bitset[char_offset] |= ((unsigned char)1 << bit_offset); return TRUE; } int cli_bitset_test(bitset_t *bs, unsigned long bit_offset) { unsigned long char_offset; char_offset = bit_offset / BITS_PER_CHAR; bit_offset = bit_offset % BITS_PER_CHAR; if (char_offset >= bs->length) { return FALSE; } return (bs->bitset[char_offset] & ((unsigned char)1 << bit_offset)); } void cl_engine_set_clcb_pre_cache(struct cl_engine *engine, clcb_pre_cache callback) { engine->cb_pre_cache = callback; } void cl_engine_set_clcb_file_inspection(struct cl_engine *engine, clcb_file_inspection callback) { engine->cb_file_inspection = callback; } void cl_engine_set_clcb_pre_scan(struct cl_engine *engine, clcb_pre_scan callback) { engine->cb_pre_scan = callback; } void cl_engine_set_clcb_post_scan(struct cl_engine *engine, clcb_post_scan callback) { engine->cb_post_scan = callback; } void cl_engine_set_clcb_virus_found(struct cl_engine *engine, clcb_virus_found callback) { engine->cb_virus_found = callback; } void cl_engine_set_clcb_sigload(struct cl_engine *engine, clcb_sigload callback, void *context) { engine->cb_sigload = callback; engine->cb_sigload_ctx = callback ? context : NULL; } void cl_engine_set_clcb_sigload_progress(struct cl_engine *engine, clcb_progress callback, void *context) { engine->cb_sigload_progress = callback; engine->cb_sigload_progress_ctx = callback ? context : NULL; } void cl_engine_set_clcb_engine_compile_progress(struct cl_engine *engine, clcb_progress callback, void *context) { engine->cb_engine_compile_progress = callback; engine->cb_engine_compile_progress_ctx = callback ? context : NULL; } void cl_engine_set_clcb_engine_free_progress(struct cl_engine *engine, clcb_progress callback, void *context) { engine->cb_engine_free_progress = callback; engine->cb_engine_free_progress_ctx = callback ? context : NULL; } void cl_engine_set_clcb_hash(struct cl_engine *engine, clcb_hash callback) { engine->cb_hash = callback; } void cl_engine_set_clcb_meta(struct cl_engine *engine, clcb_meta callback) { engine->cb_meta = callback; } void cl_engine_set_clcb_file_props(struct cl_engine *engine, clcb_file_props callback) { engine->cb_file_props = callback; } uint8_t cli_get_debug_flag() { return cli_debug_flag; } uint8_t cli_set_debug_flag(uint8_t debug_flag) { uint8_t was = cli_debug_flag; cli_debug_flag = debug_flag; return was; }