ClamAV is an open source (GPLv2) anti-virus toolkit.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
clamav/clamd/onaccess_hash.c

676 lines
15 KiB

/*
* Copyright (C) 2015 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
* Copyright (C) 2015 Sourcefire, Inc.
*
* Authors: Mickey Sola
*
* 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
#if defined(FANOTIFY)
#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>
#include <stdbool.h>
#include <sys/fanotify.h>
#include "onaccess_fan.h"
#include "onaccess_hash.h"
#include "onaccess_ddd.h"
#include "libclamav/clamav.h"
#include "libclamav/scanners.h"
#include "libclamav/str.h"
#include "shared/optparser.h"
#include "shared/output.h"
#include "server.h"
#include "others.h"
#include "scanner.h"
#include "priv_fts.h"
static struct onas_bucket *onas_bucket_init();
static void onas_free_bucket(struct onas_bucket *bckt);
static int onas_bucket_insert(struct onas_bucket *bckt, struct onas_element *elem);
static int onas_bucket_remove(struct onas_bucket *bckt, struct onas_element *elem);
static int onas_add_hashnode_child(struct onas_hnode *node, const char* dirname);
static struct onas_lnode *onas_listnode_init(void);
static struct onas_hnode *onas_hashnode_init(void);
static inline uint32_t onas_hshift(uint32_t hash) {
hash = ~hash;
hash += (hash << 15);
hash ^= (hash >> 12);
hash += (hash << 2);
hash ^= (hash >> 4);
hash += (hash << 3);
hash += (hash << 11);
hash ^= (hash >> 16);
return hash;
}
static inline int onas_hash(const char* key, size_t keylen, uint32_t size) {
uint32_t hash = 1;
uint32_t i;
for (i = 0; i < keylen; i++) {
hash += key[i];
hash = onas_hshift(hash);
}
return hash & (size - 1);
}
int onas_ht_init(struct onas_ht **ht, uint32_t size) {
if (size == 0 || (size & (~size + 1)) != size) return CL_EARG;
*ht = (struct onas_ht *) cli_malloc(sizeof(struct onas_ht));
if (!(*ht)) return CL_EMEM;
**ht = (struct onas_ht) {
.htable = NULL,
.size = size,
.nbckts = 0,
};
if (!((*ht)->htable = (struct onas_bucket **) cli_calloc(size, sizeof(struct onas_bucket *)))) {
onas_free_ht(*ht);
return CL_EMEM;
}
return CL_SUCCESS;
}
void onas_free_ht(struct onas_ht *ht) {
if (!ht || ht->size == 0) return;
if (!ht->htable) {
free(ht);
return;
}
uint32_t i = 0;
for (i = 0; i < ht->size; i++) {
onas_free_bucket(ht->htable[i]);
ht->htable[i] = NULL;
}
free(ht->htable);
ht->htable = NULL;
free(ht);
return;
}
static struct onas_bucket *onas_bucket_init() {
struct onas_bucket *bckt = (struct onas_bucket*) cli_malloc(sizeof(struct onas_bucket));
if (!bckt) return NULL;
*bckt = (struct onas_bucket) {
.size = 0,
.head = NULL,
.tail = NULL
};
return bckt;
}
static void onas_free_bucket(struct onas_bucket *bckt) {
if (!bckt) return;
uint32_t i = 0;
struct onas_element *curr = NULL;
for (i = 0; i < bckt->size; i++) {
curr = bckt->head;
bckt->head = curr->next;
onas_free_element(curr);
curr = NULL;
}
free(bckt);
return;
}
struct onas_element *onas_element_init(struct onas_hnode *value, const char *key, size_t klen) {
struct onas_element *elem = (struct onas_element *) cli_malloc(sizeof(struct onas_element));
if (!elem) return NULL;
*elem = (struct onas_element) {
.key = key,
.klen = klen,
.data = value,
.next = NULL,
.prev = NULL
};
return elem;
}
void onas_free_element(struct onas_element *elem) {
if (!elem) return;
onas_free_hashnode(elem->data);
elem->prev = NULL;
elem->next = NULL;
free(elem);
return;
}
int onas_ht_insert(struct onas_ht *ht, struct onas_element *elem) {
if (!ht || !elem || !elem->key) return CL_ENULLARG;
int idx = onas_hash(elem->key, elem->klen, ht->size);
struct onas_bucket *bckt = ht->htable[idx];
int ret = 0;
uint32_t bsize = 0;
if (bckt == NULL) {
ht->htable[idx] = onas_bucket_init();
bckt = ht->htable[idx];
}
bsize = bckt->size;
ret = onas_bucket_insert(bckt, elem);
if (ret == CL_SUCCESS)
if (bsize < bckt->size)
ht->nbckts++;
return ret;
}
static int onas_bucket_insert(struct onas_bucket *bckt, struct onas_element *elem) {
if (!bckt || !elem) return CL_ENULLARG;
if (bckt->size == 0) {
bckt->head = elem;
bckt->tail = elem;
elem->prev = NULL;
elem->next = NULL;
bckt->size++;
} else {
struct onas_element *btail = bckt->tail;
btail->next = elem;
elem->prev = btail;
elem->next = NULL;
bckt->tail = elem;
bckt->size++;
}
return CL_SUCCESS;
}
/* Checks if key exists and optionally stores address to the element corresponding to the key within elem */
int onas_ht_get(struct onas_ht *ht, const char *key, size_t klen, struct onas_element **elem) {
if (elem) *elem = NULL;
if (!ht || !key || klen <= 0) return CL_ENULLARG;
struct onas_bucket *bckt = ht->htable[onas_hash(key, klen, ht->size)];
if (!bckt || bckt->size == 0) return CL_EARG;
struct onas_element *curr = bckt->head;
while (curr && strcmp(curr->key, key)) {
curr = curr->next;
}
if (!curr) return CL_EARG;
if (elem) *elem = curr;
return CL_SUCCESS;
}
/* Removes the element corresponding to key from the hashtable and optionally returns a pointer to the removed element. */
int onas_ht_remove(struct onas_ht *ht, const char* key, size_t klen, struct onas_element **relem) {
if (!ht || !key || klen <= 0) return CL_ENULLARG;
struct onas_bucket *bckt = ht->htable[onas_hash(key, klen, ht->size)];
if (!bckt) return CL_EARG;
struct onas_element *elem = NULL;
onas_ht_get(ht, key, klen, &elem);
if (!elem) return CL_EARG;
int ret = onas_bucket_remove(bckt, elem);
if (relem) *relem = elem;
return ret;
}
static int onas_bucket_remove(struct onas_bucket *bckt, struct onas_element *elem) {
if (!bckt || !elem) return CL_ENULLARG;
struct onas_element *curr = bckt->head;
while (curr && curr != elem) {
curr = curr->next;
}
if (!curr) return CL_EARG;
if (bckt->head == elem) {
bckt->head = elem->next;
if (bckt->head) bckt->head->prev = NULL;
elem->next = NULL;
} else if (bckt->tail == elem) {
bckt->tail = elem->prev;
if (bckt->tail) bckt->tail->next = NULL;
elem->prev = NULL;
} else {
struct onas_element *tmp = NULL;
tmp = elem->prev;
if (tmp) {
tmp->next = elem->next;
tmp = elem->next;
tmp->prev = elem->prev;
}
elem->prev = NULL;
elem->next = NULL;
}
bckt->size--;
return CL_SUCCESS;
}
/* Dealing with hash nodes and list nodes */
/* Function to initialize hashnode. */
static struct onas_hnode *onas_hashnode_init(void) {
struct onas_hnode *hnode = NULL;
if(!(hnode = (struct onas_hnode *) cli_malloc(sizeof(struct onas_hnode)))) {
return NULL;
}
*hnode = (struct onas_hnode) {
.pathlen = 0,
.pathname = NULL,
.prnt_pathlen = 0,
.prnt_pathname = NULL,
.childhead = NULL,
.childtail = NULL,
.wd = 0,
.watched = 0
};
if (!(hnode->childhead = (struct onas_lnode *) onas_listnode_init())) {
onas_free_hashnode(hnode);
return NULL;
}
if (!(hnode->childtail = (struct onas_lnode *) onas_listnode_init())) {
onas_free_hashnode(hnode);
return NULL;
}
hnode->childhead->next = (struct onas_lnode *) hnode->childtail;
hnode->childtail->prev = (struct onas_lnode *) hnode->childhead;
return hnode;
}
/* Function to initialize listnode. */
static struct onas_lnode *onas_listnode_init(void) {
struct onas_lnode *lnode = NULL;
if(!(lnode = (struct onas_lnode *) cli_malloc(sizeof(struct onas_lnode)))) {
return NULL;
}
*lnode = (struct onas_lnode) {
.dirname = NULL,
.next = NULL,
.prev = NULL
};
return lnode;
}
/* Function to free hashnode. */
void onas_free_hashnode(struct onas_hnode *hnode) {
if (!hnode) return;
onas_free_dirlist(hnode->childhead);
hnode->childhead = NULL;
free(hnode->pathname);
hnode->pathname = NULL;
free(hnode->prnt_pathname);
hnode->prnt_pathname = NULL;
free(hnode);
return;
}
/* Function to free list of listnodes. */
void onas_free_dirlist(struct onas_lnode *head) {
if (!head) return;
struct onas_lnode *curr = head;
struct onas_lnode *tmp = curr;
while(curr) {
tmp = curr->next;
onas_free_listnode(curr);
curr = tmp;
}
return;
}
/* Function to free a listnode. */
void onas_free_listnode(struct onas_lnode *lnode) {
if (!lnode) return;
lnode->next = NULL;
lnode->prev = NULL;
free(lnode->dirname);
lnode->dirname = NULL;
free(lnode);
return;
}
static int onas_add_hashnode_child(struct onas_hnode *node, const char* dirname) {
if (!node || !dirname) return CL_ENULLARG;
struct onas_lnode *child = onas_listnode_init();
if (!child) return CL_EMEM;
size_t n = strlen(dirname);
child->dirname = cli_strndup(dirname, n);
onas_add_listnode(node->childtail, child);
return CL_SUCCESS;
}
/* Function to add a dir_listnode to a list */
int onas_add_listnode(struct onas_lnode *tail, struct onas_lnode *node) {
if (!tail || !node) return CL_ENULLARG;
struct onas_lnode *tmp = tail->prev;
tmp->next = node;
node->prev = tail->prev;
node->next = tail;
tail->prev = node;
return CL_SUCCESS;
}
/* Function to remove a listnode based on dirname. */
int onas_rm_listnode(struct onas_lnode *head, const char *dirname) {
if (!dirname || !head) return CL_ENULLARG;
struct onas_lnode *curr = head;
size_t n = strlen(dirname);
while ((curr = curr->next)) {
if (!strncmp(curr->dirname, dirname, n)) {
struct onas_lnode *tmp = curr->prev;
tmp->next = curr->next;
tmp = curr->next;
tmp->prev = curr->prev;
onas_free_listnode(curr);
return CL_SUCCESS;
}
}
return -1;
}
/*** Dealing with parent/child relationships in the table. ***/
/* Determines parent and returns a copy based on full pathname. */
inline static char *onas_get_parent(const char *pathname, size_t len) {
if (!pathname || len <= 1) return NULL;
int idx = len - 2;
char *ret = NULL;
while(idx >= 0 && pathname[idx] != '/') {
idx--;
}
if (idx == 0) {
idx++;
}
ret = cli_strndup(pathname, idx);
if (!ret) {
errno = ENOMEM;
return NULL;
}
return ret;
}
/* Gets the index at which the name of directory begins from the full pathname. */
inline static int onas_get_dirname_idx(const char *pathname, size_t len) {
if (!pathname || len <= 1) return -1;
int idx = len - 2;
while(idx >= 0 && pathname[idx] != '/') {
idx--;
}
if (pathname[idx] == '/')
return idx + 1;
return idx;
}
/* Emancipates the specified child from the specified parent. */
int onas_ht_rm_child(struct onas_ht *ht, const char *prntpath, size_t prntlen, const char *childpath, size_t childlen) {
if (!ht || !prntpath || prntlen <= 0 || !childpath || childlen <= 1) return CL_ENULLARG;
struct onas_element *elem = NULL;
struct onas_hnode *hnode = NULL;
int idx = onas_get_dirname_idx(childpath, childlen);
int ret = 0;
if(idx <= 0) return CL_SUCCESS;
if(onas_ht_get(ht, prntpath, prntlen, &elem) != CL_SUCCESS) return CL_EARG;
hnode = elem->data;
if ((ret = onas_rm_listnode(hnode->childhead, &(childpath[idx])))) return CL_EARG;
return CL_SUCCESS;
}
/* The specified parent adds the specified child to its list. */
int onas_ht_add_child(struct onas_ht *ht, const char *prntpath, size_t prntlen, const char *childpath, size_t childlen) {
if (!ht || !prntpath || prntlen <= 0 || !childpath || childlen <= 1) return CL_ENULLARG;
struct onas_element *elem = NULL;
struct onas_hnode *hnode = NULL;
int idx = onas_get_dirname_idx(childpath, childlen);
if(idx <= 0) return CL_SUCCESS;
if(onas_ht_get(ht, prntpath, prntlen, &elem)) return CL_EARG;
hnode = elem->data;
return onas_add_hashnode_child(hnode, &(childpath[idx]));
}
/*** Dealing with hierarchy changes. ***/
/* Adds the hierarchy under pathname to the tree and allocates all necessary memory. */
int onas_ht_add_hierarchy(struct onas_ht *ht, const char *pathname) {
if (!ht || !pathname) return CL_ENULLARG;
FTS *ftsp = NULL;
int ftspopts = FTS_PHYSICAL | FTS_XDEV;
FTSENT *curr = NULL;
FTSENT *childlist = NULL;
size_t len = strlen(pathname);
char *prnt = onas_get_parent(pathname, len);
if (prnt) onas_ht_add_child(ht, prnt, strlen(prnt), pathname, len);
free(prnt);
char * const pathargv[] = { (char*) pathname, NULL };
if (!(ftsp = _priv_fts_open(pathargv, ftspopts, NULL))) {
logg("!ScanOnAccess: Could not open '%s'\n", pathname);
return CL_EARG;
}
while((curr = _priv_fts_read(ftsp))) {
struct onas_hnode *hnode = NULL;
/* May want to handle other options in the future. */
switch (curr->fts_info) {
case FTS_D:
hnode = onas_hashnode_init();
if (!hnode) return CL_EMEM;
hnode->pathlen = curr->fts_pathlen;
hnode->pathname = cli_strndup(curr->fts_path, hnode->pathlen);
hnode->prnt_pathname = onas_get_parent(hnode->pathname, hnode->pathlen);
if (hnode->prnt_pathname)
hnode->prnt_pathlen = strlen(hnode->prnt_pathname);
else
hnode->prnt_pathlen = 0;
break;
default:
continue;
}
if((childlist = _priv_fts_children(ftsp, 0))) {
do {
if (childlist->fts_info == FTS_D) {
if(CL_EMEM == onas_add_hashnode_child(hnode, childlist->fts_name))
return CL_EMEM;
}
} while ((childlist = childlist->fts_link));
}
struct onas_element *elem = onas_element_init(hnode, hnode->pathname, hnode->pathlen);
if (!elem) return CL_EMEM;
if (onas_ht_insert(ht, elem)) return -1;
}
_priv_fts_close(ftsp);
return CL_SUCCESS;
}
/* Removes the underlying hierarchy from the tree and frees all associated memory. */
int onas_ht_rm_hierarchy(struct onas_ht *ht, const char* pathname, size_t len, int level) {
if (!ht || !pathname || len <= 0) return CL_ENULLARG;
struct onas_hnode *hnode = NULL;
struct onas_element *elem = NULL;
char *prntname = NULL;
size_t prntlen = 0;
if(onas_ht_get(ht, pathname, len, &elem)) return CL_EARG;
hnode = elem->data;
struct onas_lnode *curr = hnode->childhead;
if(level == 0) {
if(!(prntname = onas_get_parent(pathname, len))) return CL_EARG;
prntlen = strlen(prntname);
if(onas_ht_rm_child(ht, prntname, prntlen, pathname, len)) return CL_EARG;
free(prntname);
}
while (curr->next != hnode->childtail) {
curr = curr->next;
size_t size = len + strlen(curr->dirname) + 2;
char *child_path = (char *) cli_malloc(size);
if (child_path == NULL)
return CL_EMEM;
if (hnode->pathname[len-1] == '/')
snprintf(child_path, size, "%s%s", hnode->pathname, curr->dirname);
else
snprintf(child_path, size, "%s/%s", hnode->pathname, curr->dirname);
onas_ht_rm_hierarchy(ht, child_path, size, level + 1);
free(child_path);
}
onas_ht_remove(ht, pathname, len, NULL);
onas_free_element(elem);
return CL_SUCCESS;
}
#endif