clamd/onaccess_fan.c
de1271a1
 /*
c442ca9c
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
  *  Copyright (C) 2011-2013 Sourcefire, Inc.
de1271a1
  *
5ae59b95
  *  Authors: Tomasz Kojm, Mickey Sola
de1271a1
  *
  *  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
 
5ae59b95
 #if defined(FANOTIFY)
de1271a1
 
 #include <stdio.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <pthread.h>
 #include <string.h>
 #include <errno.h>
67e36ea7
 #include <time.h>
de1271a1
 
c50982c4
 #include <sys/fanotify.h>
b842e8bf
 
de1271a1
 #include "libclamav/clamav.h"
 #include "libclamav/scanners.h"
 
 #include "shared/optparser.h"
 #include "shared/output.h"
 
51b69a09
 #include "onaccess_others.h"
de1271a1
 #include "server.h"
 
5ae59b95
 #include "onaccess_fan.h"
 #include "onaccess_hash.h"
 #include "onaccess_ddd.h"
 
 static pthread_t ddd_pid;
abbe4c4b
 static int onas_fan_fd;
5ae59b95
 
 static void onas_fan_exit(int sig)
de1271a1
 {
5ae59b95
 	logg("*ScanOnAccess: onas_fan_exit(), signal %d\n", sig);
abbe4c4b
 
 	close(onas_fan_fd);
e0056fe6
 
 	if (ddd_pid > 0) {
 		pthread_kill(ddd_pid, SIGUSR1);
 		pthread_join(ddd_pid, NULL);
 	}
5ae59b95
 
 	pthread_exit(NULL);
 	logg("ScanOnAccess: stopped\n");
de1271a1
 }
 
5ae59b95
 static int onas_fan_scanfile(int fan_fd, const char *fname, struct fanotify_event_metadata *fmd, int scan, int extinfo, struct thrarg *tharg)
de1271a1
 {
 	struct fanotify_response res;
e551468a
 	const char *virname = NULL;
de1271a1
 	int ret = 0;
 
     res.fd = fmd->fd;
     res.response = FAN_ALLOW;
b71960cd
 
51b69a09
     if (scan) {
         if (onas_scan(fname, fmd->fd, &virname, tharg->engine, tharg->options, extinfo) == CL_VIRUS) {
             /* TODO : FIXME? virusaction forks. This could be extraordinarily problematic, lead to deadlocks, 
              * or at the very least lead to extreme memory consumption. Leaving disabled for now.*/ 
b71960cd
             //virusaction(fname, virname, tharg->opts);
51b69a09
             res.response = FAN_DENY;
         }
de1271a1
     }
 
     if(fmd->mask & FAN_ALL_PERM_EVENTS) {
 	ret = write(fan_fd, &res, sizeof(res));
 	if(ret == -1)
 	    logg("!ScanOnAccess: Internal error (can't write to fanotify)\n");
     }
 
     return ret;
 }
 
5ae59b95
 void *onas_fan_th(void *arg)
