security/talpa/Kconfig | 14 +++ security/talpa/Makefile | 1 + security/talpa/talpa.h | 49 ++++++++++ security/talpa/talpa_common.c | 151 +++++++++++++++++++++++++++++++ security/talpa/talpa_evaluation_calls.h | 7 ++ security/talpa/talpa_fs_exclude.c | 104 +++++++++++++++++++++ 6 files changed, 326 insertions(+), 0 deletions(-) diff --git a/security/talpa/Kconfig b/security/talpa/Kconfig index 2486bfa..4fc22ad 100644 --- a/security/talpa/Kconfig +++ b/security/talpa/Kconfig @@ -38,6 +38,20 @@ config TALPA_OPERATION_EXCLUSION If you are unsure how to answer this question, answer Y. +config TALPA_FS_EXCLUDE + bool "Filesystem type exclusions" + depends on TALPA + default y + help + This filter adds the ability to exclude some filesystem types + from interception. + + It is generally desireable to exclude things like proc, sysfs + etc. + + If you are unsure how to answer this question, answer Y. + + config TALPA_THREAD_EXCLUSION bool "Thread exclusions" depends on TALPA diff --git a/security/talpa/Makefile b/security/talpa/Makefile index a01f8ce..2abaaa2 100644 --- a/security/talpa/Makefile +++ b/security/talpa/Makefile @@ -10,4 +10,5 @@ talpa-y := talpa_interceptor.o \ talpa-$(CONFIG_TALPA_CACHE) += talpa_cache.o talpa-$(CONFIG_TALPA_OPERATION_EXCLUSION) += talpa_operation_exclude.o +talpa-$(CONFIG_TALPA_FS_EXCLUDE) += talpa_fs_exclude.o talpa-$(CONFIG_TALPA_THREAD_EXCLUSION) += talpa_thread_exclude.o diff --git a/security/talpa/talpa.h b/security/talpa/talpa.h index 99c1cd7..b151723 100644 --- a/security/talpa/talpa.h +++ b/security/talpa/talpa.h @@ -19,6 +19,8 @@ #define __TALPA_H__ #include +#include +#include #include /* Talpa filter interface definitions. */ @@ -111,4 +113,51 @@ extern ssize_t talpa_generic_get_ulong(struct talpa_configuration *cfg, char *bu extern ssize_t talpa_generic_set_ulong(struct talpa_configuration *cfg, char *buf, size_t len); extern ssize_t talpa_generic_get_long(struct talpa_configuration *cfg, char *buf, size_t len); +#if defined CONFIG_TALPA_FS_EXCLUDE +/** + * struct talpa_path_inclexcl - internal path inclusion or exclusion record + * @path_len:length of this path + * @subdir:true if this exclusion should match sub-directories, flase for exact match + * @path:actual path + * @head:list head for linking + */ +struct talpa_path_inclexcl { + size_t path_len; + unsigned int subdir; + char *path; + struct list_head head; +}; + +/** + * struct talpa_path_list - internal list of path inclusions or exclusions + * @list: list head + * @mutex: mutex protecting list modifications + */ +struct talpa_path_list { + struct list_head list; + struct mutex mutex; +}; + +#define TALPA_PATH_LIST(name) \ + struct talpa_path_list name = { \ + .list = LIST_HEAD_INIT(name.list), \ + .mutex = __MUTEX_INITIALIZER(name.mutex), \ + } + +/** + * talpa_match_path - tries to match a path in a list + * @path: path to match + * @list: list of paths to match agains + * + * Returns non-zero if match is found. + */ +extern unsigned int talpa_match_path(const char *path, struct talpa_path_list *list); + +/* Generic path configuration callbacks. */ +extern ssize_t talpa_path_list(struct talpa_configuration *cfg, char *buf, size_t len); +extern int talpa_path_list_empty(struct talpa_path_list *list); +extern ssize_t talpa_path_add(struct talpa_configuration *cfg, char *buf, size_t len); +extern ssize_t talpa_path_remove(struct talpa_configuration *cfg, char *buf, size_t len); +#endif + #endif /* __TALPA_H__ */ diff --git a/security/talpa/talpa_common.c b/security/talpa/talpa_common.c index 03a3e96..7114428 100644 --- a/security/talpa/talpa_common.c +++ b/security/talpa/talpa_common.c @@ -17,7 +17,10 @@ */ #include #include +#include #include +#include +#include #include #include @@ -53,3 +56,151 @@ ssize_t talpa_generic_get_long(struct talpa_configuration *cfg, char *buf, size_ return ret; } + +#if defined CONFIG_TALPA_FS_EXCLUDE +static struct kmem_cache *path_list_cache; + +unsigned int talpa_match_path(const char *path, struct talpa_path_list *list) +{ + unsigned int matched = 0; + struct talpa_path_inclexcl *ie; + size_t path_len; + + path_len = strlen(path); + rcu_read_lock(); + list_for_each_entry_rcu(ie, &list->list, head) { + if (path_len < ie->path_len) { + /* Can't ever match if path is shorter than rule. */ + continue; + } else if (ie->subdir && !strncmp(ie->path, path, ie->path_len)) { + /* Subdir matches if first part of the path matches. */ + matched++; + break; + } else if (ie->path_len == path_len && !strcmp(ie->path, path)) { + /* Explicit inclusion must match the whole path. */ + matched++; + break; + } + } + rcu_read_unlock(); + + return matched; +} + +int talpa_path_list_empty(struct talpa_path_list *list) +{ + return list_empty(&list->list); +} + +ssize_t talpa_path_list(struct talpa_configuration *cfg, char *buf, size_t len) +{ + struct talpa_path_list *list = (struct talpa_path_list *)cfg->data; + struct talpa_path_inclexcl *ie; + int ret; + size_t count = 0; + + rcu_read_lock(); + list_for_each_entry_rcu(ie, &list->list, head) { + ret = snprintf(buf, len, "%s\n", ie->path); + if (ret > 0) { + count += ret; + len -= ret; + buf += ret; + } + } + rcu_read_unlock(); + + return count; +} + +ssize_t talpa_path_add(struct talpa_configuration *cfg, char *buf, size_t len) +{ + struct talpa_path_list *list = (struct talpa_path_list *)cfg->data; + struct talpa_path_inclexcl *ie = NULL; + struct talpa_path_inclexcl *old; + unsigned int found = 0; + + if (!path_list_cache) + return -ENOMEM; + + ie = kmem_cache_zalloc(path_list_cache, GFP_KERNEL); + if (!ie) + return -ENOMEM; + + ie->path = kstrndup(buf, len + 1, GFP_KERNEL); + if (!ie->path) { + kmem_cache_free(path_list_cache, ie); + return -ENOMEM; + } + + /* If ends with a slash mark as sub-directory rule. */ + if (ie->path[len-1] == '/') + ie->subdir = 1; + + ie->path_len = len; + + mutex_lock(&list->mutex); + /* Make sure not to add duplicates. */ + list_for_each_entry_rcu(old, &list->list, head) { + if (old->subdir == ie->subdir && old->path_len == ie->path_len && + !strcmp(old->path, ie->path)) { + found = 1; + break; + } + } + if (!found) + list_add_tail_rcu(&ie->head, &list->list); + else + len = -EEXIST; + mutex_unlock(&list->mutex); + + if (found) { + kfree(ie->path); + kmem_cache_free(path_list_cache, ie); + } + + return len; +} + +ssize_t talpa_path_remove(struct talpa_configuration *cfg, char *buf, size_t len) +{ + struct talpa_path_list *list = (struct talpa_path_list *)cfg->data; + struct talpa_path_inclexcl *old; + unsigned int found = 0; + + mutex_lock(&list->mutex); + list_for_each_entry_rcu(old, &list->list, head) { + if (!strcmp(old->path, buf)) { + found = 1; + break; + } + } + if (found) + list_del_rcu(&old->head); + else + len = -ESRCH; + mutex_unlock(&list->mutex); + + if (found) { + synchronize_sched(); + kfree(old->path); + kmem_cache_free(path_list_cache, old); + } + + return len; +} + +static __init int talpa_common_init(void) +{ + path_list_cache = kmem_cache_create("talpa_path_item", sizeof(struct talpa_path_inclexcl), + 0, 0, NULL); + if (!path_list_cache) { + pr_err("talpa: Failed to create path cache!\n"); + return -ENOMEM; + } + + return 0; +} + +__initcall(talpa_common_init); +#endif /* CONFIG_TALPA_*_{IN,EX}CLUDE */ diff --git a/security/talpa/talpa_evaluation_calls.h b/security/talpa/talpa_evaluation_calls.h index 2b635a1..8612430 100644 --- a/security/talpa/talpa_evaluation_calls.h +++ b/security/talpa/talpa_evaluation_calls.h @@ -4,6 +4,7 @@ #include "talpa_cache.h" enum talpa_action talpa_opexcl_examine(struct talpa_file_vetting *tfv); +enum talpa_action talpa_fs_exclude_examine(struct talpa_file_vetting *tfv); static inline int talpa_evaluation_calls(struct talpa_file_vetting *tfv) { @@ -34,5 +35,11 @@ static inline int talpa_evaluation_calls(struct talpa_file_vetting *tfv) return ret; #endif /* CONFIG_TALPA_OPERATION_EXCLUSION */ +#ifdef CONFIG_TALPA_FS_EXCLUDE + ret = talpa_fs_exclude_examine(tfv); + if (ret != TALPA_NEXT) + return ret; +#endif /* CONFIG_TALPA_FS_EXCLUDE */ + return ret; } diff --git a/security/talpa/talpa_fs_exclude.c b/security/talpa/talpa_fs_exclude.c new file mode 100644 index 0000000..78aa3bb --- /dev/null +++ b/security/talpa/talpa_fs_exclude.c @@ -0,0 +1,104 @@ +/* + * Copyright 2008 Sophos Plc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "talpa.h" + + +static unsigned long fs_excl_enabl; +static unsigned long fs_excl_auth = 1; +static TALPA_PATH_LIST(fs_excl_list); + +/* Filter examine function. */ +enum talpa_action talpa_fs_exclude_examine(struct talpa_file_vetting *tfv) +{ + struct inode *inode = tfv->file->f_dentry->d_inode; + const char *name = inode->i_sb->s_type->name; + + if (!fs_excl_enabl || talpa_path_list_empty(&fs_excl_list)) + return TALPA_NEXT; + + /* Do not proceed with vetting of this interception if + filesystem on which it lays is on the exclusion list. */ + if (talpa_match_path(name, &fs_excl_list)) { + tfv->authoritative = fs_excl_auth; + return TALPA_ALLOW; + } + + return TALPA_NEXT; +} + +static struct talpa_configuration talpa_exclude_cfg[] = { + { + .name = "enabled", + .mode = S_IRUSR|S_IWUSR|S_IRGRP, + .data = &fs_excl_enabl, + .get = talpa_generic_get_ulong, + .set = talpa_generic_set_ulong, + }, + { + .name = "authoritative", + .mode = S_IRUSR|S_IWUSR|S_IRGRP, + .data = &fs_excl_auth, + .get = talpa_generic_get_ulong, + .set = talpa_generic_set_ulong, + }, + { + .name = "list", + .data = &fs_excl_list, + .mode = S_IRUSR|S_IRGRP, + .get = talpa_path_list, + }, + { + .name = "add", + .data = &fs_excl_list, + .mode = S_IWUSR|S_IWGRP, + .set = talpa_path_add, + }, + { + .name = "remove", + .data = &fs_excl_list, + .mode = S_IWUSR|S_IWGRP, + .set = talpa_path_remove, + }, + { + }, +}; + +static __init int talpa_exclude_init(void) +{ + int ret; + + ret = talpa_register_configuration("fs_exclusions", talpa_exclude_cfg); + if (ret) + pr_err("talpa: Failed to register filesystem exclusion filter!\n"); + + return ret; +} + +__initcall(talpa_exclude_init);