From 9c053364edea818fbec1eadc7954607e56bd0e8d Mon Sep 17 00:00:00 2001 From: "W. Michael Petullo" Date: Fri, 7 Oct 2016 20:18:14 -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. 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 + fs/read_write.c | 8 + include/linux/security.h | 11 + include/net/ip.h | 1 + security/Kconfig | 6 + security/Makefile | 2 + security/capability.c | 6 + security/security.c | 5 + security/simple-flow/Kconfig | 26 + security/simple-flow/Makefile | 1 + security/simple-flow/hooks.c | 2886 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 2961 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/fs/read_write.c b/fs/read_write.c index 2cefa41..1922ed0 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -242,6 +242,10 @@ SYSCALL_DEFINE3(lseek, unsigned int, fd, off_t, offset, unsigned int, whence) if (!f.file) return -EBADF; + retval = security_file_lseek(f.file); + if (retval != 0) + return retval; + retval = -EINVAL; if (whence <= SEEK_MAX) { loff_t res = vfs_llseek(f.file, offset, whence); @@ -272,6 +276,10 @@ SYSCALL_DEFINE5(llseek, unsigned int, fd, unsigned long, offset_high, if (!f.file) return -EBADF; + retval = security_file_lseek(f.file); + if (retval != 0) + return retval; + retval = -EINVAL; if (whence > SEEK_MAX) goto out_putf; diff --git a/include/linux/security.h b/include/linux/security.h index 4686491..6125c17 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -647,6 +647,10 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts) * Save open-time permission checking state for later use upon * file_permission, and recheck access if anything has changed * since inode_permission. + * @file_lseek + * This hook allows security modules to control the ability of a process + * to seek within a file, as this can represent a covert channel between + * a parent and child. * * Security hooks for task operations. * @@ -1519,6 +1523,7 @@ struct security_operations { struct fown_struct *fown, int sig); int (*file_receive) (struct file *file); int (*file_open) (struct file *file, const struct cred *cred); + int (*file_lseek) (struct file *file); int (*task_create) (unsigned long clone_flags); void (*task_free) (struct task_struct *task); @@ -1783,6 +1788,7 @@ int security_file_send_sigiotask(struct task_struct *tsk, struct fown_struct *fown, int sig); int security_file_receive(struct file *file); int security_file_open(struct file *file, const struct cred *cred); +int security_file_lseek(struct file *file); int security_task_create(unsigned long clone_flags); void security_task_free(struct task_struct *task); int security_cred_alloc_blank(struct cred *cred, gfp_t gfp); @@ -2264,6 +2270,11 @@ static inline int security_file_open(struct file *file, return 0; } +static inline int security_file_lseek(struct file *file) +{ + return 0; +} + static inline int security_task_create(unsigned long clone_flags) { return 0; 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/capability.c b/security/capability.c index 1728d4e..53934a5 100644 --- a/security/capability.c +++ b/security/capability.c @@ -354,6 +354,11 @@ static int cap_file_open(struct file *file, const struct cred *cred) return 0; } +static int cap_file_lseek(struct file *file) +{ + return 0; +} + static int cap_task_create(unsigned long clone_flags) { return 0; @@ -984,6 +989,7 @@ void __init security_fixup_ops(struct security_operations *ops) set_to_cap_if_null(ops, file_send_sigiotask); set_to_cap_if_null(ops, file_receive); set_to_cap_if_null(ops, file_open); + set_to_cap_if_null(ops, file_lseek); set_to_cap_if_null(ops, task_create); set_to_cap_if_null(ops, task_free); set_to_cap_if_null(ops, cred_alloc_blank); diff --git a/security/security.c b/security/security.c index a3dce87..653059b 100644 --- a/security/security.c +++ b/security/security.c @@ -772,6 +772,11 @@ int security_file_open(struct file *file, const struct cred *cred) return fsnotify_perm(file, MAY_OPEN); } +int security_file_lseek(struct file *file) +{ + return security_ops->file_lseek(file); +} + int security_task_create(unsigned long clone_flags) { return security_ops->task_create(clone_flags); 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..e6f792d --- /dev/null +++ b/security/simple-flow/hooks.c @@ -0,0 +1,2886 @@ +#include +#include +#include +#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 "never". */ +#define SIMPLE_FLOW_XATTR_MAX_TAINTED (sizeof("never") - 1) + +#define SIMPLE_FLOW_XATTR_NAME_TRUSTED "security.simple-flow.trusted" + +#define SIMPLE_FLOW_XATTR_MAX_TRUSTED (sizeof("true") - 1) + +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 file. + * The file data structure seems not to be shared by processes, whereas the + * inode structure does. Thus we use the file structure with pseudo terminals + * each process which opens /dev/ptmx receives a distinct in-kernel terminal + * object. + */ +struct file_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; +}; + +enum { + UNTRUSTED = 0, + TRUST_WAIT = 0x1, + TRUST_ALL = ~0, +}; + +/* + * 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; + int trust_mask; +}; + +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 rc = 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 context. Create a blank one for the child process + * we are in the process of creating. + */ + tsec = kzalloc(sizeof(*tsec), priority); + if (tsec == NULL) { + rc = -ENOMEM; + goto done; + } + } else { + tsec = kmemdup(oldcred->security, sizeof(*tsec), priority); + if (tsec == NULL) { + rc = -ENOMEM; + goto done; + } + } + +done: + + cred->security = tsec; + + return rc; +} + +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; +} + +static void +simple_flow_d_instantiate(struct dentry *dentry, struct inode *inode) +{ + char *buf = NULL; + struct task_struct *task = NULL; + struct super_block *sb; + + if (inode == NULL) + goto done; + + sb = inode->i_sb; + if (sb == NULL) + goto done; + + /* TODO: Store check of "proc" in sb sec. */ + if (strcmp(sb->s_type->name, "proc") == 0) { + char *path, *pid_string; + int error; + unsigned long nr; + struct pid *pid; + struct inode_security_struct *isec = NULL; + struct t_security *tsec = NULL; + + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (buf == NULL) + goto done; + + path = dentry_path_raw(dentry, buf, PATH_MAX); + if (IS_ERR(path)) + goto done; + + /* For /proc, path is //... */ + pid_string = ++path; + while (*path >= '0' && *path <= '9') + path++; + if (*path != '/') + /* Something in /proc other than //. */ + goto done; + *path = 0x00; + + error = kstrtoul(pid_string, 0, &nr); + if (error) + goto done; + + pid = find_vpid(nr); + if (pid == NULL) + goto done; + + task = get_pid_task(pid, PIDTYPE_PID); + if (task == NULL) + goto done; + + tsec = rcu_dereference_protected(task->cred, 1)->security; + if (tsec == NULL) + goto done; + + isec = inode->i_security; + isec->owner_tainted = tsec->tainted; + + if (isec->owner_tainted) { + char tcomm[sizeof(task->comm)]; + + /* Get path again, since we manipulated previous. */ + path = dentry_path_raw(dentry, buf, PATH_MAX); + if (IS_ERR(path)) + path = "[path lookup failed]"; + + get_task_comm(tcomm, task); + + pr_info(MOD ": %s tainted; mark /proc file %s\n", + tcomm, + path); + } + } + +done: + if (buf != NULL) + kfree(buf); + + if (task != NULL) + put_task_struct(task); + + return; +} + +/* + * Returns true if the process comes from a program on disk which is trusted + * per the provided mask; else returns false. + */ +static bool +simple_flow_process_is_trusted_with(struct task_struct *task, int trust_mask) +{ + struct t_security *tsec; + bool is_trusted = false; + + tsec = rcu_dereference_protected(task->cred, 1)->security; + if (tsec == NULL) + goto done; + + is_trusted = (tsec->trust_mask & trust_mask) == trust_mask; + +done: + return is_trusted; +} + +static bool simple_flow_process_set_taint(struct task_struct *task, + char *operation, + 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 rc = 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_is_trusted_with(vma->vm_mm->owner, TRUST_ALL)) { + 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 (pid: %d, euid: %d, uid: %d) attached to " + "tainted shared-memory segment.\n", + tcomm, + vma->vm_mm->owner->pid, + __kuid_val(task_uid(vma->vm_mm->owner)), + __kuid_val(vma->vm_mm->owner->real_cred->uid)); + + 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 rc; +} + +/* 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 rc = 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); + rc = SWAP_SUCCESS; + } + + return rc; +} + +/* 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 rc = 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) { + rc = -ENOMEM; + goto done; + } + + node->id = id; + node->shmid = shmid; + + list_add(&(node->list), &(shm_list_head->list)); + +done: + return rc; +} + +/* Get an extended attribute from a dentry. */ +static ssize_t +simple_flow_dentry_getxattr(void *xattr_val, + struct dentry *dentry, + const char *xattr_key, + size_t size) +{ + char *buf = NULL; + ssize_t rc = 0; + struct inode *inode; + + inode = dentry->d_inode; + if (inode == NULL) { + rc = -EOPNOTSUPP; + goto done; + } + + if (inode->i_op == NULL) { + rc = -EOPNOTSUPP; + goto done; + } + + if (inode->i_op->getxattr == NULL) { + rc = -EOPNOTSUPP; + goto done; + } + + rc = inode->i_op->getxattr(dentry, + xattr_key, + xattr_val, + size); + + if (rc < 0) { + char *path; + + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (buf == NULL) { + rc = -ENOMEM; + goto done; + } + + path = dentry_path_raw(dentry, buf, PATH_MAX); + if (IS_ERR(path)) + path = "[path lookup failed]"; + + switch (rc) { + case -ENODATA: /* Key does not exist. */ + break; + case -EOPNOTSUPP: /* Filesystem does not support xattr. */ + pr_warn(MOD ": cannot get %s; %s on a fs which does " + "not support xattr\n", xattr_key, path); + break; + case -ERANGE: /* Xattr value too large for buffer. */ + pr_warn(MOD ": xattr value on of %s on %s too large for " + "buffer\n", xattr_key, path); + break; + case -EINVAL: /* + * E.g., sysfs supports only "security" + * namespace. + */ + pr_warn(MOD ": xattr get of %s on %s is invalid\n", + xattr_key, path); + break; + default: + pr_err(MOD ": xattr write %s on %s: error code %ld unhandled\n", + xattr_key, path, rc); + break; + } + } + +done: + if (buf != NULL) + kfree(buf); + + return rc; +} + +/* Get an extended attribute from a file. */ +static ssize_t +simple_flow_getxattr(void *xattr_val, + const struct file *file, + const char *xattr_key, + size_t size) +{ + ssize_t rc = 0; + struct inode *inode = NULL; + struct dentry *dentry = NULL; + char *buf = NULL, *path = NULL; + + memset(xattr_val, 0x00, size); + + inode = file_inode((struct file *) file); + + if (inode->i_op->getxattr == NULL) + goto done; + + dentry = d_find_alias(inode); + if (dentry == NULL) { + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (buf == NULL) { + rc = -ENOMEM; + goto done; + } + + path = d_path(&file->f_path, buf, PATH_MAX); + if (path == NULL) + path = "[path lookup failed]"; + + /* TODO: How to handle? */ + goto done; + } + + rc = simple_flow_dentry_getxattr(xattr_val, dentry, xattr_key, size); + +done: + if (buf != NULL) + kfree(buf); + + if (dentry != NULL) + dput(dentry); + + return rc; +} + +/* Set an extended attribute on a dentry. */ +static ssize_t +simple_flow_dentry_setxattr(struct dentry *dentry, + const char *xattr_key, + const void *xattr_val, + size_t size, + int flags) +{ + struct inode *inode = NULL; + ssize_t rc = 0; + char *buf = NULL; + bool did_lock = false; + + inode = dentry->d_inode; + if (inode == NULL) { + rc = -EOPNOTSUPP; + goto done; + } + + if (inode->i_op == NULL) { + rc = -EOPNOTSUPP; + goto done; + } + + /* + * Comments in SELinux's hooks.c and use in fs/xattr.c seems + * to indicate this mutex is necessary. + * NOTE: If already locked, should be by vfs_setxattr. + */ + did_lock = mutex_trylock(&inode->i_mutex); + if (!did_lock) + BUG_ON(current != inode->i_mutex.owner); + + rc = __vfs_setxattr_noperm(dentry, + xattr_key, + xattr_val, + size, + flags); + + if (did_lock) + mutex_unlock(&inode->i_mutex); + + if (rc < 0) { + char *path; + + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (buf == NULL) { + rc = -ENOMEM; + goto done; + } + + path = dentry_path_raw(dentry, buf, PATH_MAX); + if (IS_ERR(path)) + path = "[path lookup failed]"; + + switch (rc) { + case -EEXIST: /* Should not happen due to flags above. */ + pr_warn(MOD ": did not like that xattr %s already " + "exists in %s\n", xattr_key, path); + goto done; + case -ENODATA: /* Should not happen due to flags above. */ + pr_warn(MOD ": did not like that xattr %s does not " + "exist in %s\n", xattr_key, path); + goto done; + case -EOPNOTSUPP: /* Filesystem does not support xattr. */ + pr_warn(MOD ": cannot set %s; %s on a fs which does " + "not support xattr\n", xattr_key, path); + goto done; + case -EDQUOT: /* Xattr write would surpass disk quota. */ + pr_err(MOD ": xattr write of %s on %s surpasses disk " + "quota\n", xattr_key, path); + goto done; + case -ENOSPC: /* Xattr write would surpass disk space. */ + pr_err(MOD ": xattr write of %s on %s surpasses disk " + "space\n", xattr_key, path); + goto done; + case -EINVAL: /* + * E.g., sysfs supports only "security" + * namespace. + */ + pr_warn(MOD ": xattr write %s from %s is invalid\n", + xattr_key, path); + goto done; + default: + pr_err(MOD ": xattr write %s on %s: error code %ld unhandled\n", + xattr_key, path, rc); + goto done; + } + } + +done: + if (buf != NULL) + kfree(buf); + + return rc; +} + +/* Set an extended attribute on a file. */ +static ssize_t +simple_flow_setxattr(const struct file *file, + const char *xattr_key, + const void *xattr_val, + size_t size, + int flags) +{ + ssize_t rc = 0; + struct dentry *dentry = NULL; + + 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; + } + + rc = simple_flow_dentry_setxattr(dentry, xattr_key, + xattr_val, size, flags); + +done: + if (dentry != NULL) + dput(dentry); + + return rc; +} + +static bool +simple_flow_mark_file(struct file *file) +{ + bool rc = true; + struct inode *inode = NULL; + struct inode_security_struct *isec = NULL; + + inode = file_inode(file); + + isec = inode->i_security; + if (isec == NULL) { + rc = false; + goto done; + } + + isec->owner_tainted = true; + + /* Try to set confidentiality attribute. */ + rc = simple_flow_setxattr(file, + SIMPLE_FLOW_XATTR_NAME_TAINTED, + "true", strlen("true"), 0); + +done: + return rc; +} + +static bool +simple_flow_mark_pseudo_tty(struct file *file) +{ + bool rc = true; + struct file_security_struct *fsec = NULL; + + fsec = file->f_security; + if (fsec == NULL) { + rc = false; + goto done; + } + + fsec->owner_tainted = true; + +done: + return rc; +} + +static bool +simple_flow_mark_pseudo_device_peer(struct file *file, char *tcomm) +{ + bool rc = true; + struct tty_file_private *priv; + struct tty_struct *peer_tty; + struct tty_file_private *peer_priv; + + BUG_ON(file->private_data == NULL); + + priv = file->private_data; + if (priv->tty == NULL) + goto done; + + peer_tty = priv->tty->link; + if (peer_tty == NULL) + goto done; + + spin_lock(&tty_files_lock); + list_for_each_entry(peer_priv, &peer_tty->tty_files, list) { + static char buf[PATH_MAX], *path; + struct file *peer_file; + + peer_file = peer_priv->file; + + path = d_path(&peer_file->f_path, buf, PATH_MAX); + if (path == NULL) + path = "[path lookup failed]"; + + pr_info(MOD ": %s tainted; mark pseudo device peer " + "at %s\n", tcomm, path); + + simple_flow_mark_pseudo_tty(peer_file); + } + spin_unlock(&tty_files_lock); + +done: + return rc; +} + +static bool +simple_flow_xattr_match(char *str, const void *xattr, size_t size) +{ + if (strlen(str) != size) + return false; + return memcmp(str, xattr, size) == 0; +} + +static int +simple_flow_how_trusted(struct file *file) +{ + int mask = UNTRUSTED; + char xattr_val[SIMPLE_FLOW_XATTR_MAX_TRUSTED]; + size_t size = simple_flow_getxattr(xattr_val, + file, + SIMPLE_FLOW_XATTR_NAME_TRUSTED, + SIMPLE_FLOW_XATTR_MAX_TRUSTED); + if (simple_flow_xattr_match("true", xattr_val, size)) + mask = TRUST_ALL; + else if (simple_flow_xattr_match("wait", xattr_val, size)) + mask = TRUST_WAIT; + + return mask; +} + +static bool +simple_flow_is_never(struct file *file) +{ + char xattr_val[SIMPLE_FLOW_XATTR_MAX_TAINTED]; + size_t size = simple_flow_getxattr(xattr_val, + file, + SIMPLE_FLOW_XATTR_NAME_TAINTED, + SIMPLE_FLOW_XATTR_MAX_TAINTED); + return simple_flow_xattr_match("never", xattr_val, size); +} + +static bool +simple_flow_is_dentry_never(struct dentry *dentry) +{ + char xattr_val[SIMPLE_FLOW_XATTR_MAX_TAINTED]; + size_t size = simple_flow_dentry_getxattr(xattr_val, + dentry, + SIMPLE_FLOW_XATTR_NAME_TAINTED, + SIMPLE_FLOW_XATTR_MAX_TAINTED); + return simple_flow_xattr_match("never", xattr_val, size); +} + +static bool +simple_flow_is_confidential(struct file *file) +{ + char xattr_val[SIMPLE_FLOW_XATTR_MAX_TAINTED]; + size_t size = simple_flow_getxattr(xattr_val, + file, + SIMPLE_FLOW_XATTR_NAME_TAINTED, + SIMPLE_FLOW_XATTR_MAX_TAINTED); + return simple_flow_xattr_match("true", xattr_val, size); +} + +static bool +simple_flow_is_dentry_confidential(struct dentry *dentry) +{ + char xattr_val[SIMPLE_FLOW_XATTR_MAX_TAINTED]; + size_t size = simple_flow_dentry_getxattr(xattr_val, + dentry, + SIMPLE_FLOW_XATTR_NAME_TAINTED, + SIMPLE_FLOW_XATTR_MAX_TAINTED); + return simple_flow_xattr_match("true", xattr_val, size); +} + +static bool +simple_flow_shared_file_set_taint(struct file *file) +{ + int did_lock = 0; + bool rc = true; + struct list_head list_head; + struct list_head *pos, *q; + struct vm_area_struct *vma; + + if (file->f_mapping == NULL) + goto done; + + if (simple_flow_is_never(file)) { + static char buf[PATH_MAX]; + char *path = d_path(&file->f_path, buf, PATH_MAX); + if (path == NULL) + path = "[path lookup failed]"; + + pr_warn(MOD ": will not taint %s with never-set " + "confidentiality", path); + rc = false; + goto done; + } + + simple_flow_mark_file(file); + + INIT_LIST_HEAD(&list_head); + + /* + * Sometimes this is already locked (probably when we get here from + * shmat, but more analysis is needed. If this is the case, then + * we check to ensure the current task has the lock. It would be a + * bug/misunderstanding if this is not the case. + */ + did_lock = mutex_trylock(&file->f_mapping->i_mmap_mutex); + if (!did_lock) + BUG_ON(current != file->f_mapping->i_mmap_mutex.owner); + vma_interval_tree_foreach(vma, &file->f_mapping->i_mmap, 0, ULONG_MAX) + simple_flow_process_build_list(NULL, vma, 0, &list_head); + if (did_lock) + 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); + bool ok = simple_flow_process_set_taint(node->process, + "shm", + "anonymous"); + list_del(pos); + kfree(node); + if (!ok) { + rc = false; + goto done; + } + } + +done: + return rc; +} + +static void +simple_flow_shm_set_taint(int id, struct shmid_kernel *shmid) { + struct shm_security_struct *shmsec; + char tcomm[sizeof(current->comm)]; + + shmsec = shmid->shm_perm.security; + + /* Already tainted; terminate recursion. */ + if (shmsec->owner_tainted) + goto done; + + /* Taint the shared memory. */ + 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); + +done: + return; +} + +/* Modify a process's security context to indicate the process is tainted. */ +static bool +simple_flow_process_set_taint(struct task_struct *task, char *operation, + char *path) +{ + bool rc = true; + struct t_security *tsec; + char tcomm[sizeof(task->comm)]; + struct shm_list_head shm_list_head; + struct list_head *pos, *q; + struct vm_area_struct *vma; + struct ipc_namespace *ns; + + get_task_comm(tcomm, task); + + if (!is_user_process(task)) { + pr_warn(MOD ": not tainting kernel thread %s " + "despite %s on %s\n", tcomm, operation, path); + goto done; + } + + tsec = rcu_dereference_protected(task->cred, 1)->security; + if (tsec == NULL) { + pr_err(MOD ": %s has no security context!\n", tcomm); + goto done; + } + + /* Already tainted; terminate recursion. */ + if (tsec->tainted) + goto done; + + /* + * FIXME: This should be undone if vma taints fail. We have to + * taint here to avoid infinite recursion, though. + */ + pr_warn(MOD ": tainting process running %s (pid: %d, euid: %d, " + "uid: %d) due to %s interaction with %s.\n", + tcomm, + task->pid, + __kuid_val(task_uid(task)), + __kuid_val(task->real_cred->uid), + operation, + path); + tsec->tainted = true; + + shm_list_head.task = task; + INIT_LIST_HEAD(&shm_list_head.list); + + 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) { + if (!simple_flow_shared_file_set_taint(vma->vm_file)) + /* + * FIXME: we will be left with files tainted + * earlier in the loop. + */ + rc = false; + } + } + + /* + * 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); + /* TODO: Why can found == 1 yet page == NULL? */ + if (found == 1 && page != NULL) { + ret = rmap_walk(page, + simple_flow_does_process_map_shm, + shm_list_head.task); + release_pages(&page, 1, 0); + if (ret == SWAP_SUCCESS) { + simple_flow_shm_set_taint(node->id, + node->shmid); + } + } + + list_del(pos); + kfree(node); + } + +done: + return rc; +} + +static int +simple_flow_inode_alloc_security(struct inode *inode) +{ + int rc = 0; + struct inode_security_struct *isec = NULL; + + isec = kzalloc(sizeof(*isec), GFP_NOFS); + if (isec == NULL) { + rc = -ENOMEM; + goto done; + } + + inode->i_security = isec; + +done: + return rc; +} + +static int +simple_flow_inode_create(struct inode *dir, + struct dentry *dentry, umode_t mode) +{ + int rc = 0; + char *buf = NULL; + char tcomm[sizeof(current->comm)]; + struct t_security *tsec; + struct inode_security_struct *isec; + + get_task_comm(tcomm, current); + + tsec = current_security(); + 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; + } + + isec = dir->i_security; + if (isec == NULL) { + /* TODO: How can this happen? */ + goto done; + } + + if (tsec->tainted) { + char *path; + + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (buf == NULL) + path = "[path lookup failed]"; + else { + path = dentry_path_raw(dentry, buf, PATH_MAX); + if (IS_ERR(path)) + path = "[path lookup failed]"; + } + + if (simple_flow_is_dentry_never(dentry->d_parent)) { + pr_warn(MOD ": tainted %s tried to create file %s, but " + "parent directory has never-taint " + "confidentiality\n", tcomm, path); + + rc = -EPERM; + + goto done; + } + + pr_info(MOD ": %s tainted; mark parent directory of new %s\n", + tcomm, path); + + isec->owner_tainted = true; + + /* Try to set confidentiality attribute on parent directory. */ + simple_flow_dentry_setxattr(dentry->d_parent, + SIMPLE_FLOW_XATTR_NAME_TAINTED, + "true", strlen("true"), 0); + } + +done: + if (buf != NULL) + kfree(buf); + + return rc; +} + +static int +simple_flow_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) +{ + int rc = 0; + char *buf = NULL, *path; + char tcomm[sizeof(current->comm)]; + bool trusted = false; + struct inode *inode; + struct inode_security_struct *isec = NULL; + + trusted = simple_flow_process_is_trusted_with(current, TRUST_ALL); + + inode = dentry->d_inode; + isec = inode->i_security; + + if (isec == NULL) + goto done; + + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (buf == NULL) + path = "[path lookup failed]"; + else { + path = dentry_path_raw(dentry, buf, PATH_MAX); + if (IS_ERR(path)) + path = "[path lookup failed]"; + } + + if (isec->owner_tainted) { + if (trusted) { + get_task_comm(tcomm, current); + pr_info(MOD ": %s trusted; not tainting despite getattr " + "on %s\n", tcomm, path); + } else { + if (!simple_flow_process_set_taint(current, + "getattr", + path)) { + rc = -EPERM; + goto done; + } + } + } + +done: + if (buf != NULL) + kfree(buf); + + return rc; +} + +static int +simple_flow_inode_mknod(struct inode *dir, struct dentry *dentry, + umode_t mode, dev_t dev) +{ + return simple_flow_inode_create(dir, dentry, mode); +} + +static int +simple_flow_inode_link(struct dentry *old_dentry, + struct inode *dir, struct dentry *new_dentry) +{ + return simple_flow_inode_create(dir, new_dentry, 0); +} + +static int +simple_flow_inode_symlink(struct inode *dir, struct dentry *dentry, + const char *old_name) +{ + return simple_flow_inode_create(dir, dentry, 0); +} + +static int +simple_flow_inode_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + return simple_flow_inode_create(new_dir, new_dentry, 0); +} + +static void +simple_flow_inode_free_security(struct inode *inode) +{ + if (inode->i_security != NULL) { + struct inode_security_struct *isec = inode->i_security; + kfree(isec); + inode->i_security = NULL; + } +} + +/* + * Most checks are performed by file_permission. However, the kernel + * invokes inode_permission while walking a path and checking directory + * permissions. Thus we consider read and execute permissions for directories + * here. A possible covert channel involves creating number of files + * with a series of ordered names, becoming tainted, and then deleting the + * files which might correspond to the zeros in a confidential message. + * Another process could note the remaining files and recreate the message + * without becoming tainted if this check were not in place. + */ +static int +simple_flow_inode_permission(struct inode *inode, int mask) +{ + int rc = 0; + struct inode_security_struct *isec = inode->i_security; + if (isec == NULL) { + /* TODO: How can this happen? */ + goto done; + } + + if (simple_flow_process_is_trusted_with(current, TRUST_ALL)) + goto done; + + if (S_ISDIR(inode->i_mode) && isec->owner_tainted + && (mask & MAY_READ || mask & MAY_EXEC)) + if (!simple_flow_process_set_taint(current, + "in-directory", + "[path unknown]")) { + rc = -EPERM; + goto done; + } + +done: + return rc; +} + +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) +{ + int rc = 0; + bool confidential, trusted; + char *buf = NULL; + + rc = simple_flow_check_xattr_perm(name); + if (rc != 0) { + /* Not allowed. */ + goto done; + } + + /* + * Taint if target file confidential; otherwise, getxattr could + * be used for covert channel. + */ + confidential = simple_flow_is_dentry_confidential(dentry); + trusted = simple_flow_process_is_trusted_with(current, TRUST_ALL); + + if (confidential) { + char tcomm[sizeof(current->comm)]; + char *path; + + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (buf == NULL) { + rc = -ENOMEM; + goto done; + } + + path = dentry_path_raw(dentry, buf, PATH_MAX); + if (IS_ERR(path)) + path = "[path lookup failed]"; + + get_task_comm(tcomm, current); + + if (trusted) { + pr_info(MOD ": %s getxattr %s; not tainting " + "because %s is trusted.\n", + tcomm, + path, + tcomm); + } else { + if (!simple_flow_process_set_taint(current, + "getxattr", + path)) { + rc = -EPERM; + goto done; + } + } + } + +done: + if (buf != NULL) + kfree(buf); + + return rc; +} + +/* + * See comments on simple_flow_inode_removexattr. + * NOTE: the kernel does not invoke this hook when + * setting an xattr in the security namespace. + */ +static void +simple_flow_inode_post_setxattr(struct dentry *dentry, + const char *name, + const void *value, + size_t size, + int flags) +{ + char *buf = NULL; + struct t_security *tsec; + char tcomm[sizeof(current->comm)]; + struct inode *inode = NULL; + struct inode_security_struct *isec = NULL; + + get_task_comm(tcomm, current); + + tsec = rcu_dereference_protected(current->cred, 1)->security; + if (tsec == NULL) { + pr_err(MOD ": %s has no security context!\n", tcomm); + goto done; + } + + inode = dentry->d_inode; + isec = inode->i_security; + + if (tsec->tainted && !isec->owner_tainted) { + char *path; + + buf = kcalloc(PATH_MAX, sizeof(*buf), GFP_KERNEL); + if (buf == NULL) { + path = "[path lookup failed]"; + } else { + path = dentry_path_raw(dentry, buf, PATH_MAX); + if (IS_ERR(path)) + path = "[path lookup failed]"; + } + + pr_info(MOD ": %s tainted; mark file at %s\n", + tcomm, path); + + isec->owner_tainted = true; + + /* Try to set confidentiality attribute. */ + simple_flow_dentry_setxattr(dentry, + SIMPLE_FLOW_XATTR_NAME_TAINTED, + "true", strlen("true"), 0); + } + +done: + if (buf != NULL) + kfree(buf); +} + +/* 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 rc; + struct inode *inode = NULL; + struct inode_security_struct *isec = NULL; + + /* First check for permission to remove. */ + rc = simple_flow_check_xattr_perm(name); + if (rc != 0) + goto done; + + /* Next, update relavent data structure when required. */ + if (strcmp(name, SIMPLE_FLOW_XATTR_NAME_TAINTED) != 0) + goto done; + + inode = dentry->d_inode; + isec = inode->i_security; + + /* + * This cannot move to simple_flow_inode_post_setxattr + * because the kernel does not invoke the post hook + * when setting an xattr in the security namespace. + */ + if (simple_flow_xattr_match("true", value, size)) + isec->owner_tainted = true; + else + isec->owner_tainted = false; + + /* + * NOTE: simple_flow_inode_post_setxattr will set file + * confidential if necessary. + */ + +done: + + return rc; +} + +static int +simple_flow_mmap_file(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) +{ + int rc = 0; + struct inode *inode = NULL; + struct inode_security_struct *isec; + bool trusted = simple_flow_process_is_trusted_with(current, TRUST_ALL); + + 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_PRIVATE)) { + /* 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) + path = "[path lookup failed]"; + + 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 mmap'ed file %s\n", + tcomm, + path); + if (flags & MAP_SHARED) { + if (!simple_flow_shared_file_set_taint(file)) { + rc = -EPERM; + goto done; + } + } else { + simple_flow_mark_file(file); + } + } + } + + if (prot & PROT_READ && 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) + path = "[path lookup failed]"; + + 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 { + if (!simple_flow_process_set_taint(current, + "mmap", + path)) { + rc = -EPERM; + goto done; + } + } + } + +done: + + return rc; +} + +static int +simple_flow_msg_queue_alloc_security(struct msg_queue *msq) +{ + int rc = 0; + struct msg_security_struct *mqsec = NULL; + + mqsec = kzalloc(sizeof(*mqsec), GFP_KERNEL); + if (mqsec == NULL) { + rc = -ENOMEM; + goto done; + } + +done: + msq->q_perm.security = mqsec; + + return rc; +} + +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) +{ + int rc = 0; + char tcomm[sizeof(current->comm)]; + bool trusted = false; + struct msg_security_struct *mqsec = NULL; + + trusted = simple_flow_process_is_trusted_with(current, TRUST_ALL); + + 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 { + if (!simple_flow_process_set_taint(current, + "msg-queue", + "anonymous")) { + rc = -EPERM; + goto done; + } + } + } + +done: + return rc; +} + +static int +simple_flow_ptrace_access_check(struct task_struct *child, + unsigned int mode) +{ + char tcomm1[sizeof(current->comm)]; + char tcomm2[sizeof(child->comm)]; + struct t_security *tsec1; + struct t_security *tsec2; + + tsec1 = current_security(); + tsec2 = rcu_dereference_protected(child->cred, 1)->security; + + if (tsec2 == NULL) + goto done; + + get_task_comm(tcomm1, current); + /* No get_task_comm here; child already locked. */ + strncpy(tcomm2, child->comm, sizeof(child->comm)); + + if (tsec2->tainted == true + && (tsec1 == NULL || tsec1->tainted == false)) { + if (simple_flow_process_is_trusted_with(current, TRUST_ALL)) { + + pr_warn(MOD ": trusted, untainted %s (pid: %d, " + "euid: %d, uid: %d) ptraced tainted %s " + "(pid: %d, euid: %d, uid: %d)\n", + tcomm1, + current->pid, + __kuid_val(task_uid(current)), + __kuid_val(current->real_cred->uid), + tcomm2, + child->pid, + __kuid_val(task_uid(current)), + __kuid_val(current->real_cred->uid)); + } else { + simple_flow_process_set_taint(current, + "ptrace", + tcomm2); + } + } + +done: + 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 rc; + struct inode *inode = NULL; + struct inode_security_struct *isec = NULL; + + /* First check for permission to remove. */ + rc = simple_flow_check_xattr_perm(name); + if (rc != 0) + goto done; + + /* Next, update relavent data structure when required. */ + if (strcmp(name, SIMPLE_FLOW_XATTR_NAME_TAINTED) != 0) + goto done; + + inode = dentry->d_inode; + isec = inode->i_security; + if (isec == NULL) + /* FIXME: How can this happen? */ + goto done; + + isec->owner_tainted = false; + +done: + return rc; +} + +static int +simple_flow_file_alloc_security(struct file *file) +{ + int rc = 0; + struct file_security_struct *fsec = NULL; + + fsec = kzalloc(sizeof(*fsec), GFP_NOFS); + if (fsec == NULL) { + rc = -ENOMEM; + goto done; + } + + file->f_security = fsec; + +done: + return rc; +} + +static void +simple_flow_file_free_security(struct file *file) +{ + if (file->f_security != NULL) { + struct file_security_struct *fsec = file->f_security; + kfree(fsec); + file->f_security = NULL; + } +} + +/* + * Cannot open for writing if process tainted and file marked "never." + * Tainting left to reads, mmaps, etc. + */ +static int +simple_flow_file_open(struct file *file, const struct cred *cred) +{ + struct t_security *tsec = cred->security; + struct inode *inode; + struct inode_security_struct *isec; + bool might_write = false; + int rc = 0; + + inode = file_inode(file); + isec = inode->i_security; + if (isec == NULL) { + /* FIXME: How can this happen? */ + goto done; + } + + might_write = file->f_flags & O_WRONLY + || file->f_flags & O_RDWR + || file->f_flags & O_APPEND; + + if (tsec->tainted && might_write && simple_flow_is_never(file)) { + static char buf[PATH_MAX]; + char tcomm[sizeof(current->comm)]; + char *path = d_path(&file->f_path, buf, PATH_MAX); + if (path == NULL) + path = "[path lookup failed]"; + + rc = -EPERM; + + get_task_comm(tcomm, current); + + pr_warn(MOD ": tainted %s tried to open file %s with " + "never-taint confidentiality\n", tcomm, path); + } else if (!isec->owner_tainted && simple_flow_is_confidential(file)) { + struct inode *inode = file_inode(file); + struct inode_security_struct *isec = inode->i_security; + if (isec != NULL) + isec->owner_tainted = true; + } + +done: + return rc; +} + +static int +simple_flow_file_lseek(struct file *file) +{ + struct t_security *tsec = NULL; + struct inode *inode = NULL; + struct inode_security_struct *isec = NULL; + char tcomm[sizeof(current->comm)]; + static char buf[PATH_MAX], *path; + int rc = 0; + + tsec = current_security(); + inode = file_inode(file); + isec = inode->i_security; + if (isec == NULL) + /* TODO: How can this happen? */ + goto done; + + path = d_path(&file->f_path, buf, PATH_MAX); + if (path == NULL) + path = "[path lookup failed]"; + + get_task_comm(tcomm, current); + + /* + * FIXME: Race condition on file->f_count? Or, okay because + * it should increment only if current forks or is created by fork? + */ + if (tsec->tainted && !isec->owner_tainted + && file->f_count.counter > 1) { + if (simple_flow_is_never(file)) { + pr_warn(MOD ": tainted %s tried to seek shared file " + " offset in %s with never-taint " + "confidentiality\n", + tcomm, + path); + + rc = -EPERM; + goto done; + } else { + pr_warn(MOD ": %s tainted; mark seek target with " + "shared file offset at %s\n", + tcomm, path); + + /* + * Do not need to set persistent xattr. + * Seek covert channel is not persistent. + */ + isec->owner_tainted = true; + } + } else if (isec->owner_tainted && file->f_count.counter > 1) { + if (simple_flow_process_is_trusted_with(current, TRUST_ALL)) { + get_task_comm(tcomm, current); + pr_info(MOD ": %s trusted; not tainting despite " + "seek in %s\n", tcomm, path); + } else { + if (!simple_flow_process_set_taint(current, + "lseek", + path)) { + rc = -EPERM; + goto done; + } + } + } + +done: + return rc; +} + +static int +simple_flow_bprm_set_creds(struct linux_binprm *bprm) +{ + int rc = 0; + struct inode *inode; + struct inode_security_struct *isec; + struct t_security *tsec; + char *buf = NULL, *path = NULL; + char tcomm[sizeof(current->comm)]; + + /* + * Check capabilities first. This seems to grant + * setuid-bit functionality. + */ + rc = cap_bprm_set_creds(bprm); + if (rc != 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) + path = "[path lookup failed]"; + + if (bprm->cred->security == NULL) { + pr_warn(MOD ": task from %s has no security context!\n", path); + goto done; + } + + tsec = bprm->cred->security; + tsec->trust_mask = simple_flow_how_trusted(bprm->file); + + inode = file_inode(bprm->file); + isec = inode->i_security; + + if (isec == NULL) + /* FIXME: How can this happen? */ + goto done; + + get_task_comm(tcomm, current); + + if (tsec->trust_mask == TRUST_ALL) { + pr_info(MOD ": %s loaded %s which is fully trusted\n", + tcomm, path); + tsec->tainted = false; + } else if (isec->owner_tainted) { + pr_warn(MOD ": tainting process running %s due to %s " + "interaction with %s (exec'ed by %s).\n", + path, + "exec", + path, + tcomm); + tsec->tainted = true; + } else if (tsec->tainted) { + pr_info(MOD ": tainted %s exec'ed tainted %s\n", tcomm, path); + } + +done: + if (buf != NULL) + kfree(buf); + + return rc; +} + +static int +simple_flow_shm_alloc_security(struct shmid_kernel *shmid) +{ + int rc = 0; + struct shm_security_struct *shmsec = NULL; + + shmsec = kzalloc(sizeof(*shmsec), GFP_KERNEL); + if (shmsec == NULL) { + rc = -ENOMEM; + goto done; + } + +done: + shmid->shm_perm.security = shmsec; + + return rc; +} + +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) +{ + int rc = 0; + 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_is_trusted_with(current, TRUST_ALL)) { + get_task_comm(tcomm, current); + pr_info(MOD ": %s trusted; not tainting despite " + "shmat\n", tcomm); + } else { + if (!simple_flow_process_set_taint(current, + "shm", + "anonymous")) { + rc = -EPERM; + goto done; + } + } + } + +done: + return rc; +} + +/* + * 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; +} + +static bool +simple_flow_is_pseudo_terminal(struct inode *inode) +{ + return S_ISCHR(inode->i_mode) + && (imajor(inode) == 5 || imajor(inode) == 136); +} + +/* + * 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) +{ + int rc = 0; + 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) + path = "[path lookup failed]"; + + 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_is_trusted_with(current, + TRUST_ALL); + + 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 (simple_flow_is_pseudo_terminal(inode)) { + struct file_security_struct *fsec = NULL; + + fsec = file->f_security; + if (fsec->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 { + if (!simple_flow_process_set_taint(current, + "read", + path)) { + rc = -EPERM; + goto done; + } + } + } + } + + 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_is_trusted_with(current, + TRUST_ALL); + tsec = current_security(); + if (!trusted && isec->owner_tainted) { + pr_warn_ratelimited( + 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) { + /* + * 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); + simple_flow_mark_file(file); + } 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); + isec->owner_tainted = true; + sksec->owner_tainted = true; + } else if (simple_flow_is_pseudo_terminal(inode)) { + /* + * Check inode sec., set file sec.; will + * repeatedly set. + */ + pr_info(MOD ": %s tainted; mark pseudo device " + "at %s\n", tcomm, path); + simple_flow_mark_pseudo_tty(file); + simple_flow_mark_pseudo_device_peer(file, tcomm); + } else { + if (simple_flow_is_never(file)) { + pr_warn(MOD ": will not taint %s with " + "never-set confidentiality", + path); + rc = -EPERM; + goto done; + } + + pr_info(MOD ": %s tainted; mark file at %s\n", + tcomm, path); + simple_flow_mark_file(file); + } + } + } + +done: + return rc; +} + +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 (simple_flow_xattr_match("0\n", value, size)) { + 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 (simple_flow_xattr_match("1\n", value, size)) { + tsec->tainted = true; + } else { + size = -EINVAL; + goto done; + } + +done: + return size; +} + +/* + * Given a port, either return a tainted process which has already reserved + * the port using bind or return NULL. + */ +static struct task_struct * +simple_flow_tainted_process_reserved_port(struct socket *sock, short port) +{ + struct task_struct *task = NULL; + struct inet_bind_hashbucket *head = NULL; + + struct inet_bind_bucket *tb; + struct sock *sk = sock->sk; + struct net *net = sock_net(sk); + struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo; + + if (hashinfo == NULL) + goto done; + if (hashinfo->bhash == NULL) + goto done; + + head = &hashinfo->bhash[inet_bhashfn(net, port, hashinfo->bhash_size)]; + spin_lock(&head->lock); + + inet_bind_bucket_for_each(tb, &head->chain) { + struct sock *sk2; + + if (!net_eq(ib_net(tb), net)) + continue; + if (hlist_empty(&tb->owners)) + continue; + + sk_for_each_bound(sk2, &tb->owners) { + struct t_security *tsec; + struct sk_security_struct *sksec = sk2->sk_security; + + if (sksec == NULL) + goto done; + if (sksec->task == NULL) + goto done; + + tsec = rcu_dereference_protected + (sksec->task->cred, 1)->security; + + if (tsec->tainted) { + task = sksec->task; + goto done; + } + } + } + +done: + if (head != NULL) + spin_unlock(&head->lock); + + return task; +} + +static int +simple_flow_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + int rc = 0; + + if (sock->type == SOCK_STREAM || sock->type == SOCK_DGRAM) { + struct sockaddr_in *address_in = (struct sockaddr_in *) address; + short port = ntohs(address_in->sin_port); + struct task_struct *task = + simple_flow_tainted_process_reserved_port(sock, port); + if (task != NULL) { + char tcomm1[sizeof(current->comm)]; + char tcomm2[sizeof(current->comm)]; + + get_task_comm(tcomm1, current); + get_task_comm(tcomm2, task); + + if (simple_flow_process_is_trusted_with(current, + TRUST_ALL)) { + pr_info(MOD ": %s trusted; not tainting " + "despite failed bind to tainted/" + "reserved port %d (%s)\n", + tcomm1, + port, + tcomm2); + } else { + if (!simple_flow_process_set_taint(current, + "bind", + tcomm2)) { + rc = -EPERM; + goto done; + } + } + } + } + +done: + return rc; +} + +/* + * 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 rc; + + switch (type) { + case SYSLOG_ACTION_READ_ALL: /* Read last kernel messages */ + case SYSLOG_ACTION_SIZE_BUFFER: /* Return size of the log buffer */ + rc = capable(CAP_SYS_ADMIN) ? 0 : -EPERM; + break; + default: + rc = 0; + break; + } + + return rc; +} + +/* + * 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(sksec); + 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) { + /* TODO: How can this happen? */ + 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 int +simple_flow_task_wait(struct task_struct *child) +{ + char tcomm1[sizeof(current->comm)]; + char tcomm2[sizeof(current->comm)]; + struct t_security *tsec1; + struct t_security *tsec2; + + if (child == NULL) + goto done; + + tsec1 = current_security(); + tsec2 = rcu_dereference_protected(child->cred, 1)->security; + + if (tsec1 == NULL) + goto done; + + if (tsec2 == NULL) + goto done; + + get_task_comm(tcomm1, current); + get_task_comm(tcomm2, child); + + if (tsec2->tainted == true && (tsec1->tainted == false)) { + if (simple_flow_process_is_trusted_with(current, TRUST_WAIT)) { + + pr_warn(MOD ": wait-trusted, untainted %s (pid: %d, " + "euid: %d, uid: %d) waiting on tainted %s " + "(pid: %d, euid: %d, uid: %d) (could " + "transfer one-byte exit code)\n", + tcomm1, + current->pid, + __kuid_val(task_uid(current)), + __kuid_val(current->real_cred->uid), + tcomm2, + child->pid, + __kuid_val(task_uid(current)), + __kuid_val(current->real_cred->uid)); + } else { + simple_flow_process_set_taint(current, + "wait", + tcomm2); + } + } + +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, + .d_instantiate = simple_flow_d_instantiate, + .file_alloc_security = simple_flow_file_alloc_security, + .file_free_security = simple_flow_file_free_security, + .file_open = simple_flow_file_open, + .file_lseek = simple_flow_file_lseek, + .file_permission = simple_flow_file_permission, + .getprocattr = simple_flow_getprocattr, + .setprocattr = simple_flow_setprocattr, + .inode_alloc_security = simple_flow_inode_alloc_security, + .inode_create = simple_flow_inode_create, + .inode_mkdir = simple_flow_inode_create, + .inode_getattr = simple_flow_inode_getattr, + .inode_mknod = simple_flow_inode_mknod, + .inode_link = simple_flow_inode_link, + .inode_permission = simple_flow_inode_permission, + .inode_symlink = simple_flow_inode_symlink, + .inode_rename = simple_flow_inode_rename, + .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, + .inode_post_setxattr = simple_flow_inode_post_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, + .ptrace_access_check = simple_flow_ptrace_access_check, + .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_bind = simple_flow_socket_bind, + .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, + .task_wait = simple_flow_task_wait, +}; + +/* + * 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? */ + goto done; + } + + if (skb->sk->sk_security == NULL) { + /* TODO: How can this happen? */ + 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? */ + goto done; + } + + if (skb->sk->sk_security == NULL) { + /* TODO: How can this happen? */ + 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