/* * Copyright (C) 2013-2019 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 #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 #include "clamav.h" #include "others.h" #include "platform.h" #include "regex/regex.h" #include "ltdl.h" #include "matcher-ac.h" static unsigned char name_salt[16] = {16, 38, 97, 12, 8, 4, 72, 196, 217, 144, 33, 124, 18, 11, 17, 253}; #ifdef CL_NOTHREADS #undef CL_THREAD_SAFE #endif #ifdef CL_THREAD_SAFE #include static pthread_mutex_t cli_gentemp_mutex = PTHREAD_MUTEX_INITIALIZER; #ifndef HAVE_CTIME_R static pthread_mutex_t cli_ctime_mutex = PTHREAD_MUTEX_INITIALIZER; #endif static pthread_mutex_t cli_strerror_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_key_t cli_ctx_tls_key; static pthread_once_t cli_ctx_tls_key_once = PTHREAD_ONCE_INIT; static void cli_ctx_tls_key_alloc(void) { pthread_key_create(&cli_ctx_tls_key, NULL); } void cli_logg_setup(const cli_ctx* ctx) { pthread_once(&cli_ctx_tls_key_once, cli_ctx_tls_key_alloc); pthread_setspecific(cli_ctx_tls_key, ctx); } void cli_logg_unsetup(void) { pthread_setspecific(cli_ctx_tls_key, NULL); } static inline void* cli_getctx(void) { cli_ctx* ctx; pthread_once(&cli_ctx_tls_key_once, cli_ctx_tls_key_alloc); ctx = pthread_getspecific(cli_ctx_tls_key); return ctx ? ctx->cb_ctx : NULL; } #else static const cli_ctx* current_ctx = NULL; void cli_logg_setup(const cli_ctx* ctx) { current_ctx = ctx; } static inline void* cli_getctx(void) { return current_ctx ? current_ctx->cb_ctx : NULL; } void cli_logg_unsetup(void) { } #endif uint8_t cli_debug_flag = 0; uint8_t cli_always_gen_section_hash = 0; static void fputs_callback(enum cl_msg severity, const char* fullmsg, const char* msg, void* context) { UNUSEDPARAM(severity); UNUSEDPARAM(msg); UNUSEDPARAM(context); fputs(fullmsg, stderr); } static clcb_msg msg_callback = fputs_callback; void cl_set_clcb_msg(clcb_msg callback) { msg_callback = callback; } #define MSGCODE(buff, len, x) \ va_list args; \ size_t len = sizeof(x) - 1; \ char buff[BUFSIZ]; \ strncpy(buff, x, len); \ va_start(args, str); \ vsnprintf(buff + len, sizeof(buff) - len, str, args); \ buff[sizeof(buff) - 1] = '\0'; \ va_end(args) void cli_warnmsg(const char* str, ...) { MSGCODE(buff, len, "LibClamAV Warning: "); msg_callback(CL_MSG_WARN, buff, buff + len, cli_getctx()); } void cli_errmsg(const char* str, ...) { MSGCODE(buff, len, "LibClamAV Error: "); msg_callback(CL_MSG_ERROR, buff, buff + len, cli_getctx()); } void cli_infomsg(const cli_ctx* ctx, const char* str, ...) { MSGCODE(buff, len, "LibClamAV info: "); msg_callback(CL_MSG_INFO_VERBOSE, buff, buff + len, ctx ? ctx->cb_ctx : NULL); } void cli_dbgmsg_internal(const char* str, ...) { MSGCODE(buff, len, "LibClamAV debug: "); fputs(buff, stderr); } int cli_matchregex(const char* str, const char* regex) { regex_t reg; int match, flags = REG_EXTENDED | REG_NOSUB; #ifdef _WIN32 flags |= REG_ICASE; #endif if(cli_regcomp(®, regex, flags) == 0) { match = (cli_regexec(®, str, 0, NULL, 0) == REG_NOMATCH) ? 0 : 1; cli_regfree(®); return match; } return 0; } void* cli_malloc(size_t size) { void* alloc; if(!size || size > CLI_MAX_ALLOCATION) { cli_errmsg("cli_malloc(): Attempt to allocate %lu bytes. Please report to https://bugzilla.clamav.net\n", (unsigned long int)size); return NULL; } alloc = malloc(size); if(!alloc) { perror("malloc_problem"); cli_errmsg("cli_malloc(): Can't allocate memory (%lu bytes).\n", (unsigned long int)size); return NULL; } else return alloc; } void* cli_calloc(size_t nmemb, size_t size) { void* alloc; if(!nmemb || !size || size > CLI_MAX_ALLOCATION || nmemb > CLI_MAX_ALLOCATION || (nmemb * size > CLI_MAX_ALLOCATION)) { cli_errmsg("cli_calloc(): Attempt to allocate %lu bytes. Please report to https://bugzilla.clamav.net\n", (unsigned long int)nmemb * size); return NULL; } alloc = calloc(nmemb, size); if(!alloc) { perror("calloc_problem"); cli_errmsg("cli_calloc(): Can't allocate memory (%lu bytes).\n", (unsigned long int)(nmemb * size)); return NULL; } else return alloc; } void* cli_realloc(void* ptr, size_t size) { void* alloc; if(!size || size > CLI_MAX_ALLOCATION) { cli_errmsg("cli_realloc(): Attempt to allocate %lu bytes. Please report to https://bugzilla.clamav.net\n", (unsigned long int)size); return NULL; } alloc = realloc(ptr, size); if(!alloc) { perror("realloc_problem"); cli_errmsg("cli_realloc(): Can't re-allocate memory to %lu bytes.\n", (unsigned long int)size); return NULL; } else return alloc; } void* cli_realloc2(void* ptr, size_t size) { void* alloc; if(!size || size > CLI_MAX_ALLOCATION) { cli_errmsg("cli_realloc2(): Attempt to allocate %lu bytes. Please report to https://bugzilla.clamav.net\n", (unsigned long int)size); return NULL; } alloc = realloc(ptr, size); if(!alloc) { perror("realloc_problem"); cli_errmsg("cli_realloc2(): Can't re-allocate memory to %lu bytes.\n", (unsigned long int)size); if(ptr) free(ptr); return NULL; } else return alloc; } char* cli_strdup(const char* s) { char* alloc; if(s == NULL) { cli_errmsg("cli_strdup(): s == NULL. Please report to https://bugzilla.clamav.net\n"); return NULL; } alloc = strdup(s); if(!alloc) { perror("strdup_problem"); cli_errmsg("cli_strdup(): Can't allocate memory (%u bytes).\n", (unsigned int)strlen(s)); return NULL; } return alloc; } /* returns converted timestamp, in case of error the returned string contains at least one character */ const char* cli_ctime(const time_t* timep, char* buf, const size_t bufsize) { const char* ret; if(bufsize < 26) { /* standard says we must have at least 26 bytes buffer */ cli_warnmsg("buffer too small for ctime\n"); return " "; } if((uint32_t)(*timep) > 0x7fffffff) { /* some systems can consider these timestamps invalid */ strncpy(buf, "invalid timestamp", bufsize - 1); buf[bufsize - 1] = '\0'; return buf; } #ifdef HAVE_CTIME_R #ifdef HAVE_CTIME_R_2 ret = ctime_r(timep, buf); #else ret = ctime_r(timep, buf, bufsize); #endif #else /* no ctime_r */ #ifdef CL_THREAD_SAFE pthread_mutex_lock(&cli_ctime_mutex); #endif ret = ctime(timep); if(ret) { strncpy(buf, ret, bufsize - 1); buf[bufsize - 1] = '\0'; ret = buf; } #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&cli_ctime_mutex); #endif #endif /* common */ if(!ret) { buf[0] = ' '; buf[1] = '\0'; return buf; } return ret; } /* Function: readn Try hard to read the requested number of bytes */ int cli_readn(int fd, void* buff, unsigned int count) { int retval; unsigned int todo; unsigned char* current; todo = count; current = (unsigned char*)buff; do { retval = read(fd, current, todo); if(retval == 0) { return (count - todo); } if(retval < 0) { char err[128]; if(errno == EINTR) { continue; } cli_errmsg("cli_readn: read error: %s\n", cli_strerror(errno, err, sizeof(err))); return -1; } todo -= retval; current += retval; } while(todo > 0); return count; } /* Function: writen Try hard to write the specified number of bytes */ int cli_writen(int fd, const void* buff, unsigned int count) { int retval; unsigned int todo; const unsigned char* current; todo = count; current = (const unsigned char*)buff; do { retval = write(fd, current, todo); if(retval < 0) { char err[128]; if(errno == EINTR) { continue; } cli_errmsg("cli_writen: write error: %s\n", cli_strerror(errno, err, sizeof(err))); return -1; } todo -= retval; current += retval; } while(todo > 0); return count; } int cli_filecopy(const char* src, const char* dest) { #ifdef _WIN32 return CopyFileA(src, dest, 0) ? 0 : -1; #else char* buffer; int s, d, bytes; if((s = open(src, O_RDONLY | O_BINARY)) == -1) return -1; if((d = open(dest, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, S_IRWXU)) == -1) { close(s); return -1; } if(!(buffer = cli_malloc(FILEBUFF))) { close(s); close(d); return -1; } while((bytes = cli_readn(s, buffer, FILEBUFF)) > 0) cli_writen(d, buffer, bytes); free(buffer); close(s); return close(d); #endif } #ifndef P_tmpdir #ifdef _WIN32 #define P_tmpdir "C:\\" #else #define P_tmpdir "/tmp" #endif /* _WIN32 */ #endif /* P_tmpdir */ const char* cli_gettmpdir(void) { const char* tmpdir; unsigned int i; #ifdef _WIN32 char* envs[] = {"TEMP", "TMP", NULL}; #else char* envs[] = {"TMPDIR", NULL}; #endif for(i = 0; envs[i] != NULL; i++) if((tmpdir = getenv(envs[i]))) return tmpdir; return P_tmpdir; } struct dirent_data { char* filename; const char* dirname; STATBUF* statbuf; long ino; /* -1: inode not available */ int is_dir; /* 0 - no, 1 - yes */ }; /* sort files before directories, and lower inodes before higher inodes */ static int ftw_compare(const void* a, const void* b) { const struct dirent_data* da = a; const struct dirent_data* db = b; long diff = da->is_dir - db->is_dir; if(!diff) { diff = da->ino - db->ino; } return diff; } enum filetype { ft_unknown, ft_link, ft_directory, ft_regular, ft_skipped_special, ft_skipped_link }; static inline int ft_skipped(enum filetype ft) { return ft != ft_regular && ft != ft_directory; } #define FOLLOW_SYMLINK_MASK (CLI_FTW_FOLLOW_FILE_SYMLINK | CLI_FTW_FOLLOW_DIR_SYMLINK) static int get_filetype(const char* fname, int flags, int need_stat, STATBUF* statbuf, enum filetype* ft) { int stated = 0; if(*ft == ft_unknown || *ft == ft_link) { need_stat = 1; if((flags & FOLLOW_SYMLINK_MASK) != FOLLOW_SYMLINK_MASK) { /* Following only one of directory/file symlinks, or none, may * need to lstat. * If we're following both file and directory symlinks, we don't need * to lstat(), we can just stat() directly.*/ if(*ft != ft_link) { /* need to lstat to determine if it is a symlink */ if(LSTAT(fname, statbuf) == -1) return -1; if(S_ISLNK(statbuf->st_mode)) { *ft = ft_link; } else { /* It was not a symlink, stat() not needed */ need_stat = 0; stated = 1; } } if(*ft == ft_link && !(flags & FOLLOW_SYMLINK_MASK)) { /* This is a symlink, but we don't follow any symlinks */ *ft = ft_skipped_link; return 0; } } } if(need_stat) { if(CLAMSTAT(fname, statbuf) == -1) return -1; stated = 1; } if(*ft == ft_unknown || *ft == ft_link) { if(S_ISDIR(statbuf->st_mode) && (*ft != ft_link || (flags & CLI_FTW_FOLLOW_DIR_SYMLINK))) { /* A directory, or (a symlink to a directory and we're following dir * symlinks) */ *ft = ft_directory; } else if(S_ISREG(statbuf->st_mode) && (*ft != ft_link || (flags & CLI_FTW_FOLLOW_FILE_SYMLINK))) { /* A file, or (a symlink to a file and we're following file symlinks) */ *ft = ft_regular; } else { /* default: skipped */ *ft = S_ISLNK(statbuf->st_mode) ? ft_skipped_link : ft_skipped_special; } } return stated; } static int handle_filetype(const char* fname, int flags, STATBUF* statbuf, int* stated, enum filetype* ft, cli_ftw_cb callback, struct cli_ftw_cbdata* data) { int ret; *stated = get_filetype(fname, flags, flags & CLI_FTW_NEED_STAT, statbuf, ft); if(*stated == -1) { /* we failed a stat() or lstat() */ ret = callback(NULL, NULL, fname, error_stat, data); if(ret != CL_SUCCESS) return ret; *ft = ft_unknown; } else if(*ft == ft_skipped_link || *ft == ft_skipped_special) { /* skipped filetype */ ret = callback(stated ? statbuf : NULL, NULL, fname, *ft == ft_skipped_link ? warning_skipped_link : warning_skipped_special, data); if(ret != CL_SUCCESS) return ret; } return CL_SUCCESS; } static int cli_ftw_dir(const char* dirname, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata* data, cli_ftw_pathchk pathchk); static int handle_entry(struct dirent_data* entry, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata* data, cli_ftw_pathchk pathchk) { if(!entry->is_dir) { return callback(entry->statbuf, entry->filename, entry->filename, visit_file, data); } else { return cli_ftw_dir(entry->dirname, flags, maxdepth, callback, data, pathchk); } } int cli_ftw(char* path, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata* data, cli_ftw_pathchk pathchk) { STATBUF statbuf; enum filetype ft = ft_unknown; struct dirent_data entry; int stated = 0; int ret; if(((flags & CLI_FTW_TRIM_SLASHES) || pathchk) && path[0] && path[1]) { char* pathend; /* trim slashes so that dir and dir/ behave the same when * they are symlinks, and we are not following symlinks */ #ifndef _WIN32 while(path[0] == *PATHSEP && path[1] == *PATHSEP) path++; #endif pathend = path + strlen(path); while(pathend > path && pathend[-1] == *PATHSEP) --pathend; *pathend = '\0'; } if(pathchk && pathchk(path, data) == 1) return CL_SUCCESS; ret = handle_filetype(path, flags, &statbuf, &stated, &ft, callback, data); if(ret != CL_SUCCESS) return ret; if(ft_skipped(ft)) return CL_SUCCESS; entry.statbuf = stated ? &statbuf : NULL; entry.is_dir = ft == ft_directory; entry.filename = entry.is_dir ? NULL : strdup(path); entry.dirname = entry.is_dir ? path : NULL; if(entry.is_dir) { ret = callback(entry.statbuf, NULL, path, visit_directory_toplev, data); if(ret != CL_SUCCESS) return ret; } return handle_entry(&entry, flags, maxdepth, callback, data, pathchk); } static int cli_ftw_dir(const char* dirname, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata* data, cli_ftw_pathchk pathchk) { DIR* dd; #if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2) union { struct dirent d; char b[offsetof(struct dirent, d_name) + NAME_MAX + 1]; } result; #endif struct dirent_data* entries = NULL; size_t i, entries_cnt = 0; int ret; if(maxdepth < 0) { /* exceeded recursion limit */ ret = callback(NULL, NULL, dirname, warning_skipped_dir, data); return ret; } if((dd = opendir(dirname)) != NULL) { struct dirent* dent; int err; errno = 0; ret = CL_SUCCESS; #ifdef HAVE_READDIR_R_3 while(!(err = readdir_r(dd, &result.d, &dent)) && dent) { #elif defined(HAVE_READDIR_R_2) while((dent = (struct dirent*)readdir_r(dd, &result.d))) { #else while((dent = readdir(dd))) { #endif int stated = 0; enum filetype ft; char* fname; STATBUF statbuf; STATBUF* statbufp; if(!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) continue; #ifdef _DIRENT_HAVE_D_TYPE switch(dent->d_type) { case DT_DIR: ft = ft_directory; break; case DT_LNK: if(!(flags & FOLLOW_SYMLINK_MASK)) { /* we don't follow symlinks, don't bother * stating it */ errno = 0; continue; } ft = ft_link; break; case DT_REG: ft = ft_regular; break; case DT_UNKNOWN: ft = ft_unknown; break; default: ft = ft_skipped_special; break; } #else ft = ft_unknown; #endif fname = (char*)cli_malloc(strlen(dirname) + strlen(dent->d_name) + 2); if(!fname) { ret = callback(NULL, NULL, dirname, error_mem, data); if(ret != CL_SUCCESS) break; continue; /* have to skip this one if continuing after error */ } if(!strcmp(dirname, PATHSEP)) sprintf(fname, PATHSEP "%s", dent->d_name); else sprintf(fname, "%s" PATHSEP "%s", dirname, dent->d_name); if(pathchk && pathchk(fname, data) == 1) { free(fname); continue; } ret = handle_filetype(fname, flags, &statbuf, &stated, &ft, callback, data); if(ret != CL_SUCCESS) { free(fname); break; } if(ft_skipped(ft)) { /* skip */ free(fname); errno = 0; continue; } if(stated && (flags & CLI_FTW_NEED_STAT)) { statbufp = cli_malloc(sizeof(*statbufp)); if(!statbufp) { ret = callback(stated ? &statbuf : NULL, NULL, fname, error_mem, data); free(fname); if(ret != CL_SUCCESS) break; else { errno = 0; continue; } } memcpy(statbufp, &statbuf, sizeof(statbuf)); } else { statbufp = 0; } entries_cnt++; entries = cli_realloc(entries, entries_cnt * sizeof(*entries)); if(!entries) { ret = callback(stated ? &statbuf : NULL, NULL, fname, error_mem, data); free(fname); if(statbufp) free(statbufp); break; } else { struct dirent_data* entry = &entries[entries_cnt - 1]; entry->filename = fname; entry->statbuf = statbufp; entry->is_dir = ft == ft_directory; entry->dirname = entry->is_dir ? fname : NULL; #ifdef _XOPEN_UNIX entry->ino = dent->d_ino; #else entry->ino = -1; #endif } errno = 0; } #ifndef HAVE_READDIR_R_3 err = errno; #endif closedir(dd); ret = CL_SUCCESS; if(err) { char errs[128]; cli_errmsg("Unable to readdir() directory %s: %s\n", dirname, cli_strerror(errno, errs, sizeof(errs))); /* report error to callback using error_stat */ ret = callback(NULL, NULL, dirname, error_stat, data); if(ret != CL_SUCCESS) { if(entries) { for(i = 0; i < entries_cnt; i++) { struct dirent_data* entry = &entries[i]; free(entry->filename); free(entry->statbuf); } free(entries); } return ret; } } if(entries) { cli_qsort(entries, entries_cnt, sizeof(*entries), ftw_compare); for(i = 0; i < entries_cnt; i++) { struct dirent_data* entry = &entries[i]; ret = handle_entry(entry, flags, maxdepth - 1, callback, data, pathchk); if(entry->is_dir) free(entry->filename); if(entry->statbuf) free(entry->statbuf); if(ret != CL_SUCCESS) break; } for(i++; i < entries_cnt; i++) { struct dirent_data* entry = &entries[i]; free(entry->filename); free(entry->statbuf); } free(entries); } } else { ret = callback(NULL, NULL, dirname, error_stat, data); } return ret; } /* strerror_r is not available everywhere, (and when it is there are two variants, * the XSI, and the GNU one, so provide a wrapper to make sure correct one is * used */ const char* cli_strerror(int errnum, char* buf, size_t len) { char* err; #ifdef CL_THREAD_SAFE pthread_mutex_lock(&cli_strerror_mutex); #endif err = strerror(errnum); strncpy(buf, err, len); buf[len - 1] = '\0'; /* just in case */ #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&cli_strerror_mutex); #endif return buf; } static char* cli_md5buff(const unsigned char* buffer, unsigned int len, unsigned char* dig) { unsigned char digest[16]; char *md5str, *pt; int i; cl_hash_data("md5", buffer, len, digest, NULL); if(dig) memcpy(dig, digest, 16); if(!(md5str = (char*)cli_calloc(32 + 1, sizeof(char)))) return NULL; pt = md5str; for(i = 0; i < 16; i++) { sprintf(pt, "%02x", digest[i]); pt += 2; } return md5str; } unsigned int cli_rndnum(unsigned int max) { if(name_salt[0] == 16) { /* minimizes re-seeding after the first call to cli_gentemp() */ struct timeval tv; gettimeofday(&tv, (struct timezone*)0); srand(tv.tv_usec + clock() + rand()); } return 1 + (unsigned int)(max * (rand() / (1.0 + RAND_MAX))); } char* cli_sanitize_filepath(const char* filepath, size_t filepath_len) { uint32_t depth = 0; size_t index = 0; size_t sanitized_index = 0; char* sanitized_filepath = NULL; if((NULL == filepath) || (0 == filepath_len) || (MAX_PATH < filepath_len)) { goto done; } sanitized_filepath = cli_calloc(filepath_len + 1, sizeof(unsigned char)); if(NULL == sanitized_filepath) { cli_dbgmsg("cli_sanitize_filepath: out of memory\n"); goto done; } while(index < filepath_len) { char* next_pathsep = NULL; if(0 == strncmp(filepath + index, PATHSEP, strlen(PATHSEP))) { /* * Is "/" (or "\\" on Windows) */ /* Skip leading pathsep in absolute path, or extra pathsep) */ index += strlen(PATHSEP); continue; } else if(0 == strncmp(filepath + index, "." PATHSEP, strlen("." PATHSEP))) { /* * Is "./" (or ".\\" on Windows) */ /* Current directory indicator is meaningless and should not add to the depth. Skip it. */ index += strlen("." PATHSEP); continue; } else if(0 == strncmp(filepath + index, ".." PATHSEP, strlen(".." PATHSEP))) { /* * Is "../" (or "..\\" on Windows) */ if(depth == 0) { /* Relative path would traverse parent directory. Skip it. */ index += strlen(".." PATHSEP); continue; } else { /* Relative path is safe. Allow it. */ strncpy(sanitized_filepath + sanitized_index, filepath + index, strlen(".." PATHSEP)); sanitized_index += strlen(".." PATHSEP); index += strlen(".." PATHSEP); depth--; } } #ifdef _WIN32 /* * Windows' POSIX style API's accept both "/" and "\\" style path separators. * The following checks using POSIX style path separators on Windows. */ else if(0 == strncmp(filepath + index, "/", strlen("/"))) { /* * Is "/". */ /* Skip leading pathsep in absolute path, or extra pathsep) */ index += strlen("/"); continue; } else if(0 == strncmp(filepath + index, "./", strlen("./"))) { /* * Is "./" */ /* Current directory indicator is meaningless and should not add to the depth. Skip it. */ index += strlen("./"); continue; } else if(0 == strncmp(filepath + index, "../", strlen("../"))) { /* * Is "../" */ if(depth == 0) { /* Relative path would traverse parent directory. Skip it. */ index += strlen("../"); continue; } else { /* Relative path is safe. Allow it. */ strncpy(sanitized_filepath + sanitized_index, filepath + index, strlen("../")); sanitized_index += strlen("../"); index += strlen("../"); depth--; } } #endif else { /* * Is not "/", "./", or "../". */ /* Find the next path separator. */ next_pathsep = cli_strnstr(filepath + index, PATHSEP, filepath_len - index); if(NULL == next_pathsep) { /* No more path separators, copy the rest (filename) into the sanitized path */ strncpy(sanitized_filepath + sanitized_index, filepath + index, filepath_len - index); break; } next_pathsep += strlen(PATHSEP); /* Include the path separator in the copy */ /* Copy next directory name into the sanitized path */ strncpy(sanitized_filepath + sanitized_index, filepath + index, next_pathsep - (filepath + index)); sanitized_index += next_pathsep - (filepath + index); index += next_pathsep - (filepath + index); depth++; } } done: if((NULL != sanitized_filepath) && (0 == strlen(sanitized_filepath))) { free(sanitized_filepath); sanitized_filepath = NULL; } return sanitized_filepath; } char* cli_genfname(const char* prefix) { char* sanitized_prefix = NULL; char* fname = NULL; unsigned char salt[16 + 32]; char* tmp; int i; size_t len; if(prefix && (strlen(prefix) > 0)) { sanitized_prefix = cli_sanitize_filepath(prefix, strlen(prefix)); len = strlen(sanitized_prefix) + 1 + 5 + 1; /* {prefix}.{5}\0 */ } else { len = 6 + 1 + 48 + 4 + 1; /* clamav-{48}.tmp\0 */ } fname = (char*)cli_calloc(len, sizeof(char)); if(!fname) { cli_dbgmsg("cli_genfname: out of memory\n"); return NULL; } #ifdef CL_THREAD_SAFE pthread_mutex_lock(&cli_gentemp_mutex); #endif memcpy(salt, name_salt, 16); for(i = 16; i < 48; i++) salt[i] = cli_rndnum(255); tmp = cli_md5buff(salt, 48, name_salt); #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&cli_gentemp_mutex); #endif if(!tmp) { free(fname); cli_dbgmsg("cli_genfname: out of memory\n"); return NULL; } if(sanitized_prefix && (strlen(sanitized_prefix) > 0)) { fname[5] = '\0'; snprintf(fname, len, "%s.%s", sanitized_prefix, tmp); free(sanitized_prefix); } else { snprintf(fname, len, "clamav-%s.tmp", tmp); } free(tmp); return (fname); } char* cli_gentemp_with_prefix(const char* dir, const char* prefix) { char* fname; char* fullpath; const char* mdir; int i; size_t len; mdir = dir ? dir : cli_gettmpdir(); fname = cli_genfname(prefix); if(!fname) { cli_dbgmsg("cli_gentemp('%s'): out of memory\n", mdir); return NULL; } len = strlen(mdir) + strlen(PATHSEP) + strlen(fname) + 1; /* mdir/fname\0 */ fullpath = (char*)cli_calloc(len, sizeof(char)); if(!fullpath) { free(fname); cli_dbgmsg("cli_gentemp('%s'): out of memory\n", mdir); return NULL; } snprintf(fullpath, len, "%s" PATHSEP "%s", mdir, fname); free(fname); return (fullpath); } char* cli_gentemp(const char* dir) { return cli_gentemp_with_prefix(dir, NULL); } cl_error_t cli_gentempfd(const char* dir, char** name, int* fd) { return cli_gentempfd_with_prefix(dir, NULL, name, fd); } cl_error_t cli_gentempfd_with_prefix(const char* dir, char* prefix, char** name, int* fd) { *name = cli_gentemp_with_prefix(dir, prefix); if(!*name) return CL_EMEM; *fd = open(*name, O_RDWR | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, S_IRWXU); /* * EEXIST is almost impossible to occur, so we just treat it as other * errors */ if(*fd == -1) { if((EILSEQ == errno) || (EINVAL == errno) || (ENAMETOOLONG == errno)) { cli_dbgmsg("cli_gentempfd_with_prefix: Can't create temp file using prefix. Using a randomly generated name instead.\n"); free(*name); *name = cli_gentemp(dir); if(!*name) return CL_EMEM; *fd = open(*name, O_RDWR | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, S_IRWXU); if(*fd == -1) { cli_errmsg("cli_gentempfd_with_prefix: Can't create temporary file %s: %s\n", *name, strerror(errno)); free(*name); *name = NULL; return CL_ECREAT; } } else { cli_errmsg("cli_gentempfd_with_prefix: Can't create temporary file %s: %s\n", *name, strerror(errno)); free(*name); *name = NULL; return CL_ECREAT; } } return CL_SUCCESS; } int cli_regcomp(regex_t* preg, const char* pattern, int cflags) { if(!strncmp(pattern, "(?i)", 4)) { pattern += 4; cflags |= REG_ICASE; } return cli_regcomp_real(preg, pattern, cflags); } cl_error_t cli_get_filepath_from_filedesc(int desc, char** filepath) { cl_error_t status = CL_EARG; if(NULL == filepath) { cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n"); goto done; } #ifdef __linux__ char fname[PATH_MAX]; char link[32]; ssize_t linksz; memset(&fname, 0, PATH_MAX); snprintf(link, sizeof(link), "/proc/self/fd/%u", desc); link[sizeof(link) - 1] = '\0'; if(-1 == (linksz = readlink(link, fname, PATH_MAX - 1))) { cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d (%s)\n", desc, link); status = CL_EOPEN; goto done; } /* Success. Add null terminator */ fname[linksz] = '\0'; *filepath = cli_strndup(fname, cli_strnlen(fname, PATH_MAX)); if(NULL == *filepath) { cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate memory to store filename\n"); status = CL_EMEM; goto done; } #elif __APPLE__ char fname[PATH_MAX]; memset(&fname, 0, PATH_MAX); if(fcntl(desc, F_GETPATH, &fname) < 0) { printf("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc); status = CL_EOPEN; goto done; } *filepath = cli_strndup(fname, cli_strnlen(fname, PATH_MAX)); if(NULL == *filepath) { cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate memory to store filename\n"); status = CL_EMEM; goto done; } #elif _WIN32 DWORD dwRet = 0; intptr_t hFile = _get_osfhandle(desc); dwRet = GetFinalPathNameByHandleA((HANDLE)hFile, NULL, 0, VOLUME_NAME_NT); if(dwRet == 0) { cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc); status = CL_EOPEN; goto done; } *filepath = calloc(dwRet + 1, 1); if(NULL == *filepath) { cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate %u bytes to store filename\n", dwRet + 1); status = CL_EMEM; goto done; } dwRet = GetFinalPathNameByHandleA((HANDLE)hFile, *filepath, dwRet + 1, VOLUME_NAME_NT); if(dwRet == 0) { cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc); free(*filepath); *filepath = NULL; status = CL_EOPEN; goto done; } #else cli_dbgmsg("cli_get_filepath_from_filedesc: No mechanism implemented to determine filename from file descriptor.\n"); *filepath = NULL; status = CL_BREAK; goto done; #endif cli_dbgmsg("cli_get_filepath_from_filedesc: File path for fd [%d] is: %s\n", desc, *filepath); status = CL_SUCCESS; done: return status; }