Skyld AV  0.6
On access virus scanning for Linux
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
FanotifyPolling.cc
Go to the documentation of this file.
1 /*
2  * File: FanotifyPolling.cc
3  *
4  * Copyright 2012 Heinrich Schuchardt <xypron.glpk@gmx.de>
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  */
19 
24 #include <errno.h>
25 #include <linux/fcntl.h>
26 #include <linux/limits.h>
27 #include <malloc.h>
28 #include <poll.h>
29 #include <signal.h>
30 #include <sstream>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string>
34 #include <string.h>
35 #include <sys/fanotify.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38 #include <unistd.h>
39 #include "FanotifyPolling.h"
40 #include "Messaging.h"
41 
42 #define SKYLD_POLLFANOTIFY_BUFLEN 4096
43 
50 void *FanotifyPolling::run(void *obj) {
51 
55  FanotifyPolling *fp;
59  struct pollfd fds;
63  nfds_t nfds = 1;
67  char buf[SKYLD_POLLFANOTIFY_BUFLEN];
68 
69  // Copy fanotify object
70  if (obj) {
71  fp = static_cast<FanotifyPolling *> (obj);
72  } else {
73  return NULL;
74  }
75 
76  fds.fd = fp->fd;
77  fds.events = POLLIN;
78  fds.revents = 0;
79 
80  fp->status = RUNNING;
81  // Continue while the status is not changed.
82  while (fp->status == RUNNING) {
83  /*
84  * number of structures with nonzero revents fields, 0 = timeout
85  */
86  int ret;
87  char errbuf[256];
88  // Poll for 1 s. Then recheck status.
89  ret = poll(&fds, nfds, 1000);
90  if (ret > 0) {
91  if (fds.revents & POLLIN) {
92  for (;;) {
93  ret = read(fp->fd, (void *) &buf,
95  if (ret > 0) {
96  fp->handleFanotifyEvents(&buf, ret);
97  break;
98  } else if (ret < 0) {
99  if (errno & (EINTR | EAGAIN | ETXTBSY | EWOULDBLOCK)) {
100  break;
101  }
102  std::stringstream msg;
103  msg << "Reading from fanotify failed: "
104  << strerror_r(errno, errbuf, sizeof (errbuf));
107  "Fanotiy thread stopped.");
108  fp->status = FAILURE;
109  return NULL;
110  }
111  }
112  }
113  fds.revents = 0;
114  } else if (ret < 0) {
115  if (errno != EINTR) {
116  std::stringstream msg;
117  msg << "Poll failed: "
118  << strerror_r(errno, errbuf, sizeof (errbuf));
121  "Fanotiy thread stopped.");
122  fp->status = FAILURE;
123  return NULL;
124  }
125  }
126  }
127  Messaging::message(Messaging::DEBUG, "Fanotiy thread stopped.");
128  fp->status = SUCCESS;
129  return NULL;
130 }
131 
138 int FanotifyPolling::exclude(const int fd) {
139  int path_len;
140  char path[PATH_MAX + 1];
142  StringSet::iterator pos;
143  std::string fname;
144 
145  // Get absolute file path.
146  snprintf(path, sizeof (path), "/proc/self/fd/%d", fd);
147  path_len = readlink(path, path, sizeof (path) - 1);
148  if (path_len < 0) {
149  path_len = 0;
150  }
151  path[path_len] = '\0';
152  fname = path;
153 
154  // Search in exclude paths.
155  exclude = e->getExcludePaths();
156 
157  for (pos = exclude->begin(); pos != exclude->end(); ++pos) {
158  std::string *str = *pos;
159 
160  if (0 == fname.compare(0, str->size(), *str)) {
161  return 1;
162  }
163  }
164  return 0;
165 }
166 
170 void* FanotifyPolling::scanFile(void *workitem) {
171  struct ScanTask *task = (struct ScanTask *) workitem;
172  struct fanotify_response response;
173  pid_t pid;
174  struct stat statbuf;
175 
176  if (task->metadata.mask & FAN_ALL_PERM_EVENTS) {
177  int ret;
178  ret = fstat(task->metadata.fd, &statbuf);
179  if (ret == -1) {
180  char errbuf[256];
181  std::stringstream msg;
182  msg << "scanFile: failure to read file status: "
183  << strerror_r(errno, errbuf, sizeof (errbuf));
185  } else {
186  response.fd = task->metadata.fd;
187  // For same process always allow.
188  pid = getpid();
189  if (pid == task->metadata.pid) {
190  // for Skyld AV process always allow.
191  response.response = FAN_ALLOW;
192  } else if (!S_ISREG(statbuf.st_mode)) {
193  // For directories always allow.
194  response.response = FAN_ALLOW;
195  } else if (task->fp->exclude(task->metadata.fd)) {
196  // In exclude path.
197  response.response = FAN_ALLOW;
198  } else if (task->fp->virusScan->scan(task->metadata.fd)
199  == VirusScan::SCANOK) {
200  // No virus found.
201  response.response = FAN_ALLOW;
202  } else {
203  response.response = FAN_DENY;
204  }
205  task->fp->writeResponse(response, 1);
206  }
207  }
208  close(task->metadata.fd);
209  free(task);
210 
211  fflush(stdout);
212 
213  return NULL;
214 }
215 
223  const struct fanotify_event_metadata *metadata) {
224 
225  int ret;
226  pid_t pid;
227  struct stat statbuf;
228  struct fanotify_response response;
229  int tobeclosed = 1;
230 
231  ret = fstat(metadata->fd, &statbuf);
232  if (ret == -1) {
233  std::stringstream msg;
234  char errbuf[256];
235  msg << "analyze: failure to read file status: "
236  << strerror_r(errno, errbuf, sizeof (errbuf));
238  ret = writeResponse(response, 0);
239  } else {
240  if (metadata->mask & FAN_CLOSE_WRITE) {
241  e->getScanCache()->remove(&statbuf);
242  }
243  if (metadata->mask & FAN_MODIFY) {
244  if (S_ISREG(statbuf.st_mode)) {
245  // It is a file. Do not receive further MODIFY events.
246  ret = fanotify_mark(fd, FAN_MARK_ADD
247  | FAN_MARK_IGNORED_MASK
248  | FAN_MARK_IGNORED_SURV_MODIFY, FAN_MODIFY,
249  metadata->fd, NULL);
250  if (ret == -1) {
251  perror("analyze: fanotify_mark");
252  }
253  e->getScanCache()->remove(&statbuf);
254  }
255  }
256  if (metadata->mask & FAN_OPEN_PERM) {
257  response.fd = metadata->fd;
258  response.response = FAN_ALLOW;
259  pid = getpid();
260  if (pid == metadata->pid) {
261  // for Skyld AV process always allow.
262  ret = writeResponse(response, 0);
263  } else if (!S_ISREG(statbuf.st_mode)) {
264  // For directories always allow.
265  ret = writeResponse(response, 0);
266  } else {
267  // It is a file. Unignore it.
268  ret = fanotify_mark(fd, FAN_MARK_REMOVE |
269  FAN_MARK_IGNORED_MASK, FAN_MODIFY, metadata->fd,
270  NULL);
271  if (ret == -1 && errno != ENOENT) {
272  std::stringstream msg;
273  char errbuf[256];
274  msg << "Failure to unignore file: "
275  << strerror_r(errno, errbuf, sizeof (errbuf));
277  }
278  response.response = e->getScanCache()->get(&statbuf);
279  if (response.response == ScanCache::CACHE_MISS) {
280  struct ScanTask *task;
281  task = (struct ScanTask *) malloc(sizeof (struct ScanTask));
282  if (task == NULL) {
283  Messaging::message(Messaging::ERROR, "Out of memory\n");
284  response.fd = metadata->fd;
285  response.response = FAN_ALLOW;
286  writeResponse(response, 0);
287  } else {
288  tobeclosed = 0;
289  task->metadata = *metadata;
290  task->fp = this;
291  tp->add((void *) task);
292  }
293  } else {
294  writeResponse(response, 0);
295  }
296  }
297  } // FAN_OPEN_PERM
298  } // ret = fstat
299  if (tobeclosed) {
300  close(metadata->fd);
301  }
302 
303  fflush(stdout);
304 }
305 
312 void FanotifyPolling::handleFanotifyEvents(const void *buf, int len) {
313  const struct fanotify_event_metadata *metadata =
314  (const struct fanotify_event_metadata *) buf;
315 
316  while (FAN_EVENT_OK(metadata, len)) {
317  if (metadata->fd == FAN_NOFD) {
319  "Received FAN_NOFD from fanotiy.");
320  } else {
321  handleFanotifyEvent(metadata);
322  }
323  metadata = FAN_EVENT_NEXT(metadata, len);
324  }
325  return;
326 }
327 
334  int ret;
335  struct timespec waiting_time_rem;
336  struct timespec waiting_time_req;
337  char errbuf[256];
338 
339  e = env;
340 
341  status = INITIAL;
342 
343  try {
344  virusScan = new VirusScan(e);
345  } catch (enum VirusScan::Status e) {
346  Messaging::message(Messaging::ERROR, "Loading database failed.\n");
347  throw FAILURE;
348  }
349 
350  ret = pthread_mutex_init(&mutex_response, NULL);
351  if (ret != 0) {
352  std::stringstream msg;
353  msg << "Failure to intialize mutex: "
354  << strerror_r(errno, errbuf, sizeof (errbuf));
356  throw FAILURE;
357  }
358 
359  tp = new ThreadPool(e->getNumberOfThreads(), scanFile);
360 
361  ret = fanotifyOpen();
362  if (ret != 0) {
363  throw FAILURE;
364  }
365 
366  ret = pthread_create(&thread, NULL, run, (void *) this);
367  if (ret != 0) {
368  std::stringstream msg;
369  msg << "Failure to create thread: "
370  << strerror_r(errno, errbuf, sizeof (errbuf));
372  throw FAILURE;
373  }
374  waiting_time_req.tv_sec = 0;
375  waiting_time_req.tv_nsec = 100;
376  while (status == INITIAL) {
377  nanosleep(&waiting_time_req, &waiting_time_rem);
378  }
379  if (status == FAILURE) {
380  throw FAILURE;
381  }
382 
383  try {
384  mp = new MountPolling(fd, e);
385  } catch (MountPolling::Status e) {
386  throw FAILURE;
387  }
388 }
389 
394  void *result;
395  int ret;
396  char errbuf[256];
397 
398  if (status != RUNNING) {
399  Messaging::message(Messaging::ERROR, "Polling not started.\n");
400  return;
401  }
402 
403  // Stop the mount polling thread.
404  if (mp) {
405  delete mp;
406  }
407 
408  // Stop the fanotify polling thread.
409  status = STOPPING;
410  ret = (int) pthread_join(thread, &result);
411  if (ret != 0) {
412  std::stringstream msg;
413  msg << "Failure to join thread: "
414  << strerror_r(errno, errbuf, sizeof (errbuf));
416  } else if (status != SUCCESS) {
418  "Ending thread signals failure.\n");
419  }
420 
421  // Close the fanotify file descriptor.
422  fanotifyClose();
423 
424  // Delete thread pool.
425  delete tp;
426 
427  // Destroy the mutex.
428  if (pthread_mutex_destroy(&mutex_response)) {
429  std::stringstream msg;
430  msg << "Failure destroying thread: "
431  << strerror_r(errno, errbuf, sizeof (errbuf));
433  }
434 
435  // Unload the virus scanner.
436  try {
437  delete virusScan;
438  } catch (enum VirusScan::Status e) {
440  "Failure unloading virus scanner\n");
441  }
442 }
443 
450 int FanotifyPolling::writeResponse(const struct fanotify_response response,
451  int doBuffer) {
452  int ret = 0;
453  struct stat statbuf;
454 
455  pthread_mutex_lock(&mutex_response);
456 
457  if (doBuffer) {
458  // Add file to scan buffer.
459  ret = fstat(response.fd, &statbuf);
460  if (ret != -1) {
461  e->getScanCache()->add(&statbuf, response.response);
462  }
463  }
464 
465  if (response.response == FAN_DENY && response.fd >= FAN_NOFD) {
466  char path[PATH_MAX];
467  int path_len;
468  sprintf(path, "/proc/self/fd/%d", response.fd);
469  path_len = readlink(path, path, sizeof (path) - 1);
470  if (path_len > 0) {
471  path[path_len] = '\0';
472  std::stringstream msg;
473  msg << "Access to file \"" << path << "\" denied.";
475  }
476  }
477 
478  ret = write(fd, &response, sizeof (struct fanotify_response));
479  if (ret == -1 && status == RUNNING && errno != ENOENT) {
480  std::stringstream msg;
481  char errbuf[256];
482  fprintf(stderr, "Failure to write response %u: %s\n",
483  response.response,
484  strerror_r(errno, errbuf, sizeof (errbuf)));
485  msg << "Failure to write response: "
486  << strerror_r(errno, errbuf, sizeof (errbuf));
488  ret = 1;
489  } else {
490  ret = 0;
491  }
492  pthread_mutex_unlock(&mutex_response);
493  return ret;
494 }
495 
505  unsigned int event_f_flags = O_RDONLY | O_CLOEXEC | O_LARGEFILE;
509  unsigned int flags = FAN_CLOEXEC | FAN_CLASS_CONTENT | FAN_NONBLOCK
510  | FAN_UNLIMITED_MARKS | FAN_UNLIMITED_QUEUE;
511 
512  fd = fanotify_init(flags, event_f_flags);
513  if (fd == -1) {
514  std::stringstream msg;
515  char errbuf[256];
516  msg << "fanotifyOpen: "
517  << strerror_r(errno, errbuf, sizeof (errbuf));
519  status = FAILURE;
520  return -1;
521  } else {
522  return 0;
523  }
524 }
525 
532  int ret = close(fd);
533  if (ret == -1) {
534  char errbuf[256];
535  status = FAILURE;
536  std::stringstream msg;
537  msg << "fanotifyClose: " << strerror_r(errno, errbuf, sizeof (errbuf));
539  return -1;
540  } else {
541  return 0;
542  }
543 }
544 
551 int FanotifyPolling::markMount(int fd, const char *mount) {
552  unsigned int flags = FAN_MARK_ADD | FAN_MARK_MOUNT;
553  uint64_t mask = FAN_OPEN_PERM | FAN_MODIFY | FAN_CLOSE_WRITE;
554  int dfd = AT_FDCWD;
555  int ret;
556 
557  ret = fanotify_mark(fd, flags, mask, dfd, mount);
558  if (ret != 0) {
559  std::stringstream msg;
560  char errbuf[256];
561  msg << "Failure to set mark on '" << mount << "': "
562  << strerror_r(errno, errbuf, sizeof (errbuf));
564  return EXIT_FAILURE;
565  }
566  std::stringstream msg;
567  msg << "Now watching: " << mount;
569  return EXIT_SUCCESS;
570 }
571 
578 int FanotifyPolling::unmarkMount(int fd, const char *mount) {
579  unsigned int flags = FAN_MARK_REMOVE | FAN_MARK_MOUNT;
580  uint64_t mask = FAN_OPEN_PERM | FAN_MODIFY | FAN_CLOSE_WRITE;
581  int dfd = AT_FDCWD;
582  int ret;
583 
584  ret = fanotify_mark(fd, flags, mask, dfd, mount);
585  if (ret != 0 && errno != ENOENT) {
586  std::stringstream msg;
587  char errbuf[256];
588  msg << "Failure to remove mark from '"
589  << mount << "': " << strerror_r(errno, errbuf, sizeof (errbuf));
591  return EXIT_FAILURE;
592  }
593  std::stringstream msg;
594  msg << "Stopped watching: " << mount;
596  return EXIT_SUCCESS;
597 }
598 
#define SKYLD_POLLFANOTIFY_BUFLEN
void remove(const struct stat *)
Remove scan result from cache.
Definition: ScanCache.cc:168
static int markMount(int fd, const char *mount)
Marks a mount for polling fanotify events.
Error, e.g. malfunction of the code, malware detected.
Definition: Messaging.h:56
static void * scanFile(void *workitem)
Scans a file.
Status
Status of virus scanning.
Definition: VirusScan.h:44
Set of pointers to strings.
Definition: StringSet.h:56
Send messages.
pthread_t thread
Worker thread.
ThreadPool * tp
Thread pool for scanning tasks.
VirusScan * virusScan
Warning, e.g. file access has been blocked.
Definition: Messaging.h:52
static void message(const enum Level, const std::string &)
Sends message.
Definition: Messaging.cc:101
static int unmarkMount(int fd, const char *mount)
Removes a mount from polling fanotify events.
struct fanotify_event_metadata metadata
fanotify metadata
void add(const struct stat *, const unsigned int)
Adds scan result to cache.
Definition: ScanCache.cc:50
Status
Status that may occur.
Definition: MountPolling.h:42
Implements the thread pool pattern.
Definition: ThreadPool.h:35
~FanotifyPolling()
Stops polling fanotify events.
int fanotifyClose()
Closes fanotify file descriptor.
int scan(const int fd)
Scans file for virus.
Definition: VirusScan.cc:243
Polls mount and unmout events.
Definition: MountPolling.h:36
FanotifyPolling(Environment *)
Starts polling fanotify events.
Poll fanotify events.
ScanCache * getScanCache()
Gets the scan cache.
Definition: Environment.cc:110
Environment * e
Environment.
static const unsigned int CACHE_MISS
No matching element found in cache.
Definition: ScanCache.h:113
void handleFanotifyEvent(const struct fanotify_event_metadata *)
Handle fanotify events.
Polls fanotify events.
Debugging information only to be shown in the console.
Definition: Messaging.h:44
The environment holds variables that are shared by instances of multiple classes. ...
Definition: Environment.h:38
int get(const struct stat *)
Adds scan result to cache.
Definition: ScanCache.cc:123
int exclude(const int fd)
Check if file is in exclude path.
Scans files for viruses.
Definition: VirusScan.h:38
void handleFanotifyEvents(const void *buf, int len)
Handle fanotify events.
MountPolling * mp
Mount polling object.
void add(void *workItem)
Adds a work item to the work list.
Definition: ThreadPool.cc:55
int writeResponse(const struct fanotify_response, int)
Writes fanotify response.
static void * run(void *)
Thread listening to fanotify events.
pthread_mutex_t mutex_response
Mutex for fanotify response.
int fd
Fanotify file descriptor.
FanotifyPolling * fp
fanotify polling object
enum Status status
Status of fanotify polling object.
StringSet * getExcludePaths()
Gets the set of paths that shall not be scanned.
Definition: Environment.cc:55