clamd/onaccess_hash.c
5ae59b95
 /*
c442ca9c
  *  Copyright (C) 2015-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
5ae59b95
  *
  *  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
 
f87f08cb
 #if defined(FANOTIFY)
5ae59b95
 #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"
47a544dc
 #include "libclamav/str.h"
5ae59b95
 
 #include "shared/optparser.h"
 #include "shared/output.h"
 
 #include "server.h"
 #include "others.h"
 #include "scanner.h"
9a1b6ef5
 #include "priv_fts.h"
5ae59b95
 
d98d6fdb
 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);
5ae59b95
 
d98d6fdb
 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);
5ae59b95
 
 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;
d98d6fdb
 	uint32_t i;
5ae59b95
 
 	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;
 	}
 
d98d6fdb
 	uint32_t i = 0;
5ae59b95
 	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;
 
d98d6fdb
 	uint32_t i = 0;
5ae59b95
 	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;
d98d6fdb
 	uint32_t bsize = 0;
5ae59b95
 
 	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;
 
eb16d133
 	while (curr && strcmp(curr->key, key)) {
5ae59b95
 		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. */
d98d6fdb
 static struct onas_hnode *onas_hashnode_init(void) {
5ae59b95
 	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. */
d98d6fdb
 static struct onas_lnode *onas_listnode_init(void) {
5ae59b95
 	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);
47a544dc
 	child->dirname = cli_strndup(dirname, n);
5ae59b95
 
 	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;
19fbfac1
 	node->prev = tail->prev;
5ae59b95
 
 	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);
 
d98d6fdb
 	while ((curr = curr->next)) {
5ae59b95
 		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;
eb16d133
 	char *ret = NULL;
5ae59b95
 
 	while(idx >= 0 && pathname[idx] != '/') {
 		idx--;
 	}
 
eb16d133
 	if (idx == 0) {
 		idx++;
 	}
 
47a544dc
 	ret = cli_strndup(pathname, idx);
eb16d133
 	if (!ret) {
 		errno = ENOMEM;
 		return NULL;
 	}
 
 	return ret;
5ae59b95
 }
 
 /* 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) {
eb16d133
 
5ae59b95
 	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);
eb16d133
 	int ret = 0;
5ae59b95
 
 	if(idx <= 0) return CL_SUCCESS;
 
19fbfac1
 	if(onas_ht_get(ht, prntpath, prntlen, &elem) != CL_SUCCESS) return CL_EARG;
eb16d133
 
5ae59b95
 	hnode = elem->data;
 
d98d6fdb
 	if ((ret = onas_rm_listnode(hnode->childhead, &(childpath[idx])))) return CL_EARG;
eb16d133
 
 	return CL_SUCCESS;
5ae59b95
 }
 
 /* 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;
eb16d133
 	int ftspopts = FTS_PHYSICAL | FTS_XDEV;
5ae59b95
 	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);
 
d98d6fdb
 	char * const pathargv[] = { (char*) pathname, NULL };
9a1b6ef5
 	if (!(ftsp = _priv_fts_open(pathargv, ftspopts, NULL))) {
5ae59b95
 		logg("!ScanOnAccess: Could not open '%s'\n", pathname);
 		return CL_EARG;
 	}
 
9a1b6ef5
 	while((curr = _priv_fts_read(ftsp))) {
5ae59b95
 
 		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;
47a544dc
 				hnode->pathname = cli_strndup(curr->fts_path, hnode->pathlen);
5ae59b95
 
 				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;
 		}
 
9a1b6ef5
 		if((childlist = _priv_fts_children(ftsp, 0))) {
5ae59b95
 			do {
0bde9405
 				if (childlist->fts_info == FTS_D) {
5ae59b95
 					if(CL_EMEM == onas_add_hashnode_child(hnode, childlist->fts_name))
 						return CL_EMEM;
eb16d133
 				}
5ae59b95
 
d98d6fdb
 			} while ((childlist = childlist->fts_link));
5ae59b95
 		}
 
 		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;
 	}
 
9a1b6ef5
 	_priv_fts_close(ftsp);
5ae59b95
 	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;
eb16d133
 
19fbfac1
 		prntlen = strlen(prntname);
eb16d133
 		if(onas_ht_rm_child(ht, prntname, prntlen, pathname, len)) return CL_EARG;
 
5ae59b95
 		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);
637d2461
 		if (child_path == NULL)
 			return CL_EMEM;
5ae59b95
 		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;
 }
f87f08cb
 #endif