From 904476f2532107b2b7b76b2f7f716f9807eb35f7 Mon Sep 17 00:00:00 2001 From: "W. Michael Petullo" Date: Mon, 6 Jun 2016 15:54:32 -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 | 26 + security/simple-flow/Makefile | 1 + security/simple-flow/hooks.c | 1811 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1856 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..b55206d 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" (RFC 3514) */ #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..0d469be 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 @@ -132,6 +133,7 @@ choice default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR default DEFAULT_SECURITY_YAMA if SECURITY_YAMA + default DEFAULT_SECURITY_SIMPLEFLOW if SECURITY_SIMPLEFLOW default DEFAULT_SECURITY_DAC help @@ -153,6 +155,9 @@ choice config DEFAULT_SECURITY_YAMA bool "Yama" if SECURITY_YAMA=y + config DEFAULT_SECURITY_SIMPLEFLOW + bool "SimpleFlow" if SECURITY_SIMPLEFLOW=y + config DEFAULT_SECURITY_DAC bool "Unix Discretionary Access Controls" @@ -165,6 +170,7 @@ config DEFAULT_SECURITY default "tomoyo" if DEFAULT_SECURITY_TOMOYO default "apparmor" if DEFAULT_SECURITY_APPARMOR default "yama" if DEFAULT_SECURITY_YAMA + default "simple-flow" if DEFAULT_SECURITY_SIMPLEFLOW default "" if DEFAULT_SECURITY_DAC endmenu diff --git a/security/Makefile b/security/Makefile index c26c81e..ab26ddf 100644 --- a/security/Makefile +++ b/security/Makefile @@ -8,6 +8,7 @@ subdir-$(CONFIG_SECURITY_SMACK) += smack subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor subdir-$(CONFIG_SECURITY_YAMA) += yama +subdir-$(CONFIG_SECURITY_SIMPLEFLOW) += simple-flow # always enable default capabilities obj-y += commoncap.o @@ -23,6 +24,7 @@ obj-$(CONFIG_AUDIT) += lsm_audit.o obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/built-in.o obj-$(CONFIG_SECURITY_YAMA) += yama/built-in.o +obj-$(CONFIG_SECURITY_SIMPLEFLOW) += simple-flow/built-in.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o # Object integrity file lists diff --git a/security/simple-flow/Kconfig b/security/simple-flow/Kconfig new file mode 100644 index 0000000..35eb07b --- /dev/null +++ b/security/simple-flow/Kconfig @@ -0,0 +1,26 @@ +config SECURITY_SIMPLEFLOW + bool "SimpleFlow" + depends on SECURITY_NETWORK && NET && INET + default n + help + A simple information-flow authorization model. + If you are unsure how to answer this question, answer N. + +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..b95f0a1 --- /dev/null +++ b/security/simple-flow/hooks.c @@ -0,0 +1,1811 @@ +#include +#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" + +/* "true" or "inherited". */ +#define SIMPLE_FLOW_XATTR_MAX_TAINTED sizeof("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 RFC 3514 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 (roughly, baddie). + */ +static bool +simple_flow_ipv6_flow_label_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_evil. */ +static void +simple_flow_ipv6_flow_label_set_evil(__u8 *label) +{ + 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 = kstrtoul(str, 0, &taint_action); + if (!error) { + if (taint_action < SIMPLE_FLOW_MODE_INVALID) { + simple_flow_taint_action = taint_action; + } else { + pr_warn(MOD ": invalid taint action: %lu; using " + "default", taint_action); + } + } else + pr_warn(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 inode_security_struct { + 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 inode_security_struct 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_struct structure. + */ +struct sk_security_struct { + struct task_struct *task; + bool owner_tainted; +}; + +/* Security context for a message queue. */ +struct msg_security_struct { + bool owner_tainted; +}; + +/* Security context for shared memory. */ +struct shm_security_struct { + 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; + + tsec = kzalloc(sizeof(*tsec), priority); + if (tsec == NULL) + return -ENOMEM; + + cred->security = tsec; + + return 0; +} + +static void +simple_flow_cred_free(struct cred *cred) +{ + if (cred->security != NULL) { + kfree(cred->security); + cred->security = NULL; + } + +} + +static void +simple_flow_cred_init_security(void) +{ + int ret; + + ret = simple_flow_cred_alloc_blank((struct cred *) current->cred, + GFP_KERNEL); + if (ret != 0) + 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)]; + + if (oldcred->security == NULL) { + if (is_user_process(current)) { + get_task_comm(tcomm, current); + pr_warn(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 (tsec == NULL) { + fnval = -ENOMEM; + goto done; + } + } else { + tsec = kmemdup(oldcred->security, sizeof(*tsec), priority); + if (tsec == NULL) { + fnval = -ENOMEM; + goto done; + } + } + +done: + + cred->security = tsec; + + return fnval; +} + +static void +simple_flow_cred_transfer(struct cred *new, const struct cred *old) +{ + const struct t_security *old_security = old->security; + struct t_security *security = new->security; + + *security = *old_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; + + tsec = rcu_dereference_protected(task->cred, 1)->security; + if (tsec == NULL) + 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)]; + + get_task_comm(tcomm, vma->vm_mm->owner); + + if (simple_flow_process_trusted(vma->vm_mm->owner)) { + pr_info(MOD ": %s trusted; not tainting despite attached to " + "tainted shared-memory segment\n", tcomm); + } else { + struct process_list_node *node; + + pr_info(MOD ": %s attached to tainted shared-memory " + "segment.\n", tcomm); + + node = kmalloc(sizeof(*node), GFP_ATOMIC); + if (node == NULL) { + /* 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); + pr_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; + + shmid = container_of(shm_perm, struct shmid_kernel, shm_perm); + + node = kmalloc(sizeof(*node), GFP_ATOMIC); + if (node == NULL) { + 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_shared_file_set_taint(struct file *file) +{ + struct inode *inode = NULL; + struct inode_security_struct *isec; + struct list_head list_head; + struct list_head *pos, *q; + struct vm_area_struct *vma; + + if (file->f_mapping == NULL) + goto done; + + inode = file_inode(file); + + isec = inode->i_security; + if (isec == NULL) { + /* TODO: How can this happen? */ + goto done; + } + isec->owner_tainted = true; + + INIT_LIST_HEAD(&list_head); + + mutex_lock(&file->f_mapping->i_mmap_mutex); + vma_interval_tree_foreach(vma, &file->f_mapping->i_mmap, 0, ULONG_MAX) { + simple_flow_process_build_list(NULL, vma, 0, &list_head); + } + mutex_unlock(&file->f_mapping->i_mmap_mutex); + + 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; +} + +static void +simple_flow_shm_set_taint(int id, struct shmid_kernel *shmid) { + struct shm_security_struct *shmsec; + + /* Taint the shared memory. */ + shmsec = shmid->shm_perm.security; + + if (!shmsec->owner_tainted) { + char tcomm[sizeof(current->comm)]; + + get_task_comm(tcomm, current); + pr_info(MOD ": %s tainted; mark shm %d\n", tcomm, id); + shmsec->owner_tainted = true; + + /* Taint the attached processes. */ + simple_flow_shared_file_set_taint(shmid->shm_file); + } + + 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; + + get_task_comm(tcomm, task); + + tsec = rcu_dereference_protected(task->cred, 1)->security; + if (tsec == NULL) { + pr_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; + struct vm_area_struct *vma; + + shm_list_head.task = task; + INIT_LIST_HEAD(&shm_list_head.list); + + pr_warn(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; + + for (vma = task->mm->mmap; vma; vma = vma->vm_next) { + if (vma->vm_flags & VM_WRITE + && vma->vm_flags & VM_SHARED + && vma->vm_file != NULL) { + simple_flow_shared_file_set_taint(vma->vm_file); + } + } + + /* Taint any shared-memory this process is attached to, + * and also taint the already attached processes. + */ + ns = task->nsproxy->ipc_ns; + + /* Build a list of all (i.e., system-wide) shared-memory + * segments. + */ + if (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 (found == 1) { + ret = rmap_walk(page, + simple_flow_does_process_map_shm, + shm_list_head.task); + if (ret == SWAP_SUCCESS) { + simple_flow_shm_set_taint(node->id, + node->shmid); + } + } + + list_del(pos); + kfree(node); + + if (page != NULL) + release_pages(&page, 1, 0); + } + } + +done: + return; +} + +static int +simple_flow_inode_alloc_security(struct inode *inode) +{ + int fnval = 0; + struct inode_security_struct *isec = NULL; + + isec = kzalloc(sizeof(*isec), GFP_NOFS); + if (isec == NULL) { + fnval = -ENOMEM; + goto done; + } + +done: + inode->i_security = isec; + + return fnval; +} + +static void +simple_flow_inode_free_security(struct inode *inode) +{ + if (inode->i_security != NULL) { + 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 inode_security_struct *isec = NULL; + + /* First check for permission to remove. */ + fnval = simple_flow_check_xattr_perm(name); + if (fnval != 0) + 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 inode_security_struct *isec; + bool trusted = simple_flow_process_trusted(current); + + if (file == NULL) { + /* Mapping anonymous memory. */ + goto done; + } + + inode = file_inode(file); + + isec = inode->i_security; + if (isec == NULL) { + /* TODO: How can this happen? */ + goto done; + } + + if (prot & PROT_WRITE && flags & MAP_SHARED) { + /* Taint inode if process tainted. */ + struct t_security *tsec; + char tcomm[sizeof(current->comm)]; + + static char buf[PATH_MAX]; + char *path = d_path(&file->f_path, buf, PATH_MAX); + if (path == NULL) { + pr_err(MOD ": path lookup failed\n"); + goto done; + } + + tsec = current_security(); + get_task_comm(tcomm, current); + + if (tsec == NULL) { + if (is_user_process(current)) { + pr_err(MOD ": no security data associated " + "with user-space %s for write\n", + tcomm); + } + goto done; + } + + if (tsec->tainted && !isec->owner_tainted) { + pr_info(MOD ": %s tainted; mark shared file %s\n", + tcomm, + path); + simple_flow_shared_file_set_taint(file); + } + } + + if (prot & PROT_READ && flags & MAP_SHARED && isec->owner_tainted) { + /* Taint process if inode tainted. */ + static char buf[PATH_MAX]; + char *path = d_path(&file->f_path, buf, PATH_MAX); + if (path == NULL) { + pr_err(MOD ": path lookup failed\n"); + goto done; + } + + if (trusted) { + char tcomm[sizeof(current->comm)]; + + get_task_comm(tcomm, current); + + pr_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 msg_security_struct *mqsec = NULL; + + mqsec = kzalloc(sizeof(*mqsec), GFP_KERNEL); + if (mqsec == NULL) { + fnval = -ENOMEM; + goto done; + } + +done: + msq->q_perm.security = mqsec; + + return fnval; +} + +static void +simple_flow_msg_queue_free_security(struct msg_queue *msq) +{ + if (msq->q_perm.security != NULL) { + 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 msg_security_struct *mqsec = NULL; + char tcomm[sizeof(current->comm)]; + + tsec = current_security(); + if (tsec == NULL) { + if (is_user_process(current)) { + get_task_comm(tcomm, current); + pr_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); + pr_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 msg_security_struct *mqsec = NULL; + + trusted = simple_flow_process_trusted(current); + + mqsec = msq->q_perm.security; + if (mqsec->owner_tainted) { + if (trusted) { + get_task_comm(tcomm, current); + pr_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 inode_security_struct *isec = NULL; + + /* First check for permission to remove. */ + fnval = simple_flow_check_xattr_perm(name); + if (fnval != 0) + 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 locked = false; + int rc = 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 (buf == NULL) { + pr_err(MOD ": kcalloc failed\n"); + goto done; + } + + path = d_path(&file->f_path, buf, PATH_MAX); + if (path == NULL) { + pr_err(MOD ": path lookup failed\n"); + goto done; + } + + dentry = d_find_alias(file_inode((struct file *) file)); + if (dentry == NULL) { + /* TODO: Probably not a normal file; how to know for sure? */ + goto done; + } + + inode = dentry->d_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; + + rc = __vfs_setxattr_noperm(dentry, + xattr_key, + xattr_val, + strlen(xattr_val) + 1, + flags); + + if (rc < 0) { + switch (rc) { + case -EEXIST: /* Should not happen due to flags above. */ + pr_warn(MOD ": did not like that xattr already exists in %s\n", + path); + goto done; + case -ENODATA: /* Should not happen due to flags above. */ + pr_warn(MOD ": did not like that xattr does not exist in %s\n", + path); + goto done; + case -EOPNOTSUPP: /* Filesystem does not support xattr. */ + pr_warn(MOD ": %s on a fs which does not support xattr\n", + path); + goto done; + case -EDQUOT: /* Xattr write would surpass disk quota. */ + pr_err(MOD ": xattr write surpasses disk quota\n"); + goto done; + case -ENOSPC: /* Xattr write would surpass disk space. */ + pr_err(MOD ": xattr write surpasses disk space\n"); + goto done; + case -EINVAL: /* E.g., sysfs supports only "security" namespace. */ + pr_warn(MOD ": xattr write %s from %s is invalid\n", + SIMPLE_FLOW_XATTR_NAME_TAINTED, + path); + goto done; + default: + pr_err(MOD ": xattr write %s: error code %d unhandled\n", + path, + rc); + goto done; + } + } + +done: + if (buf != NULL) + kfree(buf); + + if (dentry != NULL) + dput(dentry); + + if (locked) + mutex_unlock(&inode->i_mutex); + + return rc >= 0; +} + +/* 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 rc = 0; + 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 (buf == NULL) { + pr_err(MOD ": kcalloc failed\n"); + goto done; + } + + path = d_path(&file->f_path, buf, PATH_MAX); + if (path == NULL) { + pr_err(MOD ": path lookup failed\n"); + goto done; + } + + /* Next, read the extended attribute. */ + + inode = file_inode((struct file *) file); + + if (inode->i_op->getxattr == NULL) + goto done; + + dentry = d_find_alias(inode); + if (dentry == NULL) { + pr_err(MOD ": could not find dentry for %s\n", path); + goto done; + } + + rc = inode->i_op->getxattr(dentry, + xattr_key, + xattr_val, + size); + + if (rc < 0) { + switch (rc) { + case -ENODATA: /* Key does not exist. */ + break; + case -EOPNOTSUPP: /* Filesystem does not support xattr. */ + pr_warn(MOD ": %s on a fs which does not support " + "xattr\n", path); + break; + case -ERANGE: /* Xattr value too large for buffer. */ + pr_err(MOD ": xattr value to large for buffer\n"); + break; + case -EINVAL: /* E.g., sysfs supports only "security" + * namespace. + */ + pr_warn(MOD ": xattr read %s from %s is invalid\n", + SIMPLE_FLOW_XATTR_NAME_TAINTED, + path); + break; + default: + pr_err(MOD ": xattr read %s: error code %d unhandled\n", + path, + rc); + break; + } + } +done: + if (buf != NULL) + kfree(buf); + + if (dentry != NULL) + dput(dentry); + + return rc >= 0; +} + +/* 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; + + /* 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 (path == NULL) { + pr_err(MOD ": path lookup failed\n"); + goto done; + } + + get_task_comm(tcomm, current); + + pr_warn(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]; + + /* Check capabilities first. This seems to grant + * setuid-bit functionality. + */ + fnval = cap_bprm_set_creds(bprm); + if (fnval != 0) + goto done; + + /* Depend on initial program/script, not interpreter. */ + if (bprm->cred_prepared) + goto done; + + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (buf == NULL) { + pr_err(MOD ": kcalloc failed\n"); + goto done; + } + + path = d_path(&bprm->file->f_path, buf, PATH_MAX); + if (path == NULL) { + pr_err(MOD ": path lookup failed\n"); + goto done; + } + + if (bprm->cred->security == NULL) { + pr_warn(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); + pr_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); + pr_info(MOD ": tainted %s exec'ed tainted %s\n", tcomm, path); + } + +done: + if (buf != NULL) + kfree(buf); + + return fnval; +} + +static int +simple_flow_shm_alloc_security(struct shmid_kernel *shmid) +{ + int fnval = 0; + struct shm_security_struct *shmsec = NULL; + + shmsec = kzalloc(sizeof(*shmsec), GFP_KERNEL); + if (shmsec == NULL) { + fnval = -ENOMEM; + goto done; + } + +done: + shmid->shm_perm.security = shmsec; + + return fnval; +} + +static void +simple_flow_shm_free_security(struct shmid_kernel *shmid) +{ + if (shmid->shm_perm.security != NULL) { + 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_struct *shmsec = NULL; + char tcomm[sizeof(current->comm)]; + + tsec = current_security(); + if (tsec == NULL) { + if (is_user_process(current)) { + get_task_comm(tcomm, current); + pr_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); + pr_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_struct *sksec = NULL; + struct sk_security_struct *other_sksec = NULL; + + socket = SOCKET_I(inode); + + other = unix_peer_get(socket->sk); + if (other == NULL) { + /* 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 (other != NULL) + 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 inode_security_struct *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]; + + inode = file_inode(file); + + isec = inode->i_security; + + path = d_path(&file->f_path, buf, PATH_MAX); + if (path == NULL) { + pr_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 (isec == NULL) { + /* 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_struct *sksec = NULL; + + socket = SOCKET_I(inode); + + sksec = socket->sk->sk_security; + + if (socket->sk->sk_family == AF_UNIX) { + /* 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) { + pr_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 (isec == NULL) + goto done; + + trusted = simple_flow_process_trusted(current); + tsec = current_security(); + if (!trusted && isec->owner_tainted) { + pr_warn(MOD ": possible integrity violation: untrusted " + "process %s wrote to %s\n", tcomm, path); + } + + if (tsec == NULL) { + if (is_user_process(current)) { + pr_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)) { + pr_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_struct *sksec = NULL; + + socket = SOCKET_I(inode); + + sksec = socket->sk->sk_security; + + pr_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)) { + pr_warn(MOD ": could not set %s as inherited\n", + 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; + + if (!capable(CAP_SYS_ADMIN)) { + /* Attempt to conceal presence of SimpleFlow. */ + len = -ENODATA; + goto done; + } + + if (p->cred == NULL) { + len = -EINVAL; + goto done; + } + + tsec = p->cred->security; + if (tsec == NULL) { + len = -EINVAL; + goto done; + } + + *value = kmalloc(2 * sizeof(char), GFP_ATOMIC); + if (*value == NULL) { + 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)]; + + /* Expect "0\n" or "1\n". */ + if (size != 2) { + size = -EINVAL; + goto done; + } + + if (p->cred == NULL) { + size = -EINVAL; + goto done; + } + + tsec = p->cred->security; + if (tsec == NULL) { + 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); + pr_warn(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) +{ + 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) +{ + 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_struct *sksec; + + sksec = kzalloc(sizeof(*sksec), priority); + if (sksec == NULL) + return -ENOMEM; + + sk->sk_security = sksec; + + return 0; +} + +static void +simple_flow_sk_free_security(struct sock *sk) +{ + if (sk->sk_security != NULL) { + struct sk_security_struct *sksec = sk->sk_security; + if (sksec->task != NULL) + 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_struct *newsksec; + struct sk_security_struct *sksec; + + newsksec = newsk->sk_security; + sksec = sk->sk_security; + + if (sksec->task != NULL) + 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_struct *sksec; + + if (sock->sk == NULL) { + /* TODO: Confirm this means this is NOT a network socket. */ + goto done; + } + + tsec = current_security(); + if (tsec == NULL) { + if (is_user_process(current)) { + char tcomm[sizeof(current->comm)]; + get_task_comm(tcomm, current); + pr_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_struct *sksec = sk->sk_security; + + if (sk->sk_family != PF_INET && sk->sk_family != PF_INET6) { + /* Only deal with IPv4 or IPv6 packets. */ + goto done; + } + + if (sksec == NULL) { + pr_warn(MOD ": socket has no security context.\n"); + goto done; + } + + if (sksec->task == NULL) { + pr_warn(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_evil(ipv6_hdr(skb)->flow_lbl)); + if (simple_flow_taint_action == SIMPLE_FLOW_MODE_EVIL_BIT && is_evil) { + char tcomm[sizeof(sksec->task->comm)]; + struct t_security *tsec = NULL; + + get_task_comm(tcomm, sksec->task); + + pr_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 (tsec == NULL) { + pr_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_struct *sksec; + + if (skb->sk == NULL) { + /* Socket closed and we are merely sending FIN? */ + pr_warn(MOD ": socket buffer has no socket.\n"); + goto done; + } + + if (skb->sk->sk_security == NULL) { + pr_warn(MOD ": socket has no security context.\n"); + goto done; + } + + sksec = skb->sk->sk_security; + if (sksec->task == NULL) { + pr_warn(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); + pr_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_struct *sksec; + + if (skb->sk == NULL) { + /* Socket closed and we are merely sending FIN? */ + pr_warn(MOD ": socket buffer has no socket.\n"); + goto done; + } + + if (skb->sk->sk_security == NULL) { + pr_warn(MOD ": socket has no security context.\n"); + goto done; + } + + sksec = skb->sk->sk_security; + if (sksec->task == NULL) { + pr_warn(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); + pr_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)) { + pr_err(MOD ": Unable to register with kernel.\n"); + return 0; + } + + if (nf_register_hooks(simple_flow_ipv4_ops, + ARRAY_SIZE(simple_flow_ipv4_ops))) { + pr_err(MOD ": Unable to register with netfilter.\n"); + return 0; + } + + pr_info(MOD ": Initializing.\n"); + + switch (simple_flow_taint_action) { + case SIMPLE_FLOW_MODE_LOG: + pr_info(MOD ": simple-flow is in log mode.\n"); + break; + case SIMPLE_FLOW_MODE_EVIL_BIT: + pr_info(MOD ": simple-flow is in evil-bit mode.\n"); + break; + case SIMPLE_FLOW_MODE_KILL: + pr_info(MOD ": simple-flow is in kill mode.\n"); + break; + default: + BUG(); + } + + return 0; +} + +module_init(simple_flow_init); -- 2.7.4