|
|
|
/*
|
|
|
|
* Copyright (C) 2013-2020 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 <stdio.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#ifndef _WIN32
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#endif
|
|
|
|
#include <time.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#ifdef HAVE_PWD_H
|
|
|
|
#include <pwd.h>
|
|
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
|
|
#include "target.h"
|
|
|
|
#ifdef HAVE_SYS_PARAM_H
|
|
|
|
#include <sys/param.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_MALLOC_H
|
|
|
|
#include <malloc.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "clamav.h"
|
|
|
|
#include "others.h"
|
|
|
|
#include "str.h"
|
|
|
|
#include "platform.h"
|
|
|
|
#include "regex/regex.h"
|
|
|
|
#include "ltdl.h"
|
|
|
|
#include "matcher-ac.h"
|
|
|
|
|
|
|
|
#define MSGBUFSIZ 8192
|
|
|
|
|
|
|
|
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 <pthread.h>
|
|
|
|
|
|
|
|
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[MSGBUFSIZ]; \
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Try hard to read the requested number of bytes
|
|
|
|
*
|
|
|
|
* @param fd File desriptor to read from.
|
|
|
|
* @param buff Buffer to read data into.
|
|
|
|
* @param count # of bytes to read.
|
|
|
|
* @return size_t # of bytes read.
|
|
|
|
* @return size_t (size_t)-1 if error.
|
|
|
|
*/
|
|
|
|
size_t cli_readn(int fd, void *buff, size_t count)
|
|
|
|
{
|
|
|
|
ssize_t retval;
|
|
|
|
size_t 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 (size_t)-1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((size_t)retval > todo) {
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
todo -= retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
current += retval;
|
|
|
|
} while (todo > 0);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Try hard to write the specified number of bytes
|
|
|
|
*
|
|
|
|
* @param fd File descriptor to write to.
|
|
|
|
* @param buff Buffer to write from.
|
|
|
|
* @param count # of bytes to write.
|
|
|
|
* @return size_t # of bytes written
|
|
|
|
* @return size_t (size_t)-1 if error.
|
|
|
|
*/
|
|
|
|
size_t cli_writen(int fd, const void *buff, size_t count)
|
|
|
|
{
|
|
|
|
ssize_t retval;
|
|
|
|
size_t todo;
|
|
|
|
const unsigned char *current;
|
|
|
|
|
|
|
|
if (!buff) {
|
|
|
|
cli_errmsg("cli_writen: invalid NULL buff argument\n");
|
|
|
|
return (size_t)-1;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 (size_t)-1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((size_t)retval > todo) {
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
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;
|
|
|
|
size_t 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_IRUSR | S_IWUSR)) == -1) {
|
|
|
|
close(s);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(buffer = cli_malloc(FILEBUFF))) {
|
|
|
|
close(s);
|
|
|
|
close(d);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bytes = cli_readn(s, buffer, FILEBUFF);
|
|
|
|
while ((bytes != (size_t)-1) && (bytes != 0)) {
|
|
|
|
cli_writen(d, buffer, bytes);
|
|
|
|
bytes = cli_readn(s, buffer, FILEBUFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
errno = 0;
|
|
|
|
ret = CL_SUCCESS;
|
|
|
|
while ((dent = readdir(dd))) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
closedir(dd);
|
|
|
|
ret = CL_SUCCESS;
|
|
|
|
|
|
|
|
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, char **sanitized_filebase)
|
|
|
|
{
|
|
|
|
uint32_t depth = 0;
|
|
|
|
size_t index = 0;
|
|
|
|
size_t sanitized_index = 0;
|
|
|
|
char *sanitized_filepath = NULL;
|
|
|
|
|
|
|
|
if ((NULL == filepath) || (0 == filepath_len) || (PATH_MAX < filepath_len)) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NULL != sanitized_filebase) {
|
|
|
|
*sanitized_filebase = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
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--;
|
|
|
|
|
|
|
|
/* Convert path separator to Windows separator */
|
|
|
|
sanitized_filepath[sanitized_index - 1] = '\\';
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Is not "/", "./", or "../".
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Find the next path separator. */
|
|
|
|
#ifdef _WIN32
|
|
|
|
char *next_windows_pathsep = NULL;
|
|
|
|
#endif
|
|
|
|
next_pathsep = CLI_STRNSTR(filepath + index, "/", filepath_len - index);
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
/* Check for both types of separators. */
|
|
|
|
next_windows_pathsep = CLI_STRNSTR(filepath + index, "\\", filepath_len - index);
|
|
|
|
if (NULL != next_windows_pathsep) {
|
|
|
|
if ((NULL == next_pathsep) || (next_windows_pathsep < next_pathsep)) {
|
|
|
|
next_pathsep = next_windows_pathsep;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (NULL != sanitized_filebase) {
|
|
|
|
/* Set output variable to point to the file base name */
|
|
|
|
*sanitized_filebase = sanitized_filepath + sanitized_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++;
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
/* Convert path separator to Windows separator */
|
|
|
|
sanitized_filepath[sanitized_index - 1] = '\\';
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
|
|
|
if ((NULL != sanitized_filepath) && (0 == strlen(sanitized_filepath))) {
|
|
|
|
free(sanitized_filepath);
|
|
|
|
sanitized_filepath = NULL;
|
|
|
|
if (NULL != sanitized_filebase) {
|
|
|
|
*sanitized_filebase = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return sanitized_filepath;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define SHORT_HASH_LENGTH 10
|
|
|
|
char *cli_genfname(const char *prefix)
|
|
|
|
{
|
|
|
|
char *sanitized_prefix = NULL;
|
|
|
|
char *sanitized_prefix_base = 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), &sanitized_prefix_base);
|
|
|
|
}
|
|
|
|
if (NULL != sanitized_prefix_base) {
|
|
|
|
len = strlen(sanitized_prefix_base) + strlen(".") + SHORT_HASH_LENGTH + 1; /* {prefix}.{SHORT_HASH_LENGTH}\0 */
|
|
|
|
} else {
|
|
|
|
len = strlen("clamav-") + 48 + strlen(".tmp") + 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 (NULL == tmp) {
|
|
|
|
free(fname);
|
|
|
|
cli_dbgmsg("cli_genfname: out of memory\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NULL != sanitized_prefix_base) {
|
|
|
|
snprintf(fname, len, "%s.%.*s", sanitized_prefix_base, SHORT_HASH_LENGTH, tmp);
|
|
|
|
} else {
|
|
|
|
snprintf(fname, len, "clamav-%s.tmp", tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NULL != sanitized_prefix) {
|
|
|
|
free(sanitized_prefix);
|
|
|
|
}
|
|
|
|
free(tmp);
|
|
|
|
|
|
|
|
return (fname);
|
|
|
|
}
|
|
|
|
|
|
|
|
char *cli_newfilepath(const char *dir, const char *fname)
|
|
|
|
{
|
|
|
|
char *fullpath;
|
|
|
|
const char *mdir;
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
mdir = dir ? dir : cli_gettmpdir();
|
|
|
|
|
|
|
|
if (!fname) {
|
|
|
|
cli_dbgmsg("cli_newfilepath('%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) {
|
|
|
|
cli_dbgmsg("cli_newfilepath('%s'): out of memory\n", mdir);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(fullpath, len, "%s" PATHSEP "%s", mdir, fname);
|
|
|
|
|
|
|
|
return (fullpath);
|
|
|
|
}
|
|
|
|
|
|
|
|
cl_error_t cli_newfilepathfd(const char *dir, char *fname, char **name, int *fd)
|
|
|
|
{
|
|
|
|
*name = cli_newfilepath(dir, fname);
|
|
|
|
if (!*name)
|
|
|
|
return CL_EMEM;
|
|
|
|
|
|
|
|
*fd = open(*name, O_RDWR | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, S_IRUSR | S_IWUSR);
|
|
|
|
/*
|
|
|
|
* EEXIST is almost impossible to occur, so we just treat it as other
|
|
|
|
* errors
|
|
|
|
*/
|
|
|
|
if (*fd == -1) {
|
|
|
|
cli_errmsg("cli_newfilepathfd: Can't create file %s: %s\n", *name, strerror(errno));
|
|
|
|
free(*name);
|
|
|
|
*name = NULL;
|
|
|
|
return CL_ECREAT;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CL_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *cli_gentemp_with_prefix(const char *dir, const char *prefix)
|
|
|
|
{
|
|
|
|
char *fname;
|
|
|
|
char *fullpath;
|
|
|
|
const char *mdir;
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
mdir = dir ? dir : cli_gettmpdir();
|
|
|
|
|
|
|
|
fname = cli_genfname(prefix);
|
|
|
|
if (!fname) {
|
|
|
|
cli_dbgmsg("cli_gentemp_with_prefix('%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_with_prefix('%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_IRUSR | S_IWUSR);
|
|
|
|
/*
|
|
|
|
* 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_IRUSR | S_IWUSR);
|
|
|
|
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)
|
|
|
|
{
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
cl_error_t status = CL_EARG;
|
|
|
|
char *evaluated_filepath = NULL;
|
|
|
|
|
|
|
|
#ifdef __linux__
|
|
|
|
char fname[PATH_MAX];
|
|
|
|
|
|
|
|
char link[32];
|
|
|
|
ssize_t linksz;
|
|
|
|
|
|
|
|
memset(&fname, 0, PATH_MAX);
|
|
|
|
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
if (NULL == filepath) {
|
|
|
|
cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
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';
|
|
|
|
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
evaluated_filepath = CLI_STRNDUP(fname, CLI_STRNLEN(fname, PATH_MAX));
|
|
|
|
if (NULL == evaluated_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);
|
|
|
|
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
if (NULL == filepath) {
|
|
|
|
cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
evaluated_filepath = CLI_STRNDUP(fname, CLI_STRNLEN(fname, PATH_MAX));
|
|
|
|
if (NULL == evaluated_filepath) {
|
|
|
|
cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate memory to store filename\n");
|
|
|
|
status = CL_EMEM;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
#elif _WIN32
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
DWORD dwRet = 0;
|
|
|
|
intptr_t hFile = _get_osfhandle(desc);
|
|
|
|
char *long_evaluated_filepath = NULL;
|
|
|
|
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
if (NULL == filepath) {
|
|
|
|
cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
dwRet = GetFinalPathNameByHandleA((HANDLE)hFile, NULL, 0, VOLUME_NAME_DOS);
|
|
|
|
if (dwRet == 0) {
|
|
|
|
cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
|
|
|
|
status = CL_EOPEN;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
long_evaluated_filepath = calloc(dwRet + 1, 1);
|
|
|
|
if (NULL == long_evaluated_filepath) {
|
|
|
|
cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate %u bytes to store filename\n", dwRet + 1);
|
|
|
|
status = CL_EMEM;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
dwRet = GetFinalPathNameByHandleA((HANDLE)hFile, long_evaluated_filepath, dwRet + 1, VOLUME_NAME_DOS);
|
|
|
|
if (dwRet == 0) {
|
|
|
|
cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
free(long_evaluated_filepath);
|
|
|
|
long_evaluated_filepath = NULL;
|
|
|
|
status = CL_EOPEN;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
evaluated_filepath = calloc(strlen(long_evaluated_filepath) - strlen("\\\\?\\") + 1, 1);
|
|
|
|
if (NULL == evaluated_filepath) {
|
|
|
|
cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate %u bytes to store filename\n", dwRet + 1);
|
|
|
|
status = CL_EMEM;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
memcpy(evaluated_filepath,
|
|
|
|
long_evaluated_filepath + strlen("\\\\?\\"),
|
|
|
|
strlen(long_evaluated_filepath) - strlen("\\\\?\\"));
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
cli_dbgmsg("cli_get_filepath_from_filedesc: No mechanism implemented to determine filename from file descriptor.\n");
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
status = CL_BREAK;
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
cli_dbgmsg("cli_get_filepath_from_filedesc: File path for fd [%d] is: %s\n", desc, *filepath);
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
status = CL_SUCCESS;
|
|
|
|
*filepath = evaluated_filepath;
|
|
|
|
|
|
|
|
done:
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
if (NULL != long_evaluated_filepath) {
|
|
|
|
free(long_evaluated_filepath);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
cl_error_t cli_realpath(const char *file_name, char **real_filename)
|
|
|
|
{
|
|
|
|
char *real_file_path = NULL;
|
|
|
|
cl_error_t status = CL_EARG;
|
|
|
|
#ifdef _WIN32
|
|
|
|
int desc = -1;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
cli_dbgmsg("Checking realpath of %s\n", file_name);
|
|
|
|
|
|
|
|
if (NULL == file_name || NULL == real_filename) {
|
|
|
|
cli_warnmsg("cli_realpath: Invalid arguments.\n");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef _WIN32
|
|
|
|
|
|
|
|
real_file_path = realpath(file_name, NULL);
|
|
|
|
if (NULL == real_file_path) {
|
|
|
|
status = CL_EMEM;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
status = CL_SUCCESS;
|
|
|
|
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
#else
|
|
|
|
|
|
|
|
if ((desc = safe_open(file_name, O_RDONLY | O_BINARY)) == -1) {
|
|
|
|
cli_warnmsg("Can't open file %s: %s\n", file_name, strerror(errno));
|
|
|
|
status = CL_EOPEN;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
status = cli_get_filepath_from_filedesc(desc, &real_file_path);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
*real_filename = real_file_path;
|
|
|
|
|
|
|
|
done:
|
|
|
|
|
clamd clients: Mitigate move/remove symlink attack
A malicious user could replace a scan target's directory with a symlink
to another path to trick clamscan, clamdscan, or clamonacc into removing
or moving a different file (eg. a critical system file). The issue would
affect users that use the `--move` or `--remove` options for clamscan,
clamdscan, and clamonacc.
This patch gets the real path for the scan target before the scan,
and if the file alerts and the --move or --remove quarantine features
are used, it mitigates the symlink attack by traversing the path one
directory at a time until reaching the leaf directory where the scan
target file resides before unlinking (or renaming) the file directly.
This commit applies a similar tactic used in the previous commit for
Windows builds, using the Win32 Native API to traverse a path and delete
or move files by handle rather than by file path.
I had some trouble using SetFileInformationByHandle to rename a file by
handle, so for Windows instead it will copy the file to the new location
and then use the safe unlink technique to remove the old file. If the
symlink attack occurs, the unlink will fail, and the system will not be
damaged.
For more information about AV quarantine attacks using links, see the
[RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software)
5 years ago
|
|
|
#ifdef _WIN32
|
|
|
|
if (-1 != desc) {
|
|
|
|
close(desc);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|