From 7622133354404ba2db5f511dd413fff5ad815d39 Mon Sep 17 00:00:00 2001 From: "W. Michael Petullo" Date: Fri, 22 Apr 2016 08:22:23 -0400 Subject: [PATCH] security: simple information-flow-based security module for Linux SimpleFlow implements a very simple view of information flow within the Linux kernel. (We do not claim to approach HiStar, etc.) Under SimpleFlow, the system administrator designates some filesystem objects as "confidential" and some programs as "trusted" (SimpleFlow stores both using extended attributes). Any process not loaded from a trusted program will become "tainted" upon reading a confidential object. The kernel transfers this taint status from process to process as a result of inter-process communication (i.e., an untainted process reads from a tainted process over an IPC channel). If a tainted process writes to the network, the packet gets its RFC 3514 evil bit set; this allows for a variety of filtering or spoofing strategies which might help determine the intention of the principal who read the confidential data in the first place. Signed-off-by: W. Michael Petullo --- fs/pipe.c | 9 + include/net/ip.h | 1 + security/Kconfig | 6 + security/Makefile | 2 + security/simple-flow/Kconfig | 23 + security/simple-flow/Makefile | 1 + security/simple-flow/hooks.c | 1919 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1961 insertions(+) create mode 100644 security/simple-flow/Kconfig create mode 100644 security/simple-flow/Makefile create mode 100644 security/simple-flow/hooks.c diff --git a/fs/pipe.c b/fs/pipe.c index d2c45e1..48f9226 100644 --- a/fs/pipe.c +++ b/fs/pipe.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -411,6 +412,14 @@ pipe_read(struct kiocb *iocb, const struct iovec *_iov, atomic = !iov_fault_in_pages_write(iov, chars); redo: addr = ops->map(pipe, buf, atomic); + + error = security_file_permission(filp, MAY_READ); + if (error) { + if (!ret) + ret = error; + break; + } + error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic); ops->unmap(pipe, buf, addr); if (unlikely(error)) { diff --git a/include/net/ip.h b/include/net/ip.h index a68f838..27426d1 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -76,6 +76,7 @@ extern struct ip_ra_chain __rcu *ip_ra_chain; #define IP_CE 0x8000 /* Flag: "Congestion" */ #define IP_DF 0x4000 /* Flag: "Don't Fragment" */ #define IP_MF 0x2000 /* Flag: "More Fragments" */ +#define IP_EVIL 0x8000 /* Flag: "Evil" */ #define IP_OFFSET 0x1FFF /* "Fragment Offset" part */ #define IP_FRAG_TIME (30 * HZ) /* fragment lifetime */ diff --git a/security/Kconfig b/security/Kconfig index e9c6ac7..51b1be0 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -118,6 +118,7 @@ config LSM_MMAP_MIN_ADDR systems running LSM. source security/selinux/Kconfig +source security/simple-flow/Kconfig source security/smack/Kconfig source security/tomoyo/Kconfig source security/apparmor/Kconfig @@ -128,6 +129,7 @@ source security/integrity/Kconfig choice prompt "Default security module" default DEFAULT_SECURITY_SELINUX if SECURITY_SELINUX + default DEFAULT_SECURITY_SIMPLEFLOW if SECURITY_SIMPLEFLOW default DEFAULT_SECURITY_SMACK if SECURITY_SMACK default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR @@ -141,6 +143,9 @@ choice config DEFAULT_SECURITY_SELINUX bool "SELinux" if SECURITY_SELINUX=y + config DEFAULT_SECURITY_SIMPLEFLOW + bool "SimpleFlow" if SECURITY_SIMPLEFLOW=y + config DEFAULT_SECURITY_SMACK bool "Simplified Mandatory Access Control" if SECURITY_SMACK=y @@ -161,6 +166,7 @@ endchoice config DEFAULT_SECURITY string default "selinux" if DEFAULT_SECURITY_SELINUX + default "simple-flow" if DEFAULT_SECURITY_SIMPLEFLOW default "smack" if DEFAULT_SECURITY_SMACK default "tomoyo" if DEFAULT_SECURITY_TOMOYO default "apparmor" if DEFAULT_SECURITY_APPARMOR diff --git a/security/Makefile b/security/Makefile index c26c81e..c8c4d14 100644 --- a/security/Makefile +++ b/security/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_KEYS) += keys/ subdir-$(CONFIG_SECURITY_SELINUX) += selinux +subdir-$(CONFIG_SECURITY_SIMPLEFLOW) += simple-flow subdir-$(CONFIG_SECURITY_SMACK) += smack subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor @@ -18,6 +19,7 @@ obj-$(CONFIG_SECURITY) += security.o capability.o obj-$(CONFIG_SECURITYFS) += inode.o # Must precede capability.o in order to stack properly. obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o +obj-$(CONFIG_SECURITY_SIMPLEFLOW) += simple-flow/built-in.o obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o obj-$(CONFIG_AUDIT) += lsm_audit.o obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o diff --git a/security/simple-flow/Kconfig b/security/simple-flow/Kconfig new file mode 100644 index 0000000..0ef9b8b --- /dev/null +++ b/security/simple-flow/Kconfig @@ -0,0 +1,23 @@ +config SECURITY_SIMPLEFLOW +bool "SimpleFlow" +default y +help +A simple information-flow authorization model. + +config SECURITY_SIMPLEFLOW_TAINT_ACTION + int "SimpleFlow default taint action" + depends on SECURITY_SIMPLEFLOW + range 0 2 + default 1 + help + This option sets the default value for the kernel parameter + 'taint_action', which allows the action taken on tainted processes + to be set at boot. If this option is set to 0 (zero), the taint_action + kernel parameter will default to 0, placing SimpleFlow in log mode at + boot. If this option is set to 1 (one), the taint_action + kernel parameter will default to 1, placing SimpleFlow in evil_bit mode at + boot. If this option is set to 2 (two), the taint_action + kernel parameter will default to 2, placing SimpleFlow in kill mode at + boot (this mode is currently unsupported). + + If you are unsure how to answer this question, answer 1. diff --git a/security/simple-flow/Makefile b/security/simple-flow/Makefile new file mode 100644 index 0000000..9e450c8 --- /dev/null +++ b/security/simple-flow/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SECURITY_SIMPLEFLOW) += hooks.o diff --git a/security/simple-flow/hooks.c b/security/simple-flow/hooks.c new file mode 100644 index 0000000..ce1593b --- /dev/null +++ b/security/simple-flow/hooks.c @@ -0,0 +1,1919 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* FIXME: This is private to the ipc directory, so we have to redefine it! */ +#define IPC_SHM_IDS 2 + +/* The original template for this module was from code + * associated with Trent Jaeger's CSE544 course at Penn State. + */ + +#define MOD "SimpleFlow" +#define SIMPLE_FLOW_XATTR_NAME_TAINTED "security.simple-flow.confidential" +#define SIMPLE_FLOW_XATTR_MAX_TAINTED sizeof("inherited") /* "true" or "inherited". */ +#define SIMPLE_FLOW_XATTR_NAME_TRUSTED "security.simple-flow.trusted" +#define SIMPLE_FLOW_XATTR_MAX_TRUSTED sizeof("true") + +MODULE_LICENSE("GPL"); + +extern struct security_operations *security_ops; + +/* Configure constraints on tainted processes: + * + * taint_action=[mode] + * + * The mode can be one of: + * log: Permit everything, but log actions related to tainted processes. + * evil_bit: Set evil bit on packets generated by tainted processes. + * kill: Kill a tainted process which attempts to write to the network. + * + * SimpleFlow will log regardless of the selected action. + */ +enum simple_flow_mode +{ + SIMPLE_FLOW_MODE_LOG = 0, + SIMPLE_FLOW_MODE_EVIL_BIT = 1, + SIMPLE_FLOW_MODE_KILL = 2, + SIMPLE_FLOW_MODE_INVALID = 3, +}; + +/* IPv6 does not have an "evil bit," so we abuse the flow label field and + * define evil IPv6 traffic as traffic that contains this flow label. + * See RFC 6437. The evil flow label is 0xBAD1E. + */ +static bool +simple_flow_ipv6_flow_label_is_evil(__u8 *label) +{ + /* Check to ensure odd ipv6hdr fields have not changed. + * The flow_lbl "steals" one nibble from the traffic + * class field, so the kernel has to sort things out + * when it uses either. + */ + BUG_ON(offsetof(struct ipv6hdr, flow_lbl) != 1); + + return (label[0] & 0x0F) == 0x0B + && label[1] == 0xAD + && label[2] == 0x1E; +} + +/* See comments is simple_flow_ipv6_flow_label_is_evil. */ +static void +simple_flow_ipv6_flow_label_set_evil(__u8 *label) +{ + BUG_ON(offsetof(struct ipv6hdr, flow_lbl) != 1); + + label[0] &= 0xf0; + label[0] |= 0x0B; + label[1] = 0xAD; + label[2] = 0x1E; +} + +/* Boot-time configuration. */ +static unsigned long simple_flow_taint_action = CONFIG_SECURITY_SIMPLEFLOW_TAINT_ACTION; +module_param_named(taint_action, simple_flow_taint_action, ulong, S_IRUSR); + +static int __init simple_flow_taint_action_setup(char *str) +{ + unsigned long taint_action; + int error = strict_strtoul(str, 0, &taint_action); + if (! error) { + if (taint_action < SIMPLE_FLOW_MODE_INVALID) { + simple_flow_taint_action = taint_action; + } else { + printk(KERN_WARNING MOD ": invalid taint action: %lu; using default", taint_action); + } + } else { + printk(KERN_WARNING MOD ": cannot convert %s to an integer.", str); + } + return 1; +} + +__setup("taint_action=", simple_flow_taint_action_setup); + +/* Security context for an inode. + * We think each file-like object in the kernel has a single inode, and + * references to this single inode are shared by each object in the kernel + * which has a reference to the file-like object. For example, if processes + * P1 and P2 both have references to a pipe, then the objects within the + * kernel which manage this pipe contain references to the same inode object. + * This is important because we want a single security context to be avaliable + * to both P1 and P2. + */ +struct i_security { + bool owner_tainted; +}; + +/* Security context for a socket. + * It is not clear which task owns a socket when a packet is sent on it. Thus + * we set a socket's security context at the time of its creation. + * + * We used to rely on i_security for Unix-socket taint tacking. However, if the + * peer process exits before a process reads from a Unix socket, then the + * struct sock associated with the peer process no longer exists. Without + * this, the kernel cannot trace back to the peer process's inode structure. + * Thus we explicitly track whether a socket it tainted directly in its + * sk_security structure. + */ +struct sk_security { + struct task_struct *task; + bool owner_tainted; +}; + +/* Security context for a message queue. */ +struct mq_security { + bool owner_tainted; +}; + +/* Security context for shared memory. */ +struct shm_security { + bool owner_tainted; +}; + +struct shm_list_head { + struct task_struct *task; + struct list_head list; +}; + +struct shm_list_node { + int id; + struct shmid_kernel *shmid; + struct list_head list; +}; + +struct process_list_node { + struct task_struct *process; + struct list_head list; +}; + +/* Security context for a task/process. + * If a process is tainted, then: + * (1) the kernel will carefully log the activity of the process, + * (2) the kernel will set the evil bit on packets it produces, or + * (3) the kernel will kill the process if it writes to the network. + */ +struct t_security { + bool tainted; + bool trusted; +}; + +static int +simple_flow_cred_alloc_blank(struct cred *cred, gfp_t priority) +{ + struct t_security *tsec; + + BUG_ON(NULL == cred); + + tsec = kzalloc(sizeof(*tsec), priority); + if(NULL == tsec) { + return -ENOMEM; + } + + cred->security = tsec; + + return 0; +} + +static void +simple_flow_cred_free(struct cred *cred) +{ + BUG_ON(NULL == cred); + + if (NULL != cred->security) { + kfree(cred->security); + cred->security = NULL; + } + +} + +static void +simple_flow_cred_init_security(void) +{ + int ret; + + BUG_ON(NULL == current); + + ret = simple_flow_cred_alloc_blank((struct cred *) current->cred, GFP_KERNEL); + if (0 != ret) { + panic(MOD ": Failed to initialize initial task.\n"); + } +} + +/* Is the given process in user-space (as opposed to a kernel thread)? */ +static bool +is_user_process(struct task_struct *task) +{ + return task != NULL && task->mm != NULL; +} + +static int +simple_flow_cred_prepare(struct cred *cred, const struct cred *oldcred, gfp_t priority) +{ + int fnval = 0; + struct t_security *tsec = NULL; + char tcomm[sizeof(current->comm)]; + + BUG_ON(NULL == oldcred); + + if (NULL == oldcred->security) { + if (is_user_process(current)) { + get_task_comm(tcomm, current); + printk(KERN_WARNING MOD ": old user-space task %s has " + "no security context!\n", + tcomm); + } + + /* If we didn't get a printk above, then the current process + * should be a kernel thread which appropriately does not have + * a security contect. Create a blank one for the child process + * we are in the process of creating. + */ + tsec = kzalloc(sizeof(*tsec), priority); + if (NULL == tsec) { + fnval = -ENOMEM; + goto done; + } + } else { + tsec = kmemdup(oldcred->security, sizeof(*tsec), priority); + if (NULL == tsec) { + fnval = -ENOMEM; + goto done; + } + } + +done: + BUG_ON(NULL == cred); + BUG_ON(NULL != cred->security); + + cred->security = tsec; + + return fnval; +} + +static void +simple_flow_cred_transfer(struct cred *cred, const struct cred *oldcred) +{ + BUG_ON(NULL == cred->security); + BUG_ON(NULL == oldcred->security); + + *(struct t_security *) cred->security = *(struct t_security *) oldcred->security; +} + +/* Returns true if the process comes from a trusted program on disk; + * else returns false. + */ +static bool +simple_flow_process_trusted(struct task_struct *task) +{ + struct t_security *tsec; + bool is_trusted = false; + + BUG_ON(NULL == task); + + tsec = rcu_dereference_protected(task->cred, 1)->security; + if (NULL == tsec) { + goto done; + } + + is_trusted = tsec->trusted; + +done: + return is_trusted; +} + +static void simple_flow_process_set_taint(struct task_struct *task, char *path); +/* We presently have a function call loop: + * + * shm_shmat\ file_open file_permission + * | \ | | + * | +------------------------|-----------------+ + * \ / \ / + * + + + * ----> shm_set_taint process_set_taint + * | | | | ^ | | + * | | | | / \ | | + * | | | | (for each process | | | | (for each of process's + * | | | | attached to shm.) | | | | shm. segments.) + * | | | | | | | | + * | | [ process_build_list ] | | [ shm_build_list ] + * | | | | | | | | + * | | -------------------------- | | + * | | | | + * --------------------------------------------- + * + * TODO: Does this threaten the kernel's stack size? + */ + +/* Invoked by rmap_walk; build a list of processes which map a shared-memory segment. */ +static int +simple_flow_process_build_list(struct page *page, struct vm_area_struct *vma, + unsigned long addr, void *_list_head) +{ + int fnval = SWAP_AGAIN; + struct list_head *list_head = _list_head; + char tcomm[sizeof(current->comm)]; + + BUG_ON(NULL == vma); + BUG_ON(NULL == vma->vm_mm); + BUG_ON(NULL == vma->vm_mm->owner); + + get_task_comm(tcomm, vma->vm_mm->owner); + + if (simple_flow_process_trusted(vma->vm_mm->owner)) { + printk(KERN_INFO MOD ": %s trusted; not tainting " + "despite attached to tainted shared-memory segment\n", + tcomm); + } else { + struct process_list_node *node; + + printk(KERN_INFO MOD ": %s attached to tainted shared-memory segment.\n", tcomm); + + node = kmalloc(sizeof(*node), GFP_ATOMIC); + if (NULL == node) { + /* FIXME: What to do? */ + goto done; + } + + /* FIXME: What if this process exits before we act on this? */ + node->process = vma->vm_mm->owner; + + list_add(&(node->list), list_head); + } + +done: + return fnval; +} + +/* Invoked by rmap_walk; determine if process maps shared-memory segment. */ +static int +simple_flow_does_process_map_shm(struct page *page, struct vm_area_struct *vma, + unsigned long addr, void *_task) +{ + int fnval = SWAP_AGAIN; + char tcomm[sizeof(current->comm)]; + struct task_struct *task = _task; + + if (vma->vm_mm->owner == task) { + /* Process maps shm. */ + get_task_comm(tcomm, task); + printk(KERN_INFO MOD ": %s maps shared-memory segment.\n", tcomm); + fnval = SWAP_SUCCESS; + } + + return fnval; +} + +/* Invoked by idr_for_each; build a list of all shared-memory segments. */ +static int +simple_flow_shm_build_list(int id, void *_shm_perm, void *_shm_list_head) +{ + int fnval = 0; + struct kern_ipc_perm *shm_perm = _shm_perm; + struct shmid_kernel *shmid; + struct shm_list_head *shm_list_head = _shm_list_head; + struct shm_list_node *node; + + BUG_ON(NULL == shm_perm); + BUG_ON(NULL == shm_list_head); + + shmid = container_of(shm_perm, struct shmid_kernel, shm_perm); + + node = kmalloc(sizeof(*node), GFP_ATOMIC); + if (NULL == node) { + fnval = -ENOMEM; + goto done; + } + + node->id = id; + node->shmid = shmid; + + list_add(&(node->list), &(shm_list_head->list)); + +done: + + return fnval; +} + +static void +simple_flow_shm_set_taint(int id, struct shmid_kernel *shmid) { + struct shm_security *shmsec; + + BUG_ON(NULL == shmid); + BUG_ON(NULL == shmid->shm_perm.security); + + /* Taint the shared memory. */ + shmsec = shmid->shm_perm.security; + BUG_ON(NULL == shmsec); + + if (! shmsec->owner_tainted) { + struct page *page; + struct address_space *mapping; + int found; + char tcomm[sizeof(current->comm)]; + struct list_head list_head; + struct list_head *pos, *q; + + INIT_LIST_HEAD(&list_head); + + get_task_comm(tcomm, current); + printk(KERN_INFO MOD ": %s tainted; mark shm %d\n", tcomm, id); + shmsec->owner_tainted = true; + + /* Taint the attached processes. */ + mapping = shmid->shm_file->f_mapping; + found = find_get_pages(mapping, 0, 1, &page); + if (1 != found) { + goto done; + } + + rmap_walk(page, simple_flow_process_build_list, &list_head); + release_pages(&page, 1, 0); + + list_for_each_safe(pos, q, &list_head) { + struct process_list_node *node = list_entry(pos, + struct process_list_node, + list); + simple_flow_process_set_taint(node->process, "shm"); + list_del(pos); + kfree(node); + } + } + +done: + return; +} + +/* Modify a process's security context to indicate the process is tainted. */ +static void +simple_flow_process_set_taint(struct task_struct *task, char *path) +{ + struct t_security *tsec; + char tcomm[sizeof(task->comm)]; + struct ipc_namespace *ns; + + BUG_ON(NULL == task); + BUG_ON(NULL == task->cred); + + get_task_comm(tcomm, task); + + tsec = rcu_dereference_protected(task->cred, 1)->security; + if (NULL == tsec) { + printk(KERN_ERR MOD ": %s has no security context!\n", tcomm); + goto done; + } + + if (! tsec->tainted) { + struct shm_list_head shm_list_head; + struct list_head *pos, *q; + + shm_list_head.task = task; + INIT_LIST_HEAD(&shm_list_head.list); + + printk(KERN_WARNING MOD ": tainting process running %s (pid: %d, euid: %d, " + "uid: %d) due to interaction with %s.\n", + tcomm, + task->pid, + __kuid_val(task_uid(task)), + __kuid_val(task->real_cred->uid), + path); + tsec->tainted = true; + + /* Taint any shared-memory this process is attached to, + * and also taint the already attached processes. + */ + ns = task->nsproxy->ipc_ns; + BUG_ON(NULL == ns); + + /* Build a list of all (i.e., system-wide) shared-memory segments. */ + if (0 != ns->ids[IPC_SHM_IDS].in_use) { + down_write(&ns->ids[IPC_SHM_IDS].rw_mutex); + if (ns->ids[IPC_SHM_IDS].in_use) { + /* Build list here; process it outside of + * critical section. Recursive nature of + * processing will otherwise cause deadlock. + */ + /* FIXME: shared memory not available until it + * is read from or written to, likely because + * of lazy mapping. Test programs currently + * go out of their way to read/write early. + */ + idr_for_each(&ns->ids[IPC_SHM_IDS].ipcs_idr, + &simple_flow_shm_build_list, + &shm_list_head); + } + up_write(&ns->ids[IPC_SHM_IDS].rw_mutex); + } + + /* Process the list. */ + list_for_each_safe(pos, q, &shm_list_head.list) { + int ret; + struct address_space *mapping; + struct page *page = NULL; + int found; + struct shm_list_node *node = list_entry(pos, + struct shm_list_node, + list); + + mapping = node->shmid->shm_file->f_mapping; + found = find_get_pages(mapping, 0, 1, &page); + if (1 == found) { + ret = rmap_walk(page, + simple_flow_does_process_map_shm, + shm_list_head.task); + if (SWAP_SUCCESS == ret) { + simple_flow_shm_set_taint(node->id, node->shmid); + } + } + + list_del(pos); + kfree(node); + + if (NULL != page) { + release_pages(&page, 1, 0); + } + } + } + +done: + return; +} + +static int +simple_flow_inode_alloc_security(struct inode *inode) +{ + int fnval = 0; + struct i_security *isec = NULL; + + BUG_ON(NULL == inode); + BUG_ON(NULL != inode->i_security); + + isec = kzalloc(sizeof(*isec), GFP_NOFS); + if(NULL == isec) { + fnval = -ENOMEM; + goto done; + } + +done: + inode->i_security = isec; + + return fnval; +} + +static void +simple_flow_inode_free_security(struct inode *inode) +{ + BUG_ON(NULL == inode); + + if (NULL != inode->i_security) { + kfree(inode->i_security); + inode->i_security = NULL; + } +} + +static int +simple_flow_check_xattr_perm(const char *name) +{ + if (! strncmp(name, XATTR_SECURITY_PREFIX, + sizeof XATTR_SECURITY_PREFIX - 1) + && ! capable(CAP_SYS_ADMIN)) { + /* patch, ls, and other mishandle -EPERM; + * see https://bugzilla.redhat.com/show_bug.cgi?id=1312575. + */ + return -ENODATA; + } + + return 0; +} + +static int +simple_flow_inode_getxattr(struct dentry *dentry, const char *name) +{ + return simple_flow_check_xattr_perm(name); +} + +/* See comments on simple_flow_inode_removexattr. */ +static int +simple_flow_inode_setxattr(struct dentry *dentry, + const char *name, + const void *value, + size_t size, + int flags) +{ + int fnval; + struct inode *inode = NULL; + struct i_security *isec = NULL; + + BUG_ON(NULL == dentry); + BUG_ON(NULL == dentry->d_inode); + BUG_ON(NULL == dentry->d_inode->i_security); + + /* First check for permission to remove. */ + fnval = simple_flow_check_xattr_perm(name); + if (0 != fnval) { + goto done; + } + + /* Next, update relavent data structure when required. */ + if (strcmp(name, SIMPLE_FLOW_XATTR_NAME_TAINTED)) { + goto done; + } + + inode = dentry->d_inode; + isec = inode->i_security; + + if (! memcmp(value, "true", sizeof("true"))) { + isec->owner_tainted = true; + } else { + isec->owner_tainted = false; + } + +done: + return fnval; +} + +static int +simple_flow_mmap_file(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) +{ + struct inode *inode = NULL; + struct i_security *isec; + + if (NULL == file) { + /* Mapping anonymous memory. */ + goto done; + } + + inode = file_inode(file); + BUG_ON(NULL == inode); + + isec = inode->i_security; + + if (NULL == isec) { + /* TODO: How can this happen? */ + goto done; + } + + if (isec->owner_tainted) { + static char buf[PATH_MAX]; + bool trusted = simple_flow_process_trusted(current); + char *path = d_path(&file->f_path, buf, PATH_MAX); + if (NULL == path) { + printk(KERN_ERR MOD ": path lookup failed\n"); + goto done; + } + + if (trusted) { + char tcomm[sizeof(current->comm)]; + + get_task_comm(tcomm, current); + + printk(KERN_INFO MOD ": %s mmap %s; not " + "tainting because %s is " + "trusted.\n", + tcomm, + path, + tcomm); + } else { + simple_flow_process_set_taint(current, path); + } + } + +done: + + return 0; +} + +static int +simple_flow_msg_queue_alloc_security(struct msg_queue *msq) +{ + int fnval = 0; + struct mq_security *mqsec = NULL; + + BUG_ON(NULL == msq); + BUG_ON(NULL != msq->q_perm.security); + + mqsec = kzalloc(sizeof(*mqsec), GFP_KERNEL); + if(NULL == mqsec) { + fnval = -ENOMEM; + goto done; + } + +done: + msq->q_perm.security = mqsec; + + return fnval; +} + +static void +simple_flow_msg_queue_free_security(struct msg_queue *msq) +{ + BUG_ON(NULL == msq); + + if (NULL != msq->q_perm.security) { + kfree(msq->q_perm.security); + msq->q_perm.security = NULL; + } +} + +static int +simple_flow_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg, int msqflg) +{ + struct t_security *tsec = NULL; + struct mq_security *mqsec = NULL; + char tcomm[sizeof(current->comm)]; + + BUG_ON(NULL == msq); + BUG_ON(NULL == msq->q_perm.security); + + tsec = current_security(); + if (NULL == tsec) { + if (is_user_process(current)) { + get_task_comm(tcomm, current); + printk(KERN_ERR MOD ": no security data " + "associated with user-" + "space %s for msgsnd\n", + tcomm); + } + goto done; + } + + mqsec = msq->q_perm.security; + if (tsec->tainted && ! mqsec->owner_tainted) { + get_task_comm(tcomm, current); + printk(KERN_INFO MOD ": %s tainted; mark message queue\n", tcomm); + mqsec->owner_tainted = true; + } + +done: + return 0; +} + +static int +simple_flow_msg_queue_msgrcv(struct msg_queue *msq, struct msg_msg *msg, + struct task_struct *target, + long type, int mode) +{ + char tcomm[sizeof(current->comm)]; + bool trusted = false; + struct mq_security *mqsec = NULL; + + BUG_ON(NULL == msq); + BUG_ON(NULL == msq->q_perm.security); + + trusted = simple_flow_process_trusted(current); + + mqsec = msq->q_perm.security; + if (mqsec->owner_tainted) { + if (trusted) { + get_task_comm(tcomm, current); + printk(KERN_INFO MOD ": %s trusted; not tainting despite msgrcv\n", + tcomm); + } else { + simple_flow_process_set_taint(current, "msg-queue"); + } + } + + return 0; +} + +/* If the "confidential" attribute is removed from a filesystem node, + * then the kernel must unset isec->owner_tainted. Otherwise, + * caching might cause the kernel data structure to become + * unsynchronized from the metadata on disk. A subsequent write + * by a tainted process will again set isec->owner_tainted. + * + * Note that this is only required in the case of an intersection + * between a filesystem node and an IPC channel, such as with a + * named pipe. See comments in simple_flow_file_permission. Recall + * that processes get tainted by *opening* confidential files, and + * isec->owner_tainted is not considered when considering the results of + * normal-file access. Considering reads, and thus isec->owner_tainted, + * is only necessary with IPC objects. + */ +static int +simple_flow_inode_removexattr(struct dentry *dentry, const char *name) +{ + int fnval; + struct inode *inode = NULL; + struct i_security *isec = NULL; + + BUG_ON(NULL == dentry); + BUG_ON(NULL == dentry->d_inode); + BUG_ON(NULL == dentry->d_inode->i_security); + + /* First check for permission to remove. */ + fnval = simple_flow_check_xattr_perm(name); + if (0 != fnval) { + goto done; + } + + /* Next, update relavent data structure when required. */ + if (strcmp(name, SIMPLE_FLOW_XATTR_NAME_TAINTED)) { + goto done; + } + + inode = dentry->d_inode; + isec = inode->i_security; + isec->owner_tainted = false; + +done: + return fnval; +} + +/* Set an extended attribute on a file. */ +static bool +simple_flow_setxattr(const struct file *file, const char *xattr_key, const char *xattr_val, int flags) +{ + bool fnval = false, locked = false; + int error = 0; + struct inode *inode = NULL; + struct dentry *dentry = NULL; + char *buf = NULL, *path = NULL; + + /* First, get the path associated with file. */ + + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (NULL == buf) { + printk(KERN_ERR MOD ": kcalloc failed\n"); + goto done; + } + + path = d_path(&file->f_path, buf, PATH_MAX); + if (NULL == path) { + printk(KERN_ERR MOD ": path lookup failed\n"); + goto done; + } + + dentry = d_find_alias(file_inode((struct file *) file)); + if (NULL == dentry) { + /* TODO: Probably not a normal file; how to know for sure? */ + goto done; + } + + inode = dentry->d_inode; + BUG_ON(NULL == inode); + + /* Comments in SELinux's hooks.c and use in fs/xattr.c seems + * to indicate this mutex is necessary. + */ + mutex_lock(&inode->i_mutex); + locked = true; + + error = __vfs_setxattr_noperm(dentry, + xattr_key, + xattr_val, + strlen(xattr_val) + 1, + flags); + + fnval = error >= 0; + + switch (error) { + case 0: /* No error. */ + break; + case -EEXIST: /* Should not happen due to flags above. */ + printk(KERN_WARNING MOD + ": did not like that xattr already exists in %s\n", + path); + goto done; + case -ENODATA: /* Should not happen due to flags above. */ + printk(KERN_WARNING MOD + ": did not like that xattr does not exist in %s\n", + path); + goto done; + case -EOPNOTSUPP: /* Filesystem does not support xattr. */ + printk(KERN_WARNING MOD + ": %s on a fs which does not support xattr\n", + path); + goto done; + case -EDQUOT: /* Xattr write would surpass disk quota. */ + printk(KERN_ERR MOD ": xattr write surpasses disk quota\n"); + goto done; + case -ENOSPC: /* Xattr write would surpass disk space. */ + printk(KERN_ERR MOD ": xattr write surpasses disk space\n"); + goto done; + case -EINVAL: /* E.g., sysfs supports only "security" namespace. */ + printk(KERN_WARNING MOD + ": xattr write %s from %s is invalid\n", + SIMPLE_FLOW_XATTR_NAME_TAINTED, + path); + goto done; + default: + printk(KERN_ERR MOD ": xattr write %s: error code %d unhandled\n", + path, + error); + goto done; + } + +done: + if (NULL != buf) { + kfree(buf); + } + + if (NULL != dentry) { + dput(dentry); + } + + if (locked) { + mutex_unlock(&inode->i_mutex); + } + + return fnval; +} + +/* Get an extended attribute from a file. */ +static bool +simple_flow_getxattr(void *xattr_val, const struct file *file, const char *xattr_key, int size) +{ + int error = 0; + bool fnval = false; + struct inode *inode = NULL; + struct dentry *dentry = NULL; + char *buf = NULL, *path = NULL; + + memset(xattr_val, 0x00, size); + + /* First, get the path associated with file. */ + + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (NULL == buf) { + printk(KERN_ERR MOD ": kcalloc failed\n"); + goto done; + } + + path = d_path(&file->f_path, buf, PATH_MAX); + if (NULL == path) { + printk(KERN_ERR MOD ": path lookup failed\n"); + goto done; + } + + /* Next, read the extended attribute. */ + + inode = file_inode((struct file *) file); + BUG_ON(NULL == inode); + + if (NULL == inode->i_op->getxattr) { + /* NOTE: This causes too many logs. + printk(KERN_WARNING MOD + ": %s on a fs which does not support xattr\n", + path); + */ + goto done; + } + + dentry = d_find_alias(inode); + if (NULL == dentry) { + printk(KERN_ERR MOD ": could not find dentry for %s\n", path); + goto done; + } + + error = inode->i_op->getxattr(dentry, + xattr_key, + xattr_val, + size); + + fnval = error >= 0; + + if (! fnval) { + switch (error) { + case -ENODATA: /* Key does not exist. */ + /* FIXME: too many messages: + * printk(KERN_INFO MOD ": %s has no "SIMPLE_FLOW_XATTR_NAME_TAINTED" xattr\n", path); + */ + break; + case -EOPNOTSUPP: /* Filesystem does not support xattr. */ + printk(KERN_WARNING MOD + ": %s on a fs which does not support xattr\n", + path); + break; + case -ERANGE: /* Xattr value too large for buffer. */ + printk(KERN_ERR MOD ": xattr value to large for buffer\n"); + break; + case -EINVAL: /* E.g., sysfs supports only "security" namespace. */ + printk(KERN_WARNING MOD + ": xattr read %s from %s is invalid\n", + SIMPLE_FLOW_XATTR_NAME_TAINTED, + path); + break; + default: + printk(KERN_ERR MOD ": xattr read %s: error code %d unhandled\n", + path, + error); + break; + } + } +done: + if (NULL != buf) { + kfree(buf); + } + + if (NULL != dentry) { + dput(dentry); + } + + return fnval; +} + +/* Check to see if a file is inherited. Log if it is. Tainting left to reads, + * mmaps, etc. + */ +static int +simple_flow_file_open(struct file *file, const struct cred *cred) +{ + char xattr_val[SIMPLE_FLOW_XATTR_MAX_TAINTED]; + static char buf[PATH_MAX]; + bool inherited = false; + + BUG_ON(NULL == file); + + /* Next, read the SIMPLE_FLOW_XATTR_NAME_TAINTED extended attribute. */ + simple_flow_getxattr(xattr_val, file, SIMPLE_FLOW_XATTR_NAME_TAINTED, + SIMPLE_FLOW_XATTR_MAX_TAINTED); + inherited = ! memcmp(xattr_val, "inherited", sizeof("inherited")); + + if (! simple_flow_process_trusted(current)) { + if (inherited) { + char tcomm[sizeof(current->comm)]; + char *path = d_path(&file->f_path, buf, PATH_MAX); + if (NULL == path) { + printk(KERN_ERR MOD ": path lookup failed\n"); + goto done; + } + + get_task_comm(tcomm, current); + + printk(KERN_WARNING MOD ": %s opened file %s with inherited " + "confidentiality!\n", + tcomm, + path); + } + } + +done: + + return 0; +} + +static int +simple_flow_bprm_set_creds(struct linux_binprm *bprm) +{ + int fnval = 0; + struct t_security *tsec; + char *buf = NULL, *path = NULL; + char xattr_val[SIMPLE_FLOW_XATTR_MAX_TRUSTED]; + + BUG_ON(NULL == bprm); + BUG_ON(NULL == bprm->file); + BUG_ON(NULL == bprm->cred); + + /* Check capabilities first. This seems to grant + * setuid-bit functionality. + */ + fnval = cap_bprm_set_creds(bprm); + if (0 != fnval) { + goto done; + } + + /* Depend on initial program/script, not interpreter. */ + if (bprm->cred_prepared) { + goto done; + } + + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (NULL == buf) { + printk(KERN_ERR MOD ": kcalloc failed\n"); + goto done; + } + + path = d_path(&bprm->file->f_path, buf, PATH_MAX); + if (NULL == path) { + printk(KERN_ERR MOD ": path lookup failed\n"); + goto done; + } + + if (NULL == bprm->cred->security) { + printk(KERN_WARNING MOD ": task from %s has no security context!\n", path); + goto done; + } + + simple_flow_getxattr(xattr_val, + bprm->file, + SIMPLE_FLOW_XATTR_NAME_TRUSTED, + SIMPLE_FLOW_XATTR_MAX_TRUSTED); + + tsec = bprm->cred->security; + tsec->trusted = ! memcmp(xattr_val, "true", sizeof("true")); + + if (tsec->trusted) { + char tcomm[sizeof(current->comm)]; + get_task_comm(tcomm, current); + printk(KERN_INFO MOD ": %s loaded %s which is trusted\n", tcomm, path); + tsec->tainted = false; + } else if (tsec->tainted) { + char tcomm[sizeof(current->comm)]; + get_task_comm(tcomm, current); + printk(KERN_INFO MOD ": tainted %s exec'ed tainted %s\n", tcomm, path); + } + +done: + if (NULL != buf) { + kfree(buf); + } + + return fnval; +} + +static int +simple_flow_shm_alloc_security(struct shmid_kernel *shmid) +{ + int fnval = 0; + struct shm_security *shmsec = NULL; + + BUG_ON(NULL == shmid); + BUG_ON(NULL != shmid->shm_perm.security); + + shmsec = kzalloc(sizeof(*shmsec), GFP_KERNEL); + if(NULL == shmsec) { + fnval = -ENOMEM; + goto done; + } + +done: + shmid->shm_perm.security = shmsec; + + return fnval; +} + +static void +simple_flow_shm_free_security(struct shmid_kernel *shmid) +{ + BUG_ON(NULL == shmid); + + if (NULL != shmid->shm_perm.security) { + kfree(shmid->shm_perm.security); + shmid->shm_perm.security = NULL; + } +} + +static int +simple_flow_shm_shmat(struct shmid_kernel *shmid, + char __user *shmaddr, int shmflg) +{ + struct t_security *tsec = NULL; + struct shm_security *shmsec = NULL; + char tcomm[sizeof(current->comm)]; + + BUG_ON(NULL == shmid); + BUG_ON(NULL == shmid->shm_perm.security); + + tsec = current_security(); + if (NULL == tsec) { + if (is_user_process(current)) { + get_task_comm(tcomm, current); + printk(KERN_ERR MOD ": no security data " + "associated with user-" + "space %s for shmat\n", + tcomm); + } + goto done; + } + + shmsec = shmid->shm_perm.security; + + /* If process is tainted, then we need to taint the shared memory + * and also taint all of the other processes attached to the shared + * memory. We cannot rely on mediating reads and writes of shared + * memory because such reads and writes do not require system calls. + * Hence we are conservative. + */ + /* FIXME: Why already tainted for "later" test? + * if (true == tsec->tainted && false == shmsec->owner_tainted) { + */ + if (tsec->tainted) { + /* What should -1 be? */ + simple_flow_shm_set_taint(-1, shmid); + } + + /* Taint processs if shared memory tainted. */ + if (! tsec->tainted && shmsec->owner_tainted) { + if (simple_flow_process_trusted(current)) { + get_task_comm(tcomm, current); + printk(KERN_INFO MOD ": %s trusted; not tainting " + "despite shmat\n", + tcomm); + } else { + simple_flow_process_set_taint(current, "shm"); + } + } + +done: + return 0; +} + +/* Given an inode associated with a Unix socket, look at the other end. + * If its owner is tainted, then set this side to have the same status. + */ +static void +simple_flow_copy_peer_status(struct inode *inode) +{ + struct socket *socket = NULL; + struct sock *other = NULL; + struct sk_security *sksec = NULL; + struct sk_security *other_sksec = NULL; + + BUG_ON(false == S_ISSOCK(inode->i_mode)); + BUG_ON(AF_UNIX != SOCKET_I(inode)->sk->sk_family); + + socket = SOCKET_I(inode); + BUG_ON(NULL == socket); + BUG_ON(NULL == socket->sk); + + other = unix_peer_get(socket->sk); + if (NULL == other) { + /* TODO: How can this happen? */ + goto done; + } + + sksec = socket->sk->sk_security; + other_sksec = other->sk_security; + sksec->owner_tainted = other_sksec->owner_tainted; + +done: + if (NULL != other) { + sock_put(other); + } + + return; +} + +/* Upon a file operation, inspect file operand. + * On read: If an owner is tainted, then taint the calling process. This + * is necessary because a "file" might be an IPC channel. If this is + * the case, the process on one side of the channel should become + * tainted if the other has become tainted since opening/creating + * the channel in the first place. + * On write: If caller is tainted, indicate this in the inode's security + * context. See the notes below for the exceptions. + */ +static int +simple_flow_file_permission(struct file *file, int mask) +{ + struct i_security *isec; + struct inode *inode = NULL; + char *path = NULL; + char tcomm[sizeof(current->comm)]; + + /* Avoid kcalloc; was getting: BUG: scheduling while atomic: dracut ... + * __kmalloc calls _cond_restart. + */ + static char buf[PATH_MAX]; + + BUG_ON(NULL == file); + + inode = file_inode(file); + BUG_ON(NULL == inode); + + isec = inode->i_security; + + path = d_path(&file->f_path, buf, PATH_MAX); + if (NULL == path) { + printk(KERN_ERR MOD ": path lookup failed\n"); + goto done; + } + + get_task_comm(tcomm, current); + + if (mask & MAY_READ) { + /* Taint this process if file object is tainted. */ + bool trusted = false, should_taint = false; + + if (NULL == isec) { + /* TODO: How can this happen? */ + goto done; + } + + trusted = simple_flow_process_trusted(current); + + if (S_ISSOCK(inode->i_mode)) { + struct socket *socket = NULL; + struct sk_security *sksec = NULL; + + socket = SOCKET_I(inode); + BUG_ON(NULL == socket); + BUG_ON(NULL == socket->sk); + + sksec = socket->sk->sk_security; + BUG_ON(NULL == sksec); + + if (AF_UNIX == socket->sk->sk_family) { + /* Recheck other end if a Unix-domain socket. */ + simple_flow_copy_peer_status(inode); + } + + if (sksec->owner_tainted) { + should_taint = true; + } + } else { + if (isec->owner_tainted) { + should_taint = true; + } + } + + if (should_taint) { + if (trusted) { + printk(KERN_INFO MOD ": %s read %s; not " + "tainting because %s is " + "trusted.\n", + tcomm, + path, + tcomm); + } else { + simple_flow_process_set_taint(current, path); + } + } + } + + if (mask & MAY_WRITE || mask & MAY_APPEND) { + bool trusted = false; + struct t_security *tsec; + + /* Taint file object if this process is tainted. */ + + if (NULL == isec) { + /* FIXME: too many messages: + * printk(KERN_WARNING MOD ": no security data associated with %s for write\n", path); + */ + goto done; + } + + trusted = simple_flow_process_trusted(current); + tsec = current_security(); + if (! trusted && isec->owner_tainted) { + printk(KERN_WARNING MOD ": possible integrity violation: " + "untrusted process %s wrote to %s\n", + tcomm, path); + } + + if (NULL == tsec) { + if (is_user_process(current)) { + printk(KERN_ERR MOD ": no security data " + "associated with user-" + "space %s for write\n", + tcomm); + } + goto done; + } + + if (tsec->tainted && ! isec->owner_tainted) { + /* Only mark files which might represent an IPC + * channel. We have decided to act conservatively + * and label files written to by tainted processes + * as "inherited" instead of "confidential." Inherited + * files will trigger logging, but not more decisive + * countermeasures (evil-bit packets, etc.). This will + * prevent files like .bash_history or Firefox's + * configuration from being set confidential within + * the kernel's data structures (even while the label + * they bear in the filesystem is "inherited"), leaving + * bash or Firefox unusable. IPC channels are not + * persistent in this way, so we can regulate them + * more tightly. + * + * Further note that inode caching causes the in-kernel + * data structure to dominate the filesystem label for + * a period of time. */ + if (S_ISFIFO(inode->i_mode)) { + printk(KERN_INFO MOD ": %s tainted; mark FIFO at %s\n", tcomm, path); + isec->owner_tainted = true; + } else if (S_ISSOCK(inode->i_mode)) { + struct socket *socket = NULL; + struct sk_security *sksec = NULL; + + socket = SOCKET_I(inode); + BUG_ON(NULL == socket); + BUG_ON(NULL == socket->sk); + + sksec = socket->sk->sk_security; + BUG_ON(NULL == sksec); + + printk(KERN_INFO MOD ": %s tainted; mark socket at %s\n", tcomm, path); + sksec->owner_tainted = true; + } + + /* Try to set confidentiality attribute. */ + if (! simple_flow_setxattr(file, + SIMPLE_FLOW_XATTR_NAME_TAINTED, + "inherited", 0)) { + printk(KERN_WARNING MOD ": could not set %s as inherited\n", + path); + } + + if (S_ISFIFO(inode->i_mode)) { + /* FIXME: This does not seem to work. See mail + * with subject "Walking a wait_queue_t list + * of tasks blocked on pipe. Unfortunately, + * the only other solution I have found is to + * modify pipe.c because there does not seem to + * be an LSM hook which covers this situation. + */ + + /* It is possible that a reader is already blocking + * on this FIFO, having already passed through + * simple_flow_file_permission on the read side, and thus + * missing the chance to realize the FIFO is about to go + * confidential. Thus we look for and modify blocked readers. + */ + /* simple_flow_taint_read_blocked(inode->i_pipe, path); */ + } + } + } + +done: + + return 0; +} + +static int +simple_flow_getprocattr(struct task_struct *p, char *name, char **value) +{ + int len = 0; + struct t_security *tsec = NULL; + + BUG_ON(NULL == p); + BUG_ON(NULL == name); + BUG_ON(NULL == value); + + if (! capable(CAP_SYS_ADMIN)) { + /* Attempt to conceal presence of SimpleFlow. */ + len = -ENODATA; + goto done; + } + + if (NULL == p->cred) { + len = -EINVAL; + goto done; + } + + tsec = p->cred->security; + if (NULL == tsec) { + len = -EINVAL; + goto done; + } + + *value = kmalloc(2 * sizeof(char), GFP_ATOMIC); + if(NULL == *value) { + len = -ENOMEM; + goto done; + } + + **value = tsec->tainted ? '1' : '0'; + (*value)[1] = '\n'; + len = 2; + +done: + return len; +} + +static int +simple_flow_setprocattr(struct task_struct *p, + char *name, void *value, size_t size) +{ + struct t_security *tsec = NULL; + char tcomm1[sizeof(current->comm)]; + char tcomm2[sizeof(current->comm)]; + + BUG_ON(NULL == p); + BUG_ON(NULL == name); + BUG_ON(NULL == value); + BUG_ON(NULL == current); + BUG_ON(NULL == current->cred); + + /* Expect "0\n" or "1\n". */ + if (2 != size) { + size = -EINVAL; + goto done; + } + + if (NULL == p->cred) { + size = -EINVAL; + goto done; + } + + tsec = p->cred->security; + if (NULL == tsec) { + size = -EINVAL; + goto done; + } + + if (! memcmp(value, "0\n", 2)) { + if (! uid_eq(current->cred->euid, GLOBAL_ROOT_UID)) { + size = -EPERM; + goto done; + } + + get_task_comm(tcomm1, current); + get_task_comm(tcomm2, p); + printk(KERN_WARNING MOD ": %s cleared taint of %s.\n", tcomm1, tcomm2); + + tsec->tainted = false; + } else if (! memcmp(value, "1\n", 2)) { + tsec->tainted = true; + } else { + size = -EINVAL; + goto done; + } + +done: + return size; +} + +/* Things like ping do not write on their socket. This will catch those + * cases. See "strace ping ...". + */ +static int +simple_flow_socket_sendmsg(struct socket *sock, struct msghdr *msg, + int size) +{ + BUG_ON(NULL == sock); + BUG_ON(NULL == sock->file); + + return simple_flow_file_permission(sock->file, MAY_WRITE); +} + +static int simple_flow_syslog(int type) +{ + int fnval; + + switch (type) { + case SYSLOG_ACTION_READ_ALL: /* Read last kernel messages */ + case SYSLOG_ACTION_SIZE_BUFFER: /* Return size of the log buffer */ + fnval = capable(CAP_SYS_ADMIN) ? 0 : -EPERM; + break; + default: + fnval = 0; + break; + } + + return fnval; +} + +/* Things like X11 do not write on their Unix socket. This will catch those + * cases. See "strace test-program-x11-pipe ...". + */ +static int +simple_flow_socket_recvmsg(struct socket *sock, struct msghdr *msg, + int size, int flags) +{ + BUG_ON(NULL == sock); + BUG_ON(NULL == sock->file); + + return simple_flow_file_permission(sock->file, MAY_READ); +} + +static int +simple_flow_sk_alloc_security(struct sock *sk, int family, gfp_t priority) +{ + struct sk_security *sksec; + + BUG_ON(NULL == sk); + + sksec = kzalloc(sizeof(*sksec), priority); + if (NULL == sksec) { + return -ENOMEM; + } + + sk->sk_security = sksec; + + return 0; +} + +static void +simple_flow_sk_free_security(struct sock *sk) +{ + BUG_ON(NULL == sk); + + if (NULL != sk->sk_security) { + struct sk_security *sksec = sk->sk_security; + if (NULL != sksec->task) { + put_task_struct(sksec->task); + } + kfree(sk->sk_security); + sk->sk_security = NULL; + } +} + +static void +simple_flow_sk_clone_security(const struct sock *sk, struct sock *newsk) +{ + struct sk_security *newsksec; + struct sk_security *sksec; + + BUG_ON(NULL == sk); + BUG_ON(NULL == newsk); + BUG_ON(NULL == sk->sk_security); + BUG_ON(NULL == newsk->sk_security); + + newsksec = newsk->sk_security; + sksec = sk->sk_security; + + if (NULL != sksec->task) { + get_task_struct(sksec->task); + } + + newsksec->task = sksec->task; + newsksec->owner_tainted = sksec->owner_tainted; +} + +/* Set a newly-created socket's security context based on the creating + * process. + */ +static int +simple_flow_socket_post_create(struct socket *sock, int family, int type, int protocol, int kern) +{ + struct t_security *tsec; + struct sk_security *sksec; + + BUG_ON(NULL == sock); + + if (NULL == sock->sk) { + /* TODO: Confirm this means this is NOT a network socket. */ + goto done; + } + + BUG_ON(NULL == sock->sk->sk_security); + BUG_ON(NULL == current); + BUG_ON(NULL == current->cred); + + tsec = current_security(); + if (NULL == tsec) { + if (is_user_process(current)) { + char tcomm[sizeof(current->comm)]; + get_task_comm(tcomm, current); + printk(KERN_ERR MOD ": user-space %s, which created " + "socket, has no security " + "context!\n", + tcomm); + } + goto done; + } + + get_task_struct(current); + sksec = sock->sk->sk_security; + sksec->task = current; + sksec->owner_tainted = tsec->tainted; + +done: + return 0; +} + +static int +simple_flow_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + bool is_evil = false; + struct sk_security *sksec = sk->sk_security; + + BUG_ON(NULL == sk); + BUG_ON(NULL == skb); + + if (sk->sk_family != PF_INET && sk->sk_family != PF_INET6) { + /* Only deal with IPv4 or IPv6 packets. */ + goto done; + } + + if (NULL == sksec) { + printk(KERN_WARNING MOD ": socket has no security context.\n"); + goto done; + } + + if (NULL == sksec->task) { + printk(KERN_WARNING MOD ": socket has no associated process.\n"); + goto done; + } + + is_evil = (sk->sk_family == PF_INET && ip_hdr(skb)->frag_off & htons(IP_EVIL)) + || (sk->sk_family == PF_INET6 && simple_flow_ipv6_flow_label_is_evil(ipv6_hdr(skb)->flow_lbl)); + if (SIMPLE_FLOW_MODE_EVIL_BIT == simple_flow_taint_action && is_evil) { + char tcomm[sizeof(sksec->task->comm)]; + struct t_security *tsec = NULL; + + get_task_comm(tcomm, sksec->task); + + printk(KERN_INFO MOD ": tainting socket %s (pid: %d, euid: %d, uid: %d) " + "due to incoming evil-bit packet.\n", + tcomm, + sksec->task->pid, + __kuid_val(task_uid(sksec->task)), + __kuid_val(sksec->task->real_cred->uid)); + + sksec->owner_tainted = true; + + tsec = rcu_dereference_protected(sksec->task->cred, 1)->security; + if (NULL == tsec) { + printk(KERN_ERR MOD ": %s has no security context!\n", tcomm); + goto done; + } + + tsec->tainted = true; + } + +done: + return 0; +} + +static struct +security_operations simple_flow_ops = { + .name = "simple_flow", + .cred_alloc_blank = simple_flow_cred_alloc_blank, + .cred_free = simple_flow_cred_free, + .cred_prepare = simple_flow_cred_prepare, + .cred_transfer = simple_flow_cred_transfer, + .file_open = simple_flow_file_open, + .file_permission = simple_flow_file_permission, + .getprocattr = simple_flow_getprocattr, + .setprocattr = simple_flow_setprocattr, + .inode_alloc_security = simple_flow_inode_alloc_security, + .inode_free_security = simple_flow_inode_free_security, + .inode_getxattr = simple_flow_inode_getxattr, + .inode_removexattr = simple_flow_inode_removexattr, + .inode_setxattr = simple_flow_inode_setxattr, + .mmap_file = simple_flow_mmap_file, + .msg_queue_alloc_security = simple_flow_msg_queue_alloc_security, + .msg_queue_free_security = simple_flow_msg_queue_free_security, + .msg_queue_msgrcv = simple_flow_msg_queue_msgrcv, + .msg_queue_msgsnd = simple_flow_msg_queue_msgsnd, + .bprm_set_creds = simple_flow_bprm_set_creds, + .shm_alloc_security = simple_flow_shm_alloc_security, + .shm_free_security = simple_flow_shm_free_security, + .shm_shmat = simple_flow_shm_shmat, + .sk_alloc_security = simple_flow_sk_alloc_security, + .sk_clone_security = simple_flow_sk_clone_security, + .sk_free_security = simple_flow_sk_free_security, + .socket_post_create = simple_flow_socket_post_create, + .socket_recvmsg = simple_flow_socket_recvmsg, + .socket_sendmsg = simple_flow_socket_sendmsg, + .socket_sock_rcv_skb = simple_flow_socket_sock_rcv_skb, + .syslog = simple_flow_syslog, +}; + +/* A netfilter function which sets the evil bit on an outgoing packet if the + * socket's owning process is tainted. + */ +static unsigned int simple_flow_ipv4_output(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + struct sk_security *sksec; + + if (NULL == skb->sk) { + /* Socket closed and we are merely sending FIN? */ + printk(KERN_WARNING MOD ": socket buffer has no socket.\n"); + goto done; + } + + if (NULL == skb->sk->sk_security) { + printk(KERN_WARNING MOD ": socket has no security context.\n"); + goto done; + } + + sksec = skb->sk->sk_security; + if (NULL == sksec->task) { + printk(KERN_WARNING MOD ": socket has no associated process.\n"); + goto done; + } + + if (simple_flow_taint_action == SIMPLE_FLOW_MODE_EVIL_BIT && sksec->owner_tainted) { + char tcomm[sizeof(current->comm)]; + get_task_comm(tcomm, sksec->task); + printk(KERN_INFO MOD ": setting evil bit on packet generated by %s " + "(pid: %d, euid: %d, uid: %d).\n", + tcomm, + sksec->task->pid, + __kuid_val(task_uid(sksec->task)), + __kuid_val(sksec->task->real_cred->uid)); + ip_hdr(skb)->frag_off |= htons(IP_EVIL); /* Set evil bit. */ + ip_send_check(ip_hdr(skb)); /* Recalc. checksum. */ + } + +done: + return NF_ACCEPT; +} + +/* For IPv6, we abuse the flow label header field. + */ +static unsigned int simple_flow_ipv6_output(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + struct sk_security *sksec; + + if (NULL == skb->sk) { + /* Socket closed and we are merely sending FIN? */ + printk(KERN_WARNING MOD ": socket buffer has no socket.\n"); + goto done; + } + + if (NULL == skb->sk->sk_security) { + printk(KERN_WARNING MOD ": socket has no security context.\n"); + goto done; + } + + sksec = skb->sk->sk_security; + if (NULL == sksec->task) { + printk(KERN_WARNING MOD ": socket has no associated process.\n"); + goto done; + } + + if (simple_flow_taint_action == SIMPLE_FLOW_MODE_EVIL_BIT + && sksec->owner_tainted) { + char tcomm[sizeof(current->comm)]; + get_task_comm(tcomm, sksec->task); + printk(KERN_INFO MOD ": setting IPv6 evil label on packet " + "generated by %s " + "(pid: %d, euid: %d, uid: %d).\n", + tcomm, + sksec->task->pid, + __kuid_val(task_uid(sksec->task)), + __kuid_val(sksec->task->real_cred->uid)); + simple_flow_ipv6_flow_label_set_evil(ipv6_hdr(skb)->flow_lbl); + } + +done: + return NF_ACCEPT; +} + +static struct nf_hook_ops simple_flow_ipv4_ops[] = { + { + .hook = simple_flow_ipv4_output, + .owner = THIS_MODULE, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_LOCAL_OUT, + .priority = NF_IP_PRI_MANGLE, + }, + + { + .hook = simple_flow_ipv6_output, + .owner = THIS_MODULE, + .pf = NFPROTO_IPV6, + .hooknum = NF_INET_LOCAL_OUT, + .priority = NF_IP_PRI_MANGLE, + } + +}; + +static __init +int simple_flow_init(void) +{ + simple_flow_cred_init_security(); + + if (register_security(&simple_flow_ops)) { + printk(KERN_ERR MOD ": Unable to register with kernel.\n"); + return 0; + } + + if (nf_register_hooks(simple_flow_ipv4_ops, ARRAY_SIZE(simple_flow_ipv4_ops))) { + printk(KERN_ERR MOD ": Unable to register with netfilter.\n"); + return 0; + } + + printk(KERN_INFO MOD ": Initializing.\n"); + + switch (simple_flow_taint_action) { + case SIMPLE_FLOW_MODE_LOG: + printk(KERN_INFO MOD ": simple-flow is in log mode.\n"); + break; + case SIMPLE_FLOW_MODE_EVIL_BIT: + printk(KERN_INFO MOD ": simple-flow is in evil-bit mode.\n"); + break; + case SIMPLE_FLOW_MODE_KILL: + printk(KERN_INFO MOD ": simple-flow is in kill mode.\n"); + break; + default: + BUG(); + } + + return 0; +} + +module_init(simple_flow_init); -- 2.5.5