de1271a1
 {
 	struct thrarg *tharg = (struct thrarg *) arg;
 	sigset_t sigset;
         struct sigaction act;
 	const struct optstruct *pt;
 	short int scan;
6412bda7
 	unsigned int sizelimit = 0, extinfo;
a2a004df
 	STATBUF sb;
400b4777
         uint64_t fan_mask = FAN_EVENT_ON_CHILD | FAN_CLOSE;
de1271a1
         fd_set rfds;
 	char buf[4096];
 	ssize_t bread;
 	struct fanotify_event_metadata *fmd;
 	char fname[1024];
b71960cd
 	int ret, len, check;
de1271a1
 	char err[128];
 
5ae59b95
 	pthread_attr_t ddd_attr;
 	struct ddd_thrarg *ddd_tharg = NULL;
 
e0056fe6
 	ddd_pid = 0;
 
de1271a1
     /* ignore all signals except SIGUSR1 */
     sigfillset(&sigset);
     sigdelset(&sigset, SIGUSR1);
     /* The behavior of a process is undefined after it ignores a 
      * SIGFPE, SIGILL, SIGSEGV, or SIGBUS signal */
     sigdelset(&sigset, SIGFPE);
     sigdelset(&sigset, SIGILL);
     sigdelset(&sigset, SIGSEGV);
 #ifdef SIGBUS    
     sigdelset(&sigset, SIGBUS);
 #endif
     pthread_sigmask(SIG_SETMASK, &sigset, NULL);
     memset(&act, 0, sizeof(struct sigaction));
5ae59b95
     act.sa_handler = onas_fan_exit;
de1271a1
     sigfillset(&(act.sa_mask));
     sigaction(SIGUSR1, &act, NULL);
     sigaction(SIGSEGV, &act, NULL);
 
5ae59b95
     /* Initialize fanotify */
2b122eb2
     onas_fan_fd = fanotify_init(FAN_CLASS_CONTENT | FAN_UNLIMITED_QUEUE | FAN_UNLIMITED_MARKS, O_LARGEFILE | O_RDONLY);
abbe4c4b
     if(onas_fan_fd < 0) {
de1271a1
 	logg("!ScanOnAccess: fanotify_init failed: %s\n", cli_strerror(errno, err, sizeof(err)));
 	if(errno == EPERM)
 	    logg("ScanOnAccess: clamd must be started by root\n");
 	return NULL;
     }
 
4e5ebc3e
     if (!tharg) {
 	logg("!Unable to start on-access scanner. Bad thread args.\n");
 	return NULL;
     }
 
 
cf703fa1
     if (optget(tharg->opts, "OnAccessPrevention")->enabled && !optget(tharg->opts, "OnAccessMountPath")->enabled) {
f408e5b2
 	    logg("ScanOnAccess: preventing access attempts on malicious files.\n");
 	    fan_mask |= FAN_ACCESS_PERM | FAN_OPEN_PERM;
bbb244d9
     } else {
f408e5b2
 	    logg("ScanOnAccess: notifying only for access attempts.\n");
 	    fan_mask |= FAN_ACCESS | FAN_OPEN;
bbb244d9
     }
 
2217edf5
     if ((pt = optget(tharg->opts, "OnAccessMountPath"))->enabled) {
 	    while(pt) {
f408e5b2
 		    if(fanotify_mark(onas_fan_fd, FAN_MARK_ADD | FAN_MARK_MOUNT, fan_mask, onas_fan_fd, pt->strarg) != 0) {
2217edf5
 			    logg("!ScanOnAccess: Can't include mountpoint '%s'\n", pt->strarg);
 			    return NULL;
 		    } else
 			    logg("ScanOnAccess: Protecting '%s' and rest of mount.\n", pt->strarg);
 		    pt = (struct optstruct *) pt->nextarg;
 	    }
 
     } else if (!optget(tharg->opts, "OnAccessDisableDDD")->enabled) {
d7979d4f
 		int thread_started = 1;
4a1bd5c8
 	    do {
 		    if(pthread_attr_init(&ddd_attr)) break;
 		    pthread_attr_setdetachstate(&ddd_attr, PTHREAD_CREATE_JOINABLE);
 
d7979d4f
 			/* Allocate memory for arguments. Thread is responsible for freeing it. */
 		    if (!(ddd_tharg = (struct ddd_thrarg *) calloc(sizeof(struct ddd_thrarg), 1))) break;
 			if (!(ddd_tharg->options = (struct cl_scan_options *) calloc(sizeof(struct cl_scan_options), 1))) break;
4a1bd5c8
 
d7979d4f
 			(void) memcpy(ddd_tharg->options, tharg->options, sizeof(struct cl_scan_options));
4a1bd5c8
 		    ddd_tharg->fan_fd = onas_fan_fd;
 		    ddd_tharg->fan_mask = fan_mask;
 		    ddd_tharg->opts = tharg->opts;
 		    ddd_tharg->engine = tharg->engine;
 
d7979d4f
 		    thread_started = pthread_create(&ddd_pid, &ddd_attr, onas_ddd_th, ddd_tharg);
4a1bd5c8
 	    } while(0);
d7979d4f
 
 		if (0 != thread_started) {
 			/* Failed to create thread. Free anything we may have allocated. */
 			logg("!Unable to start dynamic directory determination.\n");
 			if (NULL != ddd_tharg) {
 				if (NULL != ddd_tharg->options) {
 					free(ddd_tharg->options);
 					ddd_tharg->options = NULL;
 				}
 				free(ddd_tharg);
 				ddd_tharg = NULL;
 			}
 		}
4a1bd5c8
 
     } else {
 	    if((pt = optget(tharg->opts, "OnAccessIncludePath"))->enabled) {
 		    while(pt) {
 			    if(fanotify_mark(onas_fan_fd, FAN_MARK_ADD, fan_mask, onas_fan_fd, pt->strarg) != 0) {
 				    logg("!ScanOnAccess: Can't include path '%s'\n", pt->strarg);
 				    return NULL;
 			    } else
 				    logg("ScanOnAccess: Protecting directory '%s'\n", pt->strarg);
 			    pt = (struct optstruct *) pt->nextarg;
 		    }
 	    } else {
 		    logg("!ScanOnAccess: Please specify at least one path with OnAccessIncludePath\n");
 		    return NULL;
 	    }
     }
de1271a1
 
5ae59b95
     /* Load other options. */
de1271a1
     sizelimit = optget(tharg->opts, "OnAccessMaxFileSize")->numarg;
     if(sizelimit)
6412bda7
 	logg("ScanOnAccess: Max file size limited to %u bytes\n", sizelimit);
de1271a1
     else
 	logg("ScanOnAccess: File size limit disabled\n");
 
     extinfo = optget(tharg->opts, "ExtendedDetectionInfo")->enabled;
 
     FD_ZERO(&rfds);
abbe4c4b
     FD_SET(onas_fan_fd, &rfds);
de1271a1
     do {
405ec90b
 	if (reload) sleep(1);
abbe4c4b
         ret = select(onas_fan_fd + 1, &rfds, NULL, NULL, NULL);
0c5992d3
     } while((ret == -1 && errno == EINTR) || reload);
de1271a1
 
67e36ea7
 
     time_t start = time(NULL) - 30;
2b122eb2
     while(((bread = read(onas_fan_fd, buf, sizeof(buf))) > 0) || errno == EOVERFLOW) {
 
 	if (errno == EOVERFLOW) {
67e36ea7
 		if (time(NULL) - start >= 30) {
 			logg("!ScanOnAccess: Internal error (failed to read data) ... %s\n", strerror(errno));
 			logg("!ScanOnAccess: File too large for fanotify ... recovering and continuing scans...\n");
 			start = time(NULL);
 		}
 
2b122eb2
 		errno = 0;
 		continue;
 	}
 
de1271a1
 	fmd = (struct fanotify_event_metadata *) buf;
 	while(FAN_EVENT_OK(fmd, bread)) {
 	    scan = 1;
 	    if(fmd->fd >= 0) {
 		sprintf(fname, "/proc/self/fd/%d", fmd->fd);
 		len = readlink(fname, fname, sizeof(fname) - 1);
 		if(len == -1) {
 		    close(fmd->fd);
 		    logg("!ScanOnAccess: Internal error (readlink() failed)\n");
 		    return NULL;
 		}
 		fname[len] = 0;
 
b71960cd
 		if((check = onas_fan_checkowner(fmd->pid, tharg->opts))) {
de1271a1
 		    scan = 0;
3d9620db
 	/* TODO: Re-enable OnAccessExtraScanning once the thread resource consumption issue is resolved. */
 	#if 0
 			if ((check != CHK_SELF) || !(optget(tharg->opts, "OnAccessExtraScanning")->enabled)) {
 	#else
 			if (check != CHK_SELF) {
 	#endif
 				logg("*ScanOnAccess: %s skipped (excluded UID)\n", fname);
 			}
de1271a1
 		}
 
 		if(sizelimit) {
b377f2f6
 		    if(FSTAT(fmd->fd, &sb) != 0 || sb.st_size > sizelimit) {
de1271a1
 			scan = 0;
 			/* logg("*ScanOnAccess: %s skipped (size > %d)\n", fname, sizelimit); */
 		    }
 		}
 
abbe4c4b
 		if(onas_fan_scanfile(onas_fan_fd, fname, fmd, scan, extinfo, tharg) == -1) {
de1271a1
 		    close(fmd->fd);
 		    return NULL;
 		}
 
 		if(close(fmd->fd) == -1) {
 		    printf("!ScanOnAccess: Internal error (close(%d) failed)\n", fmd->fd);
 		    close(fmd->fd);
 		    return NULL;
 		}
 	    }
 	    fmd = FAN_EVENT_NEXT(fmd, bread);
 	}
 	do {
405ec90b
 	    if (reload) sleep(1);
abbe4c4b
 	    ret = select(onas_fan_fd + 1, &rfds, NULL, NULL, NULL);
0c5992d3
 	} while((ret == -1 && errno == EINTR) || reload);
de1271a1
     }
 
     if(bread < 0)
2b122eb2
 	logg("!ScanOnAccess: Internal error (failed to read data) ... %s\n", strerror(errno));
de1271a1
 
     return NULL;
 }
 
51b69a09
 
 /* CLAMAUTH is deprecated */
d68aba83
 #elif defined(CLAMAUTH)
 
 #include <stdio.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/uio.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <pthread.h>
 #include <string.h>
 #include <errno.h>
 
 #include "libclamav/clamav.h"
 #include "libclamav/scanners.h"
 
 #include "shared/optparser.h"
 #include "shared/output.h"
 
 #include "server.h"
 #include "others.h"
 #include "scanner.h"
 
 #define SUPPORTED_PROTOCOL  2
 
97efd6f8
 static int cauth_fd = -1;
 
d68aba83
 struct ClamAuthEvent {
     unsigned int action;
     char path[1024];
     unsigned int pid;
 };
 
 static void cauth_exit(int sig)
 {
     logg("*ScanOnAccess: cauth_exit(), signal %d\n", sig);
97efd6f8
     if(cauth_fd > 0)
 	close(cauth_fd);
d68aba83
     pthread_exit(NULL);
     logg("ScanOnAccess: stopped\n");
 }
 
 static int cauth_scanfile(const char *fname, int extinfo, struct thrarg *tharg)
 {
 	struct cb_context context;
e551468a
 	const char *virname = NULL;
d68aba83
 	int ret = 0, fd;
 
     context.filename = fname;
     context.virsize = 0;
bd6ce1e4
     context.scandata = NULL;
d68aba83
 
     fd = open(fname, O_RDONLY);
     if(fd == -1)
 	return -1;
 
d39cb658
     if(cl_scandesc_callback(fd, fname, &virname, NULL, tharg->engine, tharg->options, &context) == CL_VIRUS) {
d68aba83
 	if(extinfo && context.virsize)
 	    logg("ScanOnAccess: %s: %s(%s:%llu) FOUND\n", fname, virname, context.virhash, context.virsize);
 	else
 	    logg("ScanOnAccess: %s: %s FOUND\n", fname, virname);
 	virusaction(fname, virname, tharg->opts);
     }
     close(fd);
     return ret;
 }
 
5ae59b95
 void *onas_fan_th(void *arg)
d68aba83
 {
 	struct thrarg *tharg = (struct thrarg *) arg;
 	sigset_t sigset;
         struct sigaction act;
97efd6f8
 	int eventcnt = 1, extinfo;
d68aba83
 	char err[128];
 	struct ClamAuthEvent event;
 
     /* ignore all signals except SIGUSR1 */
     sigfillset(&sigset);
     sigdelset(&sigset, SIGUSR1);
     /* The behavior of a process is undefined after it ignores a 
      * SIGFPE, SIGILL, SIGSEGV, or SIGBUS signal */
     sigdelset(&sigset, SIGFPE);
     sigdelset(&sigset, SIGILL);
     sigdelset(&sigset, SIGSEGV);
 #ifdef SIGBUS    
     sigdelset(&sigset, SIGBUS);
 #endif
     pthread_sigmask(SIG_SETMASK, &sigset, NULL);
     memset(&act, 0, sizeof(struct sigaction));
     act.sa_handler = cauth_exit;
     sigfillset(&(act.sa_mask));
     sigaction(SIGUSR1, &act, NULL);
     sigaction(SIGSEGV, &act, NULL);
 
     extinfo = optget(tharg->opts, "ExtendedDetectionInfo")->enabled;
 
97efd6f8
     cauth_fd = open("/dev/clamauth", O_RDONLY);
     if(cauth_fd == -1) {
d68aba83
 	logg("!ScanOnAccess: Can't open /dev/clamauth\n");
 	if(errno == ENOENT)
 	    logg("!ScanOnAccess: Please make sure ClamAuth.kext is loaded\n");
 	else if(errno == EACCES)
 	    logg("!ScanOnAccess: This application requires root privileges\n");
 	else
 	    logg("!ScanOnAccess: /dev/clamauth: %s\n", cli_strerror(errno, err, sizeof(err)));
 
 	return NULL;
     }
 
     while(1) {
97efd6f8
 	if(read(cauth_fd, &event, sizeof(event)) > 0) {
d68aba83
 	    if(eventcnt == 1) {
 		if(event.action != SUPPORTED_PROTOCOL) {
 		    logg("!ScanOnAccess: Protocol version mismatch (tool: %d, driver: %d)\n", SUPPORTED_PROTOCOL, event.action);
97efd6f8
 		    close(cauth_fd);
d68aba83
 		    return NULL;
 		}
 		if(strncmp(event.path, "ClamAuth", 8)) {
 		    logg("!ScanOnAccess: Invalid version event\n");
97efd6f8
 		    close(cauth_fd);
d68aba83
 		    return NULL;
 		}
 		logg("ScanOnAccess: Driver version: %s, protocol version: %d\n", &event.path[9], event.action);
 	    } else {
 		cauth_scanfile(event.path, extinfo, tharg);
 	    }
 	    eventcnt++;
 	} else {
 	    if(errno == ENODEV) {
 		printf("^ScanOnAccess: ClamAuth module deactivated, terminating\n");
97efd6f8
 		close(cauth_fd);
d68aba83
 		return NULL;
 	    }
 	}
 	usleep(200);
     }
 }
de1271a1
 #endif