From d4a329ee417f1b75ba087828bb4b2f62c1ef57bb Mon Sep 17 00:00:00 2001 From: Auto Configured Date: Sun, 25 Oct 2015 12:19:45 -0700 Subject: [PATCH] Ci add ccsecuroty Signed-off-by: Auto Configured --- include/linux/ccsecurity.h | 926 +++++ include/linux/lsm2ccsecurity.h | 181 + security/ccsecurity/Config.in | 83 + security/ccsecurity/Kconfig | 190 + security/ccsecurity/Makefile | 122 + security/ccsecurity/gc.c | 1036 ++++++ security/ccsecurity/internal.h | 2090 +++++++++++ security/ccsecurity/load_policy.c | 352 ++ security/ccsecurity/lsm2ccsecurity.c | 192 + security/ccsecurity/memory.c | 356 ++ security/ccsecurity/permission.c | 5025 ++++++++++++++++++++++++++ security/ccsecurity/policy_io.c | 6484 ++++++++++++++++++++++++++++++++++ security/ccsecurity/realpath.c | 767 ++++ 13 files changed, 17804 insertions(+) create mode 100644 include/linux/ccsecurity.h create mode 100644 include/linux/lsm2ccsecurity.h create mode 100644 security/ccsecurity/Config.in create mode 100644 security/ccsecurity/Kconfig create mode 100644 security/ccsecurity/Makefile create mode 100644 security/ccsecurity/gc.c create mode 100644 security/ccsecurity/internal.h create mode 100644 security/ccsecurity/load_policy.c create mode 100644 security/ccsecurity/lsm2ccsecurity.c create mode 100644 security/ccsecurity/memory.c create mode 100644 security/ccsecurity/permission.c create mode 100644 security/ccsecurity/policy_io.c create mode 100644 security/ccsecurity/realpath.c diff --git a/include/linux/ccsecurity.h b/include/linux/ccsecurity.h new file mode 100644 index 0000000..6c1ca2b --- /dev/null +++ b/include/linux/ccsecurity.h @@ -0,0 +1,926 @@ +/* + * include/linux/ccsecurity.h + * + * Copyright (C) 2005-2012 NTT DATA CORPORATION + * + * Version: 1.8.4 2015/05/05 + */ + +#ifndef _LINUX_CCSECURITY_H +#define _LINUX_CCSECURITY_H + +#include + +#ifndef __user +#define __user +#endif + +struct nameidata; +struct path; +struct dentry; +struct vfsmount; +struct linux_binprm; +struct pt_regs; +struct file; +struct ctl_table; +struct socket; +struct sockaddr; +struct sock; +struct sk_buff; +struct msghdr; +struct pid_namespace; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) +int search_binary_handler(struct linux_binprm *bprm); +#else +int search_binary_handler(struct linux_binprm *bprm, struct pt_regs *regs); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) +#include +#endif + +#ifdef CONFIG_CCSECURITY + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) && LINUX_VERSION_CODE < KERNEL_VERSION(3, 2, 0) +/* Obtain prototype of __d_path(). */ +#include +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) +/* Obtain definition of kuid_t and kgid_t. */ +#include +#endif + +/* For exporting variables and functions. */ +struct ccsecurity_exports { + void (*load_policy) (const char *filename); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) && defined(CONFIG_SECURITY) + void (*add_hooks) (void); +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0) + char * (*d_absolute_path) (const struct path *, char *, int); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) + typeof(__d_path) (*__d_path); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) + spinlock_t *vfsmount_lock; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + struct task_struct * (*find_task_by_vpid) (pid_t nr); + struct task_struct * (*find_task_by_pid_ns) (pid_t nr, + struct pid_namespace *ns); +#endif +}; + +/* For doing access control. */ +struct ccsecurity_operations { + void (*check_profile) (void); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + int (*chroot_permission) (struct path *path); + int (*pivot_root_permission) (struct path *old_path, + struct path *new_path); + int (*mount_permission) (const char *dev_name, struct path *path, + const char *type, unsigned long flags, + void *data_page); +#else + int (*chroot_permission) (struct nameidata *nd); + int (*pivot_root_permission) (struct nameidata *old_nd, + struct nameidata *new_nd); + int (*mount_permission) (const char *dev_name, struct nameidata *nd, + const char *type, unsigned long flags, + void *data_page); +#endif + int (*umount_permission) (struct vfsmount *mnt, int flags); + _Bool (*lport_reserved) (const u16 port); +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32) + void (*save_open_mode) (int mode); + void (*clear_open_mode) (void); + int (*open_permission) (struct dentry *dentry, struct vfsmount *mnt, + const int flag); +#else + int (*open_permission) (struct file *file); +#endif + int (*ptrace_permission) (long request, long pid); + int (*ioctl_permission) (struct file *filp, unsigned int cmd, + unsigned long arg); + int (*parse_table) (int __user *name, int nlen, void __user *oldval, + void __user *newval, struct ctl_table *table); + _Bool (*capable) (const u8 operation); + int (*mknod_permission) (struct dentry *dentry, struct vfsmount *mnt, + unsigned int mode, unsigned int dev); + int (*mkdir_permission) (struct dentry *dentry, struct vfsmount *mnt, + unsigned int mode); + int (*rmdir_permission) (struct dentry *dentry, struct vfsmount *mnt); + int (*unlink_permission) (struct dentry *dentry, struct vfsmount *mnt); + int (*symlink_permission) (struct dentry *dentry, struct vfsmount *mnt, + const char *from); + int (*truncate_permission) (struct dentry *dentry, + struct vfsmount *mnt); + int (*rename_permission) (struct dentry *old_dentry, + struct dentry *new_dentry, + struct vfsmount *mnt); + int (*link_permission) (struct dentry *old_dentry, + struct dentry *new_dentry, + struct vfsmount *mnt); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) + int (*open_exec_permission) (struct dentry *dentry, + struct vfsmount *mnt); + int (*uselib_permission) (struct dentry *dentry, struct vfsmount *mnt); +#endif + int (*fcntl_permission) (struct file *file, unsigned int cmd, + unsigned long arg); + int (*kill_permission) (pid_t pid, int sig); + int (*tgkill_permission) (pid_t tgid, pid_t pid, int sig); + int (*tkill_permission) (pid_t pid, int sig); + int (*socket_create_permission) (int family, int type, int protocol); + int (*socket_listen_permission) (struct socket *sock); + int (*socket_connect_permission) (struct socket *sock, + struct sockaddr *addr, int addr_len); + int (*socket_bind_permission) (struct socket *sock, + struct sockaddr *addr, int addr_len); + int (*socket_post_accept_permission) (struct socket *sock, + struct socket *newsock); + int (*socket_sendmsg_permission) (struct socket *sock, + struct msghdr *msg, int size); + int (*socket_post_recvmsg_permission) (struct sock *sk, + struct sk_buff *skb, int flags); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + int (*chown_permission) (struct dentry *dentry, struct vfsmount *mnt, + kuid_t user, kgid_t group); +#else + int (*chown_permission) (struct dentry *dentry, struct vfsmount *mnt, + uid_t user, gid_t group); +#endif + int (*chmod_permission) (struct dentry *dentry, struct vfsmount *mnt, + mode_t mode); + int (*getattr_permission) (struct vfsmount *mnt, + struct dentry *dentry); + int (*sigqueue_permission) (pid_t pid, int sig); + int (*tgsigqueue_permission) (pid_t tgid, pid_t pid, int sig); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) + int (*search_binary_handler) (struct linux_binprm *bprm); +#else + int (*search_binary_handler) (struct linux_binprm *bprm, + struct pt_regs *regs); +#endif +#ifdef CONFIG_CCSECURITY_USE_EXTERNAL_TASK_SECURITY + int (*alloc_task_security) (const struct task_struct *task); + void (*free_task_security) (const struct task_struct *task); +#endif + _Bool disabled; +}; + +extern struct ccsecurity_operations ccsecurity_ops; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + +static inline int ccs_chroot_permission(struct path *path) +{ + int (*func) (struct path *) = ccsecurity_ops.chroot_permission; + return func ? func(path) : 0; +} + +static inline int ccs_pivot_root_permission(struct path *old_path, + struct path *new_path) +{ + int (*func) (struct path *, struct path *) + = ccsecurity_ops.pivot_root_permission; + return func ? func(old_path, new_path) : 0; +} + +static inline int ccs_mount_permission(const char *dev_name, struct path *path, + const char *type, unsigned long flags, + void *data_page) +{ + int (*func) (const char *, struct path *, const char *, unsigned long, + void *) = ccsecurity_ops.mount_permission; + return func ? func(dev_name, path, type, flags, data_page) : 0; +} + +#else + +static inline int ccs_chroot_permission(struct nameidata *nd) +{ + int (*func) (struct nameidata *) = ccsecurity_ops.chroot_permission; + return func ? func(nd) : 0; +} + +static inline int ccs_pivot_root_permission(struct nameidata *old_nd, + struct nameidata *new_nd) +{ + int (*func) (struct nameidata *, struct nameidata *) + = ccsecurity_ops.pivot_root_permission; + return func ? func(old_nd, new_nd) : 0; +} + +static inline int ccs_mount_permission(const char *dev_name, + struct nameidata *nd, const char *type, + unsigned long flags, void *data_page) +{ + int (*func) (const char *, struct nameidata *, const char *, + unsigned long, void *) = ccsecurity_ops.mount_permission; + return func ? func(dev_name, nd, type, flags, data_page) : 0; +} + +#endif + +static inline int ccs_umount_permission(struct vfsmount *mnt, int flags) +{ + int (*func) (struct vfsmount *, int) + = ccsecurity_ops.umount_permission; + return func ? func(mnt, flags) : 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32) + +static inline void ccs_save_open_mode(int mode) +{ + void (*func) (int) = ccsecurity_ops.save_open_mode; + if (func) + func(mode); +} + +static inline void ccs_clear_open_mode(void) +{ + void (*func) (void) = ccsecurity_ops.clear_open_mode; + if (func) + func(); +} + +static inline int ccs_open_permission(struct dentry *dentry, + struct vfsmount *mnt, const int flag) +{ + int (*func) (struct dentry *, struct vfsmount *, const int) + = ccsecurity_ops.open_permission; + return func ? func(dentry, mnt, flag) : 0; +} + +#else + +static inline int ccs_open_permission(struct file *filp) +{ + int (*func) (struct file *) = ccsecurity_ops.open_permission; + return func ? func(filp) : 0; +} + +#endif + +static inline int ccs_fcntl_permission(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int (*func) (struct file *, unsigned int, unsigned long) + = ccsecurity_ops.fcntl_permission; + return func ? func(file, cmd, arg) : 0; +} + +static inline int ccs_ioctl_permission(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + int (*func) (struct file *, unsigned int, unsigned long) + = ccsecurity_ops.ioctl_permission; + return func ? func(filp, cmd, arg) : 0; +} + +static inline int ccs_parse_table(int __user *name, int nlen, + void __user *oldval, void __user *newval, + struct ctl_table *table) +{ + int (*func) (int __user *, int, void __user *, void __user *, + struct ctl_table *) = ccsecurity_ops.parse_table; + return func ? func(name, nlen, oldval, newval, table) : 0; +} + +static inline int ccs_mknod_permission(struct dentry *dentry, + struct vfsmount *mnt, unsigned int mode, + unsigned int dev) +{ + int (*func) (struct dentry *, struct vfsmount *, unsigned int, + unsigned int) = ccsecurity_ops.mknod_permission; + return func ? func(dentry, mnt, mode, dev) : 0; +} + +static inline int ccs_mkdir_permission(struct dentry *dentry, + struct vfsmount *mnt, unsigned int mode) +{ + int (*func) (struct dentry *, struct vfsmount *, unsigned int) + = ccsecurity_ops.mkdir_permission; + return func ? func(dentry, mnt, mode) : 0; +} + +static inline int ccs_rmdir_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + int (*func) (struct dentry *, struct vfsmount *) + = ccsecurity_ops.rmdir_permission; + return func ? func(dentry, mnt) : 0; +} + +static inline int ccs_unlink_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + int (*func) (struct dentry *, struct vfsmount *) + = ccsecurity_ops.unlink_permission; + return func ? func(dentry, mnt) : 0; +} + +static inline int ccs_symlink_permission(struct dentry *dentry, + struct vfsmount *mnt, + const char *from) +{ + int (*func) (struct dentry *, struct vfsmount *, const char *) + = ccsecurity_ops.symlink_permission; + return func ? func(dentry, mnt, from) : 0; +} + +static inline int ccs_truncate_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + int (*func) (struct dentry *, struct vfsmount *) + = ccsecurity_ops.truncate_permission; + return func ? func(dentry, mnt) : 0; +} + +static inline int ccs_rename_permission(struct dentry *old_dentry, + struct dentry *new_dentry, + struct vfsmount *mnt) +{ + int (*func) (struct dentry *, struct dentry *, struct vfsmount *) + = ccsecurity_ops.rename_permission; + return func ? func(old_dentry, new_dentry, mnt) : 0; +} + +static inline int ccs_link_permission(struct dentry *old_dentry, + struct dentry *new_dentry, + struct vfsmount *mnt) +{ + int (*func) (struct dentry *, struct dentry *, struct vfsmount *) + = ccsecurity_ops.link_permission; + return func ? func(old_dentry, new_dentry, mnt) : 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) + +static inline int ccs_open_exec_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + int (*func) (struct dentry *, struct vfsmount *) + = ccsecurity_ops.open_exec_permission; + return func ? func(dentry, mnt) : 0; +} + +static inline int ccs_uselib_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + int (*func) (struct dentry *, struct vfsmount *) + = ccsecurity_ops.uselib_permission; + return func ? func(dentry, mnt) : 0; +} + +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + +static inline int ccs_chown_permission(struct dentry *dentry, + struct vfsmount *mnt, kuid_t user, + kgid_t group) +{ + int (*func) (struct dentry *, struct vfsmount *, kuid_t, kgid_t) + = ccsecurity_ops.chown_permission; + return func ? func(dentry, mnt, user, group) : 0; +} + +#else + +static inline int ccs_chown_permission(struct dentry *dentry, + struct vfsmount *mnt, uid_t user, + gid_t group) +{ + int (*func) (struct dentry *, struct vfsmount *, uid_t, gid_t) + = ccsecurity_ops.chown_permission; + return func ? func(dentry, mnt, user, group) : 0; +} + +#endif + +static inline int ccs_chmod_permission(struct dentry *dentry, + struct vfsmount *mnt, mode_t mode) +{ + int (*func) (struct dentry *, struct vfsmount *, mode_t) + = ccsecurity_ops.chmod_permission; + return func ? func(dentry, mnt, mode) : 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) + +static inline int ccs_search_binary_handler(struct linux_binprm *bprm) +{ + return ccsecurity_ops.search_binary_handler(bprm); +} + +#else + +static inline int ccs_search_binary_handler(struct linux_binprm *bprm, + struct pt_regs *regs) +{ + return ccsecurity_ops.search_binary_handler(bprm, regs); +} + +#endif + +#else + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + +static inline int ccs_chroot_permission(struct path *path) +{ + return 0; +} + +static inline int ccs_pivot_root_permission(struct path *old_path, + struct path *new_path) +{ + return 0; +} + +static inline int ccs_mount_permission(const char *dev_name, struct path *path, + const char *type, unsigned long flags, + void *data_page) +{ + return 0; +} + +#else + +static inline int ccs_chroot_permission(struct nameidata *nd) +{ + return 0; +} + +static inline int ccs_pivot_root_permission(struct nameidata *old_nd, + struct nameidata *new_nd) +{ + return 0; +} + +static inline int ccs_mount_permission(const char *dev_name, + struct nameidata *nd, const char *type, + unsigned long flags, void *data_page) +{ + return 0; +} + +#endif + +static inline int ccs_umount_permission(struct vfsmount *mnt, int flags) +{ + return 0; +} + +static inline void ccs_save_open_mode(int mode) +{ +} + +static inline void ccs_clear_open_mode(void) +{ +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32) + +static inline int ccs_open_permission(struct dentry *dentry, + struct vfsmount *mnt, const int flag) +{ + return 0; +} + +#else + +static inline int ccs_open_permission(struct file *filp) +{ + return 0; +} + +#endif + +static inline int ccs_ioctl_permission(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + return 0; +} + +static inline int ccs_parse_table(int __user *name, int nlen, + void __user *oldval, void __user *newval, + struct ctl_table *table) +{ + return 0; +} + +static inline int ccs_mknod_permission(struct dentry *dentry, + struct vfsmount *mnt, unsigned int mode, + unsigned int dev) +{ + return 0; +} + +static inline int ccs_mkdir_permission(struct dentry *dentry, + struct vfsmount *mnt, unsigned int mode) +{ + return 0; +} + +static inline int ccs_rmdir_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + return 0; +} + +static inline int ccs_unlink_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + return 0; +} + +static inline int ccs_symlink_permission(struct dentry *dentry, + struct vfsmount *mnt, + const char *from) +{ + return 0; +} + +static inline int ccs_truncate_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + return 0; +} + +static inline int ccs_rename_permission(struct dentry *old_dentry, + struct dentry *new_dentry, + struct vfsmount *mnt) +{ + return 0; +} + +static inline int ccs_link_permission(struct dentry *old_dentry, + struct dentry *new_dentry, + struct vfsmount *mnt) +{ + return 0; +} + +static inline int ccs_open_exec_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + return 0; +} + +static inline int ccs_uselib_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + return 0; +} + +static inline int ccs_fcntl_permission(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + +static inline int ccs_chown_permission(struct dentry *dentry, + struct vfsmount *mnt, kuid_t user, + kgid_t group) +{ + return 0; +} + +#else + +static inline int ccs_chown_permission(struct dentry *dentry, + struct vfsmount *mnt, uid_t user, + gid_t group) +{ + return 0; +} + +#endif + +static inline int ccs_chmod_permission(struct dentry *dentry, + struct vfsmount *mnt, mode_t mode) +{ + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) + +static inline int ccs_search_binary_handler(struct linux_binprm *bprm) +{ + return search_binary_handler(bprm); +} + +#else + +static inline int ccs_search_binary_handler(struct linux_binprm *bprm, + struct pt_regs *regs) +{ + return search_binary_handler(bprm, regs); +} + +#endif + +#endif + +#ifdef CONFIG_CCSECURITY_USE_EXTERNAL_TASK_SECURITY + +static inline int ccs_alloc_task_security(const struct task_struct *task) +{ + int (*func) (const struct task_struct *) + = ccsecurity_ops.alloc_task_security; + return func ? func(task) : 0; +} + +static inline void ccs_free_task_security(const struct task_struct *task) +{ + void (*func) (const struct task_struct *) + = ccsecurity_ops.free_task_security; + if (func) + func(task); +} + +#else + +static inline int ccs_alloc_task_security(const struct task_struct *task) +{ + return 0; +} + +static inline void ccs_free_task_security(const struct task_struct *task) +{ +} + +#endif + +#ifdef CONFIG_CCSECURITY_FILE_GETATTR + +static inline int ccs_getattr_permission(struct vfsmount *mnt, + struct dentry *dentry) +{ + int (*func) (struct vfsmount *, struct dentry *) + = ccsecurity_ops.getattr_permission; + return func ? func(mnt, dentry) : 0; +} + +#else + +static inline int ccs_getattr_permission(struct vfsmount *mnt, + struct dentry *dentry) +{ + return 0; +} + +#endif + +#ifdef CONFIG_CCSECURITY_NETWORK + +static inline int ccs_socket_listen_permission(struct socket *sock) +{ + int (*func) (struct socket *) + = ccsecurity_ops.socket_listen_permission; + return func ? func(sock) : 0; +} + +static inline int ccs_socket_connect_permission(struct socket *sock, + struct sockaddr *addr, + int addr_len) +{ + int (*func) (struct socket *, struct sockaddr *, int) + = ccsecurity_ops.socket_connect_permission; + return func ? func(sock, addr, addr_len) : 0; +} + +static inline int ccs_socket_bind_permission(struct socket *sock, + struct sockaddr *addr, + int addr_len) +{ + int (*func) (struct socket *, struct sockaddr *, int) + = ccsecurity_ops.socket_bind_permission; + return func ? func(sock, addr, addr_len) : 0; +} + +static inline int ccs_socket_post_accept_permission(struct socket *sock, + struct socket *newsock) +{ + int (*func) (struct socket *, struct socket *) + = ccsecurity_ops.socket_post_accept_permission; + return func ? func(sock, newsock) : 0; +} + +static inline int ccs_socket_sendmsg_permission(struct socket *sock, + struct msghdr *msg, + int size) +{ + int (*func) (struct socket *, struct msghdr *, int) + = ccsecurity_ops.socket_sendmsg_permission; + return func ? func(sock, msg, size) : 0; +} + +#else + +static inline int ccs_socket_listen_permission(struct socket *sock) +{ + return 0; +} + +static inline int ccs_socket_connect_permission(struct socket *sock, + struct sockaddr *addr, + int addr_len) +{ + return 0; +} + +static inline int ccs_socket_bind_permission(struct socket *sock, + struct sockaddr *addr, + int addr_len) +{ + return 0; +} + +static inline int ccs_socket_post_accept_permission(struct socket *sock, + struct socket *newsock) +{ + return 0; +} + +static inline int ccs_socket_sendmsg_permission(struct socket *sock, + struct msghdr *msg, + int size) +{ + return 0; +} + +#endif + +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + +static inline int ccs_socket_post_recvmsg_permission(struct sock *sk, + struct sk_buff *skb, + int flags) +{ + int (*func) (struct sock *, struct sk_buff *, int) + = ccsecurity_ops.socket_post_recvmsg_permission; + return func ? func(sk, skb, flags) : 0; +} + +#else + +static inline int ccs_socket_post_recvmsg_permission(struct sock *sk, + struct sk_buff *skb, + int flags) +{ + return 0; +} + +#endif + +#ifdef CONFIG_CCSECURITY_PORTRESERVE + +static inline _Bool ccs_lport_reserved(const u16 port) +{ + _Bool (*func) (const u16) = ccsecurity_ops.lport_reserved; + return func ? func(port) : 0; +} + +#else + +static inline _Bool ccs_lport_reserved(const u16 port) +{ + return 0; +} + +#endif + +#ifdef CONFIG_CCSECURITY_CAPABILITY + +static inline _Bool ccs_capable(const u8 operation) +{ + _Bool (*func) (const u8) = ccsecurity_ops.capable; + return func ? func(operation) : 1; +} + +static inline int ccs_socket_create_permission(int family, int type, + int protocol) +{ + int (*func) (int, int, int) = ccsecurity_ops.socket_create_permission; + return func ? func(family, type, protocol) : 0; +} + +static inline int ccs_ptrace_permission(long request, long pid) +{ + int (*func) (long, long) = ccsecurity_ops.ptrace_permission; + return func ? func(request, pid) : 0; +} + +#else + +static inline _Bool ccs_capable(const u8 operation) +{ + return 1; +} + +static inline int ccs_socket_create_permission(int family, int type, + int protocol) +{ + return 0; +} + +static inline int ccs_ptrace_permission(long request, long pid) +{ + return 0; +} + +#endif + +#ifdef CONFIG_CCSECURITY_IPC + +static inline int ccs_kill_permission(pid_t pid, int sig) +{ + int (*func) (pid_t, int) = ccsecurity_ops.kill_permission; + return func ? func(pid, sig) : 0; +} + +static inline int ccs_tgkill_permission(pid_t tgid, pid_t pid, int sig) +{ + int (*func) (pid_t, pid_t, int) = ccsecurity_ops.tgkill_permission; + return func ? func(tgid, pid, sig) : 0; +} + +static inline int ccs_tkill_permission(pid_t pid, int sig) +{ + int (*func) (pid_t, int) = ccsecurity_ops.tkill_permission; + return func ? func(pid, sig) : 0; +} + +static inline int ccs_sigqueue_permission(pid_t pid, int sig) +{ + int (*func) (pid_t, int) = ccsecurity_ops.sigqueue_permission; + return func ? func(pid, sig) : 0; +} + +static inline int ccs_tgsigqueue_permission(pid_t tgid, pid_t pid, int sig) +{ + int (*func) (pid_t, pid_t, int) = ccsecurity_ops.tgsigqueue_permission; + return func ? func(tgid, pid, sig) : 0; +} + +#else + +static inline int ccs_kill_permission(pid_t pid, int sig) +{ + return 0; +} + +static inline int ccs_tgkill_permission(pid_t tgid, pid_t pid, int sig) +{ + return 0; +} + +static inline int ccs_tkill_permission(pid_t pid, int sig) +{ + return 0; +} + +static inline int ccs_sigqueue_permission(pid_t pid, int sig) +{ + return 0; +} + +static inline int ccs_tgsigqueue_permission(pid_t tgid, pid_t pid, int sig) +{ + return 0; +} + +#endif + +/* Index numbers for Capability Controls. */ +enum ccs_capability_acl_index { + /* socket(PF_ROUTE, *, *) */ + CCS_USE_ROUTE_SOCKET, + /* socket(PF_PACKET, *, *) */ + CCS_USE_PACKET_SOCKET, + /* sys_reboot() */ + CCS_SYS_REBOOT, + /* sys_vhangup() */ + CCS_SYS_VHANGUP, + /* do_settimeofday(), sys_adjtimex() */ + CCS_SYS_SETTIME, + /* sys_nice(), sys_setpriority() */ + CCS_SYS_NICE, + /* sys_sethostname(), sys_setdomainname() */ + CCS_SYS_SETHOSTNAME, + /* sys_create_module(), sys_init_module(), sys_delete_module() */ + CCS_USE_KERNEL_MODULE, + /* sys_kexec_load() */ + CCS_SYS_KEXEC_LOAD, + /* sys_ptrace() */ + CCS_SYS_PTRACE, + CCS_MAX_CAPABILITY_INDEX +}; + +#endif diff --git a/include/linux/lsm2ccsecurity.h b/include/linux/lsm2ccsecurity.h new file mode 100644 index 0000000..ab4ea5c --- /dev/null +++ b/include/linux/lsm2ccsecurity.h @@ -0,0 +1,181 @@ +/* + * include/linux/lsm2ccsecurity.h + * + * Copyright (C) 2005-2012 NTT DATA CORPORATION + * + * Version: 1.8.4 2015/07/11 + */ + +#ifndef _LINUX_LSM2CCSECURITY_H +#define _LINUX_LSM2CCSECURITY_H + +#include +#include + +#ifdef CONFIG_CCSECURITY + +int ccs_settime(const struct timespec *ts, const struct timezone *tz); +int ccs_sb_mount(const char *dev_name, struct path *path, const char *type, + unsigned long flags, void *data); +int ccs_sb_umount(struct vfsmount *mnt, int flags); +int ccs_sb_pivotroot(struct path *old_path, struct path *new_path); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) +int ccs_inode_getattr(struct vfsmount *mnt, struct dentry *dentry); +#else +int ccs_inode_getattr(const struct path *path); +#endif +int ccs_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +int ccs_file_fcntl(struct file *file, unsigned int cmd, unsigned long arg); +int ccs_file_open(struct file *file, const struct cred *cred); +int ccs_socket_create(int family, int type, int protocol, int kern); +int ccs_socket_bind(struct socket *sock, struct sockaddr *address, + int addrlen); +int ccs_socket_connect(struct socket *sock, struct sockaddr *address, + int addrlen); +int ccs_socket_listen(struct socket *sock, int backlog); +int ccs_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size); +int ccs_path_unlink(struct path *dir, struct dentry *dentry); +int ccs_path_mkdir(struct path *dir, struct dentry *dentry, umode_t mode); +int ccs_path_rmdir(struct path *dir, struct dentry *dentry); +int ccs_path_mknod(struct path *dir, struct dentry *dentry, umode_t mode, + unsigned int dev); +int ccs_path_truncate(struct path *path); +int ccs_path_symlink(struct path *dir, struct dentry *dentry, + const char *old_name); +int ccs_path_link(struct dentry *old_dentry, struct path *new_dir, + struct dentry *new_dentry); +int ccs_path_rename(struct path *old_dir, struct dentry *old_dentry, + struct path *new_dir, struct dentry *new_dentry); +int ccs_path_chmod(struct path *path, umode_t mode); +int ccs_path_chown(struct path *path, kuid_t uid, kgid_t gid); +int ccs_path_chroot(struct path *path); + +#else + +static inline int ccs_settime(const struct timespec *ts, + const struct timezone *tz) +{ + return 0; +} +static inline int ccs_sb_mount(const char *dev_name, struct path *path, + const char *type, unsigned long flags, + void *data) +{ + return 0; +} +static inline int ccs_sb_umount(struct vfsmount *mnt, int flags) +{ + return 0; +} +static inline int ccs_sb_pivotroot(struct path *old_path, + struct path *new_path) +{ + return 0; +} +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) +static inline int ccs_inode_getattr(struct vfsmount *mnt, + struct dentry *dentry) +{ + return 0; +} +#else +static inline int ccs_inode_getattr(const struct path *path) +{ + return 0; +} +#endif +static inline int ccs_file_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return 0; +} +static inline int ccs_file_fcntl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return 0; +} +static inline int ccs_file_open(struct file *file, const struct cred *cred) +{ + return 0; +} +static inline int ccs_socket_create(int family, int type, int protocol, + int kern) +{ + return 0; +} +static inline int ccs_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + return 0; +} +static inline int ccs_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + return 0; +} +static inline int ccs_socket_listen(struct socket *sock, int backlog) +{ + return 0; +} +static inline int ccs_socket_sendmsg(struct socket *sock, struct msghdr *msg, + int size) +{ + return 0; +} +static inline int ccs_path_unlink(struct path *dir, struct dentry *dentry) +{ + return 0; +} +static inline int ccs_path_mkdir(struct path *dir, struct dentry *dentry, + umode_t mode) +{ + return 0; +} +static inline int ccs_path_rmdir(struct path *dir, struct dentry *dentry) +{ + return 0; +} +static inline int ccs_path_mknod(struct path *dir, struct dentry *dentry, + umode_t mode, unsigned int dev) +{ + return 0; +} +static inline int ccs_path_truncate(struct path *path) +{ + return 0; +} +static inline int ccs_path_symlink(struct path *dir, struct dentry *dentry, + const char *old_name) +{ + return 0; +} +static inline int ccs_path_link(struct dentry *old_dentry, + struct path *new_dir, + struct dentry *new_dentry) +{ + return 0; +} +static inline int ccs_path_rename(struct path *old_dir, + struct dentry *old_dentry, + struct path *new_dir, + struct dentry *new_dentry) +{ + return 0; +} +static inline int ccs_path_chmod(struct path *path, umode_t mode) +{ + return 0; +} +static inline int ccs_path_chown(struct path *path, kuid_t uid, kgid_t gid) +{ + return 0; +} +static inline int ccs_path_chroot(struct path *path) +{ + return 0; +} + +#endif /* defined(CONFIG_CCSECURITY) */ + +#endif /* !defined(_LINUX_LSM2CCSECURITY_H) */ diff --git a/security/ccsecurity/Config.in b/security/ccsecurity/Config.in new file mode 100644 index 0000000..da39bad6 --- /dev/null +++ b/security/ccsecurity/Config.in @@ -0,0 +1,83 @@ +# +# Mandatory Access Control configuration +# +mainmenu_option next_comment +comment 'Security options' + +[ -z "$CONFIG_CCSECURITY" ] && define_bool CONFIG_CCSECURITY y +bool 'CCSecurity support' CONFIG_CCSECURITY + +if [ "$CONFIG_CCSECURITY" = "y" ]; then + + [ -z "$CONFIG_CCSECURITY_LKM" ] && define_bool CONFIG_CCSECURITY_LKM n + bool 'Compile as loadable kernel module' CONFIG_CCSECURITY_LKM + + [ -z "$CONFIG_CCSECURITY_DISABLE_BY_DEFAULT" ] && define_bool CONFIG_CCSECURITY_DISABLE_BY_DEFAULT n + bool 'Disable by default' CONFIG_CCSECURITY_DISABLE_BY_DEFAULT + + [ -z "$CONFIG_CCSECURITY_MAX_ACCEPT_ENTRY" ] && define_int CONFIG_CCSECURITY_MAX_ACCEPT_ENTRY 2048 + [ $CONFIG_CCSECURITY_MAX_ACCEPT_ENTRY -lt 0 ] && define_int CONFIG_CCSECURITY_MAX_ACCEPT_ENTRY 0 + int 'Default maximal count for learning mode' CONFIG_CCSECURITY_MAX_ACCEPT_ENTRY + + [ -z "$CONFIG_CCSECURITY_MAX_AUDIT_LOG" ] && define_int CONFIG_CCSECURITY_MAX_AUDIT_LOG 1024 + [ $CONFIG_CCSECURITY_MAX_AUDIT_LOG -lt 0 ] && define_int CONFIG_CCSECURITY_MAX_AUDIT_LOG 0 + int 'Default maximal count for audit log' CONFIG_CCSECURITY_MAX_AUDIT_LOG + + [ -z "$CONFIG_CCSECURITY_OMIT_USERSPACE_LOADER" ] && define_bool CONFIG_CCSECURITY_OMIT_USERSPACE_LOADER n + bool 'Activate without calling userspace policy loader.' CONFIG_CCSECURITY_OMIT_USERSPACE_LOADER + + if [ "$CONFIG_CCSECURITY_OMIT_USERSPACE_LOADER" = "n" ]; then + + define_string CONFIG_CCSECURITY_POLICY_LOADER "/sbin/ccs-init" + string 'Location of userspace policy loader' CONFIG_CCSECURITY_POLICY_LOADER "/sbin/ccs-init" + + define_string CONFIG_CCSECURITY_ACTIVATION_TRIGGER "/sbin/init" + string 'Trigger for calling userspace policy loader' CONFIG_CCSECURITY_ACTIVATION_TRIGGER "/sbin/init" + + fi + + [ -z "$CONFIG_CCSECURITY_FILE_READDIR" ] && define_bool CONFIG_CCSECURITY_FILE_READDIR y + bool "Enable readdir operation restriction." CONFIG_CCSECURITY_FILE_READDIR + + [ -z "$CONFIG_CCSECURITY_FILE_GETATTR" ] && define_bool CONFIG_CCSECURITY_FILE_GETATTR y + bool "Enable getattr operation restriction." CONFIG_CCSECURITY_FILE_GETATTR + + if [ "$CONFIG_NET" = "y" ]; then + + [ -z "$CONFIG_CCSECURITY_NETWORK" ] && define_bool CONFIG_CCSECURITY_NETWORK y + bool "Enable socket operation restriction." CONFIG_CCSECURITY_NETWORK + + if [ "$CONFIG_CCSECURITY_NETWORK" = "y" ]; then + + #[ -z "$CONFIG_CCSECURITY_NETWORK_RECVMSG" ] && + define_bool CONFIG_CCSECURITY_NETWORK_RECVMSG y + + fi + + fi + + [ -z "$CONFIG_CCSECURITY_CAPABILITY" ] && define_bool CONFIG_CCSECURITY_CAPABILITY y + bool "Enable non-POSIX capability operation restriction." CONFIG_CCSECURITY_CAPABILITY + + [ -z "$CONFIG_CCSECURITY_IPC" ] && define_bool CONFIG_CCSECURITY_IPC y + bool "Enable IPC operation restriction." CONFIG_CCSECURITY_IPC + + [ -z "$CONFIG_CCSECURITY_MISC" ] && define_bool CONFIG_CCSECURITY_MISC y + bool "Enable environment variable names restriction." CONFIG_CCSECURITY_MISC + + [ -z "$CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER" ] && define_bool CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER y + bool "Enable execute handler functionality." CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + + [ -z "$CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION" ] && define_bool CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION y + bool "Enable domain transition without program execution request." CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + + if [ "$CONFIG_NET" = "y" ]; then + + [ -z "$CONFIG_CCSECURITY_PORTRESERVE" ] && define_bool CONFIG_CCSECURITY_PORTRESERVE y + bool "Enable local port reserver." CONFIG_CCSECURITY_PORTRESERVE + + fi + +fi + +endmenu diff --git a/security/ccsecurity/Kconfig b/security/ccsecurity/Kconfig new file mode 100644 index 0000000..8d7b200 --- /dev/null +++ b/security/ccsecurity/Kconfig @@ -0,0 +1,190 @@ +config CCSECURITY + bool "CCSecurity support" + default y + help + Say Y here to support non-LSM version of TOMOYO Linux. + http://tomoyo.osdn.jp/ + +config CCSECURITY_LKM + bool "Compile as loadable kernel module" + default n + depends on CCSECURITY && MODULES + help + This version of TOMOYO depends on patching the kernel source in order + to insert some hooks which LSM does not provide. Therefore, + recompiling the kernel is inevitable. But if you want to keep + vmlinux's size as small as possible, you can compile most part of + TOMOYO as a loadable kernel module by saying Y here. + +config CCSECURITY_DISABLE_BY_DEFAULT + bool "Disable by default" + default n + depends on CCSECURITY + help + Say Y here if you want TOMOYO disabled by default. + To enable TOMOYO, pass ccsecurity=on to kernel command line. + To disable TOMOYO, pass ccsecurity=off to kernel command line. + +config CCSECURITY_USE_EXTERNAL_TASK_SECURITY + bool "Do not modify 'struct task_struct' in order to keep KABI" + default n + depends on CCSECURITY + help + Say Y here if you want to keep KABI for prebuilt kernel modules + unchanged. TOMOYO needs "struct ccs_domain_info *" and "u32" for each + "struct task_struct". But embedding these variables into + "struct task_struct" breaks KABI for prebuilt kernel modules (which + means that you will need to rebuild prebuilt kernel modules). + If you say Y here, these variables are managed outside + "struct task_struct" rather than embedding into "struct task_struct", + but accessing these variables becomes slower because lookup operation + is performed every time the current thread needs to access them. + +config CCSECURITY_MAX_ACCEPT_ENTRY + int "Default maximal count for learning mode" + default 2048 + range 0 2147483647 + depends on CCSECURITY + help + This is the default value for maximal ACL entries + that are automatically appended into policy at "learning mode". + Some programs access thousands of objects, so running + such programs in "learning mode" dulls the system response + and consumes much memory. + This is the safeguard for such programs. + +config CCSECURITY_MAX_AUDIT_LOG + int "Default maximal count for audit log" + default 1024 + range 0 2147483647 + depends on CCSECURITY + help + This is the default value for maximal entries for + audit logs that the kernel can hold on memory. + You can read the log via /proc/ccs/audit. + If you don't need audit logs, you may set this value to 0. + +config CCSECURITY_OMIT_USERSPACE_LOADER + bool "Activate without calling userspace policy loader." + default n + depends on CCSECURITY + ---help--- + Say Y here if you want to activate access control as soon as built-in + policy was loaded. This option will be useful for systems where + operations which can lead to the hijacking of the boot sequence are + needed before loading the policy. For example, you can activate + immediately after loading the fixed part of policy which will allow + only operations needed for mounting a partition which contains the + variant part of policy and verifying (e.g. running GPG check) and + loading the variant part of policy. Since you can start using + enforcing mode from the beginning, you can reduce the possibility of + hijacking the boot sequence. + + If you say Y to both "Compile as loadable kernel module" option and + "Activate without calling userspace policy loader." option, be sure + to excplicitly load the kernel module from the userspace, for + the kernel will not call /sbin/ccs-init when /sbin/init starts. + +config CCSECURITY_POLICY_LOADER + string "Location of userspace policy loader" + default "/sbin/ccs-init" + depends on CCSECURITY + depends on !CCSECURITY_OMIT_USERSPACE_LOADER + ---help--- + This is the default pathname of policy loader which is called before + activation. You can override this setting via CCS_loader= kernel + command line option. + +config CCSECURITY_ACTIVATION_TRIGGER + string "Trigger for calling userspace policy loader" + default "/sbin/init" + depends on CCSECURITY + depends on !CCSECURITY_OMIT_USERSPACE_LOADER + ---help--- + This is the default pathname of activation trigger. + You can override this setting via CCS_trigger= kernel command line + option. For example, if you pass init=/bin/systemd option, you may + want to also pass CCS_trigger=/bin/systemd option. + + Say Y here if you want to enable only specific functionality in order + to reduce object file size. + +config CCSECURITY_FILE_READDIR + bool "Enable readdir operation restriction." + default y + depends on CCSECURITY + ---help--- + Say Y here if you want to enable analysis/restriction of opening + directories for reading. Reading directory entries is a commonly + requested operation and damage caused by not restricting it as MAC + might be acceptable for you. + +config CCSECURITY_FILE_GETATTR + bool "Enable getattr operation restriction." + default y + depends on CCSECURITY + ---help--- + Say Y here if you want to enable analysis/restriction of getting + information of files. Getting file's information is a commonly + requested operation and damage caused by not restricting it as MAC + might be acceptable for you. + +config CCSECURITY_NETWORK + bool "Enable socket operation restriction." + default y + depends on NET + depends on CCSECURITY + ---help--- + Say Y here if you want to enable analysis/restriction of INET and + UNIX domain socket's operations. + +config CCSECURITY_CAPABILITY + bool "Enable non-POSIX capability operation restriction." + default y + depends on CCSECURITY + ---help--- + Say Y here if you want to enable analysis/restriction of non-POSIX + capabilities. + +config CCSECURITY_IPC + bool "Enable IPC operation restriction." + default y + depends on CCSECURITY + ---help--- + Say Y here if you want to enable analysis/restriction of sending + signals. + +config CCSECURITY_MISC + bool "Enable environment variable names restriction." + default y + depends on CCSECURITY + ---help--- + Say Y here if you want to enable analysis/restriction of environment + variable names passed upon program execution request. + +config CCSECURITY_TASK_EXECUTE_HANDLER + bool "Enable execute handler functionality." + default y + depends on CCSECURITY + ---help--- + Say Y here if you want to enable execute handler functionality. + +config CCSECURITY_TASK_DOMAIN_TRANSITION + bool "Enable domain transition without program execution request." + default y + depends on CCSECURITY + ---help--- + Say Y here if you want to enable domain transition without involving + program execution request. + +config CCSECURITY_PORTRESERVE + bool "Enable local port reserver." + default y + depends on NET + depends on CCSECURITY + ---help--- + Say Y here if you want to implement + /proc/sys/net/ipv4/ip_local_reserved_ports as a MAC policy. + +config CCSECURITY_NETWORK_RECVMSG + def_bool CCSECURITY_NETWORK diff --git a/security/ccsecurity/Makefile b/security/ccsecurity/Makefile new file mode 100644 index 0000000..79c3632 --- /dev/null +++ b/security/ccsecurity/Makefile @@ -0,0 +1,122 @@ +ccsecurity-objs := permission.o gc.o memory.o policy_io.o realpath.o + +ifeq ($(VERSION)$(PATCHLEVEL),24) + +ifdef CONFIG_CCSECURITY +O_TARGET := ccsecurity.o +ifdef CONFIG_CCSECURITY_LKM +all_targets: load_policy.o +obj-m := ccsecurity.o +obj-y := $(ccsecurity-objs) +else +all_targets: ccsecurity.o +obj-y := load_policy.o $(ccsecurity-objs) +endif +export-objs := load_policy.o +endif +include $(TOPDIR)/Rules.make + +policy/profile.conf: + @mkdir -p policy/ + @echo Creating an empty policy/profile.conf + @touch $@ + +policy/exception_policy.conf: + @mkdir -p policy/ + @echo Creating a default policy/exception_policy.conf + @echo initialize_domain /sbin/modprobe from any >> $@ + @echo initialize_domain /sbin/hotplug from any >> $@ + +policy/domain_policy.conf: + @mkdir -p policy/ + @echo Creating an empty policy/domain_policy.conf + @touch $@ + +policy/manager.conf: + @mkdir -p policy/ + @echo Creating an empty policy/manager.conf + @touch $@ + +policy/stat.conf: + @mkdir -p policy/ + @echo Creating an empty policy/stat.conf + @touch $@ + +builtin-policy.h: policy/profile.conf policy/exception_policy.conf policy/domain_policy.conf policy/manager.conf policy/stat.conf + @echo Generating built-in policy for TOMOYO 1.8.x. + @echo "static char ccs_builtin_profile[] __initdata =" > $@.tmp + @sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < policy/profile.conf >> $@.tmp + @echo "\"\";" >> $@.tmp + @echo "static char ccs_builtin_exception_policy[] __initdata =" >> $@.tmp + @sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < policy/exception_policy.conf >> $@.tmp + @echo "\"\";" >> $@.tmp + @echo "static char ccs_builtin_domain_policy[] __initdata =" >> $@.tmp + @sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < policy/domain_policy.conf >> $@.tmp + @echo "\"\";" >> $@.tmp + @echo "static char ccs_builtin_manager[] __initdata =" >> $@.tmp + @sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < policy/manager.conf >> $@.tmp + @echo "\"\";" >> $@.tmp + @echo "static char ccs_builtin_stat[] __initdata =" >> $@.tmp + @sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < policy/stat.conf >> $@.tmp + @echo "\"\";" >> $@.tmp + @mv $@.tmp $@ + +policy_io.o: builtin-policy.h + +else + +obj-y += load_policy.o +ifdef CONFIG_CCSECURITY_LKM +obj-m += ccsecurity.o +else +obj-y += ccsecurity.o +endif + +$(obj)/policy/profile.conf: + @mkdir -p $(obj)/policy/ + @echo Creating an empty policy/profile.conf + @touch $@ + +$(obj)/policy/exception_policy.conf: + @mkdir -p $(obj)/policy/ + @echo Creating a default policy/exception_policy.conf + @echo initialize_domain /sbin/modprobe from any >> $@ + @echo initialize_domain /sbin/hotplug from any >> $@ + +$(obj)/policy/domain_policy.conf: + @mkdir -p $(obj)/policy/ + @echo Creating an empty policy/domain_policy.conf + @touch $@ + +$(obj)/policy/manager.conf: + @mkdir -p $(obj)/policy/ + @echo Creating an empty policy/manager.conf + @touch $@ + +$(obj)/policy/stat.conf: + @mkdir -p $(obj)/policy/ + @echo Creating an empty policy/stat.conf + @touch $@ + +$(obj)/builtin-policy.h: $(obj)/policy/profile.conf $(obj)/policy/exception_policy.conf $(obj)/policy/domain_policy.conf $(obj)/policy/manager.conf $(obj)/policy/stat.conf + @echo Generating built-in policy for TOMOYO 1.8.x. + @echo "static char ccs_builtin_profile[] __initdata =" > $@.tmp + @sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < $(obj)/policy/profile.conf >> $@.tmp + @echo "\"\";" >> $@.tmp + @echo "static char ccs_builtin_exception_policy[] __initdata =" >> $@.tmp + @sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < $(obj)/policy/exception_policy.conf >> $@.tmp + @echo "\"\";" >> $@.tmp + @echo "static char ccs_builtin_domain_policy[] __initdata =" >> $@.tmp + @sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < $(obj)/policy/domain_policy.conf >> $@.tmp + @echo "\"\";" >> $@.tmp + @echo "static char ccs_builtin_manager[] __initdata =" >> $@.tmp + @sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < $(obj)/policy/manager.conf >> $@.tmp + @echo "\"\";" >> $@.tmp + @echo "static char ccs_builtin_stat[] __initdata =" >> $@.tmp + @sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < $(obj)/policy/stat.conf >> $@.tmp + @echo "\"\";" >> $@.tmp + @mv $@.tmp $@ + +$(obj)/policy_io.o: $(obj)/builtin-policy.h + +endif diff --git a/security/ccsecurity/gc.c b/security/ccsecurity/gc.c new file mode 100644 index 0000000..0a578ab --- /dev/null +++ b/security/ccsecurity/gc.c @@ -0,0 +1,1036 @@ +/* + * security/ccsecurity/gc.c + * + * Copyright (C) 2005-2012 NTT DATA CORPORATION + * + * Version: 1.8.4 2015/05/05 + */ + +#include "internal.h" + +/***** SECTION1: Constants definition *****/ + +/* For compatibility with older kernels. */ +#ifndef for_each_process +#define for_each_process for_each_task +#endif + +/* The list for "struct ccs_io_buffer". */ +static LIST_HEAD(ccs_io_buffer_list); +/* Lock for protecting ccs_io_buffer_list. */ +static DEFINE_SPINLOCK(ccs_io_buffer_list_lock); + +/***** SECTION2: Structure definition *****/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) + +/* + * Lock for syscall users. + * + * This lock is used for protecting single SRCU section for 2.6.18 and + * earlier kernels because they don't have SRCU support. + */ +struct ccs_lock_struct { + int counter_idx; /* Currently active index (0 or 1). */ + int counter[2]; /* Current users. Protected by ccs_counter_lock. */ +}; + +#endif + +/***** SECTION3: Prototype definition section *****/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +int ccs_lock(void); +#endif +void ccs_del_acl(struct list_head *element); +void ccs_del_condition(struct list_head *element); +void ccs_notify_gc(struct ccs_io_buffer *head, const bool is_register); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +void ccs_unlock(const int idx); +#endif + +static bool ccs_domain_used_by_task(struct ccs_domain_info *domain); +static bool ccs_name_used_by_io_buffer(const char *string, const size_t size); +static bool ccs_struct_used_by_io_buffer(const struct list_head *element); +static int ccs_gc_thread(void *unused); +static void ccs_collect_acl(struct list_head *list); +static void ccs_collect_entry(void); +static void ccs_collect_member(const enum ccs_policy_id id, + struct list_head *member_list); +static void ccs_memory_free(const void *ptr, const enum ccs_policy_id type); +static void ccs_put_name_union(struct ccs_name_union *ptr); +static void ccs_put_number_union(struct ccs_number_union *ptr); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +static void ccs_synchronize_counter(void); +#endif +static void ccs_try_to_gc(const enum ccs_policy_id type, + struct list_head *element); + +/***** SECTION4: Standalone functions section *****/ + +/***** SECTION5: Variables definition section *****/ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) + +/* + * Lock for syscall users. + * + * This lock is held for only protecting single SRCU section. + */ +struct srcu_struct ccs_ss; + +#else + +static struct ccs_lock_struct ccs_counter; +/* Lock for protecting ccs_counter. */ +static DEFINE_SPINLOCK(ccs_counter_lock); + +#endif + +/***** SECTION6: Dependent functions section *****/ + +/** + * ccs_memory_free - Free memory for elements. + * + * @ptr: Pointer to allocated memory. + * @type: One of values in "enum ccs_policy_id". + * + * Returns nothing. + * + * Caller holds ccs_policy_lock mutex. + */ +static void ccs_memory_free(const void *ptr, const enum ccs_policy_id type) +{ + /* Size of an element. */ + static const u8 e[CCS_MAX_POLICY] = { +#ifdef CONFIG_CCSECURITY_PORTRESERVE + [CCS_ID_RESERVEDPORT] = sizeof(struct ccs_reserved), +#endif + [CCS_ID_GROUP] = sizeof(struct ccs_group), +#ifdef CONFIG_CCSECURITY_NETWORK + [CCS_ID_ADDRESS_GROUP] = sizeof(struct ccs_address_group), +#endif + [CCS_ID_PATH_GROUP] = sizeof(struct ccs_path_group), + [CCS_ID_NUMBER_GROUP] = sizeof(struct ccs_number_group), + [CCS_ID_AGGREGATOR] = sizeof(struct ccs_aggregator), + [CCS_ID_TRANSITION_CONTROL] + = sizeof(struct ccs_transition_control), + [CCS_ID_MANAGER] = sizeof(struct ccs_manager), + /* [CCS_ID_CONDITION] = "struct ccs_condition"->size, */ + /* [CCS_ID_NAME] = "struct ccs_name"->size, */ + /* [CCS_ID_ACL] = a["struct ccs_acl_info"->type], */ + [CCS_ID_DOMAIN] = sizeof(struct ccs_domain_info), + }; + /* Size of a domain ACL element. */ + static const u8 a[] = { + [CCS_TYPE_PATH_ACL] = sizeof(struct ccs_path_acl), + [CCS_TYPE_PATH2_ACL] = sizeof(struct ccs_path2_acl), + [CCS_TYPE_PATH_NUMBER_ACL] + = sizeof(struct ccs_path_number_acl), + [CCS_TYPE_MKDEV_ACL] = sizeof(struct ccs_mkdev_acl), + [CCS_TYPE_MOUNT_ACL] = sizeof(struct ccs_mount_acl), +#ifdef CONFIG_CCSECURITY_NETWORK + [CCS_TYPE_INET_ACL] = sizeof(struct ccs_inet_acl), + [CCS_TYPE_UNIX_ACL] = sizeof(struct ccs_unix_acl), +#endif +#ifdef CONFIG_CCSECURITY_MISC + [CCS_TYPE_ENV_ACL] = sizeof(struct ccs_env_acl), +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + [CCS_TYPE_CAPABILITY_ACL] = sizeof(struct ccs_capability_acl), +#endif +#ifdef CONFIG_CCSECURITY_IPC + [CCS_TYPE_SIGNAL_ACL] = sizeof(struct ccs_signal_acl), +#endif +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + [CCS_TYPE_AUTO_EXECUTE_HANDLER] + = sizeof(struct ccs_handler_acl), + [CCS_TYPE_DENIED_EXECUTE_HANDLER] + = sizeof(struct ccs_handler_acl), +#endif +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + [CCS_TYPE_AUTO_TASK_ACL] = sizeof(struct ccs_task_acl), + [CCS_TYPE_MANUAL_TASK_ACL] = sizeof(struct ccs_task_acl), +#endif + }; + size_t size; + if (type == CCS_ID_ACL) + size = a[container_of(ptr, typeof(struct ccs_acl_info), + list)->type]; + else if (type == CCS_ID_NAME) + size = container_of(ptr, typeof(struct ccs_name), + head.list)->size; + else if (type == CCS_ID_CONDITION) + size = container_of(ptr, typeof(struct ccs_condition), + head.list)->size; + else + size = e[type]; + ccs_memory_used[CCS_MEMORY_POLICY] -= ccs_round2(size); + kfree(ptr); +} + +/** + * ccs_put_name_union - Drop reference on "struct ccs_name_union". + * + * @ptr: Pointer to "struct ccs_name_union". + * + * Returns nothing. + */ +static void ccs_put_name_union(struct ccs_name_union *ptr) +{ + ccs_put_group(ptr->group); + ccs_put_name(ptr->filename); +} + +/** + * ccs_put_number_union - Drop reference on "struct ccs_number_union". + * + * @ptr: Pointer to "struct ccs_number_union". + * + * Returns nothing. + */ +static void ccs_put_number_union(struct ccs_number_union *ptr) +{ + ccs_put_group(ptr->group); +} + +/** + * ccs_struct_used_by_io_buffer - Check whether the list element is used by /proc/ccs/ users or not. + * + * @element: Pointer to "struct list_head". + * + * Returns true if @element is used by /proc/ccs/ users, false otherwise. + */ +static bool ccs_struct_used_by_io_buffer(const struct list_head *element) +{ + struct ccs_io_buffer *head; + bool in_use = false; + spin_lock(&ccs_io_buffer_list_lock); + list_for_each_entry(head, &ccs_io_buffer_list, list) { + head->users++; + spin_unlock(&ccs_io_buffer_list_lock); + mutex_lock(&head->io_sem); + if (head->r.domain == element || head->r.group == element || + head->r.acl == element || &head->w.domain->list == element) + in_use = true; + mutex_unlock(&head->io_sem); + spin_lock(&ccs_io_buffer_list_lock); + head->users--; + if (in_use) + break; + } + spin_unlock(&ccs_io_buffer_list_lock); + return in_use; +} + +/** + * ccs_name_used_by_io_buffer - Check whether the string is used by /proc/ccs/ users or not. + * + * @string: String to check. + * @size: Memory allocated for @string . + * + * Returns true if @string is used by /proc/ccs/ users, false otherwise. + */ +static bool ccs_name_used_by_io_buffer(const char *string, const size_t size) +{ + struct ccs_io_buffer *head; + bool in_use = false; + spin_lock(&ccs_io_buffer_list_lock); + list_for_each_entry(head, &ccs_io_buffer_list, list) { + int i; + head->users++; + spin_unlock(&ccs_io_buffer_list_lock); + mutex_lock(&head->io_sem); + for (i = 0; i < CCS_MAX_IO_READ_QUEUE; i++) { + const char *w = head->r.w[i]; + if (w < string || w > string + size) + continue; + in_use = true; + break; + } + mutex_unlock(&head->io_sem); + spin_lock(&ccs_io_buffer_list_lock); + head->users--; + if (in_use) + break; + } + spin_unlock(&ccs_io_buffer_list_lock); + return in_use; +} + +/** + * ccs_del_transition_control - Delete members in "struct ccs_transition_control". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +static inline void ccs_del_transition_control(struct list_head *element) +{ + struct ccs_transition_control *ptr = + container_of(element, typeof(*ptr), head.list); + ccs_put_name(ptr->domainname); + ccs_put_name(ptr->program); +} + +/** + * ccs_del_aggregator - Delete members in "struct ccs_aggregator". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +static inline void ccs_del_aggregator(struct list_head *element) +{ + struct ccs_aggregator *ptr = + container_of(element, typeof(*ptr), head.list); + ccs_put_name(ptr->original_name); + ccs_put_name(ptr->aggregated_name); +} + +/** + * ccs_del_manager - Delete members in "struct ccs_manager". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +static inline void ccs_del_manager(struct list_head *element) +{ + struct ccs_manager *ptr = + container_of(element, typeof(*ptr), head.list); + ccs_put_name(ptr->manager); +} + +/** + * ccs_domain_used_by_task - Check whether the given pointer is referenced by a task. + * + * @domain: Pointer to "struct ccs_domain_info". + * + * Returns true if @domain is in use, false otherwise. + */ +static bool ccs_domain_used_by_task(struct ccs_domain_info *domain) +{ + bool in_use = false; + /* + * Don't delete this domain if somebody is doing execve(). + * + * Since ccs_finish_execve() first reverts ccs_domain_info and then + * updates ccs_flags, we need smp_rmb() to make sure that GC first + * checks ccs_flags and then checks ccs_domain_info. + */ +#ifdef CONFIG_CCSECURITY_USE_EXTERNAL_TASK_SECURITY + int idx; + rcu_read_lock(); + for (idx = 0; idx < CCS_MAX_TASK_SECURITY_HASH; idx++) { + struct ccs_security *ptr; + struct list_head *list = &ccs_task_security_list[idx]; + list_for_each_entry_rcu(ptr, list, list) { + if (!(ptr->ccs_flags & CCS_TASK_IS_IN_EXECVE)) { + smp_rmb(); /* Avoid out of order execution. */ + if (ptr->ccs_domain_info != domain) + continue; + } + in_use = true; + goto out; + } + } +out: + rcu_read_unlock(); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + struct task_struct *g; + struct task_struct *t; + ccs_tasklist_lock(); + do_each_thread(g, t) { + if (!(t->ccs_flags & CCS_TASK_IS_IN_EXECVE)) { + smp_rmb(); /* Avoid out of order execution. */ + if (t->ccs_domain_info != domain) + continue; + } + in_use = true; + goto out; + } while_each_thread(g, t); +out: + ccs_tasklist_unlock(); +#else + struct task_struct *p; + ccs_tasklist_lock(); + for_each_process(p) { + if (!(p->ccs_flags & CCS_TASK_IS_IN_EXECVE)) { + smp_rmb(); /* Avoid out of order execution. */ + if (p->ccs_domain_info != domain) + continue; + } + in_use = true; + break; + } + ccs_tasklist_unlock(); +#endif + return in_use; +} + +/** + * ccs_del_acl - Delete members in "struct ccs_acl_info". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +void ccs_del_acl(struct list_head *element) +{ + struct ccs_acl_info *acl = container_of(element, typeof(*acl), list); + ccs_put_condition(acl->cond); + switch (acl->type) { + case CCS_TYPE_PATH_ACL: + { + struct ccs_path_acl *entry = + container_of(acl, typeof(*entry), head); + ccs_put_name_union(&entry->name); + } + break; + case CCS_TYPE_PATH2_ACL: + { + struct ccs_path2_acl *entry = + container_of(acl, typeof(*entry), head); + ccs_put_name_union(&entry->name1); + ccs_put_name_union(&entry->name2); + } + break; + case CCS_TYPE_PATH_NUMBER_ACL: + { + struct ccs_path_number_acl *entry = + container_of(acl, typeof(*entry), head); + ccs_put_name_union(&entry->name); + ccs_put_number_union(&entry->number); + } + break; + case CCS_TYPE_MKDEV_ACL: + { + struct ccs_mkdev_acl *entry = + container_of(acl, typeof(*entry), head); + ccs_put_name_union(&entry->name); + ccs_put_number_union(&entry->mode); + ccs_put_number_union(&entry->major); + ccs_put_number_union(&entry->minor); + } + break; + case CCS_TYPE_MOUNT_ACL: + { + struct ccs_mount_acl *entry = + container_of(acl, typeof(*entry), head); + ccs_put_name_union(&entry->dev_name); + ccs_put_name_union(&entry->dir_name); + ccs_put_name_union(&entry->fs_type); + ccs_put_number_union(&entry->flags); + } + break; +#ifdef CONFIG_CCSECURITY_NETWORK + case CCS_TYPE_INET_ACL: + { + struct ccs_inet_acl *entry = + container_of(acl, typeof(*entry), head); + ccs_put_group(entry->address.group); + ccs_put_number_union(&entry->port); + } + break; + case CCS_TYPE_UNIX_ACL: + { + struct ccs_unix_acl *entry = + container_of(acl, typeof(*entry), head); + ccs_put_name_union(&entry->name); + } + break; +#endif +#ifdef CONFIG_CCSECURITY_MISC + case CCS_TYPE_ENV_ACL: + { + struct ccs_env_acl *entry = + container_of(acl, typeof(*entry), head); + ccs_put_name(entry->env); + } + break; +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + case CCS_TYPE_CAPABILITY_ACL: + { + /* Nothing to do. */ + } + break; +#endif +#ifdef CONFIG_CCSECURITY_IPC + case CCS_TYPE_SIGNAL_ACL: + { + struct ccs_signal_acl *entry = + container_of(acl, typeof(*entry), head); + ccs_put_number_union(&entry->sig); + ccs_put_name(entry->domainname); + } + break; +#endif +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + case CCS_TYPE_AUTO_EXECUTE_HANDLER: + case CCS_TYPE_DENIED_EXECUTE_HANDLER: + { + struct ccs_handler_acl *entry = + container_of(acl, typeof(*entry), head); + ccs_put_name(entry->handler); + } + break; +#endif +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + case CCS_TYPE_AUTO_TASK_ACL: + case CCS_TYPE_MANUAL_TASK_ACL: + { + struct ccs_task_acl *entry = + container_of(acl, typeof(*entry), head); + ccs_put_name(entry->domainname); + } + break; +#endif + } +} + +/** + * ccs_del_domain - Delete members in "struct ccs_domain_info". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + * + * Caller holds ccs_policy_lock mutex. + */ +static inline void ccs_del_domain(struct list_head *element) +{ + struct ccs_domain_info *domain = + container_of(element, typeof(*domain), list); + struct ccs_acl_info *acl; + struct ccs_acl_info *tmp; + /* + * Since this domain is referenced from neither "struct ccs_io_buffer" + * nor "struct task_struct", we can delete elements without checking + * for is_deleted flag. + */ + list_for_each_entry_safe(acl, tmp, &domain->acl_info_list, list) { + ccs_del_acl(&acl->list); + ccs_memory_free(acl, CCS_ID_ACL); + } + ccs_put_name(domain->domainname); +} + +/** + * ccs_del_path_group - Delete members in "struct ccs_path_group". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +static inline void ccs_del_path_group(struct list_head *element) +{ + struct ccs_path_group *member = + container_of(element, typeof(*member), head.list); + ccs_put_name(member->member_name); +} + +/** + * ccs_del_group - Delete "struct ccs_group". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +static inline void ccs_del_group(struct list_head *element) +{ + struct ccs_group *group = + container_of(element, typeof(*group), head.list); + ccs_put_name(group->group_name); +} + +/** + * ccs_del_address_group - Delete members in "struct ccs_address_group". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +static inline void ccs_del_address_group(struct list_head *element) +{ + /* Nothing to do. */ +} + +/** + * ccs_del_number_group - Delete members in "struct ccs_number_group". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +static inline void ccs_del_number_group(struct list_head *element) +{ + /* Nothing to do. */ +} + +/** + * ccs_del_reservedport - Delete members in "struct ccs_reserved". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +static inline void ccs_del_reservedport(struct list_head *element) +{ + /* Nothing to do. */ +} + +/** + * ccs_del_condition - Delete members in "struct ccs_condition". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +void ccs_del_condition(struct list_head *element) +{ + struct ccs_condition *cond = container_of(element, typeof(*cond), + head.list); + const u16 condc = cond->condc; + const u16 numbers_count = cond->numbers_count; + const u16 names_count = cond->names_count; + const u16 argc = cond->argc; + const u16 envc = cond->envc; + unsigned int i; + const struct ccs_condition_element *condp + = (const struct ccs_condition_element *) (cond + 1); + struct ccs_number_union *numbers_p + = (struct ccs_number_union *) (condp + condc); + struct ccs_name_union *names_p + = (struct ccs_name_union *) (numbers_p + numbers_count); + const struct ccs_argv *argv + = (const struct ccs_argv *) (names_p + names_count); + const struct ccs_envp *envp + = (const struct ccs_envp *) (argv + argc); + for (i = 0; i < numbers_count; i++) + ccs_put_number_union(numbers_p++); + for (i = 0; i < names_count; i++) + ccs_put_name_union(names_p++); + for (i = 0; i < argc; argv++, i++) + ccs_put_name(argv->value); + for (i = 0; i < envc; envp++, i++) { + ccs_put_name(envp->name); + ccs_put_name(envp->value); + } + ccs_put_name(cond->transit); +} + +/** + * ccs_del_name - Delete members in "struct ccs_name". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +static inline void ccs_del_name(struct list_head *element) +{ + /* Nothing to do. */ +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) + +/** + * ccs_lock - Alternative for srcu_read_lock(). + * + * Returns index number which has to be passed to ccs_unlock(). + */ +int ccs_lock(void) +{ + int idx; + spin_lock(&ccs_counter_lock); + idx = ccs_counter.counter_idx; + ccs_counter.counter[idx]++; + spin_unlock(&ccs_counter_lock); + return idx; +} + +/** + * ccs_unlock - Alternative for srcu_read_unlock(). + * + * @idx: Index number returned by ccs_lock(). + * + * Returns nothing. + */ +void ccs_unlock(const int idx) +{ + spin_lock(&ccs_counter_lock); + ccs_counter.counter[idx]--; + spin_unlock(&ccs_counter_lock); +} + +/** + * ccs_synchronize_counter - Alternative for synchronize_srcu(). + * + * Returns nothing. + */ +static void ccs_synchronize_counter(void) +{ + int idx; + int v; + /* + * Change currently active counter's index. Make it visible to other + * threads by doing it with ccs_counter_lock held. + * This function is called by garbage collector thread, and the garbage + * collector thread is exclusive. Therefore, it is guaranteed that + * SRCU grace period has expired when returning from this function. + */ + spin_lock(&ccs_counter_lock); + idx = ccs_counter.counter_idx; + ccs_counter.counter_idx ^= 1; + v = ccs_counter.counter[idx]; + spin_unlock(&ccs_counter_lock); + /* Wait for previously active counter to become 0. */ + while (v) { + ssleep(1); + spin_lock(&ccs_counter_lock); + v = ccs_counter.counter[idx]; + spin_unlock(&ccs_counter_lock); + } +} + +#endif + +/** + * ccs_try_to_gc - Try to kfree() an entry. + * + * @type: One of values in "enum ccs_policy_id". + * @element: Pointer to "struct list_head". + * + * Returns nothing. + * + * Caller holds ccs_policy_lock mutex. + */ +static void ccs_try_to_gc(const enum ccs_policy_id type, + struct list_head *element) +{ + /* + * __list_del_entry() guarantees that the list element became no longer + * reachable from the list which the element was originally on (e.g. + * ccs_domain_list). Also, synchronize_srcu() guarantees that the list + * element became no longer referenced by syscall users. + */ + __list_del_entry(element); + mutex_unlock(&ccs_policy_lock); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) + synchronize_srcu(&ccs_ss); +#else + ccs_synchronize_counter(); +#endif + /* + * However, there are two users which may still be using the list + * element. We need to defer until both users forget this element. + * + * Don't kfree() until "struct ccs_io_buffer"->r.{domain,group,acl} and + * "struct ccs_io_buffer"->w.domain forget this element. + */ + if (ccs_struct_used_by_io_buffer(element)) + goto reinject; + switch (type) { + case CCS_ID_TRANSITION_CONTROL: + ccs_del_transition_control(element); + break; + case CCS_ID_MANAGER: + ccs_del_manager(element); + break; + case CCS_ID_AGGREGATOR: + ccs_del_aggregator(element); + break; + case CCS_ID_GROUP: + ccs_del_group(element); + break; + case CCS_ID_PATH_GROUP: + ccs_del_path_group(element); + break; +#ifdef CONFIG_CCSECURITY_NETWORK + case CCS_ID_ADDRESS_GROUP: + ccs_del_address_group(element); + break; +#endif + case CCS_ID_NUMBER_GROUP: + ccs_del_number_group(element); + break; +#ifdef CONFIG_CCSECURITY_PORTRESERVE + case CCS_ID_RESERVEDPORT: + ccs_del_reservedport(element); + break; +#endif + case CCS_ID_CONDITION: + ccs_del_condition(element); + break; + case CCS_ID_NAME: + /* + * Don't kfree() until all "struct ccs_io_buffer"->r.w[] forget + * this element. + */ + if (ccs_name_used_by_io_buffer + (container_of(element, typeof(struct ccs_name), + head.list)->entry.name, + container_of(element, typeof(struct ccs_name), + head.list)->size)) + goto reinject; + ccs_del_name(element); + break; + case CCS_ID_ACL: + ccs_del_acl(element); + break; + case CCS_ID_DOMAIN: + /* + * Don't kfree() until all "struct task_struct" forget this + * element. + */ + if (ccs_domain_used_by_task + (container_of(element, typeof(struct ccs_domain_info), + list))) + goto reinject; + break; + case CCS_MAX_POLICY: + break; + } + mutex_lock(&ccs_policy_lock); + if (type == CCS_ID_DOMAIN) + ccs_del_domain(element); + ccs_memory_free(element, type); + return; +reinject: + /* + * We can safely reinject this element here bacause + * (1) Appending list elements and removing list elements are protected + * by ccs_policy_lock mutex. + * (2) Only this function removes list elements and this function is + * exclusively executed by ccs_gc_mutex mutex. + * are true. + */ + mutex_lock(&ccs_policy_lock); + list_add_rcu(element, element->prev); +} + +/** + * ccs_collect_member - Delete elements with "struct ccs_acl_head". + * + * @id: One of values in "enum ccs_policy_id". + * @member_list: Pointer to "struct list_head". + * + * Returns nothing. + * + * Caller holds ccs_policy_lock mutex. + */ +static void ccs_collect_member(const enum ccs_policy_id id, + struct list_head *member_list) +{ + struct ccs_acl_head *member; + struct ccs_acl_head *tmp; + list_for_each_entry_safe(member, tmp, member_list, list) { + if (!member->is_deleted) + continue; + member->is_deleted = CCS_GC_IN_PROGRESS; + ccs_try_to_gc(id, &member->list); + } +} + +/** + * ccs_collect_acl - Delete elements in "struct ccs_domain_info". + * + * @list: Pointer to "struct list_head". + * + * Returns nothing. + * + * Caller holds ccs_policy_lock mutex. + */ +static void ccs_collect_acl(struct list_head *list) +{ + struct ccs_acl_info *acl; + struct ccs_acl_info *tmp; + list_for_each_entry_safe(acl, tmp, list, list) { + if (!acl->is_deleted) + continue; + acl->is_deleted = CCS_GC_IN_PROGRESS; + ccs_try_to_gc(CCS_ID_ACL, &acl->list); + } +} + +/** + * ccs_collect_entry - Try to kfree() deleted elements. + * + * Returns nothing. + */ +static void ccs_collect_entry(void) +{ + int i; + enum ccs_policy_id id; + struct ccs_policy_namespace *ns; + mutex_lock(&ccs_policy_lock); + { + struct ccs_domain_info *domain; + struct ccs_domain_info *tmp; + list_for_each_entry_safe(domain, tmp, &ccs_domain_list, list) { + ccs_collect_acl(&domain->acl_info_list); + if (!domain->is_deleted || + ccs_domain_used_by_task(domain)) + continue; + ccs_try_to_gc(CCS_ID_DOMAIN, &domain->list); + } + } + list_for_each_entry(ns, &ccs_namespace_list, namespace_list) { + for (id = 0; id < CCS_MAX_POLICY; id++) + ccs_collect_member(id, &ns->policy_list[id]); + for (i = 0; i < CCS_MAX_ACL_GROUPS; i++) + ccs_collect_acl(&ns->acl_group[i]); + } + { + struct ccs_shared_acl_head *ptr; + struct ccs_shared_acl_head *tmp; + list_for_each_entry_safe(ptr, tmp, &ccs_condition_list, list) { + if (atomic_read(&ptr->users) > 0) + continue; + atomic_set(&ptr->users, CCS_GC_IN_PROGRESS); + ccs_try_to_gc(CCS_ID_CONDITION, &ptr->list); + } + } + list_for_each_entry(ns, &ccs_namespace_list, namespace_list) { + for (i = 0; i < CCS_MAX_GROUP; i++) { + struct list_head *list = &ns->group_list[i]; + struct ccs_group *group; + struct ccs_group *tmp; + switch (i) { + case 0: + id = CCS_ID_PATH_GROUP; + break; + case 1: + id = CCS_ID_NUMBER_GROUP; + break; + default: +#ifdef CONFIG_CCSECURITY_NETWORK + id = CCS_ID_ADDRESS_GROUP; +#else + continue; +#endif + break; + } + list_for_each_entry_safe(group, tmp, list, head.list) { + ccs_collect_member(id, &group->member_list); + if (!list_empty(&group->member_list) || + atomic_read(&group->head.users) > 0) + continue; + atomic_set(&group->head.users, + CCS_GC_IN_PROGRESS); + ccs_try_to_gc(CCS_ID_GROUP, &group->head.list); + } + } + } + for (i = 0; i < CCS_MAX_HASH; i++) { + struct list_head *list = &ccs_name_list[i]; + struct ccs_shared_acl_head *ptr; + struct ccs_shared_acl_head *tmp; + list_for_each_entry_safe(ptr, tmp, list, list) { + if (atomic_read(&ptr->users) > 0) + continue; + atomic_set(&ptr->users, CCS_GC_IN_PROGRESS); + ccs_try_to_gc(CCS_ID_NAME, &ptr->list); + } + } + mutex_unlock(&ccs_policy_lock); +} + +/** + * ccs_gc_thread - Garbage collector thread function. + * + * @unused: Unused. + * + * Returns 0. + */ +static int ccs_gc_thread(void *unused) +{ + /* Garbage collector thread is exclusive. */ + static DEFINE_MUTEX(ccs_gc_mutex); + if (!mutex_trylock(&ccs_gc_mutex)) + goto out; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 6) + /* daemonize() not needed. */ +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) + daemonize("GC for CCS"); +#else + daemonize(); + reparent_to_init(); +#if defined(TASK_DEAD) + { + struct task_struct *task = current; + spin_lock_irq(&task->sighand->siglock); + siginitsetinv(&task->blocked, 0); + recalc_sigpending(); + spin_unlock_irq(&task->sighand->siglock); + } +#else + { + struct task_struct *task = current; + spin_lock_irq(&task->sigmask_lock); + siginitsetinv(&task->blocked, 0); + recalc_sigpending(task); + spin_unlock_irq(&task->sigmask_lock); + } +#endif + snprintf(current->comm, sizeof(current->comm) - 1, "GC for CCS"); +#endif + ccs_collect_entry(); + { + struct ccs_io_buffer *head; + struct ccs_io_buffer *tmp; + spin_lock(&ccs_io_buffer_list_lock); + list_for_each_entry_safe(head, tmp, &ccs_io_buffer_list, + list) { + if (head->users) + continue; + list_del(&head->list); + kfree(head->read_buf); + kfree(head->write_buf); + kfree(head); + } + spin_unlock(&ccs_io_buffer_list_lock); + } + mutex_unlock(&ccs_gc_mutex); +out: + /* This acts as do_exit(0). */ + return 0; +} + +/** + * ccs_notify_gc - Register/unregister /proc/ccs/ users. + * + * @head: Pointer to "struct ccs_io_buffer". + * @is_register: True if register, false if unregister. + * + * Returns nothing. + */ +void ccs_notify_gc(struct ccs_io_buffer *head, const bool is_register) +{ + bool is_write = false; + spin_lock(&ccs_io_buffer_list_lock); + if (is_register) { + head->users = 1; + list_add(&head->list, &ccs_io_buffer_list); + } else { + is_write = head->write_buf != NULL; + if (!--head->users) { + list_del(&head->list); + kfree(head->read_buf); + kfree(head->write_buf); + kfree(head); + } + } + spin_unlock(&ccs_io_buffer_list_lock); + if (is_write) { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 6) + struct task_struct *task = kthread_create(ccs_gc_thread, NULL, + "GC for CCS"); + if (!IS_ERR(task)) + wake_up_process(task); +#else + kernel_thread(ccs_gc_thread, NULL, 0); +#endif + } +} diff --git a/security/ccsecurity/internal.h b/security/ccsecurity/internal.h new file mode 100644 index 0000000..3f703f2 --- /dev/null +++ b/security/ccsecurity/internal.h @@ -0,0 +1,2090 @@ +/* + * security/ccsecurity/internal.h + * + * Copyright (C) 2005-2012 NTT DATA CORPORATION + * + * Version: 1.8.4 2015/05/05 + */ + +#ifndef _SECURITY_CCSECURITY_INTERNAL_H +#define _SECURITY_CCSECURITY_INTERNAL_H + +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 38) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) +#include +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) +#include +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) +#include +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) +#include +#endif +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) || defined(RHEL_MAJOR) +#include +#endif +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18) || (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33) && defined(CONFIG_SYSCTL_SYSCALL)) +#include +#endif +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 6) +#include +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) +#define sk_family family +#define sk_protocol protocol +#define sk_type type +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) + +/* Structure for holding "struct vfsmount *" and "struct dentry *". */ +struct path { + struct vfsmount *mnt; + struct dentry *dentry; +}; + +#endif + +#ifndef __printf +#define __printf(a,b) __attribute__((format(printf,a,b))) +#endif +#ifndef __packed +#define __packed __attribute__((__packed__)) +#endif +#ifndef bool +#define bool _Bool +#endif +#ifndef false +#define false 0 +#endif +#ifndef true +#define true 1 +#endif + +#ifndef __user +#define __user +#endif + +#ifndef current_uid +#define current_uid() (current->uid) +#endif +#ifndef current_gid +#define current_gid() (current->gid) +#endif +#ifndef current_euid +#define current_euid() (current->euid) +#endif +#ifndef current_egid +#define current_egid() (current->egid) +#endif +#ifndef current_suid +#define current_suid() (current->suid) +#endif +#ifndef current_sgid +#define current_sgid() (current->sgid) +#endif +#ifndef current_fsuid +#define current_fsuid() (current->fsuid) +#endif +#ifndef current_fsgid +#define current_fsgid() (current->fsgid) +#endif + +#ifndef DEFINE_SPINLOCK +#define DEFINE_SPINLOCK(x) spinlock_t x = SPIN_LOCK_UNLOCKED +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16) +#define mutex semaphore +#define mutex_init(mutex) init_MUTEX(mutex) +#define mutex_unlock(mutex) up(mutex) +#define mutex_lock(mutex) down(mutex) +#define mutex_lock_interruptible(mutex) down_interruptible(mutex) +#define mutex_trylock(mutex) (!down_trylock(mutex)) +#define DEFINE_MUTEX(mutexname) DECLARE_MUTEX(mutexname) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15) +#define MS_UNBINDABLE (1<<17) /* change to unbindable */ +#define MS_PRIVATE (1<<18) /* change to private */ +#define MS_SLAVE (1<<19) /* change to slave */ +#define MS_SHARED (1<<20) /* change to shared */ +#endif + +#ifndef container_of +#define container_of(ptr, type, member) ({ \ + const typeof(((type *)0)->member) *__mptr = (ptr); \ + (type *)((char *)__mptr - offsetof(type, member)); }) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) +#define smp_read_barrier_depends smp_rmb +#endif + +#ifndef ACCESS_ONCE +#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x)) +#endif + +#ifndef rcu_dereference +#define rcu_dereference(p) ({ \ + typeof(p) _________p1 = ACCESS_ONCE(p); \ + smp_read_barrier_depends(); /* see RCU */ \ + (_________p1); \ + }) +#endif + +#ifndef rcu_assign_pointer +#define rcu_assign_pointer(p, v) \ + ({ \ + if (!__builtin_constant_p(v) || \ + ((v) != NULL)) \ + smp_wmb(); /* see RCU */ \ + (p) = (v); \ + }) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) +#define f_vfsmnt f_path.mnt +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14) + +/** + * kzalloc() - Allocate memory. The memory is set to zero. + * + * @size: Size to allocate. + * @flags: GFP flags. + * + * Returns pointer to allocated memory on success, NULL otherwise. + * + * This is for compatibility with older kernels. + * + * Since several distributions backported kzalloc(), I define it as a macro + * rather than an inlined function in order to avoid multiple definition error. + */ +#define kzalloc(size, flags) ({ \ + void *ret = kmalloc((size), (flags)); \ + if (ret) \ + memset(ret, 0, (size)); \ + ret; }) + +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25) + +/** + * path_put - Drop reference on "struct path". + * + * @path: Pointer to "struct path". + * + * Returns nothing. + * + * This is for compatibility with older kernels. + */ +static inline void path_put(struct path *path) +{ + dput(path->dentry); + mntput(path->mnt); +} + +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + +/** + * __list_add_rcu - Insert a new entry between two known consecutive entries. + * + * @new: Pointer to "struct list_head". + * @prev: Pointer to "struct list_head". + * @next: Pointer to "struct list_head". + * + * Returns nothing. + * + * This is for compatibility with older kernels. + */ +static inline void __list_add_rcu(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + new->next = next; + new->prev = prev; + rcu_assign_pointer(prev->next, new); + next->prev = new; +} + +/** + * list_add_tail_rcu - Add a new entry to rcu-protected list. + * + * @new: Pointer to "struct list_head". + * @head: Pointer to "struct list_head". + * + * Returns nothing. + * + * This is for compatibility with older kernels. + */ +static inline void list_add_tail_rcu(struct list_head *new, + struct list_head *head) +{ + __list_add_rcu(new, head->prev, head); +} + +/** + * list_add_rcu - Add a new entry to rcu-protected list. + * + * @new: Pointer to "struct list_head". + * @head: Pointer to "struct list_head". + * + * Returns nothing. + * + * This is for compatibility with older kernels. + */ +static inline void list_add_rcu(struct list_head *new, struct list_head *head) +{ + __list_add_rcu(new, head, head->next); +} + +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 38) + +/** + * __list_del_entry - Deletes entry from list without re-initialization. + * + * @entry: Pointer to "struct list_head". + * + * Returns nothing. + * + * This is for compatibility with older kernels. + */ +static inline void __list_del_entry(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +#endif + +#ifndef list_for_each_entry_safe + +/** + * list_for_each_entry_safe - Iterate over list of given type safe against removal of list entry. + * + * @pos: The "type *" to use as a loop cursor. + * @n: Another "type *" to use as temporary storage. + * @head: Pointer to "struct list_head". + * @member: The name of the list_struct within the struct. + * + * This is for compatibility with older kernels. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +#endif + +#ifndef srcu_dereference + +/** + * srcu_dereference - Fetch SRCU-protected pointer with checking. + * + * @p: The pointer to read, prior to dereferencing. + * @ss: Pointer to "struct srcu_struct". + * + * Returns @p. + * + * This is for compatibility with older kernels. + */ +#define srcu_dereference(p, ss) rcu_dereference(p) + +#endif + +#ifndef list_for_each_entry_srcu + +/** + * list_for_each_entry_srcu - Iterate over rcu list of given type. + * + * @pos: The type * to use as a loop cursor. + * @head: The head for your list. + * @member: The name of the list_struct within the struct. + * @ss: Pointer to "struct srcu_struct". + * + * As of 2.6.36, this macro is not provided because only TOMOYO wants it. + */ +#define list_for_each_entry_srcu(pos, head, member, ss) \ + for (pos = list_entry(srcu_dereference((head)->next, ss), \ + typeof(*pos), member); \ + prefetch(pos->member.next), &pos->member != (head); \ + pos = list_entry(srcu_dereference(pos->member.next, ss), \ + typeof(*pos), member)) + +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 30) || (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 9)) + +#if LINUX_VERSION_CODE == KERNEL_VERSION(2, 4, 21) +#undef ssleep +#endif + +#ifndef ssleep + +/** + * ssleep - Sleep for specified seconds. + * + * @secs: Seconds to sleep. + * + * Returns nothing. + * + * This is for compatibility with older kernels. + * + * Since several distributions backported ssleep(), I define it as a macro + * rather than an inlined function in order to avoid multiple definition error. + */ +#define ssleep(secs) { \ + set_current_state(TASK_UNINTERRUPTIBLE); \ + schedule_timeout((HZ * secs) + 1); \ + } + +#endif + +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0) + +/** + * from_kuid - Convert kuid_t to uid_t. + * + * @ns: Unused. + * @uid: kuid_t value. + * + * Returns uid seen from init's user namespace. + */ +#define from_kuid(ns, uid) (uid) + +/** + * from_kgid - Convert kgid_t to gid_t. + * + * @ns: Unused. + * @gid: kgid_t value. + * + * Returns gid seen from init's user namespace. + */ +#define from_kgid(ns, gid) (gid) + +/** + * uid_eq - Check whether the uids are equals or not. + * + * @left: Uid seen from current user namespace. + * @right: Uid seen from current user namespace. + * + * Returns true if uid is root in init's user namespace, false otherwise. + */ +#define uid_eq(left, right) ((left) == (right)) +#define GLOBAL_ROOT_UID 0 + +#endif + +/* + * TOMOYO specific part start. + */ + +#include + +/* Enumeration definition for internal use. */ + +/* Index numbers for Access Controls. */ +enum ccs_acl_entry_type_index { + CCS_TYPE_PATH_ACL, + CCS_TYPE_PATH2_ACL, + CCS_TYPE_PATH_NUMBER_ACL, + CCS_TYPE_MKDEV_ACL, + CCS_TYPE_MOUNT_ACL, +#ifdef CONFIG_CCSECURITY_MISC + CCS_TYPE_ENV_ACL, +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + CCS_TYPE_CAPABILITY_ACL, +#endif +#ifdef CONFIG_CCSECURITY_NETWORK + CCS_TYPE_INET_ACL, + CCS_TYPE_UNIX_ACL, +#endif +#ifdef CONFIG_CCSECURITY_IPC + CCS_TYPE_SIGNAL_ACL, +#endif +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + CCS_TYPE_AUTO_EXECUTE_HANDLER, + CCS_TYPE_DENIED_EXECUTE_HANDLER, +#endif +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + CCS_TYPE_AUTO_TASK_ACL, + CCS_TYPE_MANUAL_TASK_ACL, +#endif +}; + +/* Index numbers for "struct ccs_condition". */ +enum ccs_conditions_index { + CCS_TASK_UID, /* current_uid() */ + CCS_TASK_EUID, /* current_euid() */ + CCS_TASK_SUID, /* current_suid() */ + CCS_TASK_FSUID, /* current_fsuid() */ + CCS_TASK_GID, /* current_gid() */ + CCS_TASK_EGID, /* current_egid() */ + CCS_TASK_SGID, /* current_sgid() */ + CCS_TASK_FSGID, /* current_fsgid() */ + CCS_TASK_PID, /* sys_getpid() */ + CCS_TASK_PPID, /* sys_getppid() */ + CCS_EXEC_ARGC, /* "struct linux_binprm *"->argc */ + CCS_EXEC_ENVC, /* "struct linux_binprm *"->envc */ + CCS_TYPE_IS_SOCKET, /* S_IFSOCK */ + CCS_TYPE_IS_SYMLINK, /* S_IFLNK */ + CCS_TYPE_IS_FILE, /* S_IFREG */ + CCS_TYPE_IS_BLOCK_DEV, /* S_IFBLK */ + CCS_TYPE_IS_DIRECTORY, /* S_IFDIR */ + CCS_TYPE_IS_CHAR_DEV, /* S_IFCHR */ + CCS_TYPE_IS_FIFO, /* S_IFIFO */ + CCS_MODE_SETUID, /* S_ISUID */ + CCS_MODE_SETGID, /* S_ISGID */ + CCS_MODE_STICKY, /* S_ISVTX */ + CCS_MODE_OWNER_READ, /* S_IRUSR */ + CCS_MODE_OWNER_WRITE, /* S_IWUSR */ + CCS_MODE_OWNER_EXECUTE, /* S_IXUSR */ + CCS_MODE_GROUP_READ, /* S_IRGRP */ + CCS_MODE_GROUP_WRITE, /* S_IWGRP */ + CCS_MODE_GROUP_EXECUTE, /* S_IXGRP */ + CCS_MODE_OTHERS_READ, /* S_IROTH */ + CCS_MODE_OTHERS_WRITE, /* S_IWOTH */ + CCS_MODE_OTHERS_EXECUTE, /* S_IXOTH */ + CCS_TASK_TYPE, /* ((u8) task->ccs_flags) & + CCS_TASK_IS_EXECUTE_HANDLER */ + CCS_TASK_EXECUTE_HANDLER, /* CCS_TASK_IS_EXECUTE_HANDLER */ + CCS_EXEC_REALPATH, + CCS_SYMLINK_TARGET, + CCS_PATH1_UID, + CCS_PATH1_GID, + CCS_PATH1_INO, + CCS_PATH1_MAJOR, + CCS_PATH1_MINOR, + CCS_PATH1_PERM, + CCS_PATH1_TYPE, + CCS_PATH1_DEV_MAJOR, + CCS_PATH1_DEV_MINOR, + CCS_PATH2_UID, + CCS_PATH2_GID, + CCS_PATH2_INO, + CCS_PATH2_MAJOR, + CCS_PATH2_MINOR, + CCS_PATH2_PERM, + CCS_PATH2_TYPE, + CCS_PATH2_DEV_MAJOR, + CCS_PATH2_DEV_MINOR, + CCS_PATH1_PARENT_UID, + CCS_PATH1_PARENT_GID, + CCS_PATH1_PARENT_INO, + CCS_PATH1_PARENT_PERM, + CCS_PATH2_PARENT_UID, + CCS_PATH2_PARENT_GID, + CCS_PATH2_PARENT_INO, + CCS_PATH2_PARENT_PERM, + CCS_MAX_CONDITION_KEYWORD, + CCS_NUMBER_UNION, + CCS_NAME_UNION, + CCS_ARGV_ENTRY, + CCS_ENVP_ENTRY, +}; + +/* Index numbers for domain's attributes. */ +enum ccs_domain_info_flags_index { + /* Quota warnning flag. */ + CCS_DIF_QUOTA_WARNED, + /* + * This domain was unable to create a new domain at + * ccs_find_next_domain() because the name of the domain to be created + * was too long or it could not allocate memory. + * More than one process continued execve() without domain transition. + */ + CCS_DIF_TRANSITION_FAILED, + CCS_MAX_DOMAIN_INFO_FLAGS +}; + +/* Index numbers for audit type. */ +enum ccs_grant_log { + /* Follow profile's configuration. */ + CCS_GRANTLOG_AUTO, + /* Do not generate grant log. */ + CCS_GRANTLOG_NO, + /* Generate grant_log. */ + CCS_GRANTLOG_YES, +}; + +/* Index numbers for group entries. */ +enum ccs_group_id { + CCS_PATH_GROUP, + CCS_NUMBER_GROUP, +#ifdef CONFIG_CCSECURITY_NETWORK + CCS_ADDRESS_GROUP, +#endif + CCS_MAX_GROUP +}; + +/* Index numbers for category of functionality. */ +enum ccs_mac_category_index { + CCS_MAC_CATEGORY_FILE, +#ifdef CONFIG_CCSECURITY_NETWORK + CCS_MAC_CATEGORY_NETWORK, +#endif +#ifdef CONFIG_CCSECURITY_MISC + CCS_MAC_CATEGORY_MISC, +#endif +#ifdef CONFIG_CCSECURITY_IPC + CCS_MAC_CATEGORY_IPC, +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + CCS_MAC_CATEGORY_CAPABILITY, +#endif + CCS_MAX_MAC_CATEGORY_INDEX +}; + +/* Index numbers for functionality. */ +enum ccs_mac_index { + CCS_MAC_FILE_EXECUTE, + CCS_MAC_FILE_OPEN, + CCS_MAC_FILE_CREATE, + CCS_MAC_FILE_UNLINK, +#ifdef CONFIG_CCSECURITY_FILE_GETATTR + CCS_MAC_FILE_GETATTR, +#endif + CCS_MAC_FILE_MKDIR, + CCS_MAC_FILE_RMDIR, + CCS_MAC_FILE_MKFIFO, + CCS_MAC_FILE_MKSOCK, + CCS_MAC_FILE_TRUNCATE, + CCS_MAC_FILE_SYMLINK, + CCS_MAC_FILE_MKBLOCK, + CCS_MAC_FILE_MKCHAR, + CCS_MAC_FILE_LINK, + CCS_MAC_FILE_RENAME, + CCS_MAC_FILE_CHMOD, + CCS_MAC_FILE_CHOWN, + CCS_MAC_FILE_CHGRP, + CCS_MAC_FILE_IOCTL, + CCS_MAC_FILE_CHROOT, + CCS_MAC_FILE_MOUNT, + CCS_MAC_FILE_UMOUNT, + CCS_MAC_FILE_PIVOT_ROOT, +#ifdef CONFIG_CCSECURITY_NETWORK + CCS_MAC_NETWORK_INET_STREAM_BIND, + CCS_MAC_NETWORK_INET_STREAM_LISTEN, + CCS_MAC_NETWORK_INET_STREAM_CONNECT, + CCS_MAC_NETWORK_INET_STREAM_ACCEPT, + CCS_MAC_NETWORK_INET_DGRAM_BIND, + CCS_MAC_NETWORK_INET_DGRAM_SEND, +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + CCS_MAC_NETWORK_INET_DGRAM_RECV, +#endif + CCS_MAC_NETWORK_INET_RAW_BIND, + CCS_MAC_NETWORK_INET_RAW_SEND, +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + CCS_MAC_NETWORK_INET_RAW_RECV, +#endif + CCS_MAC_NETWORK_UNIX_STREAM_BIND, + CCS_MAC_NETWORK_UNIX_STREAM_LISTEN, + CCS_MAC_NETWORK_UNIX_STREAM_CONNECT, + CCS_MAC_NETWORK_UNIX_STREAM_ACCEPT, + CCS_MAC_NETWORK_UNIX_DGRAM_BIND, + CCS_MAC_NETWORK_UNIX_DGRAM_SEND, +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + CCS_MAC_NETWORK_UNIX_DGRAM_RECV, +#endif + CCS_MAC_NETWORK_UNIX_SEQPACKET_BIND, + CCS_MAC_NETWORK_UNIX_SEQPACKET_LISTEN, + CCS_MAC_NETWORK_UNIX_SEQPACKET_CONNECT, + CCS_MAC_NETWORK_UNIX_SEQPACKET_ACCEPT, +#endif +#ifdef CONFIG_CCSECURITY_MISC + CCS_MAC_ENVIRON, +#endif +#ifdef CONFIG_CCSECURITY_IPC + CCS_MAC_SIGNAL, +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + CCS_MAC_CAPABILITY_USE_ROUTE_SOCKET, + CCS_MAC_CAPABILITY_USE_PACKET_SOCKET, + CCS_MAC_CAPABILITY_SYS_REBOOT, + CCS_MAC_CAPABILITY_SYS_VHANGUP, + CCS_MAC_CAPABILITY_SYS_SETTIME, + CCS_MAC_CAPABILITY_SYS_NICE, + CCS_MAC_CAPABILITY_SYS_SETHOSTNAME, + CCS_MAC_CAPABILITY_USE_KERNEL_MODULE, + CCS_MAC_CAPABILITY_SYS_KEXEC_LOAD, + CCS_MAC_CAPABILITY_SYS_PTRACE, +#endif + CCS_MAX_MAC_INDEX +}; + +/* Index numbers for /proc/ccs/stat interface. */ +enum ccs_memory_stat_type { + CCS_MEMORY_POLICY, + CCS_MEMORY_AUDIT, + CCS_MEMORY_QUERY, + CCS_MAX_MEMORY_STAT +}; + +/* Index numbers for access controls with one pathname and three numbers. */ +enum ccs_mkdev_acl_index { + CCS_TYPE_MKBLOCK, + CCS_TYPE_MKCHAR, + CCS_MAX_MKDEV_OPERATION +}; + +/* Index numbers for operation mode. */ +enum ccs_mode_value { + CCS_CONFIG_DISABLED, + CCS_CONFIG_LEARNING, + CCS_CONFIG_PERMISSIVE, + CCS_CONFIG_ENFORCING, + CCS_CONFIG_MAX_MODE, + CCS_CONFIG_WANT_REJECT_LOG = 64, + CCS_CONFIG_WANT_GRANT_LOG = 128, + CCS_CONFIG_USE_DEFAULT = 255, +}; + +/* Index numbers for socket operations. */ +enum ccs_network_acl_index { + CCS_NETWORK_BIND, /* bind() operation. */ + CCS_NETWORK_LISTEN, /* listen() operation. */ + CCS_NETWORK_CONNECT, /* connect() operation. */ + CCS_NETWORK_ACCEPT, /* accept() operation. */ + CCS_NETWORK_SEND, /* send() operation. */ +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + CCS_NETWORK_RECV, /* recv() operation. */ +#endif + CCS_MAX_NETWORK_OPERATION +}; + +/* Index numbers for access controls with two pathnames. */ +enum ccs_path2_acl_index { + CCS_TYPE_LINK, + CCS_TYPE_RENAME, + CCS_TYPE_PIVOT_ROOT, + CCS_MAX_PATH2_OPERATION +}; + +/* Index numbers for access controls with one pathname. */ +enum ccs_path_acl_index { + CCS_TYPE_EXECUTE, + CCS_TYPE_READ, + CCS_TYPE_WRITE, + CCS_TYPE_APPEND, + CCS_TYPE_UNLINK, +#ifdef CONFIG_CCSECURITY_FILE_GETATTR + CCS_TYPE_GETATTR, +#endif + CCS_TYPE_RMDIR, + CCS_TYPE_TRUNCATE, + CCS_TYPE_SYMLINK, + CCS_TYPE_CHROOT, + CCS_TYPE_UMOUNT, + CCS_MAX_PATH_OPERATION +}; + +/* Index numbers for access controls with one pathname and one number. */ +enum ccs_path_number_acl_index { + CCS_TYPE_CREATE, + CCS_TYPE_MKDIR, + CCS_TYPE_MKFIFO, + CCS_TYPE_MKSOCK, + CCS_TYPE_IOCTL, + CCS_TYPE_CHMOD, + CCS_TYPE_CHOWN, + CCS_TYPE_CHGRP, + CCS_MAX_PATH_NUMBER_OPERATION +}; + +/* Index numbers for stat(). */ +enum ccs_path_stat_index { + /* Do not change this order. */ + CCS_PATH1, + CCS_PATH1_PARENT, + CCS_PATH2, + CCS_PATH2_PARENT, + CCS_MAX_PATH_STAT +}; + +/* Index numbers for entry type. */ +enum ccs_policy_id { +#ifdef CONFIG_CCSECURITY_PORTRESERVE + CCS_ID_RESERVEDPORT, +#endif + CCS_ID_GROUP, +#ifdef CONFIG_CCSECURITY_NETWORK + CCS_ID_ADDRESS_GROUP, +#endif + CCS_ID_PATH_GROUP, + CCS_ID_NUMBER_GROUP, + CCS_ID_AGGREGATOR, + CCS_ID_TRANSITION_CONTROL, + CCS_ID_MANAGER, + CCS_ID_CONDITION, + CCS_ID_NAME, + CCS_ID_ACL, + CCS_ID_DOMAIN, + CCS_MAX_POLICY +}; + +/* Index numbers for /proc/ccs/stat interface. */ +enum ccs_policy_stat_type { + /* Do not change this order. */ + CCS_STAT_POLICY_UPDATES, + CCS_STAT_POLICY_LEARNING, /* == CCS_CONFIG_LEARNING */ + CCS_STAT_POLICY_PERMISSIVE, /* == CCS_CONFIG_PERMISSIVE */ + CCS_STAT_POLICY_ENFORCING, /* == CCS_CONFIG_ENFORCING */ + CCS_MAX_POLICY_STAT +}; + +/* Index numbers for profile's PREFERENCE values. */ +enum ccs_pref_index { + CCS_PREF_MAX_AUDIT_LOG, + CCS_PREF_MAX_LEARNING_ENTRY, + CCS_PREF_ENFORCING_PENALTY, + CCS_MAX_PREF +}; + +/* Index numbers for /proc/ccs/ interfaces. */ +enum ccs_proc_interface_index { + CCS_DOMAIN_POLICY, + CCS_EXCEPTION_POLICY, + CCS_PROCESS_STATUS, + CCS_STAT, + CCS_AUDIT, + CCS_VERSION, + CCS_PROFILE, + CCS_QUERY, + CCS_MANAGER, +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + CCS_EXECUTE_HANDLER, +#endif +}; + +/* Index numbers for special mount operations. */ +enum ccs_special_mount { + CCS_MOUNT_BIND, /* mount --bind /source /dest */ + CCS_MOUNT_MOVE, /* mount --move /old /new */ + CCS_MOUNT_REMOUNT, /* mount -o remount /dir */ + CCS_MOUNT_MAKE_UNBINDABLE, /* mount --make-unbindable /dir */ + CCS_MOUNT_MAKE_PRIVATE, /* mount --make-private /dir */ + CCS_MOUNT_MAKE_SLAVE, /* mount --make-slave /dir */ + CCS_MOUNT_MAKE_SHARED, /* mount --make-shared /dir */ + CCS_MAX_SPECIAL_MOUNT +}; + +/* Index numbers for domain transition control keywords. */ +enum ccs_transition_type { + /* Do not change this order, */ + CCS_TRANSITION_CONTROL_NO_RESET, + CCS_TRANSITION_CONTROL_RESET, + CCS_TRANSITION_CONTROL_NO_INITIALIZE, + CCS_TRANSITION_CONTROL_INITIALIZE, + CCS_TRANSITION_CONTROL_NO_KEEP, + CCS_TRANSITION_CONTROL_KEEP, + CCS_MAX_TRANSITION_TYPE +}; + +/* Index numbers for type of numeric values. */ +enum ccs_value_type { + CCS_VALUE_TYPE_INVALID, + CCS_VALUE_TYPE_DECIMAL, + CCS_VALUE_TYPE_OCTAL, + CCS_VALUE_TYPE_HEXADECIMAL, +}; + +/* Constants definition for internal use. */ + +/* + * TOMOYO uses this hash only when appending a string into the string table. + * Frequency of appending strings is very low. So we don't need large (e.g. + * 64k) hash size. 256 will be sufficient. + */ +#define CCS_HASH_BITS 8 +#define CCS_MAX_HASH (1u << CCS_HASH_BITS) + +/* + * TOMOYO checks only SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_SEQPACKET. + * Therefore, we don't need SOCK_MAX. + */ +#define CCS_SOCK_MAX 6 + +/* Size of temporary buffer for execve() operation. */ +#define CCS_EXEC_TMPSIZE 4096 + +/* Garbage collector is trying to kfree() this element. */ +#define CCS_GC_IN_PROGRESS -1 + +/* Profile number is an integer between 0 and 255. */ +#define CCS_MAX_PROFILES 256 + +/* Group number is an integer between 0 and 255. */ +#define CCS_MAX_ACL_GROUPS 256 + +/* Current thread is doing open(O_RDONLY | O_TRUNC) ? */ +#define CCS_OPEN_FOR_READ_TRUNCATE 1 +/* Current thread is doing open(3) ? */ +#define CCS_OPEN_FOR_IOCTL_ONLY 2 +/* Current thread is doing do_execve() ? */ +#define CCS_TASK_IS_IN_EXECVE 4 +/* Current thread is running as an execute handler program? */ +#define CCS_TASK_IS_EXECUTE_HANDLER 8 +/* Current thread is allowed to modify policy via /proc/ccs/ interface? */ +#define CCS_TASK_IS_MANAGER 16 + +/* + * Retry this request. Returned by ccs_supervisor() if policy violation has + * occurred in enforcing mode and the userspace daemon decided to retry. + * + * We must choose a positive value in order to distinguish "granted" (which is + * 0) and "rejected" (which is a negative value) and "retry". + */ +#define CCS_RETRY_REQUEST 1 + +/* Ignore gfp flags which are not supported. */ +#ifndef __GFP_HIGHIO +#define __GFP_HIGHIO 0 +#endif +#ifndef __GFP_NOWARN +#define __GFP_NOWARN 0 +#endif +#ifndef __GFP_NORETRY +#define __GFP_NORETRY 0 +#endif +#ifndef __GFP_NOMEMALLOC +#define __GFP_NOMEMALLOC 0 +#endif + +/* The gfp flags used by TOMOYO. */ +#define CCS_GFP_FLAGS (__GFP_WAIT | __GFP_IO | __GFP_HIGHIO | __GFP_NOWARN | \ + __GFP_NORETRY | __GFP_NOMEMALLOC) + +/* Size of read buffer for /proc/ccs/ interface. */ +#define CCS_MAX_IO_READ_QUEUE 64 + +/* Structure definition for internal use. */ + +/* Common header for holding ACL entries. */ +struct ccs_acl_head { + struct list_head list; + s8 is_deleted; /* true or false or CCS_GC_IN_PROGRESS */ +} __packed; + +/* Common header for shared entries. */ +struct ccs_shared_acl_head { + struct list_head list; + atomic_t users; +} __packed; + +/* Common header for individual entries. */ +struct ccs_acl_info { + struct list_head list; + struct ccs_condition *cond; /* Maybe NULL. */ + s8 is_deleted; /* true or false or CCS_GC_IN_PROGRESS */ + u8 type; /* One of values in "enum ccs_acl_entry_type_index". */ + u16 perm; +} __packed; + +/* Structure for holding a word. */ +struct ccs_name_union { + /* Either @filename or @group is NULL. */ + const struct ccs_path_info *filename; + struct ccs_group *group; +}; + +/* Structure for holding a number. */ +struct ccs_number_union { + unsigned long values[2]; + struct ccs_group *group; /* Maybe NULL. */ + /* One of values in "enum ccs_value_type". */ + u8 value_type[2]; +}; + +/* Structure for holding an IP address. */ +struct ccs_ipaddr_union { + struct in6_addr ip[2]; /* Big endian. */ + struct ccs_group *group; /* Pointer to address group. */ + bool is_ipv6; /* Valid only if @group == NULL. */ +}; + +/* Structure for "path_group"/"number_group"/"address_group" directive. */ +struct ccs_group { + struct ccs_shared_acl_head head; + /* Name of group (without leading '@'). */ + const struct ccs_path_info *group_name; + /* + * List of "struct ccs_path_group" or "struct ccs_number_group" or + * "struct ccs_address_group". + */ + struct list_head member_list; +}; + +/* Structure for "path_group" directive. */ +struct ccs_path_group { + struct ccs_acl_head head; + const struct ccs_path_info *member_name; +}; + +/* Structure for "number_group" directive. */ +struct ccs_number_group { + struct ccs_acl_head head; + struct ccs_number_union number; +}; + +/* Structure for "address_group" directive. */ +struct ccs_address_group { + struct ccs_acl_head head; + /* Structure for holding an IP address. */ + struct ccs_ipaddr_union address; +}; + +/* Subset of "struct stat". Used by conditional ACL and audit logs. */ +struct ccs_mini_stat { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + kuid_t uid; + kgid_t gid; +#else + uid_t uid; + gid_t gid; +#endif + ino_t ino; + umode_t mode; + dev_t dev; + dev_t rdev; +}; + +/* Structure for dumping argv[] and envp[] of "struct linux_binprm". */ +struct ccs_page_dump { + struct page *page; /* Previously dumped page. */ + char *data; /* Contents of "page". Size is PAGE_SIZE. */ +}; + +/* Structure for attribute checks in addition to pathname checks. */ +struct ccs_obj_info { + /* True if ccs_get_attributes() was already called, false otherwise. */ + bool validate_done; + /* True if @stat[] is valid. */ + bool stat_valid[CCS_MAX_PATH_STAT]; + /* First pathname. Initialized with { NULL, NULL } if no path. */ + struct path path1; + /* Second pathname. Initialized with { NULL, NULL } if no path. */ + struct path path2; + /* + * Information on @path1, @path1's parent directory, @path2, @path2's + * parent directory. + */ + struct ccs_mini_stat stat[CCS_MAX_PATH_STAT]; + /* + * Content of symbolic link to be created. NULL for operations other + * than symlink(). + */ + struct ccs_path_info *symlink_target; +}; + +/* Structure for entries which follows "struct ccs_condition". */ +struct ccs_condition_element { + /* + * Left hand operand. A "struct ccs_argv" for CCS_ARGV_ENTRY, a + * "struct ccs_envp" for CCS_ENVP_ENTRY is attached to the tail + * of the array of this struct. + */ + u8 left; + /* + * Right hand operand. A "struct ccs_number_union" for + * CCS_NUMBER_UNION, a "struct ccs_name_union" for CCS_NAME_UNION is + * attached to the tail of the array of this struct. + */ + u8 right; + /* Equation operator. True if equals or overlaps, false otherwise. */ + bool equals; +}; + +/* Structure for optional arguments. */ +struct ccs_condition { + struct ccs_shared_acl_head head; + u32 size; /* Memory size allocated for this entry. */ + u16 condc; /* Number of conditions in this struct. */ + u16 numbers_count; /* Number of "struct ccs_number_union values". */ + u16 names_count; /* Number of "struct ccs_name_union names". */ + u16 argc; /* Number of "struct ccs_argv". */ + u16 envc; /* Number of "struct ccs_envp". */ + u8 grant_log; /* One of values in "enum ccs_grant_log". */ + bool exec_transit; /* True if transit is for "file execute". */ + const struct ccs_path_info *transit; /* Maybe NULL. */ + /* + * struct ccs_condition_element condition[condc]; + * struct ccs_number_union values[numbers_count]; + * struct ccs_name_union names[names_count]; + * struct ccs_argv argv[argc]; + * struct ccs_envp envp[envc]; + */ +}; + +struct ccs_execve; +struct ccs_policy_namespace; + +/* Structure for request info. */ +struct ccs_request_info { + /* + * For holding parameters specific to operations which deal files. + * NULL if not dealing files. + */ + struct ccs_obj_info *obj; + /* + * For holding parameters specific to execve() request. + * NULL if not dealing do_execve(). + */ + struct ccs_execve *ee; + /* + * For holding parameters. + * Pointers in this union are not NULL except path->matched_path. + */ + union { + struct { + const struct ccs_path_info *filename; + /* + * For using wildcards at ccs_find_next_domain(). + * + * The matched_acl cannot be used because it may refer + * a "struct ccs_path_acl" with ->is_group == true. + * We want to use exact "struct ccs_path_info" rather + * than "struct ccs_path_acl". + */ + const struct ccs_path_info *matched_path; + /* One of values in "enum ccs_path_acl_index". */ + u8 operation; + } path; + struct { + const struct ccs_path_info *filename1; + const struct ccs_path_info *filename2; + /* One of values in "enum ccs_path2_acl_index". */ + u8 operation; + } path2; + struct { + const struct ccs_path_info *filename; + unsigned int mode; + unsigned int major; + unsigned int minor; + /* One of values in "enum ccs_mkdev_acl_index". */ + u8 operation; + } mkdev; + struct { + const struct ccs_path_info *filename; + unsigned long number; + /* + * One of values in "enum ccs_path_number_acl_index". + */ + u8 operation; + } path_number; +#ifdef CONFIG_CCSECURITY_NETWORK + struct { + const u32 *address; /* Big endian. */ + u16 port; /* Host endian. */ + /* One of values smaller than CCS_SOCK_MAX. */ + u8 protocol; + /* One of values in "enum ccs_network_acl_index". */ + u8 operation; + bool is_ipv6; + } inet_network; + struct { + const struct ccs_path_info *address; + /* One of values smaller than CCS_SOCK_MAX. */ + u8 protocol; + /* One of values in "enum ccs_network_acl_index". */ + u8 operation; + } unix_network; +#endif +#ifdef CONFIG_CCSECURITY_MISC + struct { + const struct ccs_path_info *name; + } environ; +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + struct { + /* One of values in "enum ccs_capability_acl_index". */ + u8 operation; + } capability; +#endif +#ifdef CONFIG_CCSECURITY_IPC + struct { + const char *dest_pattern; + int sig; + } signal; +#endif + struct { + const struct ccs_path_info *type; + const struct ccs_path_info *dir; + const struct ccs_path_info *dev; + unsigned long flags; + int need_dev; + } mount; +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + struct { + const struct ccs_path_info *domainname; + } task; +#endif + } param; + /* + * For updating current->ccs_domain_info at ccs_update_task_domain(). + * Initialized to NULL at ccs_init_request_info(). + * Matching "struct ccs_acl_info" is copied if access request was + * granted. Re-initialized to NULL at ccs_update_task_domain(). + */ + struct ccs_acl_info *matched_acl; + u8 param_type; /* One of values in "enum ccs_acl_entry_type_index". */ + bool granted; /* True if granted, false otherwise. */ + /* True if current thread should not be carried sleep penalty. */ + bool dont_sleep_on_enforce_error; + /* + * For counting number of retries made for this request. + * This counter is incremented whenever ccs_supervisor() returned + * CCS_RETRY_REQUEST. + */ + u8 retry; + /* + * For holding profile number used for this request. + * One of values between 0 and CCS_MAX_PROFILES - 1. + */ + u8 profile; + /* + * For holding operation mode used for this request. + * One of CCS_CONFIG_DISABLED, CCS_CONFIG_LEARNING, + * CCS_CONFIG_PERMISSIVE, CCS_CONFIG_ENFORCING. + */ + u8 mode; + /* + * For holding operation index used for this request. + * Used by ccs_init_request_info() / ccs_get_mode() / + * ccs_write_log(). One of values in "enum ccs_mac_index". + */ + u8 type; +}; + +/* Structure for holding a token. */ +struct ccs_path_info { + const char *name; + u32 hash; /* = full_name_hash(name, strlen(name)) */ + u16 total_len; /* = strlen(name) */ + u16 const_len; /* = ccs_const_part_length(name) */ + bool is_dir; /* = ccs_strendswith(name, "/") */ + bool is_patterned; /* = const_len < total_len */ +}; + +/* Structure for execve() operation. */ +struct ccs_execve { + struct ccs_request_info r; + struct ccs_obj_info obj; + struct linux_binprm *bprm; + struct ccs_domain_info *previous_domain; + const struct ccs_path_info *transition; + /* For execute_handler */ + const struct ccs_path_info *handler; + char *handler_path; /* = kstrdup(handler->name, CCS_GFP_FLAGS) */ + /* For dumping argv[] and envp[]. */ + struct ccs_page_dump dump; + /* For temporary use. */ + char *tmp; /* Size is CCS_EXEC_TMPSIZE bytes */ +}; + +/* Structure for domain information. */ +struct ccs_domain_info { + struct list_head list; + struct list_head acl_info_list; + /* Name of this domain. Never NULL. */ + const struct ccs_path_info *domainname; + /* Namespace for this domain. Never NULL. */ + struct ccs_policy_namespace *ns; + /* Group numbers to use. */ + unsigned long group[CCS_MAX_ACL_GROUPS / BITS_PER_LONG]; + u8 profile; /* Profile number to use. */ + bool is_deleted; /* Delete flag. */ + bool flags[CCS_MAX_DOMAIN_INFO_FLAGS]; +}; + +/* + * Structure for "reset_domain"/"no_reset_domain"/"initialize_domain"/ + * "no_initialize_domain"/"keep_domain"/"no_keep_domain" keyword. + */ +struct ccs_transition_control { + struct ccs_acl_head head; + u8 type; /* One of values in "enum ccs_transition_type" */ + bool is_last_name; /* True if the domainname is ccs_last_word(). */ + const struct ccs_path_info *domainname; /* Maybe NULL */ + const struct ccs_path_info *program; /* Maybe NULL */ +}; + +/* Structure for "aggregator" keyword. */ +struct ccs_aggregator { + struct ccs_acl_head head; + const struct ccs_path_info *original_name; + const struct ccs_path_info *aggregated_name; +}; + +/* Structure for "deny_autobind" keyword. */ +struct ccs_reserved { + struct ccs_acl_head head; + struct ccs_number_union port; +}; + +/* Structure for policy manager. */ +struct ccs_manager { + struct ccs_acl_head head; + /* A path to program or a domainname. */ + const struct ccs_path_info *manager; +}; + +/* Structure for argv[]. */ +struct ccs_argv { + unsigned long index; + const struct ccs_path_info *value; + bool is_not; +}; + +/* Structure for envp[]. */ +struct ccs_envp { + const struct ccs_path_info *name; + const struct ccs_path_info *value; + bool is_not; +}; + +/* + * Structure for "task auto_execute_handler" and "task denied_execute_handler" + * directive. + * + * If "task auto_execute_handler" directive exists and the current process is + * not an execute handler, all execve() requests are replaced by execve() + * requests of a program specified by "task auto_execute_handler" directive. + * If the current process is an execute handler, "task auto_execute_handler" + * and "task denied_execute_handler" directives are ignored. + * The program specified by "task execute_handler" validates execve() + * parameters and executes the original execve() requests if appropriate. + * + * "task denied_execute_handler" directive is used only when execve() request + * was rejected in enforcing mode (i.e. CONFIG::file::execute={ mode=enforcing + * }). The program specified by "task denied_execute_handler" does whatever it + * wants to do (e.g. silently terminate, change firewall settings, redirect the + * user to honey pot etc.). + */ +struct ccs_handler_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_*_EXECUTE_HANDLER */ + const struct ccs_path_info *handler; /* Pointer to single pathname. */ +}; + +/* + * Structure for "task auto_domain_transition" and + * "task manual_domain_transition" directive. + */ +struct ccs_task_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_*_TASK_ACL */ + /* Pointer to domainname. */ + const struct ccs_path_info *domainname; +}; + +/* + * Structure for "file execute", "file read", "file write", "file append", + * "file unlink", "file getattr", "file rmdir", "file truncate", + * "file symlink", "file chroot" and "file unmount" directive. + */ +struct ccs_path_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_PATH_ACL */ + struct ccs_name_union name; +}; + +/* + * Structure for "file rename", "file link" and "file pivot_root" directive. + */ +struct ccs_path2_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_PATH2_ACL */ + struct ccs_name_union name1; + struct ccs_name_union name2; +}; + +/* + * Structure for "file create", "file mkdir", "file mkfifo", "file mksock", + * "file ioctl", "file chmod", "file chown" and "file chgrp" directive. + */ +struct ccs_path_number_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_PATH_NUMBER_ACL */ + struct ccs_name_union name; + struct ccs_number_union number; +}; + +/* Structure for "file mkblock" and "file mkchar" directive. */ +struct ccs_mkdev_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_MKDEV_ACL */ + struct ccs_name_union name; + struct ccs_number_union mode; + struct ccs_number_union major; + struct ccs_number_union minor; +}; + +/* Structure for "file mount" directive. */ +struct ccs_mount_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_MOUNT_ACL */ + struct ccs_name_union dev_name; + struct ccs_name_union dir_name; + struct ccs_name_union fs_type; + struct ccs_number_union flags; +}; + +/* Structure for "misc env" directive in domain policy. */ +struct ccs_env_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_ENV_ACL */ + const struct ccs_path_info *env; /* environment variable */ +}; + +/* Structure for "capability" directive. */ +struct ccs_capability_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_CAPABILITY_ACL */ + u8 operation; /* One of values in "enum ccs_capability_acl_index". */ +}; + +/* Structure for "ipc signal" directive. */ +struct ccs_signal_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_SIGNAL_ACL */ + struct ccs_number_union sig; + /* Pointer to destination pattern. */ + const struct ccs_path_info *domainname; +}; + +/* Structure for "network inet" directive. */ +struct ccs_inet_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_INET_ACL */ + u8 protocol; + struct ccs_ipaddr_union address; + struct ccs_number_union port; +}; + +/* Structure for "network unix" directive. */ +struct ccs_unix_acl { + struct ccs_acl_info head; /* type = CCS_TYPE_UNIX_ACL */ + u8 protocol; + struct ccs_name_union name; +}; + +/* Structure for holding string data. */ +struct ccs_name { + struct ccs_shared_acl_head head; + int size; /* Memory size allocated for this entry. */ + struct ccs_path_info entry; +}; + +/* Structure for holding a line from /proc/ccs/ interface. */ +struct ccs_acl_param { + char *data; /* Unprocessed data. */ + struct list_head *list; /* List to add or remove. */ + struct ccs_policy_namespace *ns; /* Namespace to use. */ + bool is_delete; /* True if it is a delete request. */ + union ccs_acl_union { + struct ccs_acl_info acl_info; + struct ccs_handler_acl handler_acl; + struct ccs_task_acl task_acl; + struct ccs_path_acl path_acl; + struct ccs_path2_acl path2_acl; + struct ccs_path_number_acl path_number_acl; + struct ccs_mkdev_acl mkdev_acl; + struct ccs_mount_acl mount_acl; + struct ccs_env_acl env_acl; + struct ccs_capability_acl capability_acl; + struct ccs_signal_acl signal_acl; + struct ccs_inet_acl inet_acl; + struct ccs_unix_acl unix_acl; + /**/ + struct ccs_acl_head acl_head; + struct ccs_transition_control transition_control; + struct ccs_aggregator aggregator; + struct ccs_reserved reserved; + struct ccs_manager manager; + struct ccs_path_group path_group; + struct ccs_number_group number_group; + struct ccs_address_group address_group; + } e; +}; + +/* Structure for reading/writing policy via /proc/ccs/ interfaces. */ +struct ccs_io_buffer { + /* Exclusive lock for this structure. */ + struct mutex io_sem; + char __user *read_user_buf; + size_t read_user_buf_avail; + struct { + struct list_head *ns; + struct list_head *domain; + struct list_head *group; + struct list_head *acl; + size_t avail; + unsigned int step; + unsigned int query_index; + u16 index; + u16 cond_index; + u8 acl_group_index; + u8 cond_step; + u8 bit; + u8 w_pos; + bool eof; + bool print_this_domain_only; + bool print_transition_related_only; + bool print_cond_part; + const char *w[CCS_MAX_IO_READ_QUEUE]; + } r; + struct { + struct ccs_policy_namespace *ns; + struct ccs_domain_info *domain; + size_t avail; + bool is_delete; + } w; + /* Buffer for reading. */ + char *read_buf; + /* Size of read buffer. */ + size_t readbuf_size; + /* Buffer for writing. */ + char *write_buf; + /* Size of write buffer. */ + size_t writebuf_size; + /* Type of interface. */ + enum ccs_proc_interface_index type; + /* Users counter protected by ccs_io_buffer_list_lock. */ + u8 users; + /* List for telling GC not to kfree() elements. */ + struct list_head list; +}; + +/* Structure for /proc/ccs/profile interface. */ +struct ccs_profile { + const struct ccs_path_info *comment; + u8 default_config; + u8 config[CCS_MAX_MAC_INDEX + CCS_MAX_MAC_CATEGORY_INDEX]; + unsigned int pref[CCS_MAX_PREF]; +}; + +/* Structure for representing YYYY/MM/DD hh/mm/ss. */ +struct ccs_time { + u16 year; + u8 month; + u8 day; + u8 hour; + u8 min; + u8 sec; +}; + +/* Structure for policy namespace. */ +struct ccs_policy_namespace { + /* Profile table. Memory is allocated as needed. */ + struct ccs_profile *profile_ptr[CCS_MAX_PROFILES]; + /* List of "struct ccs_group". */ + struct list_head group_list[CCS_MAX_GROUP]; + /* List of policy. */ + struct list_head policy_list[CCS_MAX_POLICY]; + /* The global ACL referred by "use_group" keyword. */ + struct list_head acl_group[CCS_MAX_ACL_GROUPS]; + /* List for connecting to ccs_namespace_list list. */ + struct list_head namespace_list; + /* Profile version. Currently only 20150505 is supported. */ + unsigned int profile_version; + /* Name of this namespace (e.g. "", "" ). */ + const char *name; +}; + +/* Prototype definition for "struct ccsecurity_operations". */ + +void __init ccs_permission_init(void); +void __init ccs_mm_init(void); + +/* Prototype definition for internal use. */ + +bool ccs_dump_page(struct linux_binprm *bprm, unsigned long pos, + struct ccs_page_dump *dump); +bool ccs_memory_ok(const void *ptr, const unsigned int size); +char *ccs_encode(const char *str); +char *ccs_encode2(const char *str, int str_len); +char *ccs_realpath(const struct path *path); +const char *ccs_get_exe(void); +const struct ccs_path_info *ccs_get_name(const char *name); +int ccs_audit_log(struct ccs_request_info *r); +int ccs_check_acl(struct ccs_request_info *r); +int ccs_init_request_info(struct ccs_request_info *r, const u8 index); +struct ccs_domain_info *ccs_assign_domain(const char *domainname, + const bool transit); +u8 ccs_get_config(const u8 profile, const u8 index); +void *ccs_commit_ok(void *data, const unsigned int size); +void ccs_del_acl(struct list_head *element); +void ccs_del_condition(struct list_head *element); +void ccs_fill_path_info(struct ccs_path_info *ptr); +void ccs_get_attributes(struct ccs_obj_info *obj); +void ccs_notify_gc(struct ccs_io_buffer *head, const bool is_register); +void ccs_transition_failed(const char *domainname); +void ccs_warn_oom(const char *function); +void ccs_write_log(struct ccs_request_info *r, const char *fmt, ...) + __printf(2, 3); + +/* Variable definition for internal use. */ + +extern bool ccs_policy_loaded; +extern const char * const ccs_dif[CCS_MAX_DOMAIN_INFO_FLAGS]; +extern const u8 ccs_c2mac[CCS_MAX_CAPABILITY_INDEX]; +extern const u8 ccs_pn2mac[CCS_MAX_PATH_NUMBER_OPERATION]; +extern const u8 ccs_pnnn2mac[CCS_MAX_MKDEV_OPERATION]; +extern const u8 ccs_pp2mac[CCS_MAX_PATH2_OPERATION]; +extern struct ccs_domain_info ccs_kernel_domain; +extern struct list_head ccs_condition_list; +extern struct list_head ccs_domain_list; +extern struct list_head ccs_name_list[CCS_MAX_HASH]; +extern struct list_head ccs_namespace_list; +extern struct mutex ccs_policy_lock; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +extern struct srcu_struct ccs_ss; +#endif +extern unsigned int ccs_memory_quota[CCS_MAX_MEMORY_STAT]; +extern unsigned int ccs_memory_used[CCS_MAX_MEMORY_STAT]; + +/* Inlined functions for internal use. */ + +/** + * ccs_pathcmp - strcmp() for "struct ccs_path_info" structure. + * + * @a: Pointer to "struct ccs_path_info". + * @b: Pointer to "struct ccs_path_info". + * + * Returns true if @a != @b, false otherwise. + */ +static inline bool ccs_pathcmp(const struct ccs_path_info *a, + const struct ccs_path_info *b) +{ + return a->hash != b->hash || strcmp(a->name, b->name); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) + +/** + * ccs_read_lock - Take lock for protecting policy. + * + * Returns index number for ccs_read_unlock(). + */ +static inline int ccs_read_lock(void) +{ + return srcu_read_lock(&ccs_ss); +} + +/** + * ccs_read_unlock - Release lock for protecting policy. + * + * @idx: Index number returned by ccs_read_lock(). + * + * Returns nothing. + */ +static inline void ccs_read_unlock(const int idx) +{ + srcu_read_unlock(&ccs_ss, idx); +} + +#else + +int ccs_lock(void); +void ccs_unlock(const int idx); + +/** + * ccs_read_lock - Take lock for protecting policy. + * + * Returns index number for ccs_read_unlock(). + */ +static inline int ccs_read_lock(void) +{ + return ccs_lock(); +} + +/** + * ccs_read_unlock - Release lock for protecting policy. + * + * @idx: Index number returned by ccs_read_lock(). + * + * Returns nothing. + */ +static inline void ccs_read_unlock(const int idx) +{ + ccs_unlock(idx); +} + +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) + +/** + * ccs_tasklist_lock - Take lock for reading list of "struct task_struct". + * + * Returns nothing. + */ +static inline void ccs_tasklist_lock(void) +{ + rcu_read_lock(); +} + +/** + * ccs_tasklist_unlock - Release lock for reading list of "struct task_struct". + * + * Returns nothing. + */ +static inline void ccs_tasklist_unlock(void) +{ + rcu_read_unlock(); +} + +#else + +/** + * ccs_tasklist_lock - Take lock for reading list of "struct task_struct". + * + * Returns nothing. + */ +static inline void ccs_tasklist_lock(void) +{ + read_lock(&tasklist_lock); +} + +/** + * ccs_tasklist_unlock - Release lock for reading list of "struct task_struct". + * + * Returns nothing. + */ +static inline void ccs_tasklist_unlock(void) +{ + read_unlock(&tasklist_lock); +} + +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + +/** + * ccs_sys_getppid - Copy of getppid(). + * + * Returns parent process's PID. + * + * Alpha does not have getppid() defined. To be able to build this module on + * Alpha, I have to copy getppid() from kernel/timer.c. + */ +static inline pid_t ccs_sys_getppid(void) +{ + pid_t pid; + rcu_read_lock(); + pid = task_tgid_vnr(rcu_dereference(current->real_parent)); + rcu_read_unlock(); + return pid; +} + +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) + +/** + * ccs_sys_getppid - Copy of getppid(). + * + * Returns parent process's PID. + * + * This function was rewritten to use RCU in 2.6.16.34. However, distributors + * which use earlier kernels (e.g. 2.6.8/2.6.9) did not backport the bugfix. + * Therefore, I'm using code for 2.6.16.34 for earlier kernels. + */ +static inline pid_t ccs_sys_getppid(void) +{ + pid_t pid; + rcu_read_lock(); +#if (defined(RHEL_MAJOR) && RHEL_MAJOR == 5) || (defined(AX_MAJOR) && AX_MAJOR == 3) + pid = rcu_dereference(current->parent)->tgid; +#elif defined(CONFIG_UTRACE) + /* + * RHEL 5.0 kernel does not have RHEL_MAJOR/RHEL_MINOR defined. + * Assume RHEL 5.0 if CONFIG_UTRACE is defined. + */ + pid = rcu_dereference(current->parent)->tgid; +#else + pid = rcu_dereference(current->real_parent)->tgid; +#endif + rcu_read_unlock(); + return pid; +} + +#else + +/** + * ccs_sys_getppid - Copy of getppid(). + * + * Returns parent process's PID. + * + * I can't use code for 2.6.16.34 for 2.4 kernels because 2.4 kernels does not + * have RCU. Therefore, I'm using pessimistic lock (i.e. tasklist_lock + * spinlock). + */ +static inline pid_t ccs_sys_getppid(void) +{ + pid_t pid; + read_lock(&tasklist_lock); +#ifdef TASK_DEAD + pid = current->group_leader->real_parent->tgid; +#else + pid = current->p_opptr->pid; +#endif + read_unlock(&tasklist_lock); + return pid; +} + +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + +/** + * ccs_sys_getpid - Copy of getpid(). + * + * Returns current thread's PID. + * + * Alpha does not have getpid() defined. To be able to build this module on + * Alpha, I have to copy getpid() from kernel/timer.c. + */ +static inline pid_t ccs_sys_getpid(void) +{ + return task_tgid_vnr(current); +} + +#else + +/** + * ccs_sys_getpid - Copy of getpid(). + * + * Returns current thread's PID. + */ +static inline pid_t ccs_sys_getpid(void) +{ + return current->tgid; +} + +#endif + +/** + * ccs_get_mode - Get mode for specified functionality. + * + * @profile: Profile number. + * @index: Functionality number. + * + * Returns mode. + */ +static inline u8 ccs_get_mode(const u8 profile, const u8 index) +{ + return ccs_get_config(profile, index) & (CCS_CONFIG_MAX_MODE - 1); +} + +#if defined(CONFIG_SLOB) + +/** + * ccs_round2 - Round up to power of 2 for calculating memory usage. + * + * @size: Size to be rounded up. + * + * Returns @size. + * + * Since SLOB does not round up, this function simply returns @size. + */ +static inline int ccs_round2(size_t size) +{ + return size; +} + +#else + +/** + * ccs_round2 - Round up to power of 2 for calculating memory usage. + * + * @size: Size to be rounded up. + * + * Returns rounded size. + * + * Strictly speaking, SLAB may be able to allocate (e.g.) 96 bytes instead of + * (e.g.) 128 bytes. + */ +static inline int ccs_round2(size_t size) +{ +#if PAGE_SIZE == 4096 + size_t bsize = 32; +#else + size_t bsize = 64; +#endif + if (!size) + return 0; + while (size > bsize) + bsize <<= 1; + return bsize; +} + +#endif + +/** + * ccs_put_condition - Drop reference on "struct ccs_condition". + * + * @cond: Pointer to "struct ccs_condition". Maybe NULL. + * + * Returns nothing. + */ +static inline void ccs_put_condition(struct ccs_condition *cond) +{ + if (cond) + atomic_dec(&cond->head.users); +} + +/** + * ccs_put_group - Drop reference on "struct ccs_group". + * + * @group: Pointer to "struct ccs_group". Maybe NULL. + * + * Returns nothing. + */ +static inline void ccs_put_group(struct ccs_group *group) +{ + if (group) + atomic_dec(&group->head.users); +} + +/** + * ccs_put_name - Drop reference on "struct ccs_name". + * + * @name: Pointer to "struct ccs_path_info". Maybe NULL. + * + * Returns nothing. + */ +static inline void ccs_put_name(const struct ccs_path_info *name) +{ + if (name) + atomic_dec(&container_of(name, struct ccs_name, entry)-> + head.users); +} + +/* For importing variables and functions. */ +extern const struct ccsecurity_exports ccsecurity_exports; + +#ifdef CONFIG_CCSECURITY_USE_EXTERNAL_TASK_SECURITY + +/* + * Structure for holding "struct ccs_domain_info *" and "struct ccs_execve *" + * and "u32 ccs_flags" for each "struct task_struct". + * + * "struct ccs_domain_info *" and "u32 ccs_flags" for each "struct task_struct" + * are maintained outside that "struct task_struct". Therefore, ccs_security + * != task_struct . This keeps KABI for distributor's prebuilt kernels but + * entails slow access. + * + * Memory for this structure is allocated when current thread tries to access + * it. Therefore, if memory allocation failed, current thread will be killed by + * SIGKILL. Note that if current->pid == 1, sending SIGKILL won't work. + */ +struct ccs_security { + struct list_head list; + const struct task_struct *task; + struct ccs_domain_info *ccs_domain_info; + u32 ccs_flags; + struct rcu_head rcu; +}; + +#define CCS_TASK_SECURITY_HASH_BITS 12 +#define CCS_MAX_TASK_SECURITY_HASH (1u << CCS_TASK_SECURITY_HASH_BITS) +extern struct list_head ccs_task_security_list[CCS_MAX_TASK_SECURITY_HASH]; + +struct ccs_security *ccs_find_task_security(const struct task_struct *task); + +/** + * ccs_current_security - Get "struct ccs_security" for current thread. + * + * Returns pointer to "struct ccs_security" for current thread. + */ +static inline struct ccs_security *ccs_current_security(void) +{ + return ccs_find_task_security(current); +} + +/** + * ccs_task_domain - Get "struct ccs_domain_info" for specified thread. + * + * @task: Pointer to "struct task_struct". + * + * Returns pointer to "struct ccs_security" for specified thread. + */ +static inline struct ccs_domain_info *ccs_task_domain(struct task_struct *task) +{ + struct ccs_domain_info *domain; + rcu_read_lock(); + domain = ccs_find_task_security(task)->ccs_domain_info; + rcu_read_unlock(); + return domain; +} + +/** + * ccs_current_domain - Get "struct ccs_domain_info" for current thread. + * + * Returns pointer to "struct ccs_domain_info" for current thread. + */ +static inline struct ccs_domain_info *ccs_current_domain(void) +{ + return ccs_find_task_security(current)->ccs_domain_info; +} + +/** + * ccs_task_flags - Get flags for specified thread. + * + * @task: Pointer to "struct task_struct". + * + * Returns flags for specified thread. + */ +static inline u32 ccs_task_flags(struct task_struct *task) +{ + u32 ccs_flags; + rcu_read_lock(); + ccs_flags = ccs_find_task_security(task)->ccs_flags; + rcu_read_unlock(); + return ccs_flags; +} + +/** + * ccs_current_flags - Get flags for current thread. + * + * Returns flags for current thread. + */ +static inline u32 ccs_current_flags(void) +{ + return ccs_find_task_security(current)->ccs_flags; +} + +#else + +/* + * "struct ccs_domain_info *" and "u32 ccs_flags" for each "struct task_struct" + * are maintained inside that "struct task_struct". Therefore, ccs_security == + * task_struct . This allows fast access but breaks KABI checks for + * distributor's prebuilt kernels due to changes in "struct task_struct". + */ +#define ccs_security task_struct + +/** + * ccs_find_task_security - Find "struct ccs_security" for given task. + * + * @task: Pointer to "struct task_struct". + * + * Returns pointer to "struct ccs_security". + */ +static inline struct ccs_security *ccs_find_task_security(struct task_struct * + task) +{ + return task; +} + +/** + * ccs_current_security - Get "struct ccs_security" for current thread. + * + * Returns pointer to "struct ccs_security" for current thread. + */ +static inline struct ccs_security *ccs_current_security(void) +{ + return ccs_find_task_security(current); +} + +/** + * ccs_task_domain - Get "struct ccs_domain_info" for specified thread. + * + * @task: Pointer to "struct task_struct". + * + * Returns pointer to "struct ccs_security" for specified thread. + */ +static inline struct ccs_domain_info *ccs_task_domain(struct task_struct *task) +{ + struct ccs_domain_info *domain = task->ccs_domain_info; + return domain ? domain : &ccs_kernel_domain; +} + +/** + * ccs_current_domain - Get "struct ccs_domain_info" for current thread. + * + * Returns pointer to "struct ccs_domain_info" for current thread. + * + * If current thread does not belong to a domain (which is true for initial + * init_task in order to hide ccs_kernel_domain from this module), + * current thread enters into ccs_kernel_domain. + */ +static inline struct ccs_domain_info *ccs_current_domain(void) +{ + struct task_struct *task = current; + if (!task->ccs_domain_info) + task->ccs_domain_info = &ccs_kernel_domain; + return task->ccs_domain_info; +} + +/** + * ccs_task_flags - Get flags for specified thread. + * + * @task: Pointer to "struct task_struct". + * + * Returns flags for specified thread. + */ +static inline u32 ccs_task_flags(struct task_struct *task) +{ + return ccs_find_task_security(task)->ccs_flags; +} + +/** + * ccs_current_flags - Get flags for current thread. + * + * Returns flags for current thread. + */ +static inline u32 ccs_current_flags(void) +{ + return ccs_find_task_security(current)->ccs_flags; +} + +#endif + +/** + * ccs_current_namespace - Get "struct ccs_policy_namespace" for current thread. + * + * Returns pointer to "struct ccs_policy_namespace" for current thread. + */ +static inline struct ccs_policy_namespace *ccs_current_namespace(void) +{ + return ccs_current_domain()->ns; +} + +#endif diff --git a/security/ccsecurity/load_policy.c b/security/ccsecurity/load_policy.c new file mode 100644 index 0000000..fc4ae30 --- /dev/null +++ b/security/ccsecurity/load_policy.c @@ -0,0 +1,352 @@ +/* + * security/ccsecurity/load_policy.c + * + * Copyright (C) 2005-2012 NTT DATA CORPORATION + * + * Version: 1.8.4 2015/05/05 + */ + +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) +#include +/* + * Regarding 2.4 kernels, we need to define __KERNEL_SYSCALLS__ in order to use + * waitpid() because call_usermodehelper() does not support UMH_WAIT_PROC. + */ +#define __KERNEL_SYSCALLS__ +#include +#else +#include +#include +#endif +#ifndef LOOKUP_POSITIVE +#define LOOKUP_POSITIVE 0 +#endif + +/* + * TOMOYO specific part start. + */ + +#include + +/** + * ccs_setup - Set enable/disable upon boot. + * + * @str: "off" to disable, "on" to enable. + * + * Returns 0. + */ +static int __init ccs_setup(char *str) +{ + if (!strcmp(str, "off")) + ccsecurity_ops.disabled = 1; + else if (!strcmp(str, "on")) + ccsecurity_ops.disabled = 0; + return 0; +} + +__setup("ccsecurity=", ccs_setup); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) +#include "lsm2ccsecurity.c" +#endif + +#ifndef CONFIG_CCSECURITY_OMIT_USERSPACE_LOADER + +/* Path to the policy loader. (default = CONFIG_CCSECURITY_POLICY_LOADER) */ +static const char *ccs_loader; + +/** + * ccs_loader_setup - Set policy loader. + * + * @str: Program to use as a policy loader (e.g. /sbin/ccs-init ). + * + * Returns 0. + */ +static int __init ccs_loader_setup(char *str) +{ + ccs_loader = str; + return 0; +} + +__setup("CCS_loader=", ccs_loader_setup); + +/** + * ccs_policy_loader_exists - Check whether /sbin/ccs-init exists. + * + * Returns true if /sbin/ccs-init exists, false otherwise. + */ +static _Bool ccs_policy_loader_exists(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28) + struct path path; + if (!ccs_loader) + ccs_loader = CONFIG_CCSECURITY_POLICY_LOADER; + if (kern_path(ccs_loader, LOOKUP_FOLLOW | LOOKUP_POSITIVE, + &path) == 0) { + path_put(&path); + return 1; + } +#else + struct nameidata nd; + if (!ccs_loader) + ccs_loader = CONFIG_CCSECURITY_POLICY_LOADER; + if (path_lookup(ccs_loader, LOOKUP_FOLLOW | LOOKUP_POSITIVE, + &nd) == 0) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + path_put(&nd.path); +#else + path_release(&nd); +#endif + return 1; + } +#endif + printk(KERN_INFO "Not activating Mandatory Access Control " + "as %s does not exist.\n", ccs_loader); + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + +/** + * ccs_run_loader - Start /sbin/ccs-init. + * + * @unused: Not used. + * + * Returns PID of /sbin/ccs-init on success, negative value otherwise. + */ +static int ccs_run_loader(void *unused) +{ + char *argv[2]; + char *envp[3]; + printk(KERN_INFO "Calling %s to load policy. Please wait.\n", + ccs_loader); + argv[0] = (char *) ccs_loader; + argv[1] = NULL; + envp[0] = "HOME=/"; + envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + envp[2] = NULL; + return exec_usermodehelper(argv[0], argv, envp); +} + +#endif + +/* Path to the trigger. (default = CONFIG_CCSECURITY_ACTIVATION_TRIGGER) */ +static const char *ccs_trigger; + +/** + * ccs_trigger_setup - Set trigger for activation. + * + * @str: Program to use as an activation trigger (e.g. /sbin/init ). + * + * Returns 0. + */ +static int __init ccs_trigger_setup(char *str) +{ + ccs_trigger = str; + return 0; +} + +__setup("CCS_trigger=", ccs_trigger_setup); + +/** + * ccs_load_policy - Run external policy loader to load policy. + * + * @filename: The program about to start. + * + * Returns nothing. + * + * This function checks whether @filename is /sbin/init, and if so + * invoke /sbin/ccs-init and wait for the termination of /sbin/ccs-init + * and then continues invocation of /sbin/init. + * /sbin/ccs-init reads policy files in /etc/ccs/ directory and + * writes to /proc/ccs/ interfaces. + */ +static void ccs_load_policy(const char *filename) +{ + static _Bool done; + if (ccsecurity_ops.disabled || done) + return; + if (!ccs_trigger) + ccs_trigger = CONFIG_CCSECURITY_ACTIVATION_TRIGGER; + if (strcmp(filename, ccs_trigger)) + return; + if (!ccs_policy_loader_exists()) + return; + done = 1; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) + { + char *argv[2]; + char *envp[3]; + printk(KERN_INFO "Calling %s to load policy. Please wait.\n", + ccs_loader); + argv[0] = (char *) ccs_loader; + argv[1] = NULL; + envp[0] = "HOME=/"; + envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + envp[2] = NULL; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) || defined(UMH_WAIT_PROC) + call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC); +#else + call_usermodehelper(argv[0], argv, envp, 1); +#endif + } +#elif defined(TASK_DEAD) + { + /* Copied from kernel/kmod.c */ + struct task_struct *task = current; + pid_t pid = kernel_thread(ccs_run_loader, NULL, 0); + sigset_t tmpsig; + spin_lock_irq(&task->sighand->siglock); + tmpsig = task->blocked; + siginitsetinv(&task->blocked, + sigmask(SIGKILL) | sigmask(SIGSTOP)); + recalc_sigpending(); + spin_unlock_irq(&task->sighand->siglock); + if (pid >= 0) + waitpid(pid, NULL, __WCLONE); + spin_lock_irq(&task->sighand->siglock); + task->blocked = tmpsig; + recalc_sigpending(); + spin_unlock_irq(&task->sighand->siglock); + } +#else + { + /* Copied from kernel/kmod.c */ + struct task_struct *task = current; + pid_t pid = kernel_thread(ccs_run_loader, NULL, 0); + sigset_t tmpsig; + spin_lock_irq(&task->sigmask_lock); + tmpsig = task->blocked; + siginitsetinv(&task->blocked, + sigmask(SIGKILL) | sigmask(SIGSTOP)); + recalc_sigpending(task); + spin_unlock_irq(&task->sigmask_lock); + if (pid >= 0) + waitpid(pid, NULL, __WCLONE); + spin_lock_irq(&task->sigmask_lock); + task->blocked = tmpsig; + recalc_sigpending(task); + spin_unlock_irq(&task->sigmask_lock); + } +#endif + if (ccsecurity_ops.check_profile) + ccsecurity_ops.check_profile(); + else + panic("Failed to load policy."); +} + +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) + +/** + * __ccs_search_binary_handler - Load policy before calling search_binary_handler(). + * + * @bprm: Pointer to "struct linux_binprm". + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_search_binary_handler(struct linux_binprm *bprm) +{ +#ifndef CONFIG_CCSECURITY_OMIT_USERSPACE_LOADER + ccs_load_policy(bprm->filename); +#endif + /* + * ccs_load_policy() executes /sbin/ccs-init if bprm->filename is + * /sbin/init. /sbin/ccs-init executes /etc/ccs/ccs-load-module to + * load loadable kernel module. The loadable kernel module modifies + * "struct ccsecurity_ops". Thus, we need to transfer control to + * __ccs_search_binary_handler() in security/ccsecurity/permission.c + * if "struct ccsecurity_ops" was modified. + */ + if (ccsecurity_ops.search_binary_handler + != __ccs_search_binary_handler) + return ccsecurity_ops.search_binary_handler(bprm); + return search_binary_handler(bprm); +} + +#else + +/** + * __ccs_search_binary_handler - Load policy before calling search_binary_handler(). + * + * @bprm: Pointer to "struct linux_binprm". + * @regs: Pointer to "struct pt_regs". + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_search_binary_handler(struct linux_binprm *bprm, + struct pt_regs *regs) +{ +#ifndef CONFIG_CCSECURITY_OMIT_USERSPACE_LOADER + ccs_load_policy(bprm->filename); +#endif + /* + * ccs_load_policy() executes /sbin/ccs-init if bprm->filename is + * /sbin/init. /sbin/ccs-init executes /etc/ccs/ccs-load-module to + * load loadable kernel module. The loadable kernel module modifies + * "struct ccsecurity_ops". Thus, we need to transfer control to + * __ccs_search_binary_handler() in security/ccsecurity/permission.c + * if "struct ccsecurity_ops" was modified. + */ + if (ccsecurity_ops.search_binary_handler + != __ccs_search_binary_handler) + return ccsecurity_ops.search_binary_handler(bprm, regs); + return search_binary_handler(bprm, regs); +} + +#endif + +/* + * Some exports for loadable kernel module part. + * + * Although scripts/checkpatch.pl complains about use of "extern" in C file, + * we don't put these into security/ccsecurity/internal.h because we want to + * split built-in part and loadable kernel module part. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) && LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 35) +extern spinlock_t vfsmount_lock; +#endif + +/* For exporting variables and functions. */ +const struct ccsecurity_exports ccsecurity_exports = { +#ifndef CONFIG_CCSECURITY_OMIT_USERSPACE_LOADER + .load_policy = ccs_load_policy, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) && defined(CONFIG_SECURITY) + .add_hooks = ccs_add_hooks, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0) + .d_absolute_path = d_absolute_path, +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) + .__d_path = __d_path, +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) + .vfsmount_lock = &vfsmount_lock, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + .find_task_by_vpid = find_task_by_vpid, + .find_task_by_pid_ns = find_task_by_pid_ns, +#endif +}; +#ifdef CONFIG_CCSECURITY_LKM +/* Only ccsecurity module need to access this struct. */ +EXPORT_SYMBOL_GPL(ccsecurity_exports); +#endif + +/* Members are updated by loadable kernel module. */ +struct ccsecurity_operations ccsecurity_ops = { + .search_binary_handler = __ccs_search_binary_handler, +#ifdef CONFIG_CCSECURITY_DISABLE_BY_DEFAULT + .disabled = 1, +#endif +}; +/* + * Non-GPL modules might need to access this struct via inlined functions + * embedded into include/linux/security.h and include/net/ip.h + */ +EXPORT_SYMBOL(ccsecurity_ops); diff --git a/security/ccsecurity/lsm2ccsecurity.c b/security/ccsecurity/lsm2ccsecurity.c new file mode 100644 index 0000000..b81d8ed --- /dev/null +++ b/security/ccsecurity/lsm2ccsecurity.c @@ -0,0 +1,192 @@ +/* + * security/ccsecurity/lsm2ccsecurity.c + * + * Copyright (C) 2005-2012 NTT DATA CORPORATION + * + * Version: 1.8.4 2015/07/11 + */ + +#include +#include +#include + +int ccs_settime(const struct timespec *ts, const struct timezone *tz) +{ + return ccs_capable(CCS_SYS_SETTIME) ? 0 : -EPERM; +} + +int ccs_sb_mount(const char *dev_name, struct path *path, const char *type, + unsigned long flags, void *data) +{ + return ccs_mount_permission(dev_name, path, type, flags, data); +} + +int ccs_sb_umount(struct vfsmount *mnt, int flags) +{ + return ccs_umount_permission(mnt, flags); +} + +int ccs_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + return ccs_pivot_root_permission(old_path, new_path); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) +int ccs_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) +{ + return ccs_getattr_permission(mnt, dentry); +} +#else +int ccs_inode_getattr(const struct path *path) +{ + return ccs_getattr_permission(path->mnt, path->dentry); +} +#endif + +int ccs_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return ccs_ioctl_permission(file, cmd, arg); +} + +int ccs_file_fcntl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return ccs_fcntl_permission(file, cmd, arg); +} + +int ccs_file_open(struct file *file, const struct cred *cred) +{ + return ccs_open_permission(file); +} + +int ccs_socket_create(int family, int type, int protocol, int kern) +{ + return ccs_socket_create_permission(family, type, protocol); +} + +int ccs_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) +{ + return ccs_socket_bind_permission(sock, address, addrlen); +} + +int ccs_socket_connect(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + return ccs_socket_connect_permission(sock, address, addrlen); +} + +int ccs_socket_listen(struct socket *sock, int backlog) +{ + return ccs_socket_listen_permission(sock); +} + +int ccs_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) +{ + return ccs_socket_sendmsg_permission(sock, msg, size); +} + +int ccs_path_unlink(struct path *dir, struct dentry *dentry) +{ + return ccs_unlink_permission(dentry, dir->mnt); +} + +int ccs_path_mkdir(struct path *dir, struct dentry *dentry, umode_t mode) +{ + return ccs_mkdir_permission(dentry, dir->mnt, mode); +} + +int ccs_path_rmdir(struct path *dir, struct dentry *dentry) +{ + return ccs_rmdir_permission(dentry, dir->mnt); +} + +int ccs_path_mknod(struct path *dir, struct dentry *dentry, umode_t mode, + unsigned int dev) +{ + return ccs_mknod_permission(dentry, dir->mnt, mode, dev); +} + +int ccs_path_truncate(struct path *path) +{ + return ccs_truncate_permission(path->dentry, path->mnt); +} + +int ccs_path_symlink(struct path *dir, struct dentry *dentry, + const char *old_name) +{ + return ccs_symlink_permission(dentry, dir->mnt, old_name); +} + +int ccs_path_link(struct dentry *old_dentry, struct path *new_dir, + struct dentry *new_dentry) +{ + return ccs_link_permission(old_dentry, new_dentry, new_dir->mnt); +} + +int ccs_path_rename(struct path *old_dir, struct dentry *old_dentry, + struct path *new_dir, struct dentry *new_dentry) +{ + return ccs_rename_permission(old_dentry, new_dentry, new_dir->mnt); +} + +int ccs_path_chmod(struct path *path, umode_t mode) +{ + return ccs_chmod_permission(path->dentry, path->mnt, mode); +} + +int ccs_path_chown(struct path *path, kuid_t uid, kgid_t gid) +{ + return ccs_chown_permission(path->dentry, path->mnt, uid, gid); +} + +int ccs_path_chroot(struct path *path) +{ + return ccs_chroot_permission(path); +} + +#if !defined(CONFIG_SECURITY_PATH) +EXPORT_SYMBOL(ccs_path_mkdir); +EXPORT_SYMBOL(ccs_path_mknod); +EXPORT_SYMBOL(ccs_path_unlink); +EXPORT_SYMBOL(ccs_path_rename); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) && defined(CONFIG_SECURITY) + +#include + +static struct security_hook_list ccsecurity_hooks[] = { + LSM_HOOK_INIT(settime, ccs_settime), + LSM_HOOK_INIT(sb_mount, ccs_sb_mount), + LSM_HOOK_INIT(sb_umount, ccs_sb_umount), + LSM_HOOK_INIT(sb_pivotroot, ccs_sb_pivotroot), + LSM_HOOK_INIT(inode_getattr, ccs_inode_getattr), + LSM_HOOK_INIT(file_ioctl, ccs_file_ioctl), + LSM_HOOK_INIT(file_fcntl, ccs_file_fcntl), + LSM_HOOK_INIT(file_open, ccs_file_open), +#if defined(CONFIG_SECURITY_NETWORK) + LSM_HOOK_INIT(socket_create, ccs_socket_create), + LSM_HOOK_INIT(socket_bind, ccs_socket_bind), + LSM_HOOK_INIT(socket_connect, ccs_socket_connect), + LSM_HOOK_INIT(socket_listen, ccs_socket_listen), + LSM_HOOK_INIT(socket_sendmsg, ccs_socket_sendmsg), +#endif +#if defined(CONFIG_SECURITY_PATH) + LSM_HOOK_INIT(path_unlink, ccs_path_unlink), + LSM_HOOK_INIT(path_mkdir, ccs_path_mkdir), + LSM_HOOK_INIT(path_rmdir, ccs_path_rmdir), + LSM_HOOK_INIT(path_mknod, ccs_path_mknod), + LSM_HOOK_INIT(path_truncate, ccs_path_truncate), + LSM_HOOK_INIT(path_symlink, ccs_path_symlink), + LSM_HOOK_INIT(path_link, ccs_path_link), + LSM_HOOK_INIT(path_rename, ccs_path_rename), + LSM_HOOK_INIT(path_chmod, ccs_path_chmod), + LSM_HOOK_INIT(path_chown, ccs_path_chown), + LSM_HOOK_INIT(path_chroot, ccs_path_chroot), +#endif +}; + +static void ccs_add_hooks(void) +{ + security_add_hooks(ccsecurity_hooks, ARRAY_SIZE(ccsecurity_hooks)); +} +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) && defined(CONFIG_SECURITY) */ diff --git a/security/ccsecurity/memory.c b/security/ccsecurity/memory.c new file mode 100644 index 0000000..6514a26 --- /dev/null +++ b/security/ccsecurity/memory.c @@ -0,0 +1,356 @@ +/* + * security/ccsecurity/memory.c + * + * Copyright (C) 2005-2012 NTT DATA CORPORATION + * + * Version: 1.8.4 2015/05/05 + */ + +#include "internal.h" + +/***** SECTION1: Constants definition *****/ + +/***** SECTION2: Structure definition *****/ + +/***** SECTION3: Prototype definition section *****/ + +bool ccs_memory_ok(const void *ptr, const unsigned int size); +const struct ccs_path_info *ccs_get_name(const char *name); +#ifdef CONFIG_CCSECURITY_USE_EXTERNAL_TASK_SECURITY +struct ccs_security *ccs_find_task_security(const struct task_struct *task); +#endif +void *ccs_commit_ok(void *data, const unsigned int size); +void __init ccs_mm_init(void); +void ccs_warn_oom(const char *function); + +#ifdef CONFIG_CCSECURITY_USE_EXTERNAL_TASK_SECURITY +static int __ccs_alloc_task_security(const struct task_struct *task); +static void __ccs_free_task_security(const struct task_struct *task); +static void ccs_add_task_security(struct ccs_security *ptr, + struct list_head *list); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 8) +static void ccs_rcu_free(struct rcu_head *rcu); +#else +static void ccs_rcu_free(void *arg); +#endif +#endif + +/***** SECTION4: Standalone functions section *****/ + +/***** SECTION5: Variables definition section *****/ + +/* Memoy currently used by policy/audit log/query. */ +unsigned int ccs_memory_used[CCS_MAX_MEMORY_STAT]; + +/* Memory quota for "policy"/"audit log"/"query". */ +unsigned int ccs_memory_quota[CCS_MAX_MEMORY_STAT]; + +/* The list for "struct ccs_name". */ +struct list_head ccs_name_list[CCS_MAX_HASH]; + +#ifdef CONFIG_CCSECURITY_USE_EXTERNAL_TASK_SECURITY + +/* Dummy security context for avoiding NULL pointer dereference. */ +static struct ccs_security ccs_oom_security = { + .ccs_domain_info = &ccs_kernel_domain +}; + +/* Dummy security context for avoiding NULL pointer dereference. */ +static struct ccs_security ccs_default_security = { + .ccs_domain_info = &ccs_kernel_domain +}; + +/* List of "struct ccs_security". */ +struct list_head ccs_task_security_list[CCS_MAX_TASK_SECURITY_HASH]; +/* Lock for protecting ccs_task_security_list[]. */ +static DEFINE_SPINLOCK(ccs_task_security_list_lock); + +#endif + +/***** SECTION6: Dependent functions section *****/ + +/** + * ccs_warn_oom - Print out of memory warning message. + * + * @function: Function's name. + * + * Returns nothing. + */ +void ccs_warn_oom(const char *function) +{ + /* Reduce error messages. */ + static pid_t ccs_last_pid; + const pid_t pid = current->pid; + if (ccs_last_pid != pid) { + printk(KERN_WARNING "ERROR: Out of memory at %s.\n", + function); + ccs_last_pid = pid; + } + if (!ccs_policy_loaded) + panic("MAC Initialization failed.\n"); +} + +/** + * ccs_memory_ok - Check memory quota. + * + * @ptr: Pointer to allocated memory. Maybe NULL. + * @size: Size in byte. Not used if @ptr is NULL. + * + * Returns true if @ptr is not NULL and quota not exceeded, false otherwise. + * + * Caller holds ccs_policy_lock mutex. + */ +bool ccs_memory_ok(const void *ptr, const unsigned int size) +{ + if (ptr) { + const size_t s = ccs_round2(size); + ccs_memory_used[CCS_MEMORY_POLICY] += s; + if (!ccs_memory_quota[CCS_MEMORY_POLICY] || + ccs_memory_used[CCS_MEMORY_POLICY] <= + ccs_memory_quota[CCS_MEMORY_POLICY]) + return true; + ccs_memory_used[CCS_MEMORY_POLICY] -= s; + } + ccs_warn_oom(__func__); + return false; +} + +/** + * ccs_commit_ok - Allocate memory and check memory quota. + * + * @data: Data to copy from. + * @size: Size in byte. + * + * Returns pointer to allocated memory on success, NULL otherwise. + * @data is zero-cleared on success. + * + * Caller holds ccs_policy_lock mutex. + */ +void *ccs_commit_ok(void *data, const unsigned int size) +{ + void *ptr = kmalloc(size, CCS_GFP_FLAGS); + if (ccs_memory_ok(ptr, size)) { + memmove(ptr, data, size); + memset(data, 0, size); + return ptr; + } + kfree(ptr); + return NULL; +} + +/** + * ccs_get_name - Allocate memory for string data. + * + * @name: The string to store into the permernent memory. + * + * Returns pointer to "struct ccs_path_info" on success, NULL otherwise. + */ +const struct ccs_path_info *ccs_get_name(const char *name) +{ + struct ccs_name *ptr; + unsigned int hash; + int len; + int allocated_len; + struct list_head *head; + + if (!name) + return NULL; + len = strlen(name) + 1; + hash = full_name_hash((const unsigned char *) name, len - 1); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) || defined(RHEL_MAJOR) + head = &ccs_name_list[hash_long(hash, CCS_HASH_BITS)]; +#else + head = &ccs_name_list[hash % CCS_MAX_HASH]; +#endif + if (mutex_lock_interruptible(&ccs_policy_lock)) + return NULL; + list_for_each_entry(ptr, head, head.list) { + if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name) || + atomic_read(&ptr->head.users) == CCS_GC_IN_PROGRESS) + continue; + atomic_inc(&ptr->head.users); + goto out; + } + allocated_len = sizeof(*ptr) + len; + ptr = kzalloc(allocated_len, CCS_GFP_FLAGS); + if (ccs_memory_ok(ptr, allocated_len)) { + ptr->entry.name = ((char *) ptr) + sizeof(*ptr); + memmove((char *) ptr->entry.name, name, len); + atomic_set(&ptr->head.users, 1); + ccs_fill_path_info(&ptr->entry); + ptr->size = allocated_len; + list_add_tail(&ptr->head.list, head); + } else { + kfree(ptr); + ptr = NULL; + } +out: + mutex_unlock(&ccs_policy_lock); + return ptr ? &ptr->entry : NULL; +} + +#ifdef CONFIG_CCSECURITY_USE_EXTERNAL_TASK_SECURITY + +/** + * ccs_add_task_security - Add "struct ccs_security" to list. + * + * @ptr: Pointer to "struct ccs_security". + * @list: Pointer to "struct list_head". + * + * Returns nothing. + */ +static void ccs_add_task_security(struct ccs_security *ptr, + struct list_head *list) +{ + unsigned long flags; + spin_lock_irqsave(&ccs_task_security_list_lock, flags); + list_add_rcu(&ptr->list, list); + spin_unlock_irqrestore(&ccs_task_security_list_lock, flags); +} + +/** + * __ccs_alloc_task_security - Allocate memory for new tasks. + * + * @task: Pointer to "struct task_struct". + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_alloc_task_security(const struct task_struct *task) +{ + struct ccs_security *old_security = ccs_current_security(); + struct ccs_security *new_security = kzalloc(sizeof(*new_security), + GFP_KERNEL); + struct list_head *list = &ccs_task_security_list + [hash_ptr((void *) task, CCS_TASK_SECURITY_HASH_BITS)]; + if (!new_security) + return -ENOMEM; + new_security->task = task; + new_security->ccs_domain_info = old_security->ccs_domain_info; + new_security->ccs_flags = old_security->ccs_flags; + ccs_add_task_security(new_security, list); + return 0; +} + +/** + * ccs_find_task_security - Find "struct ccs_security" for given task. + * + * @task: Pointer to "struct task_struct". + * + * Returns pointer to "struct ccs_security" on success, &ccs_oom_security on + * out of memory, &ccs_default_security otherwise. + * + * If @task is current thread and "struct ccs_security" for current thread was + * not found, I try to allocate it. But if allocation failed, current thread + * will be killed by SIGKILL. Note that if current->pid == 1, sending SIGKILL + * won't work. + */ +struct ccs_security *ccs_find_task_security(const struct task_struct *task) +{ + struct ccs_security *ptr; + struct list_head *list = &ccs_task_security_list + [hash_ptr((void *) task, CCS_TASK_SECURITY_HASH_BITS)]; + /* Make sure INIT_LIST_HEAD() in ccs_mm_init() takes effect. */ + while (!list->next); + rcu_read_lock(); + list_for_each_entry_rcu(ptr, list, list) { + if (ptr->task != task) + continue; + rcu_read_unlock(); + return ptr; + } + rcu_read_unlock(); + if (task != current) + return &ccs_default_security; + /* Use GFP_ATOMIC because caller may have called rcu_read_lock(). */ + ptr = kzalloc(sizeof(*ptr), GFP_ATOMIC); + if (!ptr) { + printk(KERN_WARNING "Unable to allocate memory for pid=%u\n", + task->pid); + send_sig(SIGKILL, current, 0); + return &ccs_oom_security; + } + *ptr = ccs_default_security; + ptr->task = task; + ccs_add_task_security(ptr, list); + return ptr; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 8) + +/** + * ccs_rcu_free - RCU callback for releasing "struct ccs_security". + * + * @rcu: Pointer to "struct rcu_head". + * + * Returns nothing. + */ +static void ccs_rcu_free(struct rcu_head *rcu) +{ + struct ccs_security *ptr = container_of(rcu, typeof(*ptr), rcu); + kfree(ptr); +} + +#else + +/** + * ccs_rcu_free - RCU callback for releasing "struct ccs_security". + * + * @arg: Pointer to "void". + * + * Returns nothing. + */ +static void ccs_rcu_free(void *arg) +{ + struct ccs_security *ptr = arg; + kfree(ptr); +} + +#endif + +/** + * __ccs_free_task_security - Release memory associated with "struct task_struct". + * + * @task: Pointer to "struct task_struct". + * + * Returns nothing. + */ +static void __ccs_free_task_security(const struct task_struct *task) +{ + unsigned long flags; + struct ccs_security *ptr = ccs_find_task_security(task); + if (ptr == &ccs_default_security || ptr == &ccs_oom_security) + return; + spin_lock_irqsave(&ccs_task_security_list_lock, flags); + list_del_rcu(&ptr->list); + spin_unlock_irqrestore(&ccs_task_security_list_lock, flags); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 8) + call_rcu(&ptr->rcu, ccs_rcu_free); +#else + call_rcu(&ptr->rcu, ccs_rcu_free, ptr); +#endif +} + +#endif + +/** + * ccs_mm_init - Initialize mm related code. + * + * Returns nothing. + */ +void __init ccs_mm_init(void) +{ + int idx; + for (idx = 0; idx < CCS_MAX_HASH; idx++) + INIT_LIST_HEAD(&ccs_name_list[idx]); +#ifdef CONFIG_CCSECURITY_USE_EXTERNAL_TASK_SECURITY + for (idx = 0; idx < CCS_MAX_TASK_SECURITY_HASH; idx++) + INIT_LIST_HEAD(&ccs_task_security_list[idx]); +#endif + smp_wmb(); /* Avoid out of order execution. */ +#ifdef CONFIG_CCSECURITY_USE_EXTERNAL_TASK_SECURITY + ccsecurity_ops.alloc_task_security = __ccs_alloc_task_security; + ccsecurity_ops.free_task_security = __ccs_free_task_security; +#endif + ccs_kernel_domain.domainname = ccs_get_name(""); + list_add_tail_rcu(&ccs_kernel_domain.list, &ccs_domain_list); +} diff --git a/security/ccsecurity/permission.c b/security/ccsecurity/permission.c new file mode 100644 index 0000000..d73c237 --- /dev/null +++ b/security/ccsecurity/permission.c @@ -0,0 +1,5025 @@ +/* + * security/ccsecurity/permission.c + * + * Copyright (C) 2005-2012 NTT DATA CORPORATION + * + * Version: 1.8.4 2015/05/05 + */ + +#include "internal.h" + +/***** SECTION1: Constants definition *****/ + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32) + +/* + * may_open() receives open flags modified by open_to_namei_flags() until + * 2.6.32. We stop here in case some distributions backported ACC_MODE changes, + * for we can't determine whether may_open() receives open flags modified by + * open_to_namei_flags() or not. + */ +#ifdef ACC_MODE +#error ACC_MODE already defined. +#endif +#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE]) + +#if defined(RHEL_MAJOR) && RHEL_MAJOR == 6 +/* RHEL6 passes unmodified flags since 2.6.32-71.14.1.el6 . */ +#undef ACC_MODE +#define ACC_MODE(x) ("\004\002\006"[(x)&O_ACCMODE]) +#endif + +#endif + +/* String table for special mount operations. */ +static const char * const ccs_mounts[CCS_MAX_SPECIAL_MOUNT] = { + [CCS_MOUNT_BIND] = "--bind", + [CCS_MOUNT_MOVE] = "--move", + [CCS_MOUNT_REMOUNT] = "--remount", + [CCS_MOUNT_MAKE_UNBINDABLE] = "--make-unbindable", + [CCS_MOUNT_MAKE_PRIVATE] = "--make-private", + [CCS_MOUNT_MAKE_SLAVE] = "--make-slave", + [CCS_MOUNT_MAKE_SHARED] = "--make-shared", +}; + +/* Mapping table from "enum ccs_path_acl_index" to "enum ccs_mac_index". */ +static const u8 ccs_p2mac[CCS_MAX_PATH_OPERATION] = { + [CCS_TYPE_EXECUTE] = CCS_MAC_FILE_EXECUTE, + [CCS_TYPE_READ] = CCS_MAC_FILE_OPEN, + [CCS_TYPE_WRITE] = CCS_MAC_FILE_OPEN, + [CCS_TYPE_APPEND] = CCS_MAC_FILE_OPEN, + [CCS_TYPE_UNLINK] = CCS_MAC_FILE_UNLINK, +#ifdef CONFIG_CCSECURITY_FILE_GETATTR + [CCS_TYPE_GETATTR] = CCS_MAC_FILE_GETATTR, +#endif + [CCS_TYPE_RMDIR] = CCS_MAC_FILE_RMDIR, + [CCS_TYPE_TRUNCATE] = CCS_MAC_FILE_TRUNCATE, + [CCS_TYPE_SYMLINK] = CCS_MAC_FILE_SYMLINK, + [CCS_TYPE_CHROOT] = CCS_MAC_FILE_CHROOT, + [CCS_TYPE_UMOUNT] = CCS_MAC_FILE_UMOUNT, +}; + +/* Mapping table from "enum ccs_mkdev_acl_index" to "enum ccs_mac_index". */ +const u8 ccs_pnnn2mac[CCS_MAX_MKDEV_OPERATION] = { + [CCS_TYPE_MKBLOCK] = CCS_MAC_FILE_MKBLOCK, + [CCS_TYPE_MKCHAR] = CCS_MAC_FILE_MKCHAR, +}; + +/* Mapping table from "enum ccs_path2_acl_index" to "enum ccs_mac_index". */ +const u8 ccs_pp2mac[CCS_MAX_PATH2_OPERATION] = { + [CCS_TYPE_LINK] = CCS_MAC_FILE_LINK, + [CCS_TYPE_RENAME] = CCS_MAC_FILE_RENAME, + [CCS_TYPE_PIVOT_ROOT] = CCS_MAC_FILE_PIVOT_ROOT, +}; + +/* + * Mapping table from "enum ccs_path_number_acl_index" to "enum ccs_mac_index". + */ +const u8 ccs_pn2mac[CCS_MAX_PATH_NUMBER_OPERATION] = { + [CCS_TYPE_CREATE] = CCS_MAC_FILE_CREATE, + [CCS_TYPE_MKDIR] = CCS_MAC_FILE_MKDIR, + [CCS_TYPE_MKFIFO] = CCS_MAC_FILE_MKFIFO, + [CCS_TYPE_MKSOCK] = CCS_MAC_FILE_MKSOCK, + [CCS_TYPE_IOCTL] = CCS_MAC_FILE_IOCTL, + [CCS_TYPE_CHMOD] = CCS_MAC_FILE_CHMOD, + [CCS_TYPE_CHOWN] = CCS_MAC_FILE_CHOWN, + [CCS_TYPE_CHGRP] = CCS_MAC_FILE_CHGRP, +}; + +#ifdef CONFIG_CCSECURITY_NETWORK + +/* + * Mapping table from "enum ccs_network_acl_index" to "enum ccs_mac_index" for + * inet domain socket. + */ +static const u8 ccs_inet2mac[CCS_SOCK_MAX][CCS_MAX_NETWORK_OPERATION] = { + [SOCK_STREAM] = { + [CCS_NETWORK_BIND] = CCS_MAC_NETWORK_INET_STREAM_BIND, + [CCS_NETWORK_LISTEN] = CCS_MAC_NETWORK_INET_STREAM_LISTEN, + [CCS_NETWORK_CONNECT] = CCS_MAC_NETWORK_INET_STREAM_CONNECT, + [CCS_NETWORK_ACCEPT] = CCS_MAC_NETWORK_INET_STREAM_ACCEPT, + }, + [SOCK_DGRAM] = { + [CCS_NETWORK_BIND] = CCS_MAC_NETWORK_INET_DGRAM_BIND, + [CCS_NETWORK_SEND] = CCS_MAC_NETWORK_INET_DGRAM_SEND, +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + [CCS_NETWORK_RECV] = CCS_MAC_NETWORK_INET_DGRAM_RECV, +#endif + }, + [SOCK_RAW] = { + [CCS_NETWORK_BIND] = CCS_MAC_NETWORK_INET_RAW_BIND, + [CCS_NETWORK_SEND] = CCS_MAC_NETWORK_INET_RAW_SEND, +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + [CCS_NETWORK_RECV] = CCS_MAC_NETWORK_INET_RAW_RECV, +#endif + }, +}; + +/* + * Mapping table from "enum ccs_network_acl_index" to "enum ccs_mac_index" for + * unix domain socket. + */ +static const u8 ccs_unix2mac[CCS_SOCK_MAX][CCS_MAX_NETWORK_OPERATION] = { + [SOCK_STREAM] = { + [CCS_NETWORK_BIND] = CCS_MAC_NETWORK_UNIX_STREAM_BIND, + [CCS_NETWORK_LISTEN] = CCS_MAC_NETWORK_UNIX_STREAM_LISTEN, + [CCS_NETWORK_CONNECT] = CCS_MAC_NETWORK_UNIX_STREAM_CONNECT, + [CCS_NETWORK_ACCEPT] = CCS_MAC_NETWORK_UNIX_STREAM_ACCEPT, + }, + [SOCK_DGRAM] = { + [CCS_NETWORK_BIND] = CCS_MAC_NETWORK_UNIX_DGRAM_BIND, + [CCS_NETWORK_SEND] = CCS_MAC_NETWORK_UNIX_DGRAM_SEND, +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + [CCS_NETWORK_RECV] = CCS_MAC_NETWORK_UNIX_DGRAM_RECV, +#endif + }, + [SOCK_SEQPACKET] = { + [CCS_NETWORK_BIND] = CCS_MAC_NETWORK_UNIX_SEQPACKET_BIND, + [CCS_NETWORK_LISTEN] = CCS_MAC_NETWORK_UNIX_SEQPACKET_LISTEN, + [CCS_NETWORK_CONNECT] = CCS_MAC_NETWORK_UNIX_SEQPACKET_CONNECT, + [CCS_NETWORK_ACCEPT] = CCS_MAC_NETWORK_UNIX_SEQPACKET_ACCEPT, + }, +}; + +#endif + +#ifdef CONFIG_CCSECURITY_CAPABILITY + +/* + * Mapping table from "enum ccs_capability_acl_index" to "enum ccs_mac_index". + */ +const u8 ccs_c2mac[CCS_MAX_CAPABILITY_INDEX] = { + [CCS_USE_ROUTE_SOCKET] = CCS_MAC_CAPABILITY_USE_ROUTE_SOCKET, + [CCS_USE_PACKET_SOCKET] = CCS_MAC_CAPABILITY_USE_PACKET_SOCKET, + [CCS_SYS_REBOOT] = CCS_MAC_CAPABILITY_SYS_REBOOT, + [CCS_SYS_VHANGUP] = CCS_MAC_CAPABILITY_SYS_VHANGUP, + [CCS_SYS_SETTIME] = CCS_MAC_CAPABILITY_SYS_SETTIME, + [CCS_SYS_NICE] = CCS_MAC_CAPABILITY_SYS_NICE, + [CCS_SYS_SETHOSTNAME] = CCS_MAC_CAPABILITY_SYS_SETHOSTNAME, + [CCS_USE_KERNEL_MODULE] = CCS_MAC_CAPABILITY_USE_KERNEL_MODULE, + [CCS_SYS_KEXEC_LOAD] = CCS_MAC_CAPABILITY_SYS_KEXEC_LOAD, + [CCS_SYS_PTRACE] = CCS_MAC_CAPABILITY_SYS_PTRACE, +}; + +#endif + +/***** SECTION2: Structure definition *****/ + +/* Structure for holding inet domain socket's address. */ +struct ccs_inet_addr_info { + u16 port; /* In network byte order. */ + const u32 *address; /* In network byte order. */ + bool is_ipv6; +}; + +/* Structure for holding unix domain socket's address. */ +struct ccs_unix_addr_info { + u8 *addr; /* This may not be '\0' terminated string. */ + unsigned int addr_len; +}; + +/* Structure for holding socket address. */ +struct ccs_addr_info { + u8 protocol; + u8 operation; + struct ccs_inet_addr_info inet; + struct ccs_unix_addr_info unix0; +}; + +/***** SECTION3: Prototype definition section *****/ + +bool ccs_dump_page(struct linux_binprm *bprm, unsigned long pos, + struct ccs_page_dump *dump); +void ccs_get_attributes(struct ccs_obj_info *obj); + +static bool ccs_alphabet_char(const char c); +static bool ccs_argv(const unsigned int index, const char *arg_ptr, + const int argc, const struct ccs_argv *argv, u8 *checked); +static bool ccs_byte_range(const char *str); +static bool ccs_check_entry(struct ccs_request_info *r, + struct ccs_acl_info *ptr); +static bool ccs_check_mkdev_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr); +static bool ccs_check_mount_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr); +static bool ccs_check_path2_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr); +static bool ccs_check_path_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr); +static bool ccs_check_path_number_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr); +static bool ccs_compare_number_union(const unsigned long value, + const struct ccs_number_union *ptr); +static bool ccs_condition(struct ccs_request_info *r, + const struct ccs_condition *cond); +static bool ccs_decimal(const char c); +static bool ccs_envp(const char *env_name, const char *env_value, + const int envc, const struct ccs_envp *envp, u8 *checked); +static bool ccs_file_matches_pattern(const char *filename, + const char *filename_end, + const char *pattern, + const char *pattern_end); +static bool ccs_file_matches_pattern2(const char *filename, + const char *filename_end, + const char *pattern, + const char *pattern_end); +static bool ccs_get_realpath(struct ccs_path_info *buf, struct path *path); +static bool ccs_hexadecimal(const char c); +static bool ccs_number_matches_group(const unsigned long min, + const unsigned long max, + const struct ccs_group *group); +static bool ccs_path_matches_pattern(const struct ccs_path_info *filename, + const struct ccs_path_info *pattern); +static bool ccs_path_matches_pattern2(const char *f, const char *p); +static bool ccs_scan_bprm(struct ccs_execve *ee, const u16 argc, + const struct ccs_argv *argv, const u16 envc, + const struct ccs_envp *envp); +static bool ccs_scan_exec_realpath(struct file *file, + const struct ccs_name_union *ptr, + const bool match); +static bool ccs_scan_transition(const struct list_head *list, + const struct ccs_path_info *domainname, + const struct ccs_path_info *program, + const char *last_name, + const enum ccs_transition_type type); +static const char *ccs_last_word(const char *name); +static const struct ccs_path_info *ccs_compare_name_union +(const struct ccs_path_info *name, const struct ccs_name_union *ptr); +static const struct ccs_path_info *ccs_path_matches_group +(const struct ccs_path_info *pathname, const struct ccs_group *group); +static enum ccs_transition_type ccs_transition_type +(const struct ccs_policy_namespace *ns, const struct ccs_path_info *domainname, + const struct ccs_path_info *program); +static int __ccs_chmod_permission(struct dentry *dentry, + struct vfsmount *vfsmnt, mode_t mode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) +static int __ccs_chown_permission(struct dentry *dentry, + struct vfsmount *vfsmnt, kuid_t user, + kgid_t group); +#else +static int __ccs_chown_permission(struct dentry *dentry, + struct vfsmount *vfsmnt, uid_t user, + gid_t group); +#endif +static int __ccs_chroot_permission(struct path *path); +static int __ccs_fcntl_permission(struct file *file, unsigned int cmd, + unsigned long arg); +static int __ccs_ioctl_permission(struct file *filp, unsigned int cmd, + unsigned long arg); +static int __ccs_link_permission(struct dentry *old_dentry, + struct dentry *new_dentry, + struct vfsmount *mnt); +static int __ccs_mkdir_permission(struct dentry *dentry, struct vfsmount *mnt, + unsigned int mode); +static int __ccs_mknod_permission(struct dentry *dentry, struct vfsmount *mnt, + const unsigned int mode, unsigned int dev); +static int __ccs_mount_permission(const char *dev_name, struct path *path, + const char *type, unsigned long flags, + void *data_page); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) +static int __ccs_open_exec_permission(struct dentry *dentry, + struct vfsmount *mnt); +#endif +static int __ccs_open_permission(struct dentry *dentry, struct vfsmount *mnt, + const int flag); +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18) || (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33) && defined(CONFIG_SYSCTL_SYSCALL)) +static int __ccs_parse_table(int __user *name, int nlen, void __user *oldval, + void __user *newval, struct ctl_table *table); +#endif +static int __ccs_pivot_root_permission(struct path *old_path, + struct path *new_path); +static int __ccs_rename_permission(struct dentry *old_dentry, + struct dentry *new_dentry, + struct vfsmount *mnt); +static int __ccs_rmdir_permission(struct dentry *dentry, struct vfsmount *mnt); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) +static int __ccs_search_binary_handler(struct linux_binprm *bprm); +#else +static int __ccs_search_binary_handler(struct linux_binprm *bprm, + struct pt_regs *regs); +#endif +static int __ccs_symlink_permission(struct dentry *dentry, + struct vfsmount *mnt, const char *from); +static int __ccs_truncate_permission(struct dentry *dentry, + struct vfsmount *mnt); +static int __ccs_umount_permission(struct vfsmount *mnt, int flags); +static int __ccs_unlink_permission(struct dentry *dentry, + struct vfsmount *mnt); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) +static int __ccs_uselib_permission(struct dentry *dentry, + struct vfsmount *mnt); +#endif +static int ccs_execute_permission(struct ccs_request_info *r, + const struct ccs_path_info *filename); +static int ccs_find_next_domain(struct ccs_execve *ee); +static int ccs_get_path(const char *pathname, struct path *path); +static int ccs_kern_path(const char *pathname, int flags, struct path *path); +static int ccs_mkdev_perm(const u8 operation, struct dentry *dentry, + struct vfsmount *mnt, const unsigned int mode, + unsigned int dev); +static int ccs_mount_acl(struct ccs_request_info *r, const char *dev_name, + struct path *dir, const char *type, + unsigned long flags); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) +static int ccs_new_open_permission(struct file *filp); +#endif +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24) +static int ccs_old_chroot_permission(struct nameidata *nd); +static int ccs_old_mount_permission(const char *dev_name, struct nameidata *nd, + const char *type, unsigned long flags, + void *data_page); +static int ccs_old_pivot_root_permission(struct nameidata *old_nd, + struct nameidata *new_nd); +#endif +static int ccs_path2_perm(const u8 operation, struct dentry *dentry1, + struct vfsmount *mnt1, struct dentry *dentry2, + struct vfsmount *mnt2); +static int ccs_path_number_perm(const u8 type, struct dentry *dentry, + struct vfsmount *vfsmnt, unsigned long number); +static int ccs_path_perm(const u8 operation, struct dentry *dentry, + struct vfsmount *mnt, const char *target); +static int ccs_path_permission(struct ccs_request_info *r, u8 operation, + const struct ccs_path_info *filename); +static int ccs_start_execve(struct linux_binprm *bprm, + struct ccs_execve **eep); +static int ccs_symlink_path(const char *pathname, struct ccs_path_info *name); +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32) +static void __ccs_clear_open_mode(void); +static void __ccs_save_open_mode(int mode); +#endif +static void ccs_add_slash(struct ccs_path_info *buf); +static void ccs_finish_execve(int retval, struct ccs_execve *ee); + +#ifdef CONFIG_CCSECURITY_MISC +static bool ccs_check_env_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr); +static int ccs_env_perm(struct ccs_request_info *r, const char *env); +static int ccs_environ(struct ccs_execve *ee); +#endif + +#ifdef CONFIG_CCSECURITY_CAPABILITY +static bool __ccs_capable(const u8 operation); +static bool ccs_check_capability_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr); +static bool ccs_kernel_service(void); +static int __ccs_ptrace_permission(long request, long pid); +static int __ccs_socket_create_permission(int family, int type, int protocol); +#endif + +#ifdef CONFIG_CCSECURITY_NETWORK +static bool ccs_address_matches_group(const bool is_ipv6, const u32 *address, + const struct ccs_group *group); +static bool ccs_check_inet_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr); +static bool ccs_check_unix_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr); +static bool ccs_kernel_service(void); +static int __ccs_socket_bind_permission(struct socket *sock, + struct sockaddr *addr, int addr_len); +static int __ccs_socket_connect_permission(struct socket *sock, + struct sockaddr *addr, + int addr_len); +static int __ccs_socket_listen_permission(struct socket *sock); +static int __ccs_socket_post_accept_permission(struct socket *sock, + struct socket *newsock); +static int __ccs_socket_sendmsg_permission(struct socket *sock, + struct msghdr *msg, int size); +static int ccs_check_inet_address(const struct sockaddr *addr, + const unsigned int addr_len, const u16 port, + struct ccs_addr_info *address); +static int ccs_check_unix_address(struct sockaddr *addr, + const unsigned int addr_len, + struct ccs_addr_info *address); +static int ccs_inet_entry(const struct ccs_addr_info *address); +static int ccs_unix_entry(const struct ccs_addr_info *address); +static u8 ccs_sock_family(struct sock *sk); +#endif + +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG +static int __ccs_socket_post_recvmsg_permission(struct sock *sk, + struct sk_buff *skb, + int flags); +#endif + +#ifdef CONFIG_CCSECURITY_IPC +static bool ccs_check_signal_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr); +static int ccs_signal_acl(const int pid, const int sig); +static int ccs_signal_acl0(pid_t tgid, pid_t pid, int sig); +static int ccs_signal_acl2(const int sig, const int pid); +#endif + +#ifdef CONFIG_CCSECURITY_FILE_GETATTR +static int __ccs_getattr_permission(struct vfsmount *mnt, + struct dentry *dentry); +#endif + +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER +static bool ccs_find_execute_handler(struct ccs_execve *ee, const u8 type); +static int ccs_try_alt_exec(struct ccs_execve *ee); +static void ccs_unescape(unsigned char *dest); +#endif + +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION +static bool ccs_check_task_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr); +#endif + +/***** SECTION4: Standalone functions section *****/ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) + +/** + * ccs_copy_argv - Wrapper for copy_strings_kernel(). + * + * @arg: String to copy. + * @bprm: Pointer to "struct linux_binprm". + * + * Returns return value of copy_strings_kernel(). + */ +static inline int ccs_copy_argv(const char *arg, struct linux_binprm *bprm) +{ + const int ret = copy_strings_kernel(1, &arg, bprm); + if (ret >= 0) + bprm->argc++; + return ret; +} + +#else + +/** + * ccs_copy_argv - Wrapper for copy_strings_kernel(). + * + * @arg: String to copy. + * @bprm: Pointer to "struct linux_binprm". + * + * Returns return value of copy_strings_kernel(). + */ +static inline int ccs_copy_argv(char *arg, struct linux_binprm *bprm) +{ + const int ret = copy_strings_kernel(1, &arg, bprm); + if (ret >= 0) + bprm->argc++; + return ret; +} + +#endif + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 35) + +/** + * get_fs_root - Get reference on root directory. + * + * @fs: Pointer to "struct fs_struct". + * @root: Pointer to "struct path". + * + * Returns nothing. + * + * This is for compatibility with older kernels. + */ +static inline void get_fs_root(struct fs_struct *fs, struct path *root) +{ + read_lock(&fs->lock); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + *root = fs->root; + path_get(root); +#else + root->dentry = dget(fs->root); + root->mnt = mntget(fs->rootmnt); +#endif + read_unlock(&fs->lock); +} + +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + +/** + * module_put - Put a reference on module. + * + * @module: Pointer to "struct module". Maybe NULL. + * + * Returns nothing. + * + * This is for compatibility with older kernels. + */ +static inline void module_put(struct module *module) +{ + if (module) + __MOD_DEC_USE_COUNT(module); +} + +#endif + +/** + * ccs_put_filesystem - Wrapper for put_filesystem(). + * + * @fstype: Pointer to "struct file_system_type". + * + * Returns nothing. + * + * Since put_filesystem() is not exported, I embed put_filesystem() here. + */ +static inline void ccs_put_filesystem(struct file_system_type *fstype) +{ + module_put(fstype->owner); +} + +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 22) +#if !defined(RHEL_MAJOR) || RHEL_MAJOR != 5 +#if !defined(AX_MAJOR) || AX_MAJOR != 3 + +/** + * ip_hdr - Get "struct iphdr". + * + * @skb: Pointer to "struct sk_buff". + * + * Returns pointer to "struct iphdr". + * + * This is for compatibility with older kernels. + */ +static inline struct iphdr *ip_hdr(const struct sk_buff *skb) +{ + return skb->nh.iph; +} + +/** + * udp_hdr - Get "struct udphdr". + * + * @skb: Pointer to "struct sk_buff". + * + * Returns pointer to "struct udphdr". + * + * This is for compatibility with older kernels. + */ +static inline struct udphdr *udp_hdr(const struct sk_buff *skb) +{ + return skb->h.uh; +} + +/** + * ipv6_hdr - Get "struct ipv6hdr". + * + * @skb: Pointer to "struct sk_buff". + * + * Returns pointer to "struct ipv6hdr". + * + * This is for compatibility with older kernels. + */ +static inline struct ipv6hdr *ipv6_hdr(const struct sk_buff *skb) +{ + return skb->nh.ipv6h; +} + +#endif +#endif +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + +/** + * skb_kill_datagram - Kill a datagram forcibly. + * + * @sk: Pointer to "struct sock". + * @skb: Pointer to "struct sk_buff". + * @flags: Flags passed to skb_recv_datagram(). + * + * Returns nothing. + */ +static inline void skb_kill_datagram(struct sock *sk, struct sk_buff *skb, + int flags) +{ + /* Clear queue. */ + if (flags & MSG_PEEK) { + int clear = 0; + spin_lock_irq(&sk->receive_queue.lock); + if (skb == skb_peek(&sk->receive_queue)) { + __skb_unlink(skb, &sk->receive_queue); + clear = 1; + } + spin_unlock_irq(&sk->receive_queue.lock); + if (clear) + kfree_skb(skb); + } + skb_free_datagram(sk, skb); +} + +#elif LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16) + +/** + * skb_kill_datagram - Kill a datagram forcibly. + * + * @sk: Pointer to "struct sock". + * @skb: Pointer to "struct sk_buff". + * @flags: Flags passed to skb_recv_datagram(). + * + * Returns nothing. + */ +static inline void skb_kill_datagram(struct sock *sk, struct sk_buff *skb, + int flags) +{ + /* Clear queue. */ + if (flags & MSG_PEEK) { + int clear = 0; + spin_lock_bh(&sk->sk_receive_queue.lock); + if (skb == skb_peek(&sk->sk_receive_queue)) { + __skb_unlink(skb, &sk->sk_receive_queue); + clear = 1; + } + spin_unlock_bh(&sk->sk_receive_queue.lock); + if (clear) + kfree_skb(skb); + } + skb_free_datagram(sk, skb); +} + +#endif + +#endif + +/***** SECTION5: Variables definition section *****/ + +/* The initial domain. */ +struct ccs_domain_info ccs_kernel_domain; + +/* The list for "struct ccs_domain_info". */ +LIST_HEAD(ccs_domain_list); + +/***** SECTION6: Dependent functions section *****/ + +/** + * ccs_path_matches_group - Check whether the given pathname matches members of the given pathname group. + * + * @pathname: The name of pathname. + * @group: Pointer to "struct ccs_path_group". + * + * Returns matched member's pathname if @pathname matches pathnames in @group, + * NULL otherwise. + * + * Caller holds ccs_read_lock(). + */ +static const struct ccs_path_info *ccs_path_matches_group +(const struct ccs_path_info *pathname, const struct ccs_group *group) +{ + struct ccs_path_group *member; + list_for_each_entry_srcu(member, &group->member_list, head.list, + &ccs_ss) { + if (member->head.is_deleted) + continue; + if (!ccs_path_matches_pattern(pathname, member->member_name)) + continue; + return member->member_name; + } + return NULL; +} + +/** + * ccs_number_matches_group - Check whether the given number matches members of the given number group. + * + * @min: Min number. + * @max: Max number. + * @group: Pointer to "struct ccs_number_group". + * + * Returns true if @min and @max partially overlaps @group, false otherwise. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_number_matches_group(const unsigned long min, + const unsigned long max, + const struct ccs_group *group) +{ + struct ccs_number_group *member; + bool matched = false; + list_for_each_entry_srcu(member, &group->member_list, head.list, + &ccs_ss) { + if (member->head.is_deleted) + continue; + if (min > member->number.values[1] || + max < member->number.values[0]) + continue; + matched = true; + break; + } + return matched; +} + +/** + * ccs_check_entry - Do permission check. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true on match, false otherwise. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_check_entry(struct ccs_request_info *r, + struct ccs_acl_info *ptr) +{ + if (ptr->is_deleted || ptr->type != r->param_type) + return false; + switch (r->param_type) { + case CCS_TYPE_PATH_ACL: + return ccs_check_path_acl(r, ptr); + case CCS_TYPE_PATH2_ACL: + return ccs_check_path2_acl(r, ptr); + case CCS_TYPE_PATH_NUMBER_ACL: + return ccs_check_path_number_acl(r, ptr); + case CCS_TYPE_MKDEV_ACL: + return ccs_check_mkdev_acl(r, ptr); + case CCS_TYPE_MOUNT_ACL: + return ccs_check_mount_acl(r, ptr); +#ifdef CONFIG_CCSECURITY_MISC + case CCS_TYPE_ENV_ACL: + return ccs_check_env_acl(r, ptr); +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + case CCS_TYPE_CAPABILITY_ACL: + return ccs_check_capability_acl(r, ptr); +#endif +#ifdef CONFIG_CCSECURITY_NETWORK + case CCS_TYPE_INET_ACL: + return ccs_check_inet_acl(r, ptr); + case CCS_TYPE_UNIX_ACL: + return ccs_check_unix_acl(r, ptr); +#endif +#ifdef CONFIG_CCSECURITY_IPC + case CCS_TYPE_SIGNAL_ACL: + return ccs_check_signal_acl(r, ptr); +#endif +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + case CCS_TYPE_MANUAL_TASK_ACL: + return ccs_check_task_acl(r, ptr); +#endif + } + return true; +} + +/** + * ccs_check_acl - Do permission check. + * + * @r: Pointer to "struct ccs_request_info". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +int ccs_check_acl(struct ccs_request_info *r) +{ + const struct ccs_domain_info *domain = ccs_current_domain(); + int error; + do { + struct ccs_acl_info *ptr; + const struct list_head *list = &domain->acl_info_list; + u16 i = 0; +retry: + list_for_each_entry_srcu(ptr, list, list, &ccs_ss) { + if (!ccs_check_entry(r, ptr)) + continue; + if (!ccs_condition(r, ptr->cond)) + continue; + r->matched_acl = ptr; + r->granted = true; + ccs_audit_log(r); + return 0; + } + for (; i < CCS_MAX_ACL_GROUPS; i++) { + if (!test_bit(i, domain->group)) + continue; + list = &domain->ns->acl_group[i++]; + goto retry; + } + r->granted = false; + error = ccs_audit_log(r); + } while (error == CCS_RETRY_REQUEST && + r->type != CCS_MAC_FILE_EXECUTE); + return error; +} + +/** + * ccs_last_word - Get last component of a domainname. + * + * @name: Domainname to check. + * + * Returns the last word of @name. + */ +static const char *ccs_last_word(const char *name) +{ + const char *cp = strrchr(name, ' '); + if (cp) + return cp + 1; + return name; +} + +/** + * ccs_scan_transition - Try to find specific domain transition type. + * + * @list: Pointer to "struct list_head". + * @domainname: The name of current domain. + * @program: The name of requested program. + * @last_name: The last component of @domainname. + * @type: One of values in "enum ccs_transition_type". + * + * Returns true if found one, false otherwise. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_scan_transition(const struct list_head *list, + const struct ccs_path_info *domainname, + const struct ccs_path_info *program, + const char *last_name, + const enum ccs_transition_type type) +{ + const struct ccs_transition_control *ptr; + list_for_each_entry_srcu(ptr, list, head.list, &ccs_ss) { + if (ptr->head.is_deleted || ptr->type != type) + continue; + if (ptr->domainname) { + if (!ptr->is_last_name) { + if (ptr->domainname != domainname) + continue; + } else { + /* + * Use direct strcmp() since this is + * unlikely used. + */ + if (strcmp(ptr->domainname->name, last_name)) + continue; + } + } + if (ptr->program && ccs_pathcmp(ptr->program, program)) + continue; + return true; + } + return false; +} + +/** + * ccs_transition_type - Get domain transition type. + * + * @ns: Pointer to "struct ccs_policy_namespace". + * @domainname: The name of current domain. + * @program: The name of requested program. + * + * Returns CCS_TRANSITION_CONTROL_TRANSIT if executing @program causes domain + * transition across namespaces, CCS_TRANSITION_CONTROL_INITIALIZE if executing + * @program reinitializes domain transition within that namespace, + * CCS_TRANSITION_CONTROL_KEEP if executing @program stays at @domainname , + * others otherwise. + * + * Caller holds ccs_read_lock(). + */ +static enum ccs_transition_type ccs_transition_type +(const struct ccs_policy_namespace *ns, const struct ccs_path_info *domainname, + const struct ccs_path_info *program) +{ + const char *last_name = ccs_last_word(domainname->name); + enum ccs_transition_type type = CCS_TRANSITION_CONTROL_NO_RESET; + while (type < CCS_MAX_TRANSITION_TYPE) { + const struct list_head * const list = + &ns->policy_list[CCS_ID_TRANSITION_CONTROL]; + if (!ccs_scan_transition(list, domainname, program, last_name, + type)) { + type++; + continue; + } + if (type != CCS_TRANSITION_CONTROL_NO_RESET && + type != CCS_TRANSITION_CONTROL_NO_INITIALIZE) + break; + /* + * Do not check for reset_domain if no_reset_domain matched. + * Do not check for initialize_domain if no_initialize_domain + * matched. + */ + type++; + type++; + } + return type; +} + +/** + * ccs_find_next_domain - Find a domain. + * + * @ee: Pointer to "struct ccs_execve". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_find_next_domain(struct ccs_execve *ee) +{ + struct ccs_request_info *r = &ee->r; +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + const struct ccs_path_info *handler = ee->handler; +#endif + struct ccs_domain_info *domain = NULL; + struct ccs_domain_info * const old_domain = ccs_current_domain(); + struct linux_binprm *bprm = ee->bprm; + struct ccs_security *task = ccs_current_security(); + const struct ccs_path_info *candidate; + struct ccs_path_info exename; + int retval; + bool reject_on_transition_failure = false; + + /* Get symlink's pathname of program. */ + retval = ccs_symlink_path(bprm->filename, &exename); + if (retval < 0) + return retval; + +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + if (handler) { + /* No permission check for execute handler. */ + candidate = &exename; + if (ccs_pathcmp(candidate, handler)) { + /* Failed to verify execute handler. */ + static u8 counter = 20; + if (counter) { + counter--; + printk(KERN_WARNING "Failed to verify: %s\n", + handler->name); + } + goto out; + } + } else +#endif + { + struct ccs_aggregator *ptr; + struct list_head *list; +retry: + /* Check 'aggregator' directive. */ + candidate = &exename; + list = &old_domain->ns->policy_list[CCS_ID_AGGREGATOR]; + list_for_each_entry_srcu(ptr, list, head.list, &ccs_ss) { + if (ptr->head.is_deleted || + !ccs_path_matches_pattern(candidate, + ptr->original_name)) + continue; + candidate = ptr->aggregated_name; + break; + } + + /* Check execute permission. */ + retval = ccs_execute_permission(r, candidate); + if (retval == CCS_RETRY_REQUEST) + goto retry; + if (retval < 0) + goto out; + /* + * To be able to specify domainnames with wildcards, use the + * pathname specified in the policy (which may contain + * wildcard) rather than the pathname passed to execve() + * (which never contains wildcard). + */ + if (r->param.path.matched_path) + candidate = r->param.path.matched_path; + } + /* + * Check for domain transition preference if "file execute" matched. + * If preference is given, make do_execve() fail if domain transition + * has failed, for domain transition preference should be used with + * destination domain defined. + */ + if (r->ee->transition) { + const char *domainname = r->ee->transition->name; + reject_on_transition_failure = true; + if (!strcmp(domainname, "keep")) + goto force_keep_domain; + if (!strcmp(domainname, "child")) + goto force_child_domain; + if (!strcmp(domainname, "reset")) + goto force_reset_domain; + if (!strcmp(domainname, "initialize")) + goto force_initialize_domain; + if (!strcmp(domainname, "parent")) { + char *cp; + strncpy(ee->tmp, old_domain->domainname->name, + CCS_EXEC_TMPSIZE - 1); + cp = strrchr(ee->tmp, ' '); + if (cp) + *cp = '\0'; + } else if (*domainname == '<') + strncpy(ee->tmp, domainname, CCS_EXEC_TMPSIZE - 1); + else + snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, "%s %s", + old_domain->domainname->name, domainname); + goto force_jump_domain; + } + /* + * No domain transition preference specified. + * Calculate domain to transit to. + */ + switch (ccs_transition_type(old_domain->ns, old_domain->domainname, + candidate)) { + case CCS_TRANSITION_CONTROL_RESET: +force_reset_domain: + /* Transit to the root of specified namespace. */ + snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, "<%s>", + candidate->name); + /* + * Make do_execve() fail if domain transition across namespaces + * has failed. + */ + reject_on_transition_failure = true; + break; + case CCS_TRANSITION_CONTROL_INITIALIZE: +force_initialize_domain: + /* Transit to the child of current namespace's root. */ + snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, "%s %s", + old_domain->ns->name, candidate->name); + break; + case CCS_TRANSITION_CONTROL_KEEP: +force_keep_domain: + /* Keep current domain. */ + domain = old_domain; + break; + default: + if (old_domain == &ccs_kernel_domain && !ccs_policy_loaded) { + /* + * Needn't to transit from kernel domain before + * starting /sbin/init. But transit from kernel domain + * if executing initializers because they might start + * before /sbin/init. + */ + domain = old_domain; + break; + } +force_child_domain: + /* Normal domain transition. */ + snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, "%s %s", + old_domain->domainname->name, candidate->name); + break; + } +force_jump_domain: + /* + * Tell GC that I started execve(). + * Also, tell open_exec() to check read permission. + */ + task->ccs_flags |= CCS_TASK_IS_IN_EXECVE; + /* + * Make task->ccs_flags visible to GC before changing + * task->ccs_domain_info. + */ + smp_wmb(); + /* + * Proceed to the next domain in order to allow reaching via PID. + * It will be reverted if execve() failed. Reverting is not good. + * But it is better than being unable to reach via PID in interactive + * enforcing mode. + */ + if (!domain) + domain = ccs_assign_domain(ee->tmp, true); + if (domain) + retval = 0; + else if (reject_on_transition_failure) { + printk(KERN_WARNING + "ERROR: Domain '%s' not ready.\n", ee->tmp); + retval = -ENOMEM; + } else if (r->mode == CCS_CONFIG_ENFORCING) + retval = -ENOMEM; + else { + retval = 0; + if (!old_domain->flags[CCS_DIF_TRANSITION_FAILED]) { + old_domain->flags[CCS_DIF_TRANSITION_FAILED] = true; + r->granted = false; + ccs_write_log(r, "%s", + ccs_dif[CCS_DIF_TRANSITION_FAILED]); + printk(KERN_WARNING + "ERROR: Domain '%s' not defined.\n", ee->tmp); + } + } +out: + kfree(exename.name); + return retval; +} + +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + +/** + * ccs_unescape - Unescape escaped string. + * + * @dest: String to unescape. + * + * Returns nothing. + */ +static void ccs_unescape(unsigned char *dest) +{ + unsigned char *src = dest; + unsigned char c; + unsigned char d; + unsigned char e; + while (1) { + c = *src++; + if (!c) + break; + if (c != '\\') { + *dest++ = c; + continue; + } + c = *src++; + if (c == '\\') { + *dest++ = c; + continue; + } + if (c < '0' || c > '3') + break; + d = *src++; + if (d < '0' || d > '7') + break; + e = *src++; + if (e < '0' || e > '7') + break; + *dest++ = ((c - '0') << 6) + ((d - '0') << 3) + (e - '0'); + } + *dest = '\0'; +} + +/** + * ccs_try_alt_exec - Try to start execute handler. + * + * @ee: Pointer to "struct ccs_execve". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_try_alt_exec(struct ccs_execve *ee) +{ + /* + * Contents of modified bprm. + * The envp[] in original bprm is moved to argv[] so that + * the alternatively executed program won't be affected by + * some dangerous environment variables like LD_PRELOAD. + * + * modified bprm->argc + * = original bprm->argc + original bprm->envc + 7 + * modified bprm->envc + * = 0 + * + * modified bprm->argv[0] + * = the program's name specified by *_execute_handler + * modified bprm->argv[1] + * = ccs_current_domain()->domainname->name + * modified bprm->argv[2] + * = the current process's name + * modified bprm->argv[3] + * = the current process's information (e.g. uid/gid). + * modified bprm->argv[4] + * = original bprm->filename + * modified bprm->argv[5] + * = original bprm->argc in string expression + * modified bprm->argv[6] + * = original bprm->envc in string expression + * modified bprm->argv[7] + * = original bprm->argv[0] + * ... + * modified bprm->argv[bprm->argc + 6] + * = original bprm->argv[bprm->argc - 1] + * modified bprm->argv[bprm->argc + 7] + * = original bprm->envp[0] + * ... + * modified bprm->argv[bprm->envc + bprm->argc + 6] + * = original bprm->envp[bprm->envc - 1] + */ + struct linux_binprm *bprm = ee->bprm; + struct file *filp; + int retval; + const int original_argc = bprm->argc; + const int original_envc = bprm->envc; + + /* Close the requested program's dentry. */ + ee->obj.path1.dentry = NULL; + ee->obj.path1.mnt = NULL; + ee->obj.stat_valid[CCS_PATH1] = false; + ee->obj.stat_valid[CCS_PATH1_PARENT] = false; + ee->obj.validate_done = false; + allow_write_access(bprm->file); + fput(bprm->file); + bprm->file = NULL; + + /* Invalidate page dump cache. */ + ee->dump.page = NULL; + + /* Move envp[] to argv[] */ + bprm->argc += bprm->envc; + bprm->envc = 0; + + /* Set argv[6] */ + { + snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, "%d", original_envc); + retval = ccs_copy_argv(ee->tmp, bprm); + if (retval < 0) + goto out; + } + + /* Set argv[5] */ + { + snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, "%d", original_argc); + retval = ccs_copy_argv(ee->tmp, bprm); + if (retval < 0) + goto out; + } + + /* Set argv[4] */ + { + retval = ccs_copy_argv(bprm->filename, bprm); + if (retval < 0) + goto out; + } + + /* Set argv[3] */ + { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + /* + * Pass uid/gid seen from current user namespace, for these + * values are used by programs in current user namespace in + * order to decide whether to execve() or not (rather than by + * auditing daemon in init's user namespace). + */ + snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, + "pid=%d uid=%d gid=%d euid=%d egid=%d suid=%d " + "sgid=%d fsuid=%d fsgid=%d", ccs_sys_getpid(), + __kuid_val(current_uid()), __kgid_val(current_gid()), + __kuid_val(current_euid()), + __kgid_val(current_egid()), + __kuid_val(current_suid()), + __kgid_val(current_sgid()), + __kuid_val(current_fsuid()), + __kgid_val(current_fsgid())); +#else + snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, + "pid=%d uid=%d gid=%d euid=%d egid=%d suid=%d " + "sgid=%d fsuid=%d fsgid=%d", ccs_sys_getpid(), + current_uid(), current_gid(), current_euid(), + current_egid(), current_suid(), current_sgid(), + current_fsuid(), current_fsgid()); +#endif + retval = ccs_copy_argv(ee->tmp, bprm); + if (retval < 0) + goto out; + } + + /* Set argv[2] */ + { + char *exe = (char *) ccs_get_exe(); + if (exe) { + retval = ccs_copy_argv(exe, bprm); + kfree(exe); + } else { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) + retval = ccs_copy_argv("", bprm); +#else + snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, ""); + retval = ccs_copy_argv(ee->tmp, bprm); +#endif + } + if (retval < 0) + goto out; + } + + /* Set argv[1] */ + { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) + retval = ccs_copy_argv(ccs_current_domain()->domainname->name, + bprm); +#else + snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, "%s", + ccs_current_domain()->domainname->name); + retval = ccs_copy_argv(ee->tmp, bprm); +#endif + if (retval < 0) + goto out; + } + + /* Set argv[0] */ + { + struct path root; + char *cp; + int root_len; + int handler_len; + get_fs_root(current->fs, &root); + cp = ccs_realpath(&root); + path_put(&root); + if (!cp) { + retval = -ENOMEM; + goto out; + } + root_len = strlen(cp); + retval = strncmp(ee->handler->name, cp, root_len); + root_len--; + kfree(cp); + if (retval) { + retval = -ENOENT; + goto out; + } + handler_len = ee->handler->total_len + 1; + cp = kmalloc(handler_len, CCS_GFP_FLAGS); + if (!cp) { + retval = -ENOMEM; + goto out; + } + /* ee->handler_path is released by ccs_finish_execve(). */ + ee->handler_path = cp; + /* Adjust root directory for open_exec(). */ + memmove(cp, ee->handler->name + root_len, + handler_len - root_len); + ccs_unescape(cp); + retval = -ENOENT; + if (*cp != '/') + goto out; + retval = ccs_copy_argv(cp, bprm); + if (retval < 0) + goto out; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24) + bprm->argv_len = bprm->exec - bprm->p; +#endif +#endif + + /* + * OK, now restart the process with execute handler program's dentry. + */ + filp = open_exec(ee->handler_path); + if (IS_ERR(filp)) { + retval = PTR_ERR(filp); + goto out; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) + ee->obj.path1 = filp->f_path; +#else + ee->obj.path1.dentry = filp->f_dentry; + ee->obj.path1.mnt = filp->f_vfsmnt; +#endif + bprm->file = filp; + bprm->filename = ee->handler_path; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) + bprm->interp = bprm->filename; +#endif + retval = prepare_binprm(bprm); + if (retval < 0) + goto out; + ee->r.dont_sleep_on_enforce_error = true; + retval = ccs_find_next_domain(ee); + ee->r.dont_sleep_on_enforce_error = false; +out: + return retval; +} + +/** + * ccs_find_execute_handler - Find an execute handler. + * + * @ee: Pointer to "struct ccs_execve". + * @type: Type of execute handler. + * + * Returns true if found, false otherwise. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_find_execute_handler(struct ccs_execve *ee, const u8 type) +{ + struct ccs_request_info *r = &ee->r; + /* + * To avoid infinite execute handler loop, don't use execute handler + * if the current process is marked as execute handler. + */ + if (ccs_current_flags() & CCS_TASK_IS_EXECUTE_HANDLER) + return false; + r->param_type = type; + ccs_check_acl(r); + if (!r->granted) + return false; + ee->handler = container_of(r->matched_acl, struct ccs_handler_acl, + head)->handler; + ee->transition = r->matched_acl && r->matched_acl->cond && + r->matched_acl->cond->exec_transit ? + r->matched_acl->cond->transit : NULL; + return true; +} + +#endif + +#ifdef CONFIG_MMU +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) +#define CCS_BPRM_MMU +#elif defined(RHEL_MAJOR) && RHEL_MAJOR == 5 && defined(RHEL_MINOR) && RHEL_MINOR >= 3 +#define CCS_BPRM_MMU +#elif defined(AX_MAJOR) && AX_MAJOR == 3 && defined(AX_MINOR) && AX_MINOR >= 2 +#define CCS_BPRM_MMU +#endif +#endif + +/** + * ccs_dump_page - Dump a page to buffer. + * + * @bprm: Pointer to "struct linux_binprm". + * @pos: Location to dump. + * @dump: Poiner to "struct ccs_page_dump". + * + * Returns true on success, false otherwise. + */ +bool ccs_dump_page(struct linux_binprm *bprm, unsigned long pos, + struct ccs_page_dump *dump) +{ + struct page *page; + /* dump->data is released by ccs_start_execve(). */ + if (!dump->data) { + dump->data = kzalloc(PAGE_SIZE, CCS_GFP_FLAGS); + if (!dump->data) + return false; + } + /* Same with get_arg_page(bprm, pos, 0) in fs/exec.c */ +#ifdef CCS_BPRM_MMU + if (get_user_pages(current, bprm->mm, pos, 1, 0, 1, &page, NULL) <= 0) + return false; +#else + page = bprm->page[pos / PAGE_SIZE]; +#endif + if (page != dump->page) { + const unsigned int offset = pos % PAGE_SIZE; + /* + * Maybe kmap()/kunmap() should be used here. + * But remove_arg_zero() uses kmap_atomic()/kunmap_atomic(). + * So do I. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) + char *kaddr = kmap_atomic(page); +#else + char *kaddr = kmap_atomic(page, KM_USER0); +#endif + dump->page = page; + memcpy(dump->data + offset, kaddr + offset, + PAGE_SIZE - offset); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) + kunmap_atomic(kaddr); +#else + kunmap_atomic(kaddr, KM_USER0); +#endif + } + /* Same with put_arg_page(page) in fs/exec.c */ +#ifdef CCS_BPRM_MMU + put_page(page); +#endif + return true; +} + +/** + * ccs_start_execve - Prepare for execve() operation. + * + * @bprm: Pointer to "struct linux_binprm". + * @eep: Pointer to "struct ccs_execve *". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_start_execve(struct linux_binprm *bprm, + struct ccs_execve **eep) +{ + int retval; + struct ccs_security *task = ccs_current_security(); + struct ccs_execve *ee; + int idx; + *eep = NULL; + ee = kzalloc(sizeof(*ee), CCS_GFP_FLAGS); + if (!ee) + return -ENOMEM; + ee->tmp = kzalloc(CCS_EXEC_TMPSIZE, CCS_GFP_FLAGS); + if (!ee->tmp) { + kfree(ee); + return -ENOMEM; + } + idx = ccs_read_lock(); + /* ee->dump->data is allocated by ccs_dump_page(). */ + ee->previous_domain = task->ccs_domain_info; + /* Clear manager flag. */ + task->ccs_flags &= ~CCS_TASK_IS_MANAGER; + *eep = ee; + ccs_init_request_info(&ee->r, CCS_MAC_FILE_EXECUTE); + ee->r.ee = ee; + ee->bprm = bprm; + ee->r.obj = &ee->obj; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) + ee->obj.path1 = bprm->file->f_path; +#else + ee->obj.path1.dentry = bprm->file->f_dentry; + ee->obj.path1.mnt = bprm->file->f_vfsmnt; +#endif +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + /* + * No need to call ccs_environ() for execute handler because envp[] is + * moved to argv[]. + */ + if (ccs_find_execute_handler(ee, CCS_TYPE_AUTO_EXECUTE_HANDLER)) { + retval = ccs_try_alt_exec(ee); + goto done; + } +#endif + retval = ccs_find_next_domain(ee); +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + if (retval == -EPERM && + ccs_find_execute_handler(ee, CCS_TYPE_DENIED_EXECUTE_HANDLER)) { + retval = ccs_try_alt_exec(ee); + goto done; + } +#endif +#ifdef CONFIG_CCSECURITY_MISC + if (!retval) + retval = ccs_environ(ee); +#endif +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER +done: +#endif + ccs_read_unlock(idx); + kfree(ee->tmp); + ee->tmp = NULL; + kfree(ee->dump.data); + ee->dump.data = NULL; + return retval; +} + +/** + * ccs_finish_execve - Clean up execve() operation. + * + * @retval: Return code of an execve() operation. + * @ee: Pointer to "struct ccs_execve". + * + * Returns nothing. + */ +static void ccs_finish_execve(int retval, struct ccs_execve *ee) +{ + struct ccs_security *task = ccs_current_security(); + if (!ee) + return; + if (retval < 0) { + task->ccs_domain_info = ee->previous_domain; + /* + * Make task->ccs_domain_info visible to GC before changing + * task->ccs_flags. + */ + smp_wmb(); + } else { + /* Mark the current process as execute handler. */ + if (ee->handler) + task->ccs_flags |= CCS_TASK_IS_EXECUTE_HANDLER; + /* Mark the current process as normal process. */ + else + task->ccs_flags &= ~CCS_TASK_IS_EXECUTE_HANDLER; + } + /* Tell GC that I finished execve(). */ + task->ccs_flags &= ~CCS_TASK_IS_IN_EXECVE; + kfree(ee->handler_path); + kfree(ee); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) + +/** + * __ccs_search_binary_handler - Main routine for do_execve(). + * + * @bprm: Pointer to "struct linux_binprm". + * + * Returns 0 on success, negative value otherwise. + * + * Performs permission checks for do_execve() and domain transition. + * Domain transition by "struct ccs_domain_transition_control" and + * "auto_domain_transition=" parameter of "struct ccs_condition" are reverted + * if do_execve() failed. + * Garbage collector does not remove "struct ccs_domain_info" from + * ccs_domain_list nor kfree("struct ccs_domain_info") if the current thread is + * marked as CCS_TASK_IS_IN_EXECVE. + */ +static int __ccs_search_binary_handler(struct linux_binprm *bprm) +{ + struct ccs_execve *ee; + int retval; +#ifndef CONFIG_CCSECURITY_OMIT_USERSPACE_LOADER + if (!ccs_policy_loaded) + ccsecurity_exports.load_policy(bprm->filename); +#endif + retval = ccs_start_execve(bprm, &ee); + if (!retval) + retval = search_binary_handler(bprm); + ccs_finish_execve(retval, ee); + return retval; +} + +#else + +/** + * __ccs_search_binary_handler - Main routine for do_execve(). + * + * @bprm: Pointer to "struct linux_binprm". + * @regs: Pointer to "struct pt_regs". + * + * Returns 0 on success, negative value otherwise. + * + * Performs permission checks for do_execve() and domain transition. + * Domain transition by "struct ccs_domain_transition_control" and + * "auto_domain_transition=" parameter of "struct ccs_condition" are reverted + * if do_execve() failed. + * Garbage collector does not remove "struct ccs_domain_info" from + * ccs_domain_list nor kfree("struct ccs_domain_info") if the current thread is + * marked as CCS_TASK_IS_IN_EXECVE. + */ +static int __ccs_search_binary_handler(struct linux_binprm *bprm, + struct pt_regs *regs) +{ + struct ccs_execve *ee; + int retval; +#ifndef CONFIG_CCSECURITY_OMIT_USERSPACE_LOADER + if (!ccs_policy_loaded) + ccsecurity_exports.load_policy(bprm->filename); +#endif + retval = ccs_start_execve(bprm, &ee); + if (!retval) + retval = search_binary_handler(bprm, regs); + ccs_finish_execve(retval, ee); + return retval; +} + +#endif + +/** + * ccs_permission_init - Register permission check hooks. + * + * Returns nothing. + */ +void __init ccs_permission_init(void) +{ +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32) + ccsecurity_ops.save_open_mode = __ccs_save_open_mode; + ccsecurity_ops.clear_open_mode = __ccs_clear_open_mode; + ccsecurity_ops.open_permission = __ccs_open_permission; +#else + ccsecurity_ops.open_permission = ccs_new_open_permission; +#endif + ccsecurity_ops.fcntl_permission = __ccs_fcntl_permission; + ccsecurity_ops.ioctl_permission = __ccs_ioctl_permission; + ccsecurity_ops.chmod_permission = __ccs_chmod_permission; + ccsecurity_ops.chown_permission = __ccs_chown_permission; +#ifdef CONFIG_CCSECURITY_FILE_GETATTR + ccsecurity_ops.getattr_permission = __ccs_getattr_permission; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + ccsecurity_ops.pivot_root_permission = __ccs_pivot_root_permission; + ccsecurity_ops.chroot_permission = __ccs_chroot_permission; +#else + ccsecurity_ops.pivot_root_permission = ccs_old_pivot_root_permission; + ccsecurity_ops.chroot_permission = ccs_old_chroot_permission; +#endif + ccsecurity_ops.umount_permission = __ccs_umount_permission; + ccsecurity_ops.mknod_permission = __ccs_mknod_permission; + ccsecurity_ops.mkdir_permission = __ccs_mkdir_permission; + ccsecurity_ops.rmdir_permission = __ccs_rmdir_permission; + ccsecurity_ops.unlink_permission = __ccs_unlink_permission; + ccsecurity_ops.symlink_permission = __ccs_symlink_permission; + ccsecurity_ops.truncate_permission = __ccs_truncate_permission; + ccsecurity_ops.rename_permission = __ccs_rename_permission; + ccsecurity_ops.link_permission = __ccs_link_permission; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) + ccsecurity_ops.open_exec_permission = __ccs_open_exec_permission; + ccsecurity_ops.uselib_permission = __ccs_uselib_permission; +#endif +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18) || (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33) && defined(CONFIG_SYSCTL_SYSCALL)) + ccsecurity_ops.parse_table = __ccs_parse_table; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + ccsecurity_ops.mount_permission = __ccs_mount_permission; +#else + ccsecurity_ops.mount_permission = ccs_old_mount_permission; +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + ccsecurity_ops.socket_create_permission = + __ccs_socket_create_permission; +#endif +#ifdef CONFIG_CCSECURITY_NETWORK + ccsecurity_ops.socket_listen_permission = + __ccs_socket_listen_permission; + ccsecurity_ops.socket_connect_permission = + __ccs_socket_connect_permission; + ccsecurity_ops.socket_bind_permission = __ccs_socket_bind_permission; + ccsecurity_ops.socket_post_accept_permission = + __ccs_socket_post_accept_permission; + ccsecurity_ops.socket_sendmsg_permission = + __ccs_socket_sendmsg_permission; +#endif +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + ccsecurity_ops.socket_post_recvmsg_permission = + __ccs_socket_post_recvmsg_permission; +#endif +#ifdef CONFIG_CCSECURITY_IPC + ccsecurity_ops.kill_permission = ccs_signal_acl; + ccsecurity_ops.tgkill_permission = ccs_signal_acl0; + ccsecurity_ops.tkill_permission = ccs_signal_acl; + ccsecurity_ops.sigqueue_permission = ccs_signal_acl; + ccsecurity_ops.tgsigqueue_permission = ccs_signal_acl0; +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + ccsecurity_ops.capable = __ccs_capable; + ccsecurity_ops.ptrace_permission = __ccs_ptrace_permission; +#endif + ccsecurity_ops.search_binary_handler = __ccs_search_binary_handler; +} + +/** + * ccs_kern_path - Wrapper for kern_path(). + * + * @pathname: Pathname to resolve. Maybe NULL. + * @flags: Lookup flags. + * @path: Pointer to "struct path". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_kern_path(const char *pathname, int flags, struct path *path) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28) + if (!pathname || kern_path(pathname, flags, path)) + return -ENOENT; +#else + struct nameidata nd; + if (!pathname || path_lookup(pathname, flags, &nd)) + return -ENOENT; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + *path = nd.path; +#else + path->dentry = nd.dentry; + path->mnt = nd.mnt; +#endif +#endif + return 0; +} + +/** + * ccs_get_path - Get dentry/vfsmmount of a pathname. + * + * @pathname: The pathname to solve. Maybe NULL. + * @path: Pointer to "struct path". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_get_path(const char *pathname, struct path *path) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) + return ccs_kern_path(pathname, LOOKUP_FOLLOW, path); +#else + return ccs_kern_path(pathname, LOOKUP_FOLLOW | LOOKUP_POSITIVE, path); +#endif +} + +/** + * ccs_symlink_path - Get symlink's pathname. + * + * @pathname: The pathname to solve. Maybe NULL. + * @name: Pointer to "struct ccs_path_info". + * + * Returns 0 on success, negative value otherwise. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +static int ccs_symlink_path(const char *pathname, struct ccs_path_info *name) +{ + char *buf; + struct path path; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) + if (ccs_kern_path(pathname, 0, &path)) + return -ENOENT; +#else + if (ccs_kern_path(pathname, LOOKUP_POSITIVE, &path)) + return -ENOENT; +#endif + buf = ccs_realpath(&path); + path_put(&path); + if (buf) { + name->name = buf; + ccs_fill_path_info(name); + return 0; + } + return -ENOMEM; +} + +/** + * ccs_check_mount_acl - Check permission for path path path number operation. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool ccs_check_mount_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr) +{ + const struct ccs_mount_acl *acl = + container_of(ptr, typeof(*acl), head); + return ccs_compare_number_union(r->param.mount.flags, &acl->flags) && + ccs_compare_name_union(r->param.mount.type, &acl->fs_type) && + ccs_compare_name_union(r->param.mount.dir, &acl->dir_name) && + (!r->param.mount.need_dev || + ccs_compare_name_union(r->param.mount.dev, &acl->dev_name)); +} + +/** + * ccs_mount_acl - Check permission for mount() operation. + * + * @r: Pointer to "struct ccs_request_info". + * @dev_name: Name of device file. Maybe NULL. + * @dir: Pointer to "struct path". + * @type: Name of filesystem type. + * @flags: Mount options. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_mount_acl(struct ccs_request_info *r, const char *dev_name, + struct path *dir, const char *type, + unsigned long flags) +{ + struct ccs_obj_info obj = { }; + struct file_system_type *fstype = NULL; + const char *requested_type = NULL; + const char *requested_dir_name = NULL; + const char *requested_dev_name = NULL; + struct ccs_path_info rtype; + struct ccs_path_info rdev; + struct ccs_path_info rdir; + int need_dev = 0; + int error = -ENOMEM; + r->obj = &obj; + + /* Get fstype. */ + requested_type = ccs_encode(type); + if (!requested_type) + goto out; + rtype.name = requested_type; + ccs_fill_path_info(&rtype); + + /* Get mount point. */ + obj.path2 = *dir; + requested_dir_name = ccs_realpath(dir); + if (!requested_dir_name) { + error = -ENOMEM; + goto out; + } + rdir.name = requested_dir_name; + ccs_fill_path_info(&rdir); + + /* Compare fs name. */ + if (type == ccs_mounts[CCS_MOUNT_REMOUNT]) { + /* dev_name is ignored. */ + } else if (type == ccs_mounts[CCS_MOUNT_MAKE_UNBINDABLE] || + type == ccs_mounts[CCS_MOUNT_MAKE_PRIVATE] || + type == ccs_mounts[CCS_MOUNT_MAKE_SLAVE] || + type == ccs_mounts[CCS_MOUNT_MAKE_SHARED]) { + /* dev_name is ignored. */ + } else if (type == ccs_mounts[CCS_MOUNT_BIND] || + type == ccs_mounts[CCS_MOUNT_MOVE]) { + need_dev = -1; /* dev_name is a directory */ + } else { + fstype = get_fs_type(type); + if (!fstype) { + error = -ENODEV; + goto out; + } + if (fstype->fs_flags & FS_REQUIRES_DEV) + /* dev_name is a block device file. */ + need_dev = 1; + } + if (need_dev) { + /* Get mount point or device file. */ + if (ccs_get_path(dev_name, &obj.path1)) { + error = -ENOENT; + goto out; + } + requested_dev_name = ccs_realpath(&obj.path1); + if (!requested_dev_name) { + error = -ENOENT; + goto out; + } + } else { + /* Map dev_name to "" if no dev_name given. */ + if (!dev_name) + dev_name = ""; + requested_dev_name = ccs_encode(dev_name); + if (!requested_dev_name) { + error = -ENOMEM; + goto out; + } + } + rdev.name = requested_dev_name; + ccs_fill_path_info(&rdev); + r->param_type = CCS_TYPE_MOUNT_ACL; + r->param.mount.need_dev = need_dev; + r->param.mount.dev = &rdev; + r->param.mount.dir = &rdir; + r->param.mount.type = &rtype; + r->param.mount.flags = flags; + error = ccs_check_acl(r); +out: + kfree(requested_dev_name); + kfree(requested_dir_name); + if (fstype) + ccs_put_filesystem(fstype); + kfree(requested_type); + /* Drop refcount obtained by ccs_get_path(). */ + if (obj.path1.dentry) + path_put(&obj.path1); + return error; +} + +/** + * __ccs_mount_permission - Check permission for mount() operation. + * + * @dev_name: Name of device file. Maybe NULL. + * @path: Pointer to "struct path". + * @type: Name of filesystem type. Maybe NULL. + * @flags: Mount options. + * @data_page: Optional data. Maybe NULL. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_mount_permission(const char *dev_name, struct path *path, + const char *type, unsigned long flags, + void *data_page) +{ + struct ccs_request_info r; + int error = 0; + int idx; + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + if (flags & MS_REMOUNT) { + type = ccs_mounts[CCS_MOUNT_REMOUNT]; + flags &= ~MS_REMOUNT; + } else if (flags & MS_BIND) { + type = ccs_mounts[CCS_MOUNT_BIND]; + flags &= ~MS_BIND; + } else if (flags & MS_SHARED) { + if (flags & (MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE)) + return -EINVAL; + type = ccs_mounts[CCS_MOUNT_MAKE_SHARED]; + flags &= ~MS_SHARED; + } else if (flags & MS_PRIVATE) { + if (flags & (MS_SHARED | MS_SLAVE | MS_UNBINDABLE)) + return -EINVAL; + type = ccs_mounts[CCS_MOUNT_MAKE_PRIVATE]; + flags &= ~MS_PRIVATE; + } else if (flags & MS_SLAVE) { + if (flags & (MS_SHARED | MS_PRIVATE | MS_UNBINDABLE)) + return -EINVAL; + type = ccs_mounts[CCS_MOUNT_MAKE_SLAVE]; + flags &= ~MS_SLAVE; + } else if (flags & MS_UNBINDABLE) { + if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE)) + return -EINVAL; + type = ccs_mounts[CCS_MOUNT_MAKE_UNBINDABLE]; + flags &= ~MS_UNBINDABLE; + } else if (flags & MS_MOVE) { + type = ccs_mounts[CCS_MOUNT_MOVE]; + flags &= ~MS_MOVE; + } + if (!type) + type = ""; + idx = ccs_read_lock(); + if (ccs_init_request_info(&r, CCS_MAC_FILE_MOUNT) + != CCS_CONFIG_DISABLED) + error = ccs_mount_acl(&r, dev_name, path, type, flags); + ccs_read_unlock(idx); + return error; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24) + +/** + * ccs_old_mount_permission - Check permission for mount() operation. + * + * @dev_name: Name of device file. + * @nd: Pointer to "struct nameidata". + * @type: Name of filesystem type. Maybe NULL. + * @flags: Mount options. + * @data_page: Optional data. Maybe NULL. + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_old_mount_permission(const char *dev_name, struct nameidata *nd, + const char *type, unsigned long flags, + void *data_page) +{ + struct path path = { nd->mnt, nd->dentry }; + return __ccs_mount_permission(dev_name, &path, type, flags, data_page); +} + +#endif + +/** + * ccs_compare_number_union - Check whether a value matches "struct ccs_number_union" or not. + * + * @value: Number to check. + * @ptr: Pointer to "struct ccs_number_union". + * + * Returns true if @value matches @ptr, false otherwise. + */ +static bool ccs_compare_number_union(const unsigned long value, + const struct ccs_number_union *ptr) +{ + if (ptr->group) + return ccs_number_matches_group(value, value, ptr->group); + return value >= ptr->values[0] && value <= ptr->values[1]; +} + +/** + * ccs_compare_name_union - Check whether a name matches "struct ccs_name_union" or not. + * + * @name: Pointer to "struct ccs_path_info". + * @ptr: Pointer to "struct ccs_name_union". + * + * Returns "struct ccs_path_info" if @name matches @ptr, NULL otherwise. + */ +static const struct ccs_path_info *ccs_compare_name_union +(const struct ccs_path_info *name, const struct ccs_name_union *ptr) +{ + if (ptr->group) + return ccs_path_matches_group(name, ptr->group); + if (ccs_path_matches_pattern(name, ptr->filename)) + return ptr->filename; + return NULL; +} + +/** + * ccs_add_slash - Add trailing '/' if needed. + * + * @buf: Pointer to "struct ccs_path_info". + * + * Returns nothing. + * + * @buf must be generated by ccs_encode() because this function does not + * allocate memory for adding '/'. + */ +static void ccs_add_slash(struct ccs_path_info *buf) +{ + if (buf->is_dir) + return; + /* This is OK because ccs_encode() reserves space for appending "/". */ + strcat((char *) buf->name, "/"); + ccs_fill_path_info(buf); +} + +/** + * ccs_get_realpath - Get realpath. + * + * @buf: Pointer to "struct ccs_path_info". + * @path: Pointer to "struct path". @path->mnt may be NULL. + * + * Returns true on success, false otherwise. + */ +static bool ccs_get_realpath(struct ccs_path_info *buf, struct path *path) +{ + buf->name = ccs_realpath(path); + if (buf->name) { + ccs_fill_path_info(buf); + return true; + } + return false; +} + +/** + * ccs_check_path_acl - Check permission for path operation. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true if granted, false otherwise. + * + * To be able to use wildcard for domain transition, this function sets + * matching entry on success. Since the caller holds ccs_read_lock(), + * it is safe to set matching entry. + */ +static bool ccs_check_path_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr) +{ + const struct ccs_path_acl *acl = container_of(ptr, typeof(*acl), head); + if (ptr->perm & (1 << r->param.path.operation)) { + r->param.path.matched_path = + ccs_compare_name_union(r->param.path.filename, + &acl->name); + return r->param.path.matched_path != NULL; + } + return false; +} + +/** + * ccs_check_path_number_acl - Check permission for path number operation. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool ccs_check_path_number_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr) +{ + const struct ccs_path_number_acl *acl = + container_of(ptr, typeof(*acl), head); + return (ptr->perm & (1 << r->param.path_number.operation)) && + ccs_compare_number_union(r->param.path_number.number, + &acl->number) && + ccs_compare_name_union(r->param.path_number.filename, + &acl->name); +} + +/** + * ccs_check_path2_acl - Check permission for path path operation. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool ccs_check_path2_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr) +{ + const struct ccs_path2_acl *acl = + container_of(ptr, typeof(*acl), head); + return (ptr->perm & (1 << r->param.path2.operation)) && + ccs_compare_name_union(r->param.path2.filename1, &acl->name1) + && ccs_compare_name_union(r->param.path2.filename2, + &acl->name2); +} + +/** + * ccs_check_mkdev_acl - Check permission for path number number number operation. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool ccs_check_mkdev_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr) +{ + const struct ccs_mkdev_acl *acl = + container_of(ptr, typeof(*acl), head); + return (ptr->perm & (1 << r->param.mkdev.operation)) && + ccs_compare_number_union(r->param.mkdev.mode, &acl->mode) && + ccs_compare_number_union(r->param.mkdev.major, &acl->major) && + ccs_compare_number_union(r->param.mkdev.minor, &acl->minor) && + ccs_compare_name_union(r->param.mkdev.filename, &acl->name); +} + +/** + * ccs_path_permission - Check permission for path operation. + * + * @r: Pointer to "struct ccs_request_info". + * @operation: Type of operation. + * @filename: Filename to check. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_path_permission(struct ccs_request_info *r, u8 operation, + const struct ccs_path_info *filename) +{ + r->type = ccs_p2mac[operation]; + r->mode = ccs_get_mode(r->profile, r->type); + if (r->mode == CCS_CONFIG_DISABLED) + return 0; + r->param_type = CCS_TYPE_PATH_ACL; + r->param.path.filename = filename; + r->param.path.operation = operation; + return ccs_check_acl(r); +} + +/** + * ccs_execute_permission - Check permission for execute operation. + * + * @r: Pointer to "struct ccs_request_info". + * @filename: Filename to check. + * + * Returns 0 on success, CCS_RETRY_REQUEST on retry, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_execute_permission(struct ccs_request_info *r, + const struct ccs_path_info *filename) +{ + int error; + /* + * Unlike other permission checks, this check is done regardless of + * profile mode settings in order to check for domain transition + * preference. + */ + r->type = CCS_MAC_FILE_EXECUTE; + r->mode = ccs_get_mode(r->profile, r->type); + r->param_type = CCS_TYPE_PATH_ACL; + r->param.path.filename = filename; + r->param.path.operation = CCS_TYPE_EXECUTE; + error = ccs_check_acl(r); + r->ee->transition = r->matched_acl && r->matched_acl->cond && + r->matched_acl->cond->exec_transit ? + r->matched_acl->cond->transit : NULL; + return error; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32) + +/** + * __ccs_save_open_mode - Remember original flags passed to sys_open(). + * + * @mode: Flags passed to sys_open(). + * + * Returns nothing. + * + * TOMOYO does not check "file write" if open(path, O_TRUNC | O_RDONLY) was + * requested because write() is not permitted. Instead, TOMOYO checks + * "file truncate" if O_TRUNC is passed. + * + * TOMOYO does not check "file read" and "file write" if open(path, 3) was + * requested because read()/write() are not permitted. Instead, TOMOYO checks + * "file ioctl" when ioctl() is requested. + */ +static void __ccs_save_open_mode(int mode) +{ + if ((mode & 3) == 3) + ccs_current_security()->ccs_flags |= CCS_OPEN_FOR_IOCTL_ONLY; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 14) + /* O_TRUNC passes MAY_WRITE to ccs_open_permission(). */ + else if (!(mode & 3) && (mode & O_TRUNC)) + ccs_current_security()->ccs_flags |= + CCS_OPEN_FOR_READ_TRUNCATE; +#endif +} + +/** + * __ccs_clear_open_mode - Forget original flags passed to sys_open(). + * + * Returns nothing. + */ +static void __ccs_clear_open_mode(void) +{ + ccs_current_security()->ccs_flags &= ~(CCS_OPEN_FOR_IOCTL_ONLY | + CCS_OPEN_FOR_READ_TRUNCATE); +} + +#endif + +/** + * __ccs_open_permission - Check permission for "read" and "write". + * + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * @flag: Flags for open(). + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_open_permission(struct dentry *dentry, struct vfsmount *mnt, + const int flag) +{ + struct ccs_request_info r; + struct ccs_obj_info obj = { + .path1.dentry = dentry, + .path1.mnt = mnt, + }; + const u32 ccs_flags = ccs_current_flags(); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) + const u8 acc_mode = (flag & 3) == 3 ? 0 : ACC_MODE(flag); +#else + const u8 acc_mode = (ccs_flags & CCS_OPEN_FOR_IOCTL_ONLY) ? 0 : +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 14) + (ccs_flags & CCS_OPEN_FOR_READ_TRUNCATE) ? 4 : +#endif + ACC_MODE(flag); +#endif + int error = 0; + struct ccs_path_info buf; + int idx; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) + if (current->in_execve && !(ccs_flags & CCS_TASK_IS_IN_EXECVE)) + return 0; +#endif +#ifndef CONFIG_CCSECURITY_FILE_READDIR +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) + if (d_is_dir(dentry)) + return 0; +#else + if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) + return 0; +#endif +#endif + buf.name = NULL; + r.mode = CCS_CONFIG_DISABLED; + idx = ccs_read_lock(); + if (acc_mode && ccs_init_request_info(&r, CCS_MAC_FILE_OPEN) + != CCS_CONFIG_DISABLED) { + if (!ccs_get_realpath(&buf, &obj.path1)) { + error = -ENOMEM; + goto out; + } + r.obj = &obj; + if (acc_mode & MAY_READ) + error = ccs_path_permission(&r, CCS_TYPE_READ, &buf); + if (!error && (acc_mode & MAY_WRITE)) + error = ccs_path_permission(&r, (flag & O_APPEND) ? + CCS_TYPE_APPEND : + CCS_TYPE_WRITE, &buf); + } +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32) + if (!error && (flag & O_TRUNC) && + ccs_init_request_info(&r, CCS_MAC_FILE_TRUNCATE) + != CCS_CONFIG_DISABLED) { + if (!buf.name && !ccs_get_realpath(&buf, &obj.path1)) { + error = -ENOMEM; + goto out; + } + r.obj = &obj; + error = ccs_path_permission(&r, CCS_TYPE_TRUNCATE, &buf); + } +#endif +out: + kfree(buf.name); + ccs_read_unlock(idx); + if (r.mode != CCS_CONFIG_ENFORCING) + error = 0; + return error; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) + +/** + * ccs_new_open_permission - Check permission for "read" and "write". + * + * @filp: Pointer to "struct file". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_new_open_permission(struct file *filp) +{ + return __ccs_open_permission(filp->f_path.dentry, filp->f_path.mnt, + filp->f_flags); +} + +#endif + +/** + * ccs_path_perm - Check permission for "unlink", "rmdir", "truncate", "symlink", "append", "getattr", "chroot" and "unmount". + * + * @operation: Type of operation. + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * @target: Symlink's target if @operation is CCS_TYPE_SYMLINK, + * NULL otherwise. + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_path_perm(const u8 operation, struct dentry *dentry, + struct vfsmount *mnt, const char *target) +{ + struct ccs_request_info r; + struct ccs_obj_info obj = { + .path1.dentry = dentry, + .path1.mnt = mnt, + }; + int error = 0; + struct ccs_path_info buf; + bool is_enforce = false; + struct ccs_path_info symlink_target; + int idx; + buf.name = NULL; + symlink_target.name = NULL; + idx = ccs_read_lock(); + if (ccs_init_request_info(&r, ccs_p2mac[operation]) + == CCS_CONFIG_DISABLED) + goto out; + is_enforce = (r.mode == CCS_CONFIG_ENFORCING); + error = -ENOMEM; + if (!ccs_get_realpath(&buf, &obj.path1)) + goto out; + r.obj = &obj; + switch (operation) { + case CCS_TYPE_RMDIR: + case CCS_TYPE_CHROOT: + ccs_add_slash(&buf); + break; + case CCS_TYPE_SYMLINK: + symlink_target.name = ccs_encode(target); + if (!symlink_target.name) + goto out; + ccs_fill_path_info(&symlink_target); + obj.symlink_target = &symlink_target; + break; + } + error = ccs_path_permission(&r, operation, &buf); + if (operation == CCS_TYPE_SYMLINK) + kfree(symlink_target.name); +out: + kfree(buf.name); + ccs_read_unlock(idx); + if (!is_enforce) + error = 0; + return error; +} + +/** + * ccs_mkdev_perm - Check permission for "mkblock" and "mkchar". + * + * @operation: Type of operation. (CCS_TYPE_MKCHAR or CCS_TYPE_MKBLOCK) + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * @mode: Create mode. + * @dev: Device number. + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_mkdev_perm(const u8 operation, struct dentry *dentry, + struct vfsmount *mnt, const unsigned int mode, + unsigned int dev) +{ + struct ccs_request_info r; + struct ccs_obj_info obj = { + .path1.dentry = dentry, + .path1.mnt = mnt, + }; + int error = 0; + struct ccs_path_info buf; + bool is_enforce = false; + int idx; + idx = ccs_read_lock(); + if (ccs_init_request_info(&r, ccs_pnnn2mac[operation]) + == CCS_CONFIG_DISABLED) + goto out; + is_enforce = (r.mode == CCS_CONFIG_ENFORCING); + error = -EPERM; + if (!capable(CAP_MKNOD)) + goto out; + error = -ENOMEM; + if (!ccs_get_realpath(&buf, &obj.path1)) + goto out; + r.obj = &obj; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) + dev = new_decode_dev(dev); +#endif + r.param_type = CCS_TYPE_MKDEV_ACL; + r.param.mkdev.filename = &buf; + r.param.mkdev.operation = operation; + r.param.mkdev.mode = mode; + r.param.mkdev.major = MAJOR(dev); + r.param.mkdev.minor = MINOR(dev); + error = ccs_check_acl(&r); + kfree(buf.name); +out: + ccs_read_unlock(idx); + if (!is_enforce) + error = 0; + return error; +} + +/** + * ccs_path2_perm - Check permission for "rename", "link" and "pivot_root". + * + * @operation: Type of operation. + * @dentry1: Pointer to "struct dentry". + * @mnt1: Pointer to "struct vfsmount". Maybe NULL. + * @dentry2: Pointer to "struct dentry". + * @mnt2: Pointer to "struct vfsmount". Maybe NULL. + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_path2_perm(const u8 operation, struct dentry *dentry1, + struct vfsmount *mnt1, struct dentry *dentry2, + struct vfsmount *mnt2) +{ + struct ccs_request_info r; + int error = 0; + struct ccs_path_info buf1; + struct ccs_path_info buf2; + bool is_enforce = false; + struct ccs_obj_info obj = { + .path1.dentry = dentry1, + .path1.mnt = mnt1, + .path2.dentry = dentry2, + .path2.mnt = mnt2, + }; + int idx; + buf1.name = NULL; + buf2.name = NULL; + idx = ccs_read_lock(); + if (ccs_init_request_info(&r, ccs_pp2mac[operation]) + == CCS_CONFIG_DISABLED) + goto out; + is_enforce = (r.mode == CCS_CONFIG_ENFORCING); + error = -ENOMEM; + if (!ccs_get_realpath(&buf1, &obj.path1) || + !ccs_get_realpath(&buf2, &obj.path2)) + goto out; + switch (operation) { + case CCS_TYPE_RENAME: + case CCS_TYPE_LINK: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) + if (!d_is_dir(dentry1)) + break; +#else + if (!dentry1->d_inode || !S_ISDIR(dentry1->d_inode->i_mode)) + break; +#endif + /* fall through */ + case CCS_TYPE_PIVOT_ROOT: + ccs_add_slash(&buf1); + ccs_add_slash(&buf2); + break; + } + r.obj = &obj; + r.param_type = CCS_TYPE_PATH2_ACL; + r.param.path2.operation = operation; + r.param.path2.filename1 = &buf1; + r.param.path2.filename2 = &buf2; + error = ccs_check_acl(&r); +out: + kfree(buf1.name); + kfree(buf2.name); + ccs_read_unlock(idx); + if (!is_enforce) + error = 0; + return error; +} + +/** + * ccs_path_number_perm - Check permission for "create", "mkdir", "mkfifo", "mksock", "ioctl", "chmod", "chown", "chgrp". + * + * @type: Type of operation. + * @dentry: Pointer to "struct dentry". + * @vfsmnt: Pointer to "struct vfsmount". Maybe NULL. + * @number: Number. + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_path_number_perm(const u8 type, struct dentry *dentry, + struct vfsmount *vfsmnt, unsigned long number) +{ + struct ccs_request_info r; + struct ccs_obj_info obj = { + .path1.dentry = dentry, + .path1.mnt = vfsmnt, + }; + int error = 0; + struct ccs_path_info buf; + int idx; + if (!dentry) + return 0; + idx = ccs_read_lock(); + if (ccs_init_request_info(&r, ccs_pn2mac[type]) == CCS_CONFIG_DISABLED) + goto out; + error = -ENOMEM; + if (!ccs_get_realpath(&buf, &obj.path1)) + goto out; + r.obj = &obj; + if (type == CCS_TYPE_MKDIR) + ccs_add_slash(&buf); + r.param_type = CCS_TYPE_PATH_NUMBER_ACL; + r.param.path_number.operation = type; + r.param.path_number.filename = &buf; + r.param.path_number.number = number; + error = ccs_check_acl(&r); + kfree(buf.name); +out: + ccs_read_unlock(idx); + if (r.mode != CCS_CONFIG_ENFORCING) + error = 0; + return error; +} + +/** + * __ccs_ioctl_permission - Check permission for "ioctl". + * + * @filp: Pointer to "struct file". + * @cmd: Ioctl command number. + * @arg: Param for @cmd. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_ioctl_permission(struct file *filp, unsigned int cmd, + unsigned long arg) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) + return ccs_path_number_perm(CCS_TYPE_IOCTL, filp->f_path.dentry, + filp->f_path.mnt, cmd); +#else + return ccs_path_number_perm(CCS_TYPE_IOCTL, filp->f_dentry, + filp->f_vfsmnt, cmd); +#endif +} + +/** + * __ccs_chmod_permission - Check permission for "chmod". + * + * @dentry: Pointer to "struct dentry". + * @vfsmnt: Pointer to "struct vfsmount". Maybe NULL. + * @mode: Mode. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_chmod_permission(struct dentry *dentry, + struct vfsmount *vfsmnt, mode_t mode) +{ + return ccs_path_number_perm(CCS_TYPE_CHMOD, dentry, vfsmnt, + mode & S_IALLUGO); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + +/** + * __ccs_chown_permission - Check permission for "chown/chgrp". + * + * @dentry: Pointer to "struct dentry". + * @vfsmnt: Pointer to "struct vfsmount". Maybe NULL. + * @user: User ID. + * @group: Group ID. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_chown_permission(struct dentry *dentry, + struct vfsmount *vfsmnt, kuid_t user, + kgid_t group) +{ + int error = 0; + if (uid_valid(user)) + error = ccs_path_number_perm(CCS_TYPE_CHOWN, dentry, vfsmnt, + from_kuid(&init_user_ns, user)); + if (!error && gid_valid(group)) + error = ccs_path_number_perm(CCS_TYPE_CHGRP, dentry, vfsmnt, + from_kgid(&init_user_ns, group)); + return error; +} + +#else + +/** + * __ccs_chown_permission - Check permission for "chown/chgrp". + * + * @dentry: Pointer to "struct dentry". + * @vfsmnt: Pointer to "struct vfsmount". Maybe NULL. + * @user: User ID. + * @group: Group ID. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_chown_permission(struct dentry *dentry, + struct vfsmount *vfsmnt, uid_t user, + gid_t group) +{ + int error = 0; + if (user == (uid_t) -1 && group == (gid_t) -1) + return 0; + if (user != (uid_t) -1) + error = ccs_path_number_perm(CCS_TYPE_CHOWN, dentry, vfsmnt, + user); + if (!error && group != (gid_t) -1) + error = ccs_path_number_perm(CCS_TYPE_CHGRP, dentry, vfsmnt, + group); + return error; +} + +#endif + +/** + * __ccs_fcntl_permission - Check permission for changing O_APPEND flag. + * + * @file: Pointer to "struct file". + * @cmd: Command number. + * @arg: Value for @cmd. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_fcntl_permission(struct file *file, unsigned int cmd, + unsigned long arg) +{ + if (!(cmd == F_SETFL && ((arg ^ file->f_flags) & O_APPEND))) + return 0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) + return __ccs_open_permission(file->f_path.dentry, file->f_path.mnt, + O_WRONLY | (arg & O_APPEND)); +#elif defined(RHEL_MAJOR) && RHEL_MAJOR == 6 + return __ccs_open_permission(file->f_dentry, file->f_vfsmnt, + O_WRONLY | (arg & O_APPEND)); +#else + return __ccs_open_permission(file->f_dentry, file->f_vfsmnt, + (O_WRONLY + 1) | (arg & O_APPEND)); +#endif +} + +/** + * __ccs_pivot_root_permission - Check permission for pivot_root(). + * + * @old_path: Pointer to "struct path". + * @new_path: Pointer to "struct path". + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_pivot_root_permission(struct path *old_path, + struct path *new_path) +{ + return ccs_path2_perm(CCS_TYPE_PIVOT_ROOT, new_path->dentry, + new_path->mnt, old_path->dentry, old_path->mnt); +} + +/** + * __ccs_chroot_permission - Check permission for chroot(). + * + * @path: Pointer to "struct path". + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_chroot_permission(struct path *path) +{ + return ccs_path_perm(CCS_TYPE_CHROOT, path->dentry, path->mnt, NULL); +} + +/** + * __ccs_umount_permission - Check permission for unmount. + * + * @mnt: Pointer to "struct vfsmount". + * @flags: Unused. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_umount_permission(struct vfsmount *mnt, int flags) +{ + return ccs_path_perm(CCS_TYPE_UMOUNT, mnt->mnt_root, mnt, NULL); +} + +/** + * __ccs_mknod_permission - Check permission for vfs_mknod(). + * + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * @mode: Device type and permission. + * @dev: Device number for block or character device. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_mknod_permission(struct dentry *dentry, struct vfsmount *mnt, + const unsigned int mode, unsigned int dev) +{ + int error = 0; + const unsigned int perm = mode & S_IALLUGO; + switch (mode & S_IFMT) { + case S_IFCHR: + error = ccs_mkdev_perm(CCS_TYPE_MKCHAR, dentry, mnt, perm, + dev); + break; + case S_IFBLK: + error = ccs_mkdev_perm(CCS_TYPE_MKBLOCK, dentry, mnt, perm, + dev); + break; + case S_IFIFO: + error = ccs_path_number_perm(CCS_TYPE_MKFIFO, dentry, mnt, + perm); + break; + case S_IFSOCK: + error = ccs_path_number_perm(CCS_TYPE_MKSOCK, dentry, mnt, + perm); + break; + case 0: + case S_IFREG: + error = ccs_path_number_perm(CCS_TYPE_CREATE, dentry, mnt, + perm); + break; + } + return error; +} + +/** + * __ccs_mkdir_permission - Check permission for vfs_mkdir(). + * + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * @mode: Create mode. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_mkdir_permission(struct dentry *dentry, struct vfsmount *mnt, + unsigned int mode) +{ + return ccs_path_number_perm(CCS_TYPE_MKDIR, dentry, mnt, mode); +} + +/** + * __ccs_rmdir_permission - Check permission for vfs_rmdir(). + * + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_rmdir_permission(struct dentry *dentry, struct vfsmount *mnt) +{ + return ccs_path_perm(CCS_TYPE_RMDIR, dentry, mnt, NULL); +} + +/** + * __ccs_unlink_permission - Check permission for vfs_unlink(). + * + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_unlink_permission(struct dentry *dentry, struct vfsmount *mnt) +{ + return ccs_path_perm(CCS_TYPE_UNLINK, dentry, mnt, NULL); +} + +#ifdef CONFIG_CCSECURITY_FILE_GETATTR + +/** + * __ccs_getattr_permission - Check permission for vfs_getattr(). + * + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * @dentry: Pointer to "struct dentry". + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_getattr_permission(struct vfsmount *mnt, + struct dentry *dentry) +{ + return ccs_path_perm(CCS_TYPE_GETATTR, dentry, mnt, NULL); +} + +#endif + +/** + * __ccs_symlink_permission - Check permission for vfs_symlink(). + * + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * @from: Content of symlink. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_symlink_permission(struct dentry *dentry, + struct vfsmount *mnt, const char *from) +{ + return ccs_path_perm(CCS_TYPE_SYMLINK, dentry, mnt, from); +} + +/** + * __ccs_truncate_permission - Check permission for notify_change(). + * + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_truncate_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + return ccs_path_perm(CCS_TYPE_TRUNCATE, dentry, mnt, NULL); +} + +/** + * __ccs_rename_permission - Check permission for vfs_rename(). + * + * @old_dentry: Pointer to "struct dentry". + * @new_dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_rename_permission(struct dentry *old_dentry, + struct dentry *new_dentry, + struct vfsmount *mnt) +{ + return ccs_path2_perm(CCS_TYPE_RENAME, old_dentry, mnt, new_dentry, + mnt); +} + +/** + * __ccs_link_permission - Check permission for vfs_link(). + * + * @old_dentry: Pointer to "struct dentry". + * @new_dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". Maybe NULL. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_link_permission(struct dentry *old_dentry, + struct dentry *new_dentry, + struct vfsmount *mnt) +{ + return ccs_path2_perm(CCS_TYPE_LINK, old_dentry, mnt, new_dentry, mnt); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) + +/** + * __ccs_open_exec_permission - Check permission for open_exec(). + * + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_open_exec_permission(struct dentry *dentry, + struct vfsmount *mnt) +{ + return (ccs_current_flags() & CCS_TASK_IS_IN_EXECVE) ? + __ccs_open_permission(dentry, mnt, O_RDONLY + 1) : 0; +} + +/** + * __ccs_uselib_permission - Check permission for sys_uselib(). + * + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_uselib_permission(struct dentry *dentry, struct vfsmount *mnt) +{ + return __ccs_open_permission(dentry, mnt, O_RDONLY + 1); +} + +#endif + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18) || (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33) && defined(CONFIG_SYSCTL_SYSCALL)) + +/** + * __ccs_parse_table - Check permission for parse_table(). + * + * @name: Pointer to "int __user". + * @nlen: Number of elements in @name. + * @oldval: Pointer to "void __user". + * @newval: Pointer to "void __user". + * @table: Pointer to "struct ctl_table". + * + * Returns 0 on success, negative value otherwise. + * + * Note that this function is racy because this function checks values in + * userspace memory which could be changed after permission check. + */ +static int __ccs_parse_table(int __user *name, int nlen, void __user *oldval, + void __user *newval, struct ctl_table *table) +{ + int n; + int error = -ENOMEM; + int op = 0; + struct ccs_path_info buf; + char *buffer = NULL; + struct ccs_request_info r; + int idx; + if (oldval) + op |= 004; + if (newval) + op |= 002; + if (!op) /* Neither read nor write */ + return 0; + idx = ccs_read_lock(); + if (ccs_init_request_info(&r, CCS_MAC_FILE_OPEN) + == CCS_CONFIG_DISABLED) { + error = 0; + goto out; + } + buffer = kmalloc(PAGE_SIZE, CCS_GFP_FLAGS); + if (!buffer) + goto out; + snprintf(buffer, PAGE_SIZE - 1, "proc:/sys"); +repeat: + if (!nlen) { + error = -ENOTDIR; + goto out; + } + if (get_user(n, name)) { + error = -EFAULT; + goto out; + } + for ( ; table->ctl_name +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 21) + || table->procname +#endif + ; table++) { + int pos; + const char *cp; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 21) + if (n != table->ctl_name && table->ctl_name != CTL_ANY) + continue; +#else + if (!n || n != table->ctl_name) + continue; +#endif + pos = strlen(buffer); + cp = table->procname; + error = -ENOMEM; + if (cp) { + int len = strlen(cp); + if (len + 2 > PAGE_SIZE - 1) + goto out; + buffer[pos++] = '/'; + memmove(buffer + pos, cp, len + 1); + } else { + /* Assume nobody assigns "=\$=" for procname. */ + snprintf(buffer + pos, PAGE_SIZE - pos - 1, + "/=%d=", table->ctl_name); + if (!memchr(buffer, '\0', PAGE_SIZE - 2)) + goto out; + } + if (!table->child) + goto no_child; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 21) + if (!table->strategy) + goto no_strategy; + /* printk("sysctl='%s'\n", buffer); */ + buf.name = ccs_encode(buffer); + if (!buf.name) + goto out; + ccs_fill_path_info(&buf); + if (op & MAY_READ) + error = ccs_path_permission(&r, CCS_TYPE_READ, &buf); + else + error = 0; + if (!error && (op & MAY_WRITE)) + error = ccs_path_permission(&r, CCS_TYPE_WRITE, &buf); + kfree(buf.name); + if (error) + goto out; +no_strategy: +#endif + name++; + nlen--; + table = table->child; + goto repeat; +no_child: + /* printk("sysctl='%s'\n", buffer); */ + buf.name = ccs_encode(buffer); + if (!buf.name) + goto out; + ccs_fill_path_info(&buf); + if (op & MAY_READ) + error = ccs_path_permission(&r, CCS_TYPE_READ, &buf); + else + error = 0; + if (!error && (op & MAY_WRITE)) + error = ccs_path_permission(&r, CCS_TYPE_WRITE, &buf); + kfree(buf.name); + goto out; + } + error = -ENOTDIR; +out: + ccs_read_unlock(idx); + kfree(buffer); + return error; +} + +#endif + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24) + +/** + * ccs_old_pivot_root_permission - Check permission for pivot_root(). + * + * @old_nd: Pointer to "struct nameidata". + * @new_nd: Pointer to "struct nameidata". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_old_pivot_root_permission(struct nameidata *old_nd, + struct nameidata *new_nd) +{ + struct path old_path = { old_nd->mnt, old_nd->dentry }; + struct path new_path = { new_nd->mnt, new_nd->dentry }; + return __ccs_pivot_root_permission(&old_path, &new_path); +} + +/** + * ccs_old_chroot_permission - Check permission for chroot(). + * + * @nd: Pointer to "struct nameidata". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_old_chroot_permission(struct nameidata *nd) +{ + struct path path = { nd->mnt, nd->dentry }; + return __ccs_chroot_permission(&path); +} + +#endif + +#ifdef CONFIG_CCSECURITY_NETWORK + +/** + * ccs_address_matches_group - Check whether the given address matches members of the given address group. + * + * @is_ipv6: True if @address is an IPv6 address. + * @address: An IPv4 or IPv6 address. + * @group: Pointer to "struct ccs_address_group". + * + * Returns true if @address matches addresses in @group group, false otherwise. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_address_matches_group(const bool is_ipv6, const u32 *address, + const struct ccs_group *group) +{ + struct ccs_address_group *member; + bool matched = false; + const u8 size = is_ipv6 ? 16 : 4; + list_for_each_entry_srcu(member, &group->member_list, head.list, + &ccs_ss) { + if (member->head.is_deleted) + continue; + if (member->address.is_ipv6 != is_ipv6) + continue; + if (memcmp(&member->address.ip[0], address, size) > 0 || + memcmp(address, &member->address.ip[1], size) > 0) + continue; + matched = true; + break; + } + return matched; +} + +/** + * ccs_check_inet_acl - Check permission for inet domain socket operation. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool ccs_check_inet_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr) +{ + const struct ccs_inet_acl *acl = container_of(ptr, typeof(*acl), head); + const u8 size = r->param.inet_network.is_ipv6 ? 16 : 4; + if (!(ptr->perm & (1 << r->param.inet_network.operation)) || + !ccs_compare_number_union(r->param.inet_network.port, &acl->port)) + return false; + if (acl->address.group) + return ccs_address_matches_group(r->param.inet_network.is_ipv6, + r->param.inet_network.address, + acl->address.group); + return acl->address.is_ipv6 == r->param.inet_network.is_ipv6 && + memcmp(&acl->address.ip[0], + r->param.inet_network.address, size) <= 0 && + memcmp(r->param.inet_network.address, + &acl->address.ip[1], size) <= 0; +} + +/** + * ccs_check_unix_acl - Check permission for unix domain socket operation. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool ccs_check_unix_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr) +{ + const struct ccs_unix_acl *acl = container_of(ptr, typeof(*acl), head); + return (ptr->perm & (1 << r->param.unix_network.operation)) && + ccs_compare_name_union(r->param.unix_network.address, + &acl->name); +} + +/** + * ccs_inet_entry - Check permission for INET network operation. + * + * @address: Pointer to "struct ccs_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_inet_entry(const struct ccs_addr_info *address) +{ + const int idx = ccs_read_lock(); + struct ccs_request_info r; + int error = 0; + const u8 type = ccs_inet2mac[address->protocol][address->operation]; + if (type && ccs_init_request_info(&r, type) != CCS_CONFIG_DISABLED) { + r.param_type = CCS_TYPE_INET_ACL; + r.param.inet_network.protocol = address->protocol; + r.param.inet_network.operation = address->operation; + r.param.inet_network.is_ipv6 = address->inet.is_ipv6; + r.param.inet_network.address = address->inet.address; + r.param.inet_network.port = ntohs(address->inet.port); + r.dont_sleep_on_enforce_error = + address->operation == CCS_NETWORK_ACCEPT +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + || address->operation == CCS_NETWORK_RECV +#endif + ; + error = ccs_check_acl(&r); + } + ccs_read_unlock(idx); + return error; +} + +/** + * ccs_check_inet_address - Check permission for inet domain socket's operation. + * + * @addr: Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * @port: Port number. + * @address: Pointer to "struct ccs_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_check_inet_address(const struct sockaddr *addr, + const unsigned int addr_len, const u16 port, + struct ccs_addr_info *address) +{ + struct ccs_inet_addr_info *i = &address->inet; + switch (addr->sa_family) { + case AF_INET6: + if (addr_len < SIN6_LEN_RFC2133) + goto skip; + i->is_ipv6 = true; + i->address = (u32 *) + ((struct sockaddr_in6 *) addr)->sin6_addr.s6_addr; + i->port = ((struct sockaddr_in6 *) addr)->sin6_port; + break; + case AF_INET: + if (addr_len < sizeof(struct sockaddr_in)) + goto skip; + i->is_ipv6 = false; + i->address = (u32 *) &((struct sockaddr_in *) addr)->sin_addr; + i->port = ((struct sockaddr_in *) addr)->sin_port; + break; + default: + goto skip; + } + if (address->protocol == SOCK_RAW) + i->port = htons(port); + return ccs_inet_entry(address); +skip: + return 0; +} + +/** + * ccs_unix_entry - Check permission for UNIX network operation. + * + * @address: Pointer to "struct ccs_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_unix_entry(const struct ccs_addr_info *address) +{ + const int idx = ccs_read_lock(); + struct ccs_request_info r; + int error = 0; + const u8 type = ccs_unix2mac[address->protocol][address->operation]; + if (type && ccs_init_request_info(&r, type) != CCS_CONFIG_DISABLED) { + char *buf = address->unix0.addr; + int len = address->unix0.addr_len - sizeof(sa_family_t); + if (len <= 0) { + buf = "anonymous"; + len = 9; + } else if (buf[0]) { + len = strnlen(buf, len); + } + buf = ccs_encode2(buf, len); + if (buf) { + struct ccs_path_info addr; + addr.name = buf; + ccs_fill_path_info(&addr); + r.param_type = CCS_TYPE_UNIX_ACL; + r.param.unix_network.protocol = address->protocol; + r.param.unix_network.operation = address->operation; + r.param.unix_network.address = &addr; + r.dont_sleep_on_enforce_error = + address->operation == CCS_NETWORK_ACCEPT +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + || address->operation == CCS_NETWORK_RECV +#endif + ; + error = ccs_check_acl(&r); + kfree(buf); + } else + error = -ENOMEM; + } + ccs_read_unlock(idx); + return error; +} + +/** + * ccs_check_unix_address - Check permission for unix domain socket's operation. + * + * @addr: Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * @address: Pointer to "struct ccs_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_check_unix_address(struct sockaddr *addr, + const unsigned int addr_len, + struct ccs_addr_info *address) +{ + struct ccs_unix_addr_info *u = &address->unix0; + if (addr->sa_family != AF_UNIX) + return 0; + u->addr = ((struct sockaddr_un *) addr)->sun_path; + u->addr_len = addr_len; + return ccs_unix_entry(address); +} + +/** + * ccs_sock_family - Get socket's family. + * + * @sk: Pointer to "struct sock". + * + * Returns one of PF_INET, PF_INET6, PF_UNIX or 0. + */ +static u8 ccs_sock_family(struct sock *sk) +{ + u8 family; + if (ccs_kernel_service()) + return 0; + family = sk->sk_family; + switch (family) { + case PF_INET: + case PF_INET6: + case PF_UNIX: + return family; + default: + return 0; + } +} + +/** + * __ccs_socket_listen_permission - Check permission for listening a socket. + * + * @sock: Pointer to "struct socket". + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_socket_listen_permission(struct socket *sock) +{ + struct ccs_addr_info address; + const u8 family = ccs_sock_family(sock->sk); + const unsigned int type = sock->type; + struct sockaddr_storage addr; + int addr_len; + if (!family || (type != SOCK_STREAM && type != SOCK_SEQPACKET)) + return 0; + { + const int error = sock->ops->getname(sock, (struct sockaddr *) + &addr, &addr_len, 0); + if (error) + return error; + } + address.protocol = type; + address.operation = CCS_NETWORK_LISTEN; + if (family == PF_UNIX) + return ccs_check_unix_address((struct sockaddr *) &addr, + addr_len, &address); + return ccs_check_inet_address((struct sockaddr *) &addr, addr_len, 0, + &address); +} + +/** + * __ccs_socket_connect_permission - Check permission for setting the remote address of a socket. + * + * @sock: Pointer to "struct socket". + * @addr: Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_socket_connect_permission(struct socket *sock, + struct sockaddr *addr, int addr_len) +{ + struct ccs_addr_info address; + const u8 family = ccs_sock_family(sock->sk); + const unsigned int type = sock->type; + if (!family) + return 0; + address.protocol = type; + switch (type) { + case SOCK_DGRAM: + case SOCK_RAW: + address.operation = CCS_NETWORK_SEND; + break; + case SOCK_STREAM: + case SOCK_SEQPACKET: + address.operation = CCS_NETWORK_CONNECT; + break; + default: + return 0; + } + if (family == PF_UNIX) + return ccs_check_unix_address(addr, addr_len, &address); + return ccs_check_inet_address(addr, addr_len, sock->sk->sk_protocol, + &address); +} + +/** + * __ccs_socket_bind_permission - Check permission for setting the local address of a socket. + * + * @sock: Pointer to "struct socket". + * @addr: Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_socket_bind_permission(struct socket *sock, + struct sockaddr *addr, int addr_len) +{ + struct ccs_addr_info address; + const u8 family = ccs_sock_family(sock->sk); + const unsigned int type = sock->type; + if (!family) + return 0; + switch (type) { + case SOCK_STREAM: + case SOCK_DGRAM: + case SOCK_RAW: + case SOCK_SEQPACKET: + address.protocol = type; + address.operation = CCS_NETWORK_BIND; + break; + default: + return 0; + } + if (family == PF_UNIX) + return ccs_check_unix_address(addr, addr_len, &address); + return ccs_check_inet_address(addr, addr_len, sock->sk->sk_protocol, + &address); +} + +/** + * __ccs_socket_sendmsg_permission - Check permission for sending a datagram. + * + * @sock: Pointer to "struct socket". + * @msg: Pointer to "struct msghdr". + * @size: Unused. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_socket_sendmsg_permission(struct socket *sock, + struct msghdr *msg, int size) +{ + struct ccs_addr_info address; + const u8 family = ccs_sock_family(sock->sk); + const unsigned int type = sock->type; + if (!msg->msg_name || !family || + (type != SOCK_DGRAM && type != SOCK_RAW)) + return 0; + address.protocol = type; + address.operation = CCS_NETWORK_SEND; + if (family == PF_UNIX) + return ccs_check_unix_address((struct sockaddr *) + msg->msg_name, msg->msg_namelen, + &address); + return ccs_check_inet_address((struct sockaddr *) msg->msg_name, + msg->msg_namelen, sock->sk->sk_protocol, + &address); +} + +/** + * __ccs_socket_post_accept_permission - Check permission for accepting a socket. + * + * @sock: Pointer to "struct socket". + * @newsock: Pointer to "struct socket". + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_socket_post_accept_permission(struct socket *sock, + struct socket *newsock) +{ + struct ccs_addr_info address; + const u8 family = ccs_sock_family(sock->sk); + const unsigned int type = sock->type; + struct sockaddr_storage addr; + int addr_len; + if (!family || (type != SOCK_STREAM && type != SOCK_SEQPACKET)) + return 0; + { + const int error = newsock->ops->getname(newsock, + (struct sockaddr *) + &addr, &addr_len, 2); + if (error) + return error; + } + address.protocol = type; + address.operation = CCS_NETWORK_ACCEPT; + if (family == PF_UNIX) + return ccs_check_unix_address((struct sockaddr *) &addr, + addr_len, &address); + return ccs_check_inet_address((struct sockaddr *) &addr, addr_len, 0, + &address); +} + +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + +/** + * __ccs_socket_post_recvmsg_permission - Check permission for receiving a datagram. + * + * @sk: Pointer to "struct sock". + * @skb: Pointer to "struct sk_buff". + * @flags: Flags passed to skb_recv_datagram(). + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_socket_post_recvmsg_permission(struct sock *sk, + struct sk_buff *skb, int flags) +{ + struct ccs_addr_info address; + const u8 family = ccs_sock_family(sk); + const unsigned int type = sk->sk_type; + struct sockaddr_storage addr; + if (!family) + return 0; + switch (type) { + case SOCK_DGRAM: + case SOCK_RAW: + address.protocol = type; + break; + default: + return 0; + } + address.operation = CCS_NETWORK_RECV; + switch (family) { + case PF_INET6: + { + struct in6_addr *sin6 = (struct in6_addr *) &addr; + address.inet.is_ipv6 = true; + if (type == SOCK_DGRAM && + skb->protocol == htons(ETH_P_IP)) + ipv6_addr_set(sin6, 0, 0, htonl(0xffff), + ip_hdr(skb)->saddr); + else + *sin6 = ipv6_hdr(skb)->saddr; + break; + } + case PF_INET: + { + struct in_addr *sin4 = (struct in_addr *) &addr; + address.inet.is_ipv6 = false; + sin4->s_addr = ip_hdr(skb)->saddr; + break; + } + default: /* == PF_UNIX */ + { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) + struct unix_address *u = unix_sk(skb->sk)->addr; +#else + struct unix_address *u = + skb->sk->protinfo.af_unix.addr; +#endif + unsigned int addr_len; + if (u && u->len <= sizeof(addr)) { + addr_len = u->len; + memcpy(&addr, u->name, addr_len); + } else { + addr_len = 0; + addr.ss_family = AF_UNIX; + } + if (ccs_check_unix_address((struct sockaddr *) &addr, + addr_len, &address)) + goto out; + return 0; + } + } + address.inet.address = (u32 *) &addr; + if (type == SOCK_DGRAM) + address.inet.port = udp_hdr(skb)->source; + else + address.inet.port = htons(sk->sk_protocol); + if (ccs_inet_entry(&address)) + goto out; + return 0; +out: + /* + * Remove from queue if MSG_PEEK is used so that + * the head message from unwanted source in receive queue will not + * prevent the caller from picking up next message from wanted source + * when the caller is using MSG_PEEK flag for picking up. + */ + { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) + bool slow = false; + if (type == SOCK_DGRAM && family != PF_UNIX) + slow = lock_sock_fast(sk); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + if (type == SOCK_DGRAM && family != PF_UNIX) + lock_sock(sk); +#elif defined(RHEL_MAJOR) && RHEL_MAJOR == 5 && defined(RHEL_MINOR) && RHEL_MINOR >= 2 + if (type == SOCK_DGRAM && family != PF_UNIX) + lock_sock(sk); +#endif + skb_kill_datagram(sk, skb, flags); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) + if (type == SOCK_DGRAM && family != PF_UNIX) + unlock_sock_fast(sk, slow); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + if (type == SOCK_DGRAM && family != PF_UNIX) + release_sock(sk); +#elif defined(RHEL_MAJOR) && RHEL_MAJOR == 5 && defined(RHEL_MINOR) && RHEL_MINOR >= 2 + if (type == SOCK_DGRAM && family != PF_UNIX) + release_sock(sk); +#endif + } + return -EPERM; +} + +#endif + +#endif + +#if defined(CONFIG_CCSECURITY_CAPABILITY) || defined(CONFIG_CCSECURITY_NETWORK) + +/** + * ccs_kernel_service - Check whether I'm kernel service or not. + * + * Returns true if I'm kernel service, false otherwise. + */ +static bool ccs_kernel_service(void) +{ + /* Nothing to do if I am a kernel service. */ + return segment_eq(get_fs(), KERNEL_DS); +} + +#endif + +#ifdef CONFIG_CCSECURITY_CAPABILITY + +/** + * ccs_check_capability_acl - Check permission for capability operation. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool ccs_check_capability_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr) +{ + const struct ccs_capability_acl *acl = + container_of(ptr, typeof(*acl), head); + return acl->operation == r->param.capability.operation; +} + +/** + * ccs_capable - Check permission for capability. + * + * @operation: Type of operation. + * + * Returns true on success, false otherwise. + */ +static bool __ccs_capable(const u8 operation) +{ + struct ccs_request_info r; + int error = 0; + const int idx = ccs_read_lock(); + if (ccs_init_request_info(&r, ccs_c2mac[operation]) + != CCS_CONFIG_DISABLED) { + r.param_type = CCS_TYPE_CAPABILITY_ACL; + r.param.capability.operation = operation; + error = ccs_check_acl(&r); + } + ccs_read_unlock(idx); + return !error; +} + +/** + * __ccs_socket_create_permission - Check permission for creating a socket. + * + * @family: Protocol family. + * @type: Unused. + * @protocol: Unused. + * + * Returns 0 on success, negative value otherwise. + */ +static int __ccs_socket_create_permission(int family, int type, int protocol) +{ + if (ccs_kernel_service()) + return 0; + if (family == PF_PACKET && !ccs_capable(CCS_USE_PACKET_SOCKET)) + return -EPERM; + if (family == PF_ROUTE && !ccs_capable(CCS_USE_ROUTE_SOCKET)) + return -EPERM; + return 0; +} + +/** + * __ccs_ptrace_permission - Check permission for ptrace(). + * + * @request: Unused. + * @pid: Unused. + * + * Returns 0 on success, negative value otherwise. + * + * Since this function is called from location where it is permitted to sleep, + * it is racy to check target process's domainname anyway. Therefore, we don't + * use target process's domainname. + */ +static int __ccs_ptrace_permission(long request, long pid) +{ + return __ccs_capable(CCS_SYS_PTRACE) ? 0 : -EPERM; +} + +#endif + +#ifdef CONFIG_CCSECURITY_IPC + +/** + * ccs_check_signal_acl - Check permission for signal operation. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool ccs_check_signal_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr) +{ + const struct ccs_signal_acl *acl = + container_of(ptr, typeof(*acl), head); + if (ccs_compare_number_union(r->param.signal.sig, &acl->sig)) { + const int len = acl->domainname->total_len; + if (!strncmp(acl->domainname->name, + r->param.signal.dest_pattern, len)) { + switch (r->param.signal.dest_pattern[len]) { + case ' ': + case '\0': + return true; + } + } + } + return false; +} + +/** + * ccs_signal_acl2 - Check permission for signal. + * + * @sig: Signal number. + * @pid: Target's PID. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_signal_acl2(const int sig, const int pid) +{ + struct ccs_request_info r; + struct ccs_domain_info *dest = NULL; + const struct ccs_domain_info * const domain = ccs_current_domain(); + if (ccs_init_request_info(&r, CCS_MAC_SIGNAL) == CCS_CONFIG_DISABLED) + return 0; + if (!sig) + return 0; /* No check for NULL signal. */ + r.param_type = CCS_TYPE_SIGNAL_ACL; + r.param.signal.sig = sig; + r.param.signal.dest_pattern = domain->domainname->name; + r.granted = true; + if (ccs_sys_getpid() == pid) { + ccs_audit_log(&r); + return 0; /* No check for self process. */ + } + { /* Simplified checking. */ + struct task_struct *p = NULL; + ccs_tasklist_lock(); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + if (pid > 0) + p = ccsecurity_exports.find_task_by_vpid((pid_t) pid); + else if (pid == 0) + p = current; + else if (pid == -1) + dest = &ccs_kernel_domain; + else + p = ccsecurity_exports.find_task_by_vpid((pid_t) -pid); +#else + if (pid > 0) + p = find_task_by_pid((pid_t) pid); + else if (pid == 0) + p = current; + else if (pid == -1) + dest = &ccs_kernel_domain; + else + p = find_task_by_pid((pid_t) -pid); +#endif + if (p) + dest = ccs_task_domain(p); + ccs_tasklist_unlock(); + } + if (!dest) + return 0; /* I can't find destinatioin. */ + if (domain == dest) { + ccs_audit_log(&r); + return 0; /* No check for self domain. */ + } + r.param.signal.dest_pattern = dest->domainname->name; + return ccs_check_acl(&r); +} + +/** + * ccs_signal_acl - Check permission for signal. + * + * @pid: Target's PID. + * @sig: Signal number. + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_signal_acl(const int pid, const int sig) +{ + int error; + if (!sig) + error = 0; + else { + const int idx = ccs_read_lock(); + error = ccs_signal_acl2(sig, pid); + ccs_read_unlock(idx); + } + return error; +} + +/** + * ccs_signal_acl0 - Permission check for signal(). + * + * @tgid: Unused. + * @pid: Target's PID. + * @sig: Signal number. + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_signal_acl0(pid_t tgid, pid_t pid, int sig) +{ + return ccs_signal_acl(pid, sig); +} + +#endif + +#ifdef CONFIG_CCSECURITY_MISC + +/** + * ccs_check_env_acl - Check permission for environment variable's name. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool ccs_check_env_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr) +{ + const struct ccs_env_acl *acl = container_of(ptr, typeof(*acl), head); + return ccs_path_matches_pattern(r->param.environ.name, acl->env); +} + +/** + * ccs_env_perm - Check permission for environment variable's name. + * + * @r: Pointer to "struct ccs_request_info". + * @env: The name of environment variable. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_env_perm(struct ccs_request_info *r, const char *env) +{ + struct ccs_path_info environ; + if (!env || !*env) + return 0; + environ.name = env; + ccs_fill_path_info(&environ); + r->param_type = CCS_TYPE_ENV_ACL; + r->param.environ.name = &environ; + return ccs_check_acl(r); +} + +/** + * ccs_environ - Check permission for environment variable names. + * + * @ee: Pointer to "struct ccs_execve". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_environ(struct ccs_execve *ee) +{ + struct ccs_request_info *r = &ee->r; + struct linux_binprm *bprm = ee->bprm; + /* env_page.data is allocated by ccs_dump_page(). */ + struct ccs_page_dump env_page = { }; + char *arg_ptr; /* Size is CCS_EXEC_TMPSIZE bytes */ + int arg_len = 0; + unsigned long pos = bprm->p; + int offset = pos % PAGE_SIZE; + int argv_count = bprm->argc; + int envp_count = bprm->envc; + /* printk(KERN_DEBUG "start %d %d\n", argv_count, envp_count); */ + int error = -ENOMEM; + ee->r.type = CCS_MAC_ENVIRON; + ee->r.profile = ccs_current_domain()->profile; + ee->r.mode = ccs_get_mode(ee->r.profile, CCS_MAC_ENVIRON); + if (!r->mode || !envp_count) + return 0; + arg_ptr = kzalloc(CCS_EXEC_TMPSIZE, CCS_GFP_FLAGS); + if (!arg_ptr) + goto out; + while (error == -ENOMEM) { + if (!ccs_dump_page(bprm, pos, &env_page)) + goto out; + pos += PAGE_SIZE - offset; + /* Read. */ + while (argv_count && offset < PAGE_SIZE) { + if (!env_page.data[offset++]) + argv_count--; + } + if (argv_count) { + offset = 0; + continue; + } + while (offset < PAGE_SIZE) { + const unsigned char c = env_page.data[offset++]; + if (c && arg_len < CCS_EXEC_TMPSIZE - 10) { + if (c == '=') { + arg_ptr[arg_len++] = '\0'; + } else if (c == '\\') { + arg_ptr[arg_len++] = '\\'; + arg_ptr[arg_len++] = '\\'; + } else if (c > ' ' && c < 127) { + arg_ptr[arg_len++] = c; + } else { + arg_ptr[arg_len++] = '\\'; + arg_ptr[arg_len++] = (c >> 6) + '0'; + arg_ptr[arg_len++] + = ((c >> 3) & 7) + '0'; + arg_ptr[arg_len++] = (c & 7) + '0'; + } + } else { + arg_ptr[arg_len] = '\0'; + } + if (c) + continue; + if (ccs_env_perm(r, arg_ptr)) { + error = -EPERM; + break; + } + if (!--envp_count) { + error = 0; + break; + } + arg_len = 0; + } + offset = 0; + } +out: + if (r->mode != CCS_CONFIG_ENFORCING) + error = 0; + kfree(env_page.data); + kfree(arg_ptr); + return error; +} + +#endif + +/** + * ccs_argv - Check argv[] in "struct linux_binbrm". + * + * @index: Index number of @arg_ptr. + * @arg_ptr: Contents of argv[@index]. + * @argc: Length of @argv. + * @argv: Pointer to "struct ccs_argv". + * @checked: Set to true if @argv[@index] was found. + * + * Returns true on success, false otherwise. + */ +static bool ccs_argv(const unsigned int index, const char *arg_ptr, + const int argc, const struct ccs_argv *argv, + u8 *checked) +{ + int i; + struct ccs_path_info arg; + arg.name = arg_ptr; + for (i = 0; i < argc; argv++, checked++, i++) { + bool result; + if (index != argv->index) + continue; + *checked = 1; + ccs_fill_path_info(&arg); + result = ccs_path_matches_pattern(&arg, argv->value); + if (argv->is_not) + result = !result; + if (!result) + return false; + } + return true; +} + +/** + * ccs_envp - Check envp[] in "struct linux_binbrm". + * + * @env_name: The name of environment variable. + * @env_value: The value of environment variable. + * @envc: Length of @envp. + * @envp: Pointer to "struct ccs_envp". + * @checked: Set to true if @envp[@env_name] was found. + * + * Returns true on success, false otherwise. + */ +static bool ccs_envp(const char *env_name, const char *env_value, + const int envc, const struct ccs_envp *envp, + u8 *checked) +{ + int i; + struct ccs_path_info name; + struct ccs_path_info value; + name.name = env_name; + ccs_fill_path_info(&name); + value.name = env_value; + ccs_fill_path_info(&value); + for (i = 0; i < envc; envp++, checked++, i++) { + bool result; + if (!ccs_path_matches_pattern(&name, envp->name)) + continue; + *checked = 1; + if (envp->value) { + result = ccs_path_matches_pattern(&value, envp->value); + if (envp->is_not) + result = !result; + } else { + result = true; + if (!envp->is_not) + result = !result; + } + if (!result) + return false; + } + return true; +} + +/** + * ccs_scan_bprm - Scan "struct linux_binprm". + * + * @ee: Pointer to "struct ccs_execve". + * @argc: Length of @argc. + * @argv: Pointer to "struct ccs_argv". + * @envc: Length of @envp. + * @envp: Poiner to "struct ccs_envp". + * + * Returns true on success, false otherwise. + */ +static bool ccs_scan_bprm(struct ccs_execve *ee, + const u16 argc, const struct ccs_argv *argv, + const u16 envc, const struct ccs_envp *envp) +{ + struct linux_binprm *bprm = ee->bprm; + struct ccs_page_dump *dump = &ee->dump; + char *arg_ptr = ee->tmp; + int arg_len = 0; + unsigned long pos = bprm->p; + int offset = pos % PAGE_SIZE; + int argv_count = bprm->argc; + int envp_count = bprm->envc; + bool result = true; + u8 local_checked[32]; + u8 *checked; + if (argc + envc <= sizeof(local_checked)) { + checked = local_checked; + memset(local_checked, 0, sizeof(local_checked)); + } else { + checked = kzalloc(argc + envc, CCS_GFP_FLAGS); + if (!checked) + return false; + } + while (argv_count || envp_count) { + if (!ccs_dump_page(bprm, pos, dump)) { + result = false; + goto out; + } + pos += PAGE_SIZE - offset; + while (offset < PAGE_SIZE) { + /* Read. */ + const char *kaddr = dump->data; + const unsigned char c = kaddr[offset++]; + if (c && arg_len < CCS_EXEC_TMPSIZE - 10) { + if (c == '\\') { + arg_ptr[arg_len++] = '\\'; + arg_ptr[arg_len++] = '\\'; + } else if (c > ' ' && c < 127) { + arg_ptr[arg_len++] = c; + } else { + arg_ptr[arg_len++] = '\\'; + arg_ptr[arg_len++] = (c >> 6) + '0'; + arg_ptr[arg_len++] = + ((c >> 3) & 7) + '0'; + arg_ptr[arg_len++] = (c & 7) + '0'; + } + } else { + arg_ptr[arg_len] = '\0'; + } + if (c) + continue; + /* Check. */ + if (argv_count) { + if (!ccs_argv(bprm->argc - argv_count, + arg_ptr, argc, argv, + checked)) { + result = false; + break; + } + argv_count--; + } else if (envp_count) { + char *cp = strchr(arg_ptr, '='); + if (cp) { + *cp = '\0'; + if (!ccs_envp(arg_ptr, cp + 1, + envc, envp, + checked + argc)) { + result = false; + break; + } + } + envp_count--; + } else { + break; + } + arg_len = 0; + } + offset = 0; + if (!result) + break; + } +out: + if (result) { + int i; + /* Check not-yet-checked entries. */ + for (i = 0; i < argc; i++) { + if (checked[i]) + continue; + /* + * Return true only if all unchecked indexes in + * bprm->argv[] are not matched. + */ + if (argv[i].is_not) + continue; + result = false; + break; + } + for (i = 0; i < envc; envp++, i++) { + if (checked[argc + i]) + continue; + /* + * Return true only if all unchecked environ variables + * in bprm->envp[] are either undefined or not matched. + */ + if ((!envp->value && !envp->is_not) || + (envp->value && envp->is_not)) + continue; + result = false; + break; + } + } + if (checked != local_checked) + kfree(checked); + return result; +} + +/** + * ccs_scan_exec_realpath - Check "exec.realpath" parameter of "struct ccs_condition". + * + * @file: Pointer to "struct file". + * @ptr: Pointer to "struct ccs_name_union". + * @match: True if "exec.realpath=", false if "exec.realpath!=". + * + * Returns true on success, false otherwise. + */ +static bool ccs_scan_exec_realpath(struct file *file, + const struct ccs_name_union *ptr, + const bool match) +{ + bool result; + struct ccs_path_info exe; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) + struct path path; +#endif + if (!file) + return false; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) + exe.name = ccs_realpath(&file->f_path); +#else + path.mnt = file->f_vfsmnt; + path.dentry = file->f_dentry; + exe.name = ccs_realpath(&path); +#endif + if (!exe.name) + return false; + ccs_fill_path_info(&exe); + result = ccs_compare_name_union(&exe, ptr); + kfree(exe.name); + return result == match; +} + +/** + * ccs_get_attributes - Revalidate "struct inode". + * + * @obj: Pointer to "struct ccs_obj_info". + * + * Returns nothing. + */ +void ccs_get_attributes(struct ccs_obj_info *obj) +{ + u8 i; + struct dentry *dentry = NULL; + + for (i = 0; i < CCS_MAX_PATH_STAT; i++) { + struct inode *inode; + switch (i) { + case CCS_PATH1: + dentry = obj->path1.dentry; + if (!dentry) + continue; + break; + case CCS_PATH2: + dentry = obj->path2.dentry; + if (!dentry) + continue; + break; + default: + if (!dentry) + continue; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + spin_lock(&dcache_lock); + dentry = dget(dentry->d_parent); + spin_unlock(&dcache_lock); +#else + dentry = dget_parent(dentry); +#endif + break; + } + inode = dentry->d_inode; + if (inode) { + struct ccs_mini_stat *stat = &obj->stat[i]; + stat->uid = inode->i_uid; + stat->gid = inode->i_gid; + stat->ino = inode->i_ino; + stat->mode = inode->i_mode; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + stat->dev = inode->i_dev; +#else + stat->dev = inode->i_sb->s_dev; +#endif + stat->rdev = inode->i_rdev; + obj->stat_valid[i] = true; + } + if (i & 1) /* i == CCS_PATH1_PARENT || i == CCS_PATH2_PARENT */ + dput(dentry); + } +} + +/** + * ccs_condition - Check condition part. + * + * @r: Pointer to "struct ccs_request_info". + * @cond: Pointer to "struct ccs_condition". Maybe NULL. + * + * Returns true on success, false otherwise. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_condition(struct ccs_request_info *r, + const struct ccs_condition *cond) +{ + const u32 ccs_flags = ccs_current_flags(); + u32 i; + unsigned long min_v[2] = { 0, 0 }; + unsigned long max_v[2] = { 0, 0 }; + const struct ccs_condition_element *condp; + const struct ccs_number_union *numbers_p; + const struct ccs_name_union *names_p; + const struct ccs_argv *argv; + const struct ccs_envp *envp; + struct ccs_obj_info *obj; + u16 condc; + u16 argc; + u16 envc; + struct linux_binprm *bprm = NULL; + if (!cond) + return true; + condc = cond->condc; + argc = cond->argc; + envc = cond->envc; + obj = r->obj; + if (r->ee) + bprm = r->ee->bprm; + if (!bprm && (argc || envc)) + return false; + condp = (struct ccs_condition_element *) (cond + 1); + numbers_p = (const struct ccs_number_union *) (condp + condc); + names_p = (const struct ccs_name_union *) + (numbers_p + cond->numbers_count); + argv = (const struct ccs_argv *) (names_p + cond->names_count); + envp = (const struct ccs_envp *) (argv + argc); + for (i = 0; i < condc; i++) { + const bool match = condp->equals; + const u8 left = condp->left; + const u8 right = condp->right; + bool is_bitop[2] = { false, false }; + u8 j; + condp++; + /* Check argv[] and envp[] later. */ + if (left == CCS_ARGV_ENTRY || left == CCS_ENVP_ENTRY) + continue; + /* Check string expressions. */ + if (right == CCS_NAME_UNION) { + const struct ccs_name_union *ptr = names_p++; + switch (left) { + struct ccs_path_info *symlink; + struct ccs_execve *ee; + struct file *file; + case CCS_SYMLINK_TARGET: + symlink = obj ? obj->symlink_target : NULL; + if (!symlink || + !ccs_compare_name_union(symlink, ptr) + == match) + goto out; + break; + case CCS_EXEC_REALPATH: + ee = r->ee; + file = ee ? ee->bprm->file : NULL; + if (!ccs_scan_exec_realpath(file, ptr, match)) + goto out; + break; + } + continue; + } + /* Check numeric or bit-op expressions. */ + for (j = 0; j < 2; j++) { + const u8 index = j ? right : left; + unsigned long value = 0; + switch (index) { + case CCS_TASK_UID: + value = from_kuid(&init_user_ns, + current_uid()); + break; + case CCS_TASK_EUID: + value = from_kuid(&init_user_ns, + current_euid()); + break; + case CCS_TASK_SUID: + value = from_kuid(&init_user_ns, + current_suid()); + break; + case CCS_TASK_FSUID: + value = from_kuid(&init_user_ns, + current_fsuid()); + break; + case CCS_TASK_GID: + value = from_kgid(&init_user_ns, + current_gid()); + break; + case CCS_TASK_EGID: + value = from_kgid(&init_user_ns, + current_egid()); + break; + case CCS_TASK_SGID: + value = from_kgid(&init_user_ns, + current_sgid()); + break; + case CCS_TASK_FSGID: + value = from_kgid(&init_user_ns, + current_fsgid()); + break; + case CCS_TASK_PID: + value = ccs_sys_getpid(); + break; + case CCS_TASK_PPID: + value = ccs_sys_getppid(); + break; + case CCS_TYPE_IS_SOCKET: + value = S_IFSOCK; + break; + case CCS_TYPE_IS_SYMLINK: + value = S_IFLNK; + break; + case CCS_TYPE_IS_FILE: + value = S_IFREG; + break; + case CCS_TYPE_IS_BLOCK_DEV: + value = S_IFBLK; + break; + case CCS_TYPE_IS_DIRECTORY: + value = S_IFDIR; + break; + case CCS_TYPE_IS_CHAR_DEV: + value = S_IFCHR; + break; + case CCS_TYPE_IS_FIFO: + value = S_IFIFO; + break; + case CCS_MODE_SETUID: + value = S_ISUID; + break; + case CCS_MODE_SETGID: + value = S_ISGID; + break; + case CCS_MODE_STICKY: + value = S_ISVTX; + break; + case CCS_MODE_OWNER_READ: + value = S_IRUSR; + break; + case CCS_MODE_OWNER_WRITE: + value = S_IWUSR; + break; + case CCS_MODE_OWNER_EXECUTE: + value = S_IXUSR; + break; + case CCS_MODE_GROUP_READ: + value = S_IRGRP; + break; + case CCS_MODE_GROUP_WRITE: + value = S_IWGRP; + break; + case CCS_MODE_GROUP_EXECUTE: + value = S_IXGRP; + break; + case CCS_MODE_OTHERS_READ: + value = S_IROTH; + break; + case CCS_MODE_OTHERS_WRITE: + value = S_IWOTH; + break; + case CCS_MODE_OTHERS_EXECUTE: + value = S_IXOTH; + break; + case CCS_EXEC_ARGC: + if (!bprm) + goto out; + value = bprm->argc; + break; + case CCS_EXEC_ENVC: + if (!bprm) + goto out; + value = bprm->envc; + break; + case CCS_TASK_TYPE: + value = ((u8) ccs_flags) + & CCS_TASK_IS_EXECUTE_HANDLER; + break; + case CCS_TASK_EXECUTE_HANDLER: + value = CCS_TASK_IS_EXECUTE_HANDLER; + break; + case CCS_NUMBER_UNION: + /* Fetch values later. */ + break; + default: + if (!obj) + goto out; + if (!obj->validate_done) { + ccs_get_attributes(obj); + obj->validate_done = true; + } + { + u8 stat_index; + struct ccs_mini_stat *stat; + switch (index) { + case CCS_PATH1_UID: + case CCS_PATH1_GID: + case CCS_PATH1_INO: + case CCS_PATH1_MAJOR: + case CCS_PATH1_MINOR: + case CCS_PATH1_TYPE: + case CCS_PATH1_DEV_MAJOR: + case CCS_PATH1_DEV_MINOR: + case CCS_PATH1_PERM: + stat_index = CCS_PATH1; + break; + case CCS_PATH2_UID: + case CCS_PATH2_GID: + case CCS_PATH2_INO: + case CCS_PATH2_MAJOR: + case CCS_PATH2_MINOR: + case CCS_PATH2_TYPE: + case CCS_PATH2_DEV_MAJOR: + case CCS_PATH2_DEV_MINOR: + case CCS_PATH2_PERM: + stat_index = CCS_PATH2; + break; + case CCS_PATH1_PARENT_UID: + case CCS_PATH1_PARENT_GID: + case CCS_PATH1_PARENT_INO: + case CCS_PATH1_PARENT_PERM: + stat_index = CCS_PATH1_PARENT; + break; + case CCS_PATH2_PARENT_UID: + case CCS_PATH2_PARENT_GID: + case CCS_PATH2_PARENT_INO: + case CCS_PATH2_PARENT_PERM: + stat_index = CCS_PATH2_PARENT; + break; + default: + goto out; + } + if (!obj->stat_valid[stat_index]) + goto out; + stat = &obj->stat[stat_index]; + switch (index) { + case CCS_PATH1_UID: + case CCS_PATH2_UID: + case CCS_PATH1_PARENT_UID: + case CCS_PATH2_PARENT_UID: + value = from_kuid + (&init_user_ns, + stat->uid); + break; + case CCS_PATH1_GID: + case CCS_PATH2_GID: + case CCS_PATH1_PARENT_GID: + case CCS_PATH2_PARENT_GID: + value = from_kgid + (&init_user_ns, + stat->gid); + break; + case CCS_PATH1_INO: + case CCS_PATH2_INO: + case CCS_PATH1_PARENT_INO: + case CCS_PATH2_PARENT_INO: + value = stat->ino; + break; + case CCS_PATH1_MAJOR: + case CCS_PATH2_MAJOR: + value = MAJOR(stat->dev); + break; + case CCS_PATH1_MINOR: + case CCS_PATH2_MINOR: + value = MINOR(stat->dev); + break; + case CCS_PATH1_TYPE: + case CCS_PATH2_TYPE: + value = stat->mode & S_IFMT; + break; + case CCS_PATH1_DEV_MAJOR: + case CCS_PATH2_DEV_MAJOR: + value = MAJOR(stat->rdev); + break; + case CCS_PATH1_DEV_MINOR: + case CCS_PATH2_DEV_MINOR: + value = MINOR(stat->rdev); + break; + case CCS_PATH1_PERM: + case CCS_PATH2_PERM: + case CCS_PATH1_PARENT_PERM: + case CCS_PATH2_PARENT_PERM: + value = stat->mode & S_IALLUGO; + break; + } + } + break; + } + max_v[j] = value; + min_v[j] = value; + switch (index) { + case CCS_MODE_SETUID: + case CCS_MODE_SETGID: + case CCS_MODE_STICKY: + case CCS_MODE_OWNER_READ: + case CCS_MODE_OWNER_WRITE: + case CCS_MODE_OWNER_EXECUTE: + case CCS_MODE_GROUP_READ: + case CCS_MODE_GROUP_WRITE: + case CCS_MODE_GROUP_EXECUTE: + case CCS_MODE_OTHERS_READ: + case CCS_MODE_OTHERS_WRITE: + case CCS_MODE_OTHERS_EXECUTE: + is_bitop[j] = true; + } + } + if (left == CCS_NUMBER_UNION) { + /* Fetch values now. */ + const struct ccs_number_union *ptr = numbers_p++; + min_v[0] = ptr->values[0]; + max_v[0] = ptr->values[1]; + } + if (right == CCS_NUMBER_UNION) { + /* Fetch values now. */ + const struct ccs_number_union *ptr = numbers_p++; + if (ptr->group) { + if (ccs_number_matches_group(min_v[0], + max_v[0], + ptr->group) + == match) + continue; + } else { + if ((min_v[0] <= ptr->values[1] && + max_v[0] >= ptr->values[0]) == match) + continue; + } + goto out; + } + /* + * Bit operation is valid only when counterpart value + * represents permission. + */ + if (is_bitop[0] && is_bitop[1]) { + goto out; + } else if (is_bitop[0]) { + switch (right) { + case CCS_PATH1_PERM: + case CCS_PATH1_PARENT_PERM: + case CCS_PATH2_PERM: + case CCS_PATH2_PARENT_PERM: + if (!(max_v[0] & max_v[1]) == !match) + continue; + } + goto out; + } else if (is_bitop[1]) { + switch (left) { + case CCS_PATH1_PERM: + case CCS_PATH1_PARENT_PERM: + case CCS_PATH2_PERM: + case CCS_PATH2_PARENT_PERM: + if (!(max_v[0] & max_v[1]) == !match) + continue; + } + goto out; + } + /* Normal value range comparison. */ + if ((min_v[0] <= max_v[1] && max_v[0] >= min_v[1]) == match) + continue; +out: + return false; + } + /* Check argv[] and envp[] now. */ + if (r->ee && (argc || envc)) + return ccs_scan_bprm(r->ee, argc, argv, envc, envp); + return true; +} + +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + +/** + * ccs_check_task_acl - Check permission for task operation. + * + * @r: Pointer to "struct ccs_request_info". + * @ptr: Pointer to "struct ccs_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool ccs_check_task_acl(struct ccs_request_info *r, + const struct ccs_acl_info *ptr) +{ + const struct ccs_task_acl *acl = container_of(ptr, typeof(*acl), head); + return !ccs_pathcmp(r->param.task.domainname, acl->domainname); +} + +#endif + +/** + * ccs_init_request_info - Initialize "struct ccs_request_info" members. + * + * @r: Pointer to "struct ccs_request_info" to initialize. + * @index: Index number of functionality. + * + * Returns mode. + * + * "task auto_domain_transition" keyword is evaluated before returning mode for + * @index. If "task auto_domain_transition" keyword was specified and + * transition to that domain failed, the current thread will be killed by + * SIGKILL. Note that if current->pid == 1, sending SIGKILL won't work. + */ +int ccs_init_request_info(struct ccs_request_info *r, const u8 index) +{ +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + u8 i; + const char *buf; + for (i = 0; i < 255; i++) { + const u8 profile = ccs_current_domain()->profile; + memset(r, 0, sizeof(*r)); + r->profile = profile; + r->type = index; + r->mode = ccs_get_mode(profile, index); + r->param_type = CCS_TYPE_AUTO_TASK_ACL; + ccs_check_acl(r); + if (!r->granted) + return r->mode; + buf = container_of(r->matched_acl, typeof(struct ccs_task_acl), + head)->domainname->name; + if (!ccs_assign_domain(buf, true)) + break; + } + ccs_transition_failed(buf); + return CCS_CONFIG_DISABLED; +#else + const u8 profile = ccs_current_domain()->profile; + memset(r, 0, sizeof(*r)); + r->profile = profile; + r->type = index; + r->mode = ccs_get_mode(profile, index); + return r->mode; +#endif +} + +/** + * ccs_byte_range - Check whether the string is a \ooo style octal value. + * + * @str: Pointer to the string. + * + * Returns true if @str is a \ooo style octal value, false otherwise. + */ +static bool ccs_byte_range(const char *str) +{ + return *str >= '0' && *str++ <= '3' && + *str >= '0' && *str++ <= '7' && + *str >= '0' && *str <= '7'; +} + +/** + * ccs_decimal - Check whether the character is a decimal character. + * + * @c: The character to check. + * + * Returns true if @c is a decimal character, false otherwise. + */ +static bool ccs_decimal(const char c) +{ + return c >= '0' && c <= '9'; +} + +/** + * ccs_hexadecimal - Check whether the character is a hexadecimal character. + * + * @c: The character to check. + * + * Returns true if @c is a hexadecimal character, false otherwise. + */ +static bool ccs_hexadecimal(const char c) +{ + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'); +} + +/** + * ccs_alphabet_char - Check whether the character is an alphabet. + * + * @c: The character to check. + * + * Returns true if @c is an alphabet character, false otherwise. + */ +static bool ccs_alphabet_char(const char c) +{ + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} + +/** + * ccs_file_matches_pattern2 - Pattern matching without '/' character and "\-" pattern. + * + * @filename: The start of string to check. + * @filename_end: The end of string to check. + * @pattern: The start of pattern to compare. + * @pattern_end: The end of pattern to compare. + * + * Returns true if @filename matches @pattern, false otherwise. + */ +static bool ccs_file_matches_pattern2(const char *filename, + const char *filename_end, + const char *pattern, + const char *pattern_end) +{ + while (filename < filename_end && pattern < pattern_end) { + char c; + if (*pattern != '\\') { + if (*filename++ != *pattern++) + return false; + continue; + } + c = *filename; + pattern++; + switch (*pattern) { + int i; + int j; + case '?': + if (c == '/') { + return false; + } else if (c == '\\') { + if (filename[1] == '\\') + filename++; + else if (ccs_byte_range(filename + 1)) + filename += 3; + else + return false; + } + break; + case '\\': + if (c != '\\') + return false; + if (*++filename != '\\') + return false; + break; + case '+': + if (!ccs_decimal(c)) + return false; + break; + case 'x': + if (!ccs_hexadecimal(c)) + return false; + break; + case 'a': + if (!ccs_alphabet_char(c)) + return false; + break; + case '0': + case '1': + case '2': + case '3': + if (c == '\\' && ccs_byte_range(filename + 1) + && !strncmp(filename + 1, pattern, 3)) { + filename += 3; + pattern += 2; + break; + } + return false; /* Not matched. */ + case '*': + case '@': + for (i = 0; i <= filename_end - filename; i++) { + if (ccs_file_matches_pattern2(filename + i, + filename_end, + pattern + 1, + pattern_end)) + return true; + c = filename[i]; + if (c == '.' && *pattern == '@') + break; + if (c != '\\') + continue; + if (filename[i + 1] == '\\') + i++; + else if (ccs_byte_range(filename + i + 1)) + i += 3; + else + break; /* Bad pattern. */ + } + return false; /* Not matched. */ + default: + j = 0; + c = *pattern; + if (c == '$') { + while (ccs_decimal(filename[j])) + j++; + } else if (c == 'X') { + while (ccs_hexadecimal(filename[j])) + j++; + } else if (c == 'A') { + while (ccs_alphabet_char(filename[j])) + j++; + } + for (i = 1; i <= j; i++) { + if (ccs_file_matches_pattern2(filename + i, + filename_end, + pattern + 1, + pattern_end)) + return true; + } + return false; /* Not matched or bad pattern. */ + } + filename++; + pattern++; + } + while (*pattern == '\\' && + (*(pattern + 1) == '*' || *(pattern + 1) == '@')) + pattern += 2; + return filename == filename_end && pattern == pattern_end; +} + +/** + * ccs_file_matches_pattern - Pattern matching without '/' character. + * + * @filename: The start of string to check. + * @filename_end: The end of string to check. + * @pattern: The start of pattern to compare. + * @pattern_end: The end of pattern to compare. + * + * Returns true if @filename matches @pattern, false otherwise. + */ +static bool ccs_file_matches_pattern(const char *filename, + const char *filename_end, + const char *pattern, + const char *pattern_end) +{ + const char *pattern_start = pattern; + bool first = true; + bool result; + while (pattern < pattern_end - 1) { + /* Split at "\-" pattern. */ + if (*pattern++ != '\\' || *pattern++ != '-') + continue; + result = ccs_file_matches_pattern2(filename, filename_end, + pattern_start, pattern - 2); + if (first) + result = !result; + if (result) + return false; + first = false; + pattern_start = pattern; + } + result = ccs_file_matches_pattern2(filename, filename_end, + pattern_start, pattern_end); + return first ? result : !result; +} + +/** + * ccs_path_matches_pattern2 - Do pathname pattern matching. + * + * @f: The start of string to check. + * @p: The start of pattern to compare. + * + * Returns true if @f matches @p, false otherwise. + */ +static bool ccs_path_matches_pattern2(const char *f, const char *p) +{ + const char *f_delimiter; + const char *p_delimiter; + while (*f && *p) { + f_delimiter = strchr(f, '/'); + if (!f_delimiter) + f_delimiter = f + strlen(f); + p_delimiter = strchr(p, '/'); + if (!p_delimiter) + p_delimiter = p + strlen(p); + if (*p == '\\' && *(p + 1) == '{') + goto recursive; + if (!ccs_file_matches_pattern(f, f_delimiter, p, p_delimiter)) + return false; + f = f_delimiter; + if (*f) + f++; + p = p_delimiter; + if (*p) + p++; + } + /* Ignore trailing "\*" and "\@" in @pattern. */ + while (*p == '\\' && + (*(p + 1) == '*' || *(p + 1) == '@')) + p += 2; + return !*f && !*p; +recursive: + /* + * The "\{" pattern is permitted only after '/' character. + * This guarantees that below "*(p - 1)" is safe. + * Also, the "\}" pattern is permitted only before '/' character + * so that "\{" + "\}" pair will not break the "\-" operator. + */ + if (*(p - 1) != '/' || p_delimiter <= p + 3 || *p_delimiter != '/' || + *(p_delimiter - 1) != '}' || *(p_delimiter - 2) != '\\') + return false; /* Bad pattern. */ + do { + /* Compare current component with pattern. */ + if (!ccs_file_matches_pattern(f, f_delimiter, p + 2, + p_delimiter - 2)) + break; + /* Proceed to next component. */ + f = f_delimiter; + if (!*f) + break; + f++; + /* Continue comparison. */ + if (ccs_path_matches_pattern2(f, p_delimiter + 1)) + return true; + f_delimiter = strchr(f, '/'); + } while (f_delimiter); + return false; /* Not matched. */ +} + +/** + * ccs_path_matches_pattern - Check whether the given filename matches the given pattern. + * + * @filename: The filename to check. + * @pattern: The pattern to compare. + * + * Returns true if matches, false otherwise. + * + * The following patterns are available. + * \\ \ itself. + * \ooo Octal representation of a byte. + * \* Zero or more repetitions of characters other than '/'. + * \@ Zero or more repetitions of characters other than '/' or '.'. + * \? 1 byte character other than '/'. + * \$ One or more repetitions of decimal digits. + * \+ 1 decimal digit. + * \X One or more repetitions of hexadecimal digits. + * \x 1 hexadecimal digit. + * \A One or more repetitions of alphabet characters. + * \a 1 alphabet character. + * + * \- Subtraction operator. + * + * /\{dir\}/ '/' + 'One or more repetitions of dir/' (e.g. /dir/ /dir/dir/ + * /dir/dir/dir/ ). + */ +static bool ccs_path_matches_pattern(const struct ccs_path_info *filename, + const struct ccs_path_info *pattern) +{ + const char *f = filename->name; + const char *p = pattern->name; + const int len = pattern->const_len; + /* If @pattern doesn't contain pattern, I can use strcmp(). */ + if (!pattern->is_patterned) + return !ccs_pathcmp(filename, pattern); + /* Don't compare directory and non-directory. */ + if (filename->is_dir != pattern->is_dir) + return false; + /* Compare the initial length without patterns. */ + if (strncmp(f, p, len)) + return false; + f += len; + p += len; + return ccs_path_matches_pattern2(f, p); +} diff --git a/security/ccsecurity/policy_io.c b/security/ccsecurity/policy_io.c new file mode 100644 index 0000000..67adb50 --- /dev/null +++ b/security/ccsecurity/policy_io.c @@ -0,0 +1,6484 @@ +/* + * security/ccsecurity/policy_io.c + * + * Copyright (C) 2005-2012 NTT DATA CORPORATION + * + * Version: 1.8.4 2015/05/05 + */ + +#include "internal.h" + +/***** SECTION1: Constants definition *****/ + +/* Define this to enable debug mode. */ +/* #define DEBUG_CONDITION */ + +#ifdef DEBUG_CONDITION +#define dprintk printk +#else +#define dprintk(...) do { } while (0) +#endif + +/* Mapping table from "enum ccs_mac_index" to "enum ccs_mac_category_index". */ +static const u8 ccs_index2category[CCS_MAX_MAC_INDEX] = { + /* CONFIG::file group */ + [CCS_MAC_FILE_EXECUTE] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_OPEN] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_CREATE] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_UNLINK] = CCS_MAC_CATEGORY_FILE, +#ifdef CONFIG_CCSECURITY_FILE_GETATTR + [CCS_MAC_FILE_GETATTR] = CCS_MAC_CATEGORY_FILE, +#endif + [CCS_MAC_FILE_MKDIR] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_RMDIR] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_MKFIFO] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_MKSOCK] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_TRUNCATE] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_SYMLINK] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_MKBLOCK] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_MKCHAR] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_LINK] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_RENAME] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_CHMOD] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_CHOWN] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_CHGRP] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_IOCTL] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_CHROOT] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_MOUNT] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_UMOUNT] = CCS_MAC_CATEGORY_FILE, + [CCS_MAC_FILE_PIVOT_ROOT] = CCS_MAC_CATEGORY_FILE, +#ifdef CONFIG_CCSECURITY_MISC + /* CONFIG::misc group */ + [CCS_MAC_ENVIRON] = CCS_MAC_CATEGORY_MISC, +#endif +#ifdef CONFIG_CCSECURITY_NETWORK + /* CONFIG::network group */ + [CCS_MAC_NETWORK_INET_STREAM_BIND] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_INET_STREAM_LISTEN] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_INET_STREAM_CONNECT] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_INET_STREAM_ACCEPT] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_INET_DGRAM_BIND] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_INET_DGRAM_SEND] = CCS_MAC_CATEGORY_NETWORK, +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + [CCS_MAC_NETWORK_INET_DGRAM_RECV] = CCS_MAC_CATEGORY_NETWORK, +#endif + [CCS_MAC_NETWORK_INET_RAW_BIND] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_INET_RAW_SEND] = CCS_MAC_CATEGORY_NETWORK, +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + [CCS_MAC_NETWORK_INET_RAW_RECV] = CCS_MAC_CATEGORY_NETWORK, +#endif + [CCS_MAC_NETWORK_UNIX_STREAM_BIND] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_UNIX_STREAM_LISTEN] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_UNIX_STREAM_CONNECT] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_UNIX_STREAM_ACCEPT] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_UNIX_DGRAM_BIND] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_UNIX_DGRAM_SEND] = CCS_MAC_CATEGORY_NETWORK, +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + [CCS_MAC_NETWORK_UNIX_DGRAM_RECV] = CCS_MAC_CATEGORY_NETWORK, +#endif + [CCS_MAC_NETWORK_UNIX_SEQPACKET_BIND] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_UNIX_SEQPACKET_LISTEN] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_UNIX_SEQPACKET_CONNECT] = CCS_MAC_CATEGORY_NETWORK, + [CCS_MAC_NETWORK_UNIX_SEQPACKET_ACCEPT] = CCS_MAC_CATEGORY_NETWORK, +#endif +#ifdef CONFIG_CCSECURITY_IPC + /* CONFIG::ipc group */ + [CCS_MAC_SIGNAL] = CCS_MAC_CATEGORY_IPC, +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + /* CONFIG::capability group */ + [CCS_MAC_CAPABILITY_USE_ROUTE_SOCKET] = CCS_MAC_CATEGORY_CAPABILITY, + [CCS_MAC_CAPABILITY_USE_PACKET_SOCKET] = CCS_MAC_CATEGORY_CAPABILITY, + [CCS_MAC_CAPABILITY_SYS_REBOOT] = CCS_MAC_CATEGORY_CAPABILITY, + [CCS_MAC_CAPABILITY_SYS_VHANGUP] = CCS_MAC_CATEGORY_CAPABILITY, + [CCS_MAC_CAPABILITY_SYS_SETTIME] = CCS_MAC_CATEGORY_CAPABILITY, + [CCS_MAC_CAPABILITY_SYS_NICE] = CCS_MAC_CATEGORY_CAPABILITY, + [CCS_MAC_CAPABILITY_SYS_SETHOSTNAME] = CCS_MAC_CATEGORY_CAPABILITY, + [CCS_MAC_CAPABILITY_USE_KERNEL_MODULE] = CCS_MAC_CATEGORY_CAPABILITY, + [CCS_MAC_CAPABILITY_SYS_KEXEC_LOAD] = CCS_MAC_CATEGORY_CAPABILITY, + [CCS_MAC_CAPABILITY_SYS_PTRACE] = CCS_MAC_CATEGORY_CAPABILITY, +#endif +}; + +/* String table for operation mode. */ +static const char * const ccs_mode[CCS_CONFIG_MAX_MODE] = { + [CCS_CONFIG_DISABLED] = "disabled", + [CCS_CONFIG_LEARNING] = "learning", + [CCS_CONFIG_PERMISSIVE] = "permissive", + [CCS_CONFIG_ENFORCING] = "enforcing" +}; + +/* String table for /proc/ccs/profile interface. */ +static const char * const ccs_mac_keywords[CCS_MAX_MAC_INDEX + + CCS_MAX_MAC_CATEGORY_INDEX] = { + /* CONFIG::file group */ + [CCS_MAC_FILE_EXECUTE] = "execute", + [CCS_MAC_FILE_OPEN] = "open", + [CCS_MAC_FILE_CREATE] = "create", + [CCS_MAC_FILE_UNLINK] = "unlink", +#ifdef CONFIG_CCSECURITY_FILE_GETATTR + [CCS_MAC_FILE_GETATTR] = "getattr", +#endif + [CCS_MAC_FILE_MKDIR] = "mkdir", + [CCS_MAC_FILE_RMDIR] = "rmdir", + [CCS_MAC_FILE_MKFIFO] = "mkfifo", + [CCS_MAC_FILE_MKSOCK] = "mksock", + [CCS_MAC_FILE_TRUNCATE] = "truncate", + [CCS_MAC_FILE_SYMLINK] = "symlink", + [CCS_MAC_FILE_MKBLOCK] = "mkblock", + [CCS_MAC_FILE_MKCHAR] = "mkchar", + [CCS_MAC_FILE_LINK] = "link", + [CCS_MAC_FILE_RENAME] = "rename", + [CCS_MAC_FILE_CHMOD] = "chmod", + [CCS_MAC_FILE_CHOWN] = "chown", + [CCS_MAC_FILE_CHGRP] = "chgrp", + [CCS_MAC_FILE_IOCTL] = "ioctl", + [CCS_MAC_FILE_CHROOT] = "chroot", + [CCS_MAC_FILE_MOUNT] = "mount", + [CCS_MAC_FILE_UMOUNT] = "unmount", + [CCS_MAC_FILE_PIVOT_ROOT] = "pivot_root", +#ifdef CONFIG_CCSECURITY_MISC + /* CONFIG::misc group */ + [CCS_MAC_ENVIRON] = "env", +#endif +#ifdef CONFIG_CCSECURITY_NETWORK + /* CONFIG::network group */ + [CCS_MAC_NETWORK_INET_STREAM_BIND] = "inet_stream_bind", + [CCS_MAC_NETWORK_INET_STREAM_LISTEN] = "inet_stream_listen", + [CCS_MAC_NETWORK_INET_STREAM_CONNECT] = "inet_stream_connect", + [CCS_MAC_NETWORK_INET_STREAM_ACCEPT] = "inet_stream_accept", + [CCS_MAC_NETWORK_INET_DGRAM_BIND] = "inet_dgram_bind", + [CCS_MAC_NETWORK_INET_DGRAM_SEND] = "inet_dgram_send", +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + [CCS_MAC_NETWORK_INET_DGRAM_RECV] = "inet_dgram_recv", +#endif + [CCS_MAC_NETWORK_INET_RAW_BIND] = "inet_raw_bind", + [CCS_MAC_NETWORK_INET_RAW_SEND] = "inet_raw_send", +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + [CCS_MAC_NETWORK_INET_RAW_RECV] = "inet_raw_recv", +#endif + [CCS_MAC_NETWORK_UNIX_STREAM_BIND] = "unix_stream_bind", + [CCS_MAC_NETWORK_UNIX_STREAM_LISTEN] = "unix_stream_listen", + [CCS_MAC_NETWORK_UNIX_STREAM_CONNECT] = "unix_stream_connect", + [CCS_MAC_NETWORK_UNIX_STREAM_ACCEPT] = "unix_stream_accept", + [CCS_MAC_NETWORK_UNIX_DGRAM_BIND] = "unix_dgram_bind", + [CCS_MAC_NETWORK_UNIX_DGRAM_SEND] = "unix_dgram_send", +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + [CCS_MAC_NETWORK_UNIX_DGRAM_RECV] = "unix_dgram_recv", +#endif + [CCS_MAC_NETWORK_UNIX_SEQPACKET_BIND] = "unix_seqpacket_bind", + [CCS_MAC_NETWORK_UNIX_SEQPACKET_LISTEN] = "unix_seqpacket_listen", + [CCS_MAC_NETWORK_UNIX_SEQPACKET_CONNECT] = "unix_seqpacket_connect", + [CCS_MAC_NETWORK_UNIX_SEQPACKET_ACCEPT] = "unix_seqpacket_accept", +#endif +#ifdef CONFIG_CCSECURITY_IPC + /* CONFIG::ipc group */ + [CCS_MAC_SIGNAL] = "signal", +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + /* CONFIG::capability group */ + [CCS_MAC_CAPABILITY_USE_ROUTE_SOCKET] = "use_route", + [CCS_MAC_CAPABILITY_USE_PACKET_SOCKET] = "use_packet", + [CCS_MAC_CAPABILITY_SYS_REBOOT] = "SYS_REBOOT", + [CCS_MAC_CAPABILITY_SYS_VHANGUP] = "SYS_VHANGUP", + [CCS_MAC_CAPABILITY_SYS_SETTIME] = "SYS_TIME", + [CCS_MAC_CAPABILITY_SYS_NICE] = "SYS_NICE", + [CCS_MAC_CAPABILITY_SYS_SETHOSTNAME] = "SYS_SETHOSTNAME", + [CCS_MAC_CAPABILITY_USE_KERNEL_MODULE] = "use_kernel_module", + [CCS_MAC_CAPABILITY_SYS_KEXEC_LOAD] = "SYS_KEXEC_LOAD", + [CCS_MAC_CAPABILITY_SYS_PTRACE] = "SYS_PTRACE", +#endif + /* CONFIG group */ + [CCS_MAX_MAC_INDEX + CCS_MAC_CATEGORY_FILE] = "file", +#ifdef CONFIG_CCSECURITY_NETWORK + [CCS_MAX_MAC_INDEX + CCS_MAC_CATEGORY_NETWORK] = "network", +#endif +#ifdef CONFIG_CCSECURITY_MISC + [CCS_MAX_MAC_INDEX + CCS_MAC_CATEGORY_MISC] = "misc", +#endif +#ifdef CONFIG_CCSECURITY_IPC + [CCS_MAX_MAC_INDEX + CCS_MAC_CATEGORY_IPC] = "ipc", +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + [CCS_MAX_MAC_INDEX + CCS_MAC_CATEGORY_CAPABILITY] = "capability", +#endif +}; + +/* String table for path operation. */ +static const char * const ccs_path_keyword[CCS_MAX_PATH_OPERATION] = { + [CCS_TYPE_EXECUTE] = "execute", + [CCS_TYPE_READ] = "read", + [CCS_TYPE_WRITE] = "write", + [CCS_TYPE_APPEND] = "append", + [CCS_TYPE_UNLINK] = "unlink", +#ifdef CONFIG_CCSECURITY_FILE_GETATTR + [CCS_TYPE_GETATTR] = "getattr", +#endif + [CCS_TYPE_RMDIR] = "rmdir", + [CCS_TYPE_TRUNCATE] = "truncate", + [CCS_TYPE_SYMLINK] = "symlink", + [CCS_TYPE_CHROOT] = "chroot", + [CCS_TYPE_UMOUNT] = "unmount", +}; + +#ifdef CONFIG_CCSECURITY_NETWORK + +/* String table for socket's operation. */ +static const char * const ccs_socket_keyword[CCS_MAX_NETWORK_OPERATION] = { + [CCS_NETWORK_BIND] = "bind", + [CCS_NETWORK_LISTEN] = "listen", + [CCS_NETWORK_CONNECT] = "connect", + [CCS_NETWORK_ACCEPT] = "accept", + [CCS_NETWORK_SEND] = "send", +#ifdef CONFIG_CCSECURITY_NETWORK_RECVMSG + [CCS_NETWORK_RECV] = "recv", +#endif +}; + +/* String table for socket's protocols. */ +static const char * const ccs_proto_keyword[CCS_SOCK_MAX] = { + [SOCK_STREAM] = "stream", + [SOCK_DGRAM] = "dgram", + [SOCK_RAW] = "raw", + [SOCK_SEQPACKET] = "seqpacket", + [0] = " ", /* Dummy for avoiding NULL pointer dereference. */ + [4] = " ", /* Dummy for avoiding NULL pointer dereference. */ +}; + +#endif + +/* String table for categories. */ +static const char * const ccs_category_keywords[CCS_MAX_MAC_CATEGORY_INDEX] = { + [CCS_MAC_CATEGORY_FILE] = "file", +#ifdef CONFIG_CCSECURITY_NETWORK + [CCS_MAC_CATEGORY_NETWORK] = "network", +#endif +#ifdef CONFIG_CCSECURITY_MISC + [CCS_MAC_CATEGORY_MISC] = "misc", +#endif +#ifdef CONFIG_CCSECURITY_IPC + [CCS_MAC_CATEGORY_IPC] = "ipc", +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + [CCS_MAC_CATEGORY_CAPABILITY] = "capability", +#endif +}; + +/* String table for conditions. */ +static const char * const ccs_condition_keyword[CCS_MAX_CONDITION_KEYWORD] = { + [CCS_TASK_UID] = "task.uid", + [CCS_TASK_EUID] = "task.euid", + [CCS_TASK_SUID] = "task.suid", + [CCS_TASK_FSUID] = "task.fsuid", + [CCS_TASK_GID] = "task.gid", + [CCS_TASK_EGID] = "task.egid", + [CCS_TASK_SGID] = "task.sgid", + [CCS_TASK_FSGID] = "task.fsgid", + [CCS_TASK_PID] = "task.pid", + [CCS_TASK_PPID] = "task.ppid", + [CCS_EXEC_ARGC] = "exec.argc", + [CCS_EXEC_ENVC] = "exec.envc", + [CCS_TYPE_IS_SOCKET] = "socket", + [CCS_TYPE_IS_SYMLINK] = "symlink", + [CCS_TYPE_IS_FILE] = "file", + [CCS_TYPE_IS_BLOCK_DEV] = "block", + [CCS_TYPE_IS_DIRECTORY] = "directory", + [CCS_TYPE_IS_CHAR_DEV] = "char", + [CCS_TYPE_IS_FIFO] = "fifo", + [CCS_MODE_SETUID] = "setuid", + [CCS_MODE_SETGID] = "setgid", + [CCS_MODE_STICKY] = "sticky", + [CCS_MODE_OWNER_READ] = "owner_read", + [CCS_MODE_OWNER_WRITE] = "owner_write", + [CCS_MODE_OWNER_EXECUTE] = "owner_execute", + [CCS_MODE_GROUP_READ] = "group_read", + [CCS_MODE_GROUP_WRITE] = "group_write", + [CCS_MODE_GROUP_EXECUTE] = "group_execute", + [CCS_MODE_OTHERS_READ] = "others_read", + [CCS_MODE_OTHERS_WRITE] = "others_write", + [CCS_MODE_OTHERS_EXECUTE] = "others_execute", + [CCS_TASK_TYPE] = "task.type", + [CCS_TASK_EXECUTE_HANDLER] = "execute_handler", + [CCS_EXEC_REALPATH] = "exec.realpath", + [CCS_SYMLINK_TARGET] = "symlink.target", + [CCS_PATH1_UID] = "path1.uid", + [CCS_PATH1_GID] = "path1.gid", + [CCS_PATH1_INO] = "path1.ino", + [CCS_PATH1_MAJOR] = "path1.major", + [CCS_PATH1_MINOR] = "path1.minor", + [CCS_PATH1_PERM] = "path1.perm", + [CCS_PATH1_TYPE] = "path1.type", + [CCS_PATH1_DEV_MAJOR] = "path1.dev_major", + [CCS_PATH1_DEV_MINOR] = "path1.dev_minor", + [CCS_PATH2_UID] = "path2.uid", + [CCS_PATH2_GID] = "path2.gid", + [CCS_PATH2_INO] = "path2.ino", + [CCS_PATH2_MAJOR] = "path2.major", + [CCS_PATH2_MINOR] = "path2.minor", + [CCS_PATH2_PERM] = "path2.perm", + [CCS_PATH2_TYPE] = "path2.type", + [CCS_PATH2_DEV_MAJOR] = "path2.dev_major", + [CCS_PATH2_DEV_MINOR] = "path2.dev_minor", + [CCS_PATH1_PARENT_UID] = "path1.parent.uid", + [CCS_PATH1_PARENT_GID] = "path1.parent.gid", + [CCS_PATH1_PARENT_INO] = "path1.parent.ino", + [CCS_PATH1_PARENT_PERM] = "path1.parent.perm", + [CCS_PATH2_PARENT_UID] = "path2.parent.uid", + [CCS_PATH2_PARENT_GID] = "path2.parent.gid", + [CCS_PATH2_PARENT_INO] = "path2.parent.ino", + [CCS_PATH2_PARENT_PERM] = "path2.parent.perm", +}; + +/* String table for PREFERENCE keyword. */ +static const char * const ccs_pref_keywords[CCS_MAX_PREF] = { + [CCS_PREF_MAX_AUDIT_LOG] = "max_audit_log", + [CCS_PREF_MAX_LEARNING_ENTRY] = "max_learning_entry", + [CCS_PREF_ENFORCING_PENALTY] = "enforcing_penalty", +}; + +/* String table for domain flags. */ +const char * const ccs_dif[CCS_MAX_DOMAIN_INFO_FLAGS] = { + [CCS_DIF_QUOTA_WARNED] = "quota_exceeded\n", + [CCS_DIF_TRANSITION_FAILED] = "transition_failed\n", +}; + +/* String table for domain transition control keywords. */ +static const char * const ccs_transition_type[CCS_MAX_TRANSITION_TYPE] = { + [CCS_TRANSITION_CONTROL_NO_RESET] = "no_reset_domain ", + [CCS_TRANSITION_CONTROL_RESET] = "reset_domain ", + [CCS_TRANSITION_CONTROL_NO_INITIALIZE] = "no_initialize_domain ", + [CCS_TRANSITION_CONTROL_INITIALIZE] = "initialize_domain ", + [CCS_TRANSITION_CONTROL_NO_KEEP] = "no_keep_domain ", + [CCS_TRANSITION_CONTROL_KEEP] = "keep_domain ", +}; + +/* String table for grouping keywords. */ +static const char * const ccs_group_name[CCS_MAX_GROUP] = { + [CCS_PATH_GROUP] = "path_group ", + [CCS_NUMBER_GROUP] = "number_group ", +#ifdef CONFIG_CCSECURITY_NETWORK + [CCS_ADDRESS_GROUP] = "address_group ", +#endif +}; + +/* String table for /proc/ccs/stat interface. */ +static const char * const ccs_policy_headers[CCS_MAX_POLICY_STAT] = { + [CCS_STAT_POLICY_UPDATES] = "update:", + [CCS_STAT_POLICY_LEARNING] = "violation in learning mode:", + [CCS_STAT_POLICY_PERMISSIVE] = "violation in permissive mode:", + [CCS_STAT_POLICY_ENFORCING] = "violation in enforcing mode:", +}; + +/* String table for /proc/ccs/stat interface. */ +static const char * const ccs_memory_headers[CCS_MAX_MEMORY_STAT] = { + [CCS_MEMORY_POLICY] = "policy:", + [CCS_MEMORY_AUDIT] = "audit log:", + [CCS_MEMORY_QUERY] = "query message:", +}; + +/***** SECTION2: Structure definition *****/ + +struct iattr; + +/* Structure for query. */ +struct ccs_query { + struct list_head list; + struct ccs_domain_info *domain; + char *query; + size_t query_len; + unsigned int serial; + u8 timer; + u8 answer; + u8 retry; +}; + +/* Structure for audit log. */ +struct ccs_log { + struct list_head list; + char *log; + int size; +}; + +/***** SECTION3: Prototype definition section *****/ + +int ccs_audit_log(struct ccs_request_info *r); +struct ccs_domain_info *ccs_assign_domain(const char *domainname, + const bool transit); +u8 ccs_get_config(const u8 profile, const u8 index); +void ccs_transition_failed(const char *domainname); +void ccs_write_log(struct ccs_request_info *r, const char *fmt, ...); + +static bool ccs_correct_domain(const unsigned char *domainname); +static bool ccs_correct_path(const char *filename); +static bool ccs_correct_word(const char *string); +static bool ccs_correct_word2(const char *string, size_t len); +static bool ccs_domain_def(const unsigned char *buffer); +static bool ccs_domain_quota_ok(struct ccs_request_info *r); +static bool ccs_flush(struct ccs_io_buffer *head); +static bool ccs_get_audit(const struct ccs_request_info *r); +static bool ccs_has_more_namespace(struct ccs_io_buffer *head); +static bool ccs_manager(void); +static bool ccs_namespace_jump(const char *domainname); +static bool ccs_parse_argv(char *left, char *right, struct ccs_argv *argv); +static bool ccs_parse_envp(char *left, char *right, struct ccs_envp *envp); +static bool ccs_parse_name_union(struct ccs_acl_param *param, + struct ccs_name_union *ptr); +static bool ccs_parse_name_union_quoted(struct ccs_acl_param *param, + struct ccs_name_union *ptr); +static bool ccs_parse_number_union(struct ccs_acl_param *param, + struct ccs_number_union *ptr); +static bool ccs_permstr(const char *string, const char *keyword); +static bool ccs_print_condition(struct ccs_io_buffer *head, + const struct ccs_condition *cond); +static bool ccs_print_entry(struct ccs_io_buffer *head, + const struct ccs_acl_info *acl); +static bool ccs_print_group(struct ccs_io_buffer *head, + const struct ccs_group *group); +static bool ccs_read_acl(struct ccs_io_buffer *head, struct list_head *list); +static bool ccs_read_group(struct ccs_io_buffer *head, const int idx); +static bool ccs_read_policy(struct ccs_io_buffer *head, const int idx); +static bool ccs_same_condition(const struct ccs_condition *a, + const struct ccs_condition *b); +static bool ccs_select_domain(struct ccs_io_buffer *head, const char *data); +static bool ccs_set_lf(struct ccs_io_buffer *head); +static bool ccs_str_starts(char **src, const char *find); +static char *ccs_get_transit_preference(struct ccs_acl_param *param, + struct ccs_condition *e); +static char *ccs_init_log(struct ccs_request_info *r, int len, const char *fmt, + va_list args); +static char *ccs_print_bprm(struct linux_binprm *bprm, + struct ccs_page_dump *dump); +static char *ccs_print_header(struct ccs_request_info *r); +static char *ccs_read_token(struct ccs_acl_param *param); +static const char *ccs_yesno(const unsigned int value); +static const struct ccs_path_info *ccs_get_domainname +(struct ccs_acl_param *param); +static const struct ccs_path_info *ccs_get_dqword(char *start); +static int __init ccs_init_module(void); +static int ccs_delete_domain(char *domainname); +static int ccs_open(struct inode *inode, struct file *file); +static int ccs_parse_policy(struct ccs_io_buffer *head, char *line); +static int ccs_release(struct inode *inode, struct file *file); +static int ccs_set_mode(char *name, const char *value, + struct ccs_profile *profile); +static int ccs_supervisor(struct ccs_request_info *r, const char *fmt, ...) + __printf(2, 3); +static int ccs_truncate(char *str); +static int ccs_update_acl(const int size, struct ccs_acl_param *param); +static int ccs_update_manager_entry(const char *manager, const bool is_delete); +static int ccs_update_policy(const int size, struct ccs_acl_param *param); +static int ccs_write_acl(struct ccs_policy_namespace *ns, + struct list_head *list, char *data, + const bool is_delete); +static int ccs_write_aggregator(struct ccs_acl_param *param); +static int ccs_write_answer(struct ccs_io_buffer *head); +static int ccs_write_domain(struct ccs_io_buffer *head); +static int ccs_write_exception(struct ccs_io_buffer *head); +static int ccs_write_file(struct ccs_acl_param *param); +static int ccs_write_group(struct ccs_acl_param *param, const u8 type); +static int ccs_write_manager(struct ccs_io_buffer *head); +static int ccs_write_pid(struct ccs_io_buffer *head); +static int ccs_write_profile(struct ccs_io_buffer *head); +static int ccs_write_stat(struct ccs_io_buffer *head); +static int ccs_write_task(struct ccs_acl_param *param); +static int ccs_write_transition_control(struct ccs_acl_param *param, + const u8 type); +static s8 ccs_find_yesno(const char *string, const char *find); +static ssize_t ccs_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos); +static ssize_t ccs_read_self(struct file *file, char __user *buf, size_t count, + loff_t *ppos); +static ssize_t ccs_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos); +static struct ccs_condition *ccs_commit_condition(struct ccs_condition *entry); +static struct ccs_condition *ccs_get_condition(struct ccs_acl_param *param); +static struct ccs_domain_info *ccs_find_domain(const char *domainname); +static struct ccs_domain_info *ccs_find_domain_by_qid(unsigned int serial); +static struct ccs_group *ccs_get_group(struct ccs_acl_param *param, + const u8 idx); +static struct ccs_policy_namespace *ccs_assign_namespace +(const char *domainname); +static struct ccs_policy_namespace *ccs_find_namespace(const char *name, + const unsigned int len); +static struct ccs_profile *ccs_assign_profile(struct ccs_policy_namespace *ns, + const unsigned int profile); +static struct ccs_profile *ccs_profile(const u8 profile); +static u8 ccs_condition_type(const char *word); +static u8 ccs_make_byte(const u8 c1, const u8 c2, const u8 c3); +static u8 ccs_parse_ulong(unsigned long *result, char **str); +static unsigned int ccs_poll(struct file *file, poll_table *wait); +static void __init ccs_create_entry(const char *name, const umode_t mode, + struct proc_dir_entry *parent, + const u8 key); +static void __init ccs_load_builtin_policy(void); +static void __init ccs_policy_io_init(void); +static void __init ccs_proc_init(void); +static void ccs_add_entry(char *header); +static void ccs_addprintf(char *buffer, int len, const char *fmt, ...) + __printf(3, 4); +static void ccs_addprintf(char *buffer, int len, const char *fmt, ...); +static void ccs_check_profile(void); +static void ccs_convert_time(time_t time, struct ccs_time *stamp); +static void ccs_init_policy_namespace(struct ccs_policy_namespace *ns); +static void ccs_io_printf(struct ccs_io_buffer *head, const char *fmt, ...) + __printf(2, 3); +static void ccs_normalize_line(unsigned char *buffer); +static void ccs_print_config(struct ccs_io_buffer *head, const u8 config); +static void ccs_print_name_union(struct ccs_io_buffer *head, + const struct ccs_name_union *ptr); +static void ccs_print_name_union_quoted(struct ccs_io_buffer *head, + const struct ccs_name_union *ptr); +static void ccs_print_namespace(struct ccs_io_buffer *head); +static void ccs_print_number_union(struct ccs_io_buffer *head, + const struct ccs_number_union *ptr); +static void ccs_print_number_union_nospace(struct ccs_io_buffer *head, + const struct ccs_number_union *ptr); +static void ccs_read_domain(struct ccs_io_buffer *head); +static void ccs_read_exception(struct ccs_io_buffer *head); +static void ccs_read_log(struct ccs_io_buffer *head); +static void ccs_read_manager(struct ccs_io_buffer *head); +static void ccs_read_pid(struct ccs_io_buffer *head); +static void ccs_read_profile(struct ccs_io_buffer *head); +static void ccs_read_query(struct ccs_io_buffer *head); +static void ccs_read_stat(struct ccs_io_buffer *head); +static void ccs_read_version(struct ccs_io_buffer *head); +static void ccs_set_group(struct ccs_io_buffer *head, const char *category); +static void ccs_set_namespace_cursor(struct ccs_io_buffer *head); +static void ccs_set_slash(struct ccs_io_buffer *head); +static void ccs_set_space(struct ccs_io_buffer *head); +static void ccs_set_string(struct ccs_io_buffer *head, const char *string); +static void ccs_set_uint(unsigned int *i, const char *string, + const char *find); +static void ccs_update_stat(const u8 index); +static void ccs_update_task_domain(struct ccs_request_info *r); +static void ccs_write_log2(struct ccs_request_info *r, int len, + const char *fmt, va_list args); + +#ifdef CONFIG_CCSECURITY_PORTRESERVE +static bool __ccs_lport_reserved(const u16 port); +static int ccs_write_reserved_port(struct ccs_acl_param *param); +#endif + +#ifdef CONFIG_CCSECURITY_NETWORK +static bool ccs_parse_ipaddr_union(struct ccs_acl_param *param, + struct ccs_ipaddr_union *ptr); +static int ccs_print_ipv4(char *buffer, const unsigned int buffer_len, + const u32 *ip); +static int ccs_print_ipv6(char *buffer, const unsigned int buffer_len, + const struct in6_addr *ip); +static int ccs_write_inet_network(struct ccs_acl_param *param); +static int ccs_write_unix_network(struct ccs_acl_param *param); +static void ccs_print_ip(char *buf, const unsigned int size, + const struct ccs_ipaddr_union *ptr); +#endif + +#ifdef CONFIG_CCSECURITY_CAPABILITY +static int ccs_write_capability(struct ccs_acl_param *param); +#endif + +#ifdef CONFIG_CCSECURITY_MISC +static int ccs_write_misc(struct ccs_acl_param *param); +#endif + +#ifdef CONFIG_CCSECURITY_IPC +static int ccs_write_ipc(struct ccs_acl_param *param); +#endif + +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION +static ssize_t ccs_write_self(struct file *file, const char __user *buf, + size_t count, loff_t *ppos); +#endif + +/***** SECTION4: Standalone functions section *****/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25) + +/** + * fatal_signal_pending - Check whether SIGKILL is pending or not. + * + * @p: Pointer to "struct task_struct". + * + * Returns true if SIGKILL is pending on @p, false otherwise. + * + * This is for compatibility with older kernels. + */ +#define fatal_signal_pending(p) (signal_pending(p) && \ + sigismember(&p->pending.signal, SIGKILL)) + +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + +/** + * __wait_event_interruptible_timeout - Sleep until a condition gets true or a timeout elapses. + * + * @wq: The waitqueue to wait on. + * @condition: A C expression for the event to wait for. + * @ret: Timeout, in jiffies. + * + * Returns 0 if the @timeout elapsed, -ERESTARTSYS if it was interrupted by a + * signal, and the remaining jiffies otherwise if the condition evaluated to + * true before the timeout elapsed. + * + * This is for compatibility with older kernels. + */ +#define __wait_event_interruptible_timeout(wq, condition, ret) \ +do { \ + wait_queue_t __wait; \ + init_waitqueue_entry(&__wait, current); \ + \ + add_wait_queue(&wq, &__wait); \ + for (;;) { \ + set_current_state(TASK_INTERRUPTIBLE); \ + if (condition) \ + break; \ + if (!signal_pending(current)) { \ + ret = schedule_timeout(ret); \ + if (!ret) \ + break; \ + continue; \ + } \ + ret = -ERESTARTSYS; \ + break; \ + } \ + current->state = TASK_RUNNING; \ + remove_wait_queue(&wq, &__wait); \ +} while (0) + +/** + * wait_event_interruptible_timeout - Sleep until a condition gets true or a timeout elapses. + * + * @wq: The waitqueue to wait on. + * @condition: A C expression for the event to wait for. + * @timeout: Timeout, in jiffies. + * + * Returns 0 if the @timeout elapsed, -ERESTARTSYS if it was interrupted by a + * signal, and the remaining jiffies otherwise if the condition evaluated to + * true before the timeout elapsed. + * + * This is for compatibility with older kernels. + */ +#define wait_event_interruptible_timeout(wq, condition, timeout) \ +({ \ + long __ret = timeout; \ + if (!(condition)) \ + __wait_event_interruptible_timeout(wq, condition, __ret); \ + __ret; \ +}) + +#endif + +/** + * ccs_convert_time - Convert time_t to YYYY/MM/DD hh/mm/ss. + * + * @time: Seconds since 1970/01/01 00:00:00. + * @stamp: Pointer to "struct ccs_time". + * + * Returns nothing. + * + * This function does not handle Y2038 problem. + */ +static void ccs_convert_time(time_t time, struct ccs_time *stamp) +{ + static const u16 ccs_eom[2][12] = { + { 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + { 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } + }; + u16 y; + u8 m; + bool r; + stamp->sec = time % 60; + time /= 60; + stamp->min = time % 60; + time /= 60; + stamp->hour = time % 24; + time /= 24; + for (y = 1970; ; y++) { + const unsigned short days = (y & 3) ? 365 : 366; + if (time < days) + break; + time -= days; + } + r = (y & 3) == 0; + for (m = 0; m < 11 && time >= ccs_eom[r][m]; m++); + if (m) + time -= ccs_eom[r][m - 1]; + stamp->year = y; + stamp->month = ++m; + stamp->day = ++time; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 23) +#if !defined(RHEL_VERSION) || RHEL_VERSION != 3 + +/** + * PDE - Get "struct proc_dir_entry". + * + * @inode: Pointer to "struct inode". + * + * Returns pointer to "struct proc_dir_entry". + * + * This is for compatibility with older kernels. + */ +static inline struct proc_dir_entry *PDE(const struct inode *inode) +{ + return (struct proc_dir_entry *) inode->u.generic_ip; +} + +#endif +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + +/** + * proc_notify_change - Update inode's attributes and reflect to the dentry. + * + * @dentry: Pointer to "struct dentry". + * @iattr: Pointer to "struct iattr". + * + * Returns 0 on success, negative value otherwise. + * + * The 2.4 kernels don't allow chmod()/chown() for files in /proc, + * while the 2.6 kernels allow. + * To permit management of /proc/ccs/ interface by non-root user, + * I modified to allow chmod()/chown() of /proc/ccs/ interface like 2.6 kernels + * by adding "struct inode_operations"->setattr hook. + */ +static int proc_notify_change(struct dentry *dentry, struct iattr *iattr) +{ + struct inode *inode = dentry->d_inode; + struct proc_dir_entry *de = PDE(inode); + int error; + + error = inode_change_ok(inode, iattr); + if (error) + goto out; + + error = inode_setattr(inode, iattr); + if (error) + goto out; + + de->uid = inode->i_uid; + de->gid = inode->i_gid; + de->mode = inode->i_mode; +out: + return error; +} + +#endif + +#ifdef CONFIG_CCSECURITY_NETWORK + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) && defined(CONFIG_NET) +#define ccs_in4_pton in4_pton +#define ccs_in6_pton in6_pton +#else +/* + * Routines for parsing IPv4 or IPv6 address. + * These are copied from lib/hexdump.c net/core/utils.c . + */ +#include + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +static int hex_to_bin(char ch) +{ + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + return -1; +} +#endif + +#define IN6PTON_XDIGIT 0x00010000 +#define IN6PTON_DIGIT 0x00020000 +#define IN6PTON_COLON_MASK 0x00700000 +#define IN6PTON_COLON_1 0x00100000 /* single : requested */ +#define IN6PTON_COLON_2 0x00200000 /* second : requested */ +#define IN6PTON_COLON_1_2 0x00400000 /* :: requested */ +#define IN6PTON_DOT 0x00800000 /* . */ +#define IN6PTON_DELIM 0x10000000 +#define IN6PTON_NULL 0x20000000 /* first/tail */ +#define IN6PTON_UNKNOWN 0x40000000 + +static inline int xdigit2bin(char c, int delim) +{ + int val; + + if (c == delim || c == '\0') + return IN6PTON_DELIM; + if (c == ':') + return IN6PTON_COLON_MASK; + if (c == '.') + return IN6PTON_DOT; + + val = hex_to_bin(c); + if (val >= 0) + return val | IN6PTON_XDIGIT | (val < 10 ? IN6PTON_DIGIT : 0); + + if (delim == -1) + return IN6PTON_DELIM; + return IN6PTON_UNKNOWN; +} + +static int ccs_in4_pton(const char *src, int srclen, u8 *dst, int delim, + const char **end) +{ + const char *s; + u8 *d; + u8 dbuf[4]; + int ret = 0; + int i; + int w = 0; + + if (srclen < 0) + srclen = strlen(src); + s = src; + d = dbuf; + i = 0; + while (1) { + int c; + c = xdigit2bin(srclen > 0 ? *s : '\0', delim); + if (!(c & (IN6PTON_DIGIT | IN6PTON_DOT | IN6PTON_DELIM | + IN6PTON_COLON_MASK))) + goto out; + if (c & (IN6PTON_DOT | IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + if (w == 0) + goto out; + *d++ = w & 0xff; + w = 0; + i++; + if (c & (IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + if (i != 4) + goto out; + break; + } + goto cont; + } + w = (w * 10) + c; + if ((w & 0xffff) > 255) + goto out; +cont: + if (i >= 4) + goto out; + s++; + srclen--; + } + ret = 1; + memcpy(dst, dbuf, sizeof(dbuf)); +out: + if (end) + *end = s; + return ret; +} + +static int ccs_in6_pton(const char *src, int srclen, u8 *dst, int delim, + const char **end) +{ + const char *s, *tok = NULL; + u8 *d, *dc = NULL; + u8 dbuf[16]; + int ret = 0; + int i; + int state = IN6PTON_COLON_1_2 | IN6PTON_XDIGIT | IN6PTON_NULL; + int w = 0; + + memset(dbuf, 0, sizeof(dbuf)); + + s = src; + d = dbuf; + if (srclen < 0) + srclen = strlen(src); + + while (1) { + int c; + + c = xdigit2bin(srclen > 0 ? *s : '\0', delim); + if (!(c & state)) + goto out; + if (c & (IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + /* process one 16-bit word */ + if (!(state & IN6PTON_NULL)) { + *d++ = (w >> 8) & 0xff; + *d++ = w & 0xff; + } + w = 0; + if (c & IN6PTON_DELIM) { + /* We've processed last word */ + break; + } + /* + * COLON_1 => XDIGIT + * COLON_2 => XDIGIT|DELIM + * COLON_1_2 => COLON_2 + */ + switch (state & IN6PTON_COLON_MASK) { + case IN6PTON_COLON_2: + dc = d; + state = IN6PTON_XDIGIT | IN6PTON_DELIM; + if (dc - dbuf >= sizeof(dbuf)) + state |= IN6PTON_NULL; + break; + case IN6PTON_COLON_1|IN6PTON_COLON_1_2: + state = IN6PTON_XDIGIT | IN6PTON_COLON_2; + break; + case IN6PTON_COLON_1: + state = IN6PTON_XDIGIT; + break; + case IN6PTON_COLON_1_2: + state = IN6PTON_COLON_2; + break; + default: + state = 0; + } + tok = s + 1; + goto cont; + } + + if (c & IN6PTON_DOT) { + ret = ccs_in4_pton(tok ? tok : s, srclen + + (int)(s - tok), d, delim, &s); + if (ret > 0) { + d += 4; + break; + } + goto out; + } + + w = (w << 4) | (0xff & c); + state = IN6PTON_COLON_1 | IN6PTON_DELIM; + if (!(w & 0xf000)) + state |= IN6PTON_XDIGIT; + if (!dc && d + 2 < dbuf + sizeof(dbuf)) { + state |= IN6PTON_COLON_1_2; + state &= ~IN6PTON_DELIM; + } + if (d + 2 >= dbuf + sizeof(dbuf)) + state &= ~(IN6PTON_COLON_1|IN6PTON_COLON_1_2); +cont: + if ((dc && d + 4 < dbuf + sizeof(dbuf)) || + d + 4 == dbuf + sizeof(dbuf)) + state |= IN6PTON_DOT; + if (d >= dbuf + sizeof(dbuf)) + state &= ~(IN6PTON_XDIGIT|IN6PTON_COLON_MASK); + s++; + srclen--; + } + + i = 15; d--; + + if (dc) { + while (d >= dc) + dst[i--] = *d--; + while (i >= dc - dbuf) + dst[i--] = 0; + while (i >= 0) + dst[i--] = *d--; + } else + memcpy(dst, dbuf, sizeof(dbuf)); + + ret = 1; +out: + if (end) + *end = s; + return ret; +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) + +/* + * Routines for printing IPv4 or IPv6 address. + * These are copied from include/linux/kernel.h include/net/ipv6.h + * include/net/addrconf.h lib/hexdump.c lib/vsprintf.c and simplified. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26) +#if !defined(RHEL_MAJOR) || RHEL_MAJOR != 5 || !defined(RHEL_MINOR) || RHEL_MINOR < 9 +static const char hex_asc[] = "0123456789abcdef"; +#define hex_asc_lo(x) hex_asc[((x) & 0x0f)] +#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] + +static inline char *pack_hex_byte(char *buf, u8 byte) +{ + *buf++ = hex_asc_hi(byte); + *buf++ = hex_asc_lo(byte); + return buf; +} +#endif +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) +static inline int ipv6_addr_v4mapped(const struct in6_addr *a) +{ + return (a->s6_addr32[0] | a->s6_addr32[1] | + (a->s6_addr32[2] ^ htonl(0x0000ffff))) == 0; +} +#endif + +static inline int ipv6_addr_is_isatap(const struct in6_addr *addr) +{ + return (addr->s6_addr32[2] | htonl(0x02000000)) == htonl(0x02005EFE); +} + +static char *ip4_string(char *p, const u8 *addr) +{ + /* + * Since this function is called outside vsnprintf(), I can use + * sprintf() here. + */ + return p + + sprintf(p, "%u.%u.%u.%u", addr[0], addr[1], addr[2], addr[3]); +} + +static char *ip6_compressed_string(char *p, const char *addr) +{ + int i, j, range; + unsigned char zerolength[8]; + int longest = 1; + int colonpos = -1; + u16 word; + u8 hi, lo; + bool needcolon = false; + bool useIPv4; + struct in6_addr in6; + + memcpy(&in6, addr, sizeof(struct in6_addr)); + + useIPv4 = ipv6_addr_v4mapped(&in6) || ipv6_addr_is_isatap(&in6); + + memset(zerolength, 0, sizeof(zerolength)); + + if (useIPv4) + range = 6; + else + range = 8; + + /* find position of longest 0 run */ + for (i = 0; i < range; i++) { + for (j = i; j < range; j++) { + if (in6.s6_addr16[j] != 0) + break; + zerolength[i]++; + } + } + for (i = 0; i < range; i++) { + if (zerolength[i] > longest) { + longest = zerolength[i]; + colonpos = i; + } + } + if (longest == 1) /* don't compress a single 0 */ + colonpos = -1; + + /* emit address */ + for (i = 0; i < range; i++) { + if (i == colonpos) { + if (needcolon || i == 0) + *p++ = ':'; + *p++ = ':'; + needcolon = false; + i += longest - 1; + continue; + } + if (needcolon) { + *p++ = ':'; + needcolon = false; + } + /* hex u16 without leading 0s */ + word = ntohs(in6.s6_addr16[i]); + hi = word >> 8; + lo = word & 0xff; + if (hi) { + if (hi > 0x0f) + p = pack_hex_byte(p, hi); + else + *p++ = hex_asc_lo(hi); + p = pack_hex_byte(p, lo); + } else if (lo > 0x0f) + p = pack_hex_byte(p, lo); + else + *p++ = hex_asc_lo(lo); + needcolon = true; + } + + if (useIPv4) { + if (needcolon) + *p++ = ':'; + p = ip4_string(p, &in6.s6_addr[12]); + } + *p = '\0'; + + return p; +} +#endif + +/** + * ccs_print_ipv4 - Print an IPv4 address. + * + * @buffer: Buffer to write to. + * @buffer_len: Size of @buffer. + * @ip: Pointer to "u32 in network byte order". + * + * Returns written length. + */ +static int ccs_print_ipv4(char *buffer, const unsigned int buffer_len, + const u32 *ip) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32) + return snprintf(buffer, buffer_len, "%pI4", ip); +#else + char addr[sizeof("255.255.255.255")]; + ip4_string(addr, (const u8 *) ip); + return snprintf(buffer, buffer_len, "%s", addr); +#endif +} + +/** + * ccs_print_ipv6 - Print an IPv6 address. + * + * @buffer: Buffer to write to. + * @buffer_len: Size of @buffer. + * @ip: Pointer to "struct in6_addr". + * + * Returns written length. + */ +static int ccs_print_ipv6(char *buffer, const unsigned int buffer_len, + const struct in6_addr *ip) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32) + return snprintf(buffer, buffer_len, "%pI6c", ip); +#else + char addr[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")]; + ip6_compressed_string(addr, (const u8 *) ip); + return snprintf(buffer, buffer_len, "%s", addr); +#endif +} + +/** + * ccs_print_ip - Print an IP address. + * + * @buf: Buffer to write to. + * @size: Size of @buf. + * @ptr: Pointer to "struct ipaddr_union". + * + * Returns nothing. + */ +static void ccs_print_ip(char *buf, const unsigned int size, + const struct ccs_ipaddr_union *ptr) +{ + int len; + if (ptr->is_ipv6) + len = ccs_print_ipv6(buf, size, &ptr->ip[0]); + else + len = ccs_print_ipv4(buf, size, &ptr->ip[0].s6_addr32[0]); + if (!memcmp(&ptr->ip[0], &ptr->ip[1], 16) || len >= size / 2) + return; + buf[len++] = '-'; + if (ptr->is_ipv6) + ccs_print_ipv6(buf + len, size - len, &ptr->ip[1]); + else + ccs_print_ipv4(buf + len, size - len, + &ptr->ip[1].s6_addr32[0]); +} + +#endif + +/***** SECTION5: Variables definition section *****/ + +/* Permit policy management by non-root user? */ +static bool ccs_manage_by_non_root; + +/* Lock for protecting policy. */ +DEFINE_MUTEX(ccs_policy_lock); + +/* Has /sbin/init started? */ +bool ccs_policy_loaded; + +/* List of namespaces. */ +LIST_HEAD(ccs_namespace_list); +/* True if namespace other than ccs_kernel_namespace is defined. */ +static bool ccs_namespace_enabled; + +/* Initial namespace.*/ +static struct ccs_policy_namespace ccs_kernel_namespace; + +/* List of "struct ccs_condition". */ +LIST_HEAD(ccs_condition_list); + +#ifdef CONFIG_CCSECURITY_PORTRESERVE +/* Bitmap for reserved local port numbers.*/ +static u8 ccs_reserved_port_map[8192]; +#endif + +/* Wait queue for kernel -> userspace notification. */ +static DECLARE_WAIT_QUEUE_HEAD(ccs_query_wait); +/* Wait queue for userspace -> kernel notification. */ +static DECLARE_WAIT_QUEUE_HEAD(ccs_answer_wait); + +/* The list for "struct ccs_query". */ +static LIST_HEAD(ccs_query_list); + +/* Lock for manipulating ccs_query_list. */ +static DEFINE_SPINLOCK(ccs_query_list_lock); + +/* Number of "struct file" referring /proc/ccs/query interface. */ +static atomic_t ccs_query_observers = ATOMIC_INIT(0); + +/* Wait queue for /proc/ccs/audit. */ +static DECLARE_WAIT_QUEUE_HEAD(ccs_log_wait); + +/* The list for "struct ccs_log". */ +static LIST_HEAD(ccs_log); + +/* Lock for "struct list_head ccs_log". */ +static DEFINE_SPINLOCK(ccs_log_lock); + +/* Length of "stuct list_head ccs_log". */ +static unsigned int ccs_log_count; + +/* Timestamp counter for last updated. */ +static unsigned int ccs_stat_updated[CCS_MAX_POLICY_STAT]; + +/* Counter for number of updates. */ +static unsigned int ccs_stat_modified[CCS_MAX_POLICY_STAT]; + +/* Operations for /proc/ccs/self_domain interface. */ +static +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 17) +const +#endif +struct file_operations ccs_self_operations = { +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + .write = ccs_write_self, +#endif + .read = ccs_read_self, +}; + +/* Operations for /proc/ccs/ interface. */ +static +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 17) +const +#endif +struct file_operations ccs_operations = { + .open = ccs_open, + .release = ccs_release, + .poll = ccs_poll, + .read = ccs_read, + .write = ccs_write, +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + +/* The inode operations for /proc/ccs/ directory. */ +static struct inode_operations ccs_dir_inode_operations; + +/* The inode operations for files under /proc/ccs/ directory. */ +static struct inode_operations ccs_file_inode_operations; + +#endif + +/***** SECTION6: Dependent functions section *****/ + +/** + * list_for_each_cookie - iterate over a list with cookie. + * + * @pos: Pointer to "struct list_head". + * @head: Pointer to "struct list_head". + */ +#define list_for_each_cookie(pos, head) \ + for (pos = pos ? pos : srcu_dereference((head)->next, &ccs_ss); \ + pos != (head); pos = srcu_dereference(pos->next, &ccs_ss)) + +/** + * ccs_read_token - Read a word from a line. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns a word on success, "" otherwise. + * + * To allow the caller to skip NULL check, this function returns "" rather than + * NULL if there is no more words to read. + */ +static char *ccs_read_token(struct ccs_acl_param *param) +{ + char *pos = param->data; + char *del = strchr(pos, ' '); + if (del) + *del++ = '\0'; + else + del = pos + strlen(pos); + param->data = del; + return pos; +} + +/** + * ccs_make_byte - Make byte value from three octal characters. + * + * @c1: The first character. + * @c2: The second character. + * @c3: The third character. + * + * Returns byte value. + */ +static u8 ccs_make_byte(const u8 c1, const u8 c2, const u8 c3) +{ + return ((c1 - '0') << 6) + ((c2 - '0') << 3) + (c3 - '0'); +} + +/** + * ccs_correct_word2 - Check whether the given string follows the naming rules. + * + * @string: The byte sequence to check. Not '\0'-terminated. + * @len: Length of @string. + * + * Returns true if @string follows the naming rules, false otherwise. + */ +static bool ccs_correct_word2(const char *string, size_t len) +{ + const char *const start = string; + bool in_repetition = false; + unsigned char c; + unsigned char d; + unsigned char e; + if (!len) + goto out; + while (len--) { + c = *string++; + if (c == '\\') { + if (!len--) + goto out; + c = *string++; + switch (c) { + case '\\': /* "\\" */ + continue; + case '$': /* "\$" */ + case '+': /* "\+" */ + case '?': /* "\?" */ + case '*': /* "\*" */ + case '@': /* "\@" */ + case 'x': /* "\x" */ + case 'X': /* "\X" */ + case 'a': /* "\a" */ + case 'A': /* "\A" */ + case '-': /* "\-" */ + continue; + case '{': /* "/\{" */ + if (string - 3 < start || *(string - 3) != '/') + break; + in_repetition = true; + continue; + case '}': /* "\}/" */ + if (*string != '/') + break; + if (!in_repetition) + break; + in_repetition = false; + continue; + case '0': /* "\ooo" */ + case '1': + case '2': + case '3': + if (!len-- || !len--) + break; + d = *string++; + e = *string++; + if (d < '0' || d > '7' || e < '0' || e > '7') + break; + c = ccs_make_byte(c, d, e); + if (c <= ' ' || c >= 127) + continue; + } + goto out; + } else if (in_repetition && c == '/') { + goto out; + } else if (c <= ' ' || c >= 127) { + goto out; + } + } + if (in_repetition) + goto out; + return true; +out: + return false; +} + +/** + * ccs_correct_word - Check whether the given string follows the naming rules. + * + * @string: The string to check. + * + * Returns true if @string follows the naming rules, false otherwise. + */ +static bool ccs_correct_word(const char *string) +{ + return ccs_correct_word2(string, strlen(string)); +} + +/** + * ccs_get_group - Allocate memory for "struct ccs_path_group"/"struct ccs_number_group"/"struct ccs_address_group". + * + * @param: Pointer to "struct ccs_acl_param". + * @idx: Index number. + * + * Returns pointer to "struct ccs_group" on success, NULL otherwise. + */ +static struct ccs_group *ccs_get_group(struct ccs_acl_param *param, + const u8 idx) +{ + struct ccs_group e = { }; + struct ccs_group *group = NULL; + struct list_head *list; + const char *group_name = ccs_read_token(param); + bool found = false; + if (!ccs_correct_word(group_name) || idx >= CCS_MAX_GROUP) + return NULL; + e.group_name = ccs_get_name(group_name); + if (!e.group_name) + return NULL; + if (mutex_lock_interruptible(&ccs_policy_lock)) + goto out; + list = ¶m->ns->group_list[idx]; + list_for_each_entry(group, list, head.list) { + if (e.group_name != group->group_name || + atomic_read(&group->head.users) == CCS_GC_IN_PROGRESS) + continue; + atomic_inc(&group->head.users); + found = true; + break; + } + if (!found) { + struct ccs_group *entry = ccs_commit_ok(&e, sizeof(e)); + if (entry) { + INIT_LIST_HEAD(&entry->member_list); + atomic_set(&entry->head.users, 1); + list_add_tail_rcu(&entry->head.list, list); + group = entry; + found = true; + } + } + mutex_unlock(&ccs_policy_lock); +out: + ccs_put_name(e.group_name); + return found ? group : NULL; +} + +/** + * ccs_parse_name_union - Parse a ccs_name_union. + * + * @param: Pointer to "struct ccs_acl_param". + * @ptr: Pointer to "struct ccs_name_union". + * + * Returns true on success, false otherwise. + */ +static bool ccs_parse_name_union(struct ccs_acl_param *param, + struct ccs_name_union *ptr) +{ + char *filename; + if (param->data[0] == '@') { + param->data++; + ptr->group = ccs_get_group(param, CCS_PATH_GROUP); + return ptr->group != NULL; + } + filename = ccs_read_token(param); + if (!ccs_correct_word(filename)) + return false; + ptr->filename = ccs_get_name(filename); + return ptr->filename != NULL; +} + +/** + * ccs_parse_ulong - Parse an "unsigned long" value. + * + * @result: Pointer to "unsigned long". + * @str: Pointer to string to parse. + * + * Returns one of values in "enum ccs_value_type". + * + * The @src is updated to point the first character after the value + * on success. + */ +static u8 ccs_parse_ulong(unsigned long *result, char **str) +{ + const char *cp = *str; + char *ep; + int base = 10; + if (*cp == '0') { + char c = *(cp + 1); + if (c == 'x' || c == 'X') { + base = 16; + cp += 2; + } else if (c >= '0' && c <= '7') { + base = 8; + cp++; + } + } + *result = simple_strtoul(cp, &ep, base); + if (cp == ep) + return CCS_VALUE_TYPE_INVALID; + *str = ep; + switch (base) { + case 16: + return CCS_VALUE_TYPE_HEXADECIMAL; + case 8: + return CCS_VALUE_TYPE_OCTAL; + default: + return CCS_VALUE_TYPE_DECIMAL; + } +} + +/** + * ccs_parse_number_union - Parse a ccs_number_union. + * + * @param: Pointer to "struct ccs_acl_param". + * @ptr: Pointer to "struct ccs_number_union". + * + * Returns true on success, false otherwise. + */ +static bool ccs_parse_number_union(struct ccs_acl_param *param, + struct ccs_number_union *ptr) +{ + char *data; + u8 type; + unsigned long v; + memset(ptr, 0, sizeof(*ptr)); + if (param->data[0] == '@') { + param->data++; + ptr->group = ccs_get_group(param, CCS_NUMBER_GROUP); + return ptr->group != NULL; + } + data = ccs_read_token(param); + type = ccs_parse_ulong(&v, &data); + if (type == CCS_VALUE_TYPE_INVALID) + return false; + ptr->values[0] = v; + ptr->value_type[0] = type; + if (!*data) { + ptr->values[1] = v; + ptr->value_type[1] = type; + return true; + } + if (*data++ != '-') + return false; + type = ccs_parse_ulong(&v, &data); + if (type == CCS_VALUE_TYPE_INVALID || *data || ptr->values[0] > v) + return false; + ptr->values[1] = v; + ptr->value_type[1] = type; + return true; +} + +#ifdef CONFIG_CCSECURITY_NETWORK + +/** + * ccs_parse_ipaddr_union - Parse an IP address. + * + * @param: Pointer to "struct ccs_acl_param". + * @ptr: Pointer to "struct ccs_ipaddr_union". + * + * Returns true on success, false otherwise. + */ +static bool ccs_parse_ipaddr_union(struct ccs_acl_param *param, + struct ccs_ipaddr_union *ptr) +{ + u8 * const min = ptr->ip[0].in6_u.u6_addr8; + u8 * const max = ptr->ip[1].in6_u.u6_addr8; + char *address = ccs_read_token(param); + const char *end; + if (!strchr(address, ':') && + ccs_in4_pton(address, -1, min, '-', &end) > 0) { + ptr->is_ipv6 = false; + if (!*end) + ptr->ip[1].s6_addr32[0] = ptr->ip[0].s6_addr32[0]; + else if (*end++ != '-' || + ccs_in4_pton(end, -1, max, '\0', &end) <= 0 || *end) + return false; + return true; + } + if (ccs_in6_pton(address, -1, min, '-', &end) > 0) { + ptr->is_ipv6 = true; + if (!*end) + memmove(max, min, sizeof(u16) * 8); + else if (*end++ != '-' || + ccs_in6_pton(end, -1, max, '\0', &end) <= 0 || *end) + return false; + return true; + } + return false; +} + +#endif + +/** + * ccs_get_dqword - ccs_get_name() for a quoted string. + * + * @start: String to save. + * + * Returns pointer to "struct ccs_path_info" on success, NULL otherwise. + */ +static const struct ccs_path_info *ccs_get_dqword(char *start) +{ + char *cp = start + strlen(start) - 1; + if (cp == start || *start++ != '"' || *cp != '"') + return NULL; + *cp = '\0'; + if (*start && !ccs_correct_word(start)) + return NULL; + return ccs_get_name(start); +} + +/** + * ccs_parse_name_union_quoted - Parse a quoted word. + * + * @param: Pointer to "struct ccs_acl_param". + * @ptr: Pointer to "struct ccs_name_union". + * + * Returns true on success, false otherwise. + */ +static bool ccs_parse_name_union_quoted(struct ccs_acl_param *param, + struct ccs_name_union *ptr) +{ + char *filename = param->data; + if (*filename == '@') + return ccs_parse_name_union(param, ptr); + ptr->filename = ccs_get_dqword(filename); + return ptr->filename != NULL; +} + +/** + * ccs_parse_argv - Parse an argv[] condition part. + * + * @left: Lefthand value. + * @right: Righthand value. + * @argv: Pointer to "struct ccs_argv". + * + * Returns true on success, false otherwise. + */ +static bool ccs_parse_argv(char *left, char *right, struct ccs_argv *argv) +{ + if (ccs_parse_ulong(&argv->index, &left) != CCS_VALUE_TYPE_DECIMAL || + *left++ != ']' || *left) + return false; + argv->value = ccs_get_dqword(right); + return argv->value != NULL; +} + +/** + * ccs_parse_envp - Parse an envp[] condition part. + * + * @left: Lefthand value. + * @right: Righthand value. + * @envp: Pointer to "struct ccs_envp". + * + * Returns true on success, false otherwise. + */ +static bool ccs_parse_envp(char *left, char *right, struct ccs_envp *envp) +{ + const struct ccs_path_info *name; + const struct ccs_path_info *value; + char *cp = left + strlen(left) - 1; + if (*cp-- != ']' || *cp != '"') + goto out; + *cp = '\0'; + if (!ccs_correct_word(left)) + goto out; + name = ccs_get_name(left); + if (!name) + goto out; + if (!strcmp(right, "NULL")) { + value = NULL; + } else { + value = ccs_get_dqword(right); + if (!value) { + ccs_put_name(name); + goto out; + } + } + envp->name = name; + envp->value = value; + return true; +out: + return false; +} + +/** + * ccs_same_condition - Check for duplicated "struct ccs_condition" entry. + * + * @a: Pointer to "struct ccs_condition". + * @b: Pointer to "struct ccs_condition". + * + * Returns true if @a == @b, false otherwise. + */ +static bool ccs_same_condition(const struct ccs_condition *a, + const struct ccs_condition *b) +{ + return a->size == b->size && a->condc == b->condc && + a->numbers_count == b->numbers_count && + a->names_count == b->names_count && + a->argc == b->argc && a->envc == b->envc && + a->grant_log == b->grant_log && + a->exec_transit == b->exec_transit && a->transit == b->transit + && !memcmp(a + 1, b + 1, a->size - sizeof(*a)); +} + +/** + * ccs_condition_type - Get condition type. + * + * @word: Keyword string. + * + * Returns one of values in "enum ccs_conditions_index" on success, + * CCS_MAX_CONDITION_KEYWORD otherwise. + */ +static u8 ccs_condition_type(const char *word) +{ + u8 i; + for (i = 0; i < CCS_MAX_CONDITION_KEYWORD; i++) { + if (!strcmp(word, ccs_condition_keyword[i])) + break; + } + return i; +} + +/** + * ccs_commit_condition - Commit "struct ccs_condition". + * + * @entry: Pointer to "struct ccs_condition". + * + * Returns pointer to "struct ccs_condition" on success, NULL otherwise. + * + * This function merges duplicated entries. This function returns NULL if + * @entry is not duplicated but memory quota for policy has exceeded. + */ +static struct ccs_condition *ccs_commit_condition(struct ccs_condition *entry) +{ + struct ccs_condition *ptr; + bool found = false; + if (mutex_lock_interruptible(&ccs_policy_lock)) { + dprintk(KERN_WARNING "%u: %s failed\n", __LINE__, __func__); + ptr = NULL; + found = true; + goto out; + } + list_for_each_entry(ptr, &ccs_condition_list, head.list) { + if (!ccs_same_condition(ptr, entry) || + atomic_read(&ptr->head.users) == CCS_GC_IN_PROGRESS) + continue; + /* Same entry found. Share this entry. */ + atomic_inc(&ptr->head.users); + found = true; + break; + } + if (!found) { + if (ccs_memory_ok(entry, entry->size)) { + atomic_set(&entry->head.users, 1); + list_add(&entry->head.list, &ccs_condition_list); + } else { + found = true; + ptr = NULL; + } + } + mutex_unlock(&ccs_policy_lock); +out: + if (found) { + ccs_del_condition(&entry->head.list); + kfree(entry); + entry = ptr; + } + return entry; +} + +/** + * ccs_correct_path - Check whether the given pathname follows the naming rules. + * + * @filename: The pathname to check. + * + * Returns true if @filename follows the naming rules, false otherwise. + */ +static bool ccs_correct_path(const char *filename) +{ + return *filename == '/' && ccs_correct_word(filename); +} + +/** + * ccs_domain_def - Check whether the given token can be a domainname. + * + * @buffer: The token to check. + * + * Returns true if @buffer possibly be a domainname, false otherwise. + */ +static bool ccs_domain_def(const unsigned char *buffer) +{ + const unsigned char *cp; + int len; + if (*buffer != '<') + return false; + cp = strchr(buffer, ' '); + if (!cp) + len = strlen(buffer); + else + len = cp - buffer; + if (buffer[len - 1] != '>' || !ccs_correct_word2(buffer + 1, len - 2)) + return false; + return true; +} + +/** + * ccs_correct_domain - Check whether the given domainname follows the naming rules. + * + * @domainname: The domainname to check. + * + * Returns true if @domainname follows the naming rules, false otherwise. + */ +static bool ccs_correct_domain(const unsigned char *domainname) +{ + if (!domainname || !ccs_domain_def(domainname)) + return false; + domainname = strchr(domainname, ' '); + if (!domainname++) + return true; + while (1) { + const unsigned char *cp = strchr(domainname, ' '); + if (!cp) + break; + if (*domainname != '/' || + !ccs_correct_word2(domainname, cp - domainname)) + return false; + domainname = cp + 1; + } + return ccs_correct_path(domainname); +} + +/** + * ccs_normalize_line - Format string. + * + * @buffer: The line to normalize. + * + * Returns nothing. + * + * Leading and trailing whitespaces are removed. + * Multiple whitespaces are packed into single space. + */ +static void ccs_normalize_line(unsigned char *buffer) +{ + unsigned char *sp = buffer; + unsigned char *dp = buffer; + bool first = true; + while (*sp && (*sp <= ' ' || *sp >= 127)) + sp++; + while (*sp) { + if (!first) + *dp++ = ' '; + first = false; + while (*sp > ' ' && *sp < 127) + *dp++ = *sp++; + while (*sp && (*sp <= ' ' || *sp >= 127)) + sp++; + } + *dp = '\0'; +} + +/** + * ccs_get_domainname - Read a domainname from a line. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns a domainname on success, NULL otherwise. + */ +static const struct ccs_path_info *ccs_get_domainname +(struct ccs_acl_param *param) +{ + char *start = param->data; + char *pos = start; + while (*pos) { + if (*pos++ != ' ' || *pos++ == '/') + continue; + pos -= 2; + *pos++ = '\0'; + break; + } + param->data = pos; + if (ccs_correct_domain(start)) + return ccs_get_name(start); + return NULL; +} + +/** + * ccs_get_transit_preference - Parse domain transition preference for execve(). + * + * @param: Pointer to "struct ccs_acl_param". + * @e: Pointer to "struct ccs_condition". + * + * Returns the condition string part. + */ +static char *ccs_get_transit_preference(struct ccs_acl_param *param, + struct ccs_condition *e) +{ + char * const pos = param->data; + bool flag; + if (*pos == '<') { + e->transit = ccs_get_domainname(param); + goto done; + } + { + char *cp = strchr(pos, ' '); + if (cp) + *cp = '\0'; + flag = ccs_correct_path(pos) || !strcmp(pos, "keep") || + !strcmp(pos, "initialize") || !strcmp(pos, "reset") || + !strcmp(pos, "child") || !strcmp(pos, "parent"); + if (cp) + *cp = ' '; + } + if (!flag) + return pos; + e->transit = ccs_get_name(ccs_read_token(param)); +done: + if (e->transit) { + e->exec_transit = true; + return param->data; + } + /* + * Return a bad read-only condition string that will let + * ccs_get_condition() return NULL. + */ + return "/"; +} + +/** + * ccs_get_condition - Parse condition part. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns pointer to "struct ccs_condition" on success, NULL otherwise. + */ +struct ccs_condition *ccs_get_condition(struct ccs_acl_param *param) +{ + struct ccs_condition *entry = NULL; + struct ccs_condition_element *condp = NULL; + struct ccs_number_union *numbers_p = NULL; + struct ccs_name_union *names_p = NULL; + struct ccs_argv *argv = NULL; + struct ccs_envp *envp = NULL; + struct ccs_condition e = { }; + char * const start_of_string = ccs_get_transit_preference(param, &e); + char * const end_of_string = start_of_string + strlen(start_of_string); + char *pos; +rerun: + pos = start_of_string; + while (1) { + u8 left = -1; + u8 right = -1; + char *left_word = pos; + char *cp; + char *right_word; + bool is_not; + if (!*left_word) + break; + /* + * Since left-hand condition does not allow use of "path_group" + * or "number_group" and environment variable's names do not + * accept '=', it is guaranteed that the original line consists + * of one or more repetition of $left$operator$right blocks + * where "$left is free from '=' and ' '" and "$operator is + * either '=' or '!='" and "$right is free from ' '". + * Therefore, we can reconstruct the original line at the end + * of dry run even if we overwrite $operator with '\0'. + */ + cp = strchr(pos, ' '); + if (cp) { + *cp = '\0'; /* Will restore later. */ + pos = cp + 1; + } else { + pos = ""; + } + right_word = strchr(left_word, '='); + if (!right_word || right_word == left_word) + goto out; + is_not = *(right_word - 1) == '!'; + if (is_not) + *(right_word++ - 1) = '\0'; /* Will restore later. */ + else if (*(right_word + 1) != '=') + *right_word++ = '\0'; /* Will restore later. */ + else + goto out; + dprintk(KERN_WARNING "%u: <%s>%s=<%s>\n", __LINE__, left_word, + is_not ? "!" : "", right_word); + if (!strcmp(left_word, "grant_log")) { + if (entry) { + if (is_not || + entry->grant_log != CCS_GRANTLOG_AUTO) + goto out; + else if (!strcmp(right_word, "yes")) + entry->grant_log = CCS_GRANTLOG_YES; + else if (!strcmp(right_word, "no")) + entry->grant_log = CCS_GRANTLOG_NO; + else + goto out; + } + continue; + } + if (!strcmp(left_word, "auto_domain_transition")) { + if (entry) { + if (is_not || entry->transit) + goto out; + entry->transit = ccs_get_dqword(right_word); + if (!entry->transit || + (entry->transit->name[0] != '/' && + !ccs_domain_def(entry->transit->name))) + goto out; + } + continue; + } + if (!strncmp(left_word, "exec.argv[", 10)) { + if (!argv) { + e.argc++; + e.condc++; + } else { + e.argc--; + e.condc--; + left = CCS_ARGV_ENTRY; + argv->is_not = is_not; + if (!ccs_parse_argv(left_word + 10, + right_word, argv++)) + goto out; + } + goto store_value; + } + if (!strncmp(left_word, "exec.envp[\"", 11)) { + if (!envp) { + e.envc++; + e.condc++; + } else { + e.envc--; + e.condc--; + left = CCS_ENVP_ENTRY; + envp->is_not = is_not; + if (!ccs_parse_envp(left_word + 11, + right_word, envp++)) + goto out; + } + goto store_value; + } + left = ccs_condition_type(left_word); + dprintk(KERN_WARNING "%u: <%s> left=%u\n", __LINE__, left_word, + left); + if (left == CCS_MAX_CONDITION_KEYWORD) { + if (!numbers_p) { + e.numbers_count++; + } else { + e.numbers_count--; + left = CCS_NUMBER_UNION; + param->data = left_word; + if (*left_word == '@' || + !ccs_parse_number_union(param, + numbers_p++)) + goto out; + } + } + if (!condp) + e.condc++; + else + e.condc--; + if (left == CCS_EXEC_REALPATH || left == CCS_SYMLINK_TARGET) { + if (!names_p) { + e.names_count++; + } else { + e.names_count--; + right = CCS_NAME_UNION; + param->data = right_word; + if (!ccs_parse_name_union_quoted(param, + names_p++)) + goto out; + } + goto store_value; + } + right = ccs_condition_type(right_word); + if (right == CCS_MAX_CONDITION_KEYWORD) { + if (!numbers_p) { + e.numbers_count++; + } else { + e.numbers_count--; + right = CCS_NUMBER_UNION; + param->data = right_word; + if (!ccs_parse_number_union(param, + numbers_p++)) + goto out; + } + } +store_value: + if (!condp) { + dprintk(KERN_WARNING "%u: dry_run left=%u right=%u " + "match=%u\n", __LINE__, left, right, !is_not); + continue; + } + condp->left = left; + condp->right = right; + condp->equals = !is_not; + dprintk(KERN_WARNING "%u: left=%u right=%u match=%u\n", + __LINE__, condp->left, condp->right, + condp->equals); + condp++; + } + dprintk(KERN_INFO "%u: cond=%u numbers=%u names=%u ac=%u ec=%u\n", + __LINE__, e.condc, e.numbers_count, e.names_count, e.argc, + e.envc); + if (entry) { + BUG_ON(e.names_count | e.numbers_count | e.argc | e.envc | + e.condc); + return ccs_commit_condition(entry); + } + e.size = sizeof(*entry) + + e.condc * sizeof(struct ccs_condition_element) + + e.numbers_count * sizeof(struct ccs_number_union) + + e.names_count * sizeof(struct ccs_name_union) + + e.argc * sizeof(struct ccs_argv) + + e.envc * sizeof(struct ccs_envp); + entry = kzalloc(e.size, CCS_GFP_FLAGS); + if (!entry) + goto out2; + *entry = e; + e.transit = NULL; + condp = (struct ccs_condition_element *) (entry + 1); + numbers_p = (struct ccs_number_union *) (condp + e.condc); + names_p = (struct ccs_name_union *) (numbers_p + e.numbers_count); + argv = (struct ccs_argv *) (names_p + e.names_count); + envp = (struct ccs_envp *) (argv + e.argc); + { + bool flag = false; + for (pos = start_of_string; pos < end_of_string; pos++) { + if (*pos) + continue; + if (flag) /* Restore " ". */ + *pos = ' '; + else if (*(pos + 1) == '=') /* Restore "!=". */ + *pos = '!'; + else /* Restore "=". */ + *pos = '='; + flag = !flag; + } + } + goto rerun; +out: + dprintk(KERN_WARNING "%u: %s failed\n", __LINE__, __func__); + if (entry) { + ccs_del_condition(&entry->head.list); + kfree(entry); + } +out2: + ccs_put_name(e.transit); + return NULL; +} + +/** + * ccs_yesno - Return "yes" or "no". + * + * @value: Bool value. + * + * Returns "yes" if @value is not 0, "no" otherwise. + */ +static const char *ccs_yesno(const unsigned int value) +{ + return value ? "yes" : "no"; +} + +/** + * ccs_addprintf - strncat()-like-snprintf(). + * + * @buffer: Buffer to write to. Must be '\0'-terminated. + * @len: Size of @buffer. + * @fmt: The printf()'s format string, followed by parameters. + * + * Returns nothing. + */ +static void ccs_addprintf(char *buffer, int len, const char *fmt, ...) +{ + va_list args; + const int pos = strlen(buffer); + va_start(args, fmt); + vsnprintf(buffer + pos, len - pos - 1, fmt, args); + va_end(args); +} + +/** + * ccs_flush - Flush queued string to userspace's buffer. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns true if all data was flushed, false otherwise. + */ +static bool ccs_flush(struct ccs_io_buffer *head) +{ + while (head->r.w_pos) { + const char *w = head->r.w[0]; + size_t len = strlen(w); + if (len) { + if (len > head->read_user_buf_avail) + len = head->read_user_buf_avail; + if (!len) + return false; + if (copy_to_user(head->read_user_buf, w, len)) + return false; + head->read_user_buf_avail -= len; + head->read_user_buf += len; + w += len; + } + head->r.w[0] = w; + if (*w) + return false; + /* Add '\0' for audit logs and query. */ + if (head->type == CCS_AUDIT || head->type == CCS_QUERY) { + if (!head->read_user_buf_avail || + copy_to_user(head->read_user_buf, "", 1)) + return false; + head->read_user_buf_avail--; + head->read_user_buf++; + } + head->r.w_pos--; + for (len = 0; len < head->r.w_pos; len++) + head->r.w[len] = head->r.w[len + 1]; + } + head->r.avail = 0; + return true; +} + +/** + * ccs_set_string - Queue string to "struct ccs_io_buffer" structure. + * + * @head: Pointer to "struct ccs_io_buffer". + * @string: String to print. + * + * Returns nothing. + * + * Note that @string has to be kept valid until @head is kfree()d. + * This means that char[] allocated on stack memory cannot be passed to + * this function. Use ccs_io_printf() for char[] allocated on stack memory. + */ +static void ccs_set_string(struct ccs_io_buffer *head, const char *string) +{ + if (head->r.w_pos < CCS_MAX_IO_READ_QUEUE) { + head->r.w[head->r.w_pos++] = string; + ccs_flush(head); + } else + printk(KERN_WARNING "Too many words in a line.\n"); +} + +/** + * ccs_io_printf - printf() to "struct ccs_io_buffer" structure. + * + * @head: Pointer to "struct ccs_io_buffer". + * @fmt: The printf()'s format string, followed by parameters. + * + * Returns nothing. + */ +static void ccs_io_printf(struct ccs_io_buffer *head, const char *fmt, ...) +{ + va_list args; + size_t len; + size_t pos = head->r.avail; + int size = head->readbuf_size - pos; + if (size <= 0) + return; + va_start(args, fmt); + len = vsnprintf(head->read_buf + pos, size, fmt, args) + 1; + va_end(args); + if (pos + len >= head->readbuf_size) { + printk(KERN_WARNING "Too many words in a line.\n"); + return; + } + head->r.avail += len; + ccs_set_string(head, head->read_buf + pos); +} + +/** + * ccs_set_space - Put a space to "struct ccs_io_buffer" structure. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + */ +static void ccs_set_space(struct ccs_io_buffer *head) +{ + ccs_set_string(head, " "); +} + +/** + * ccs_set_lf - Put a line feed to "struct ccs_io_buffer" structure. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns true if all data was flushed, false otherwise. + */ +static bool ccs_set_lf(struct ccs_io_buffer *head) +{ + ccs_set_string(head, "\n"); + return !head->r.w_pos; +} + +/** + * ccs_set_slash - Put a shash to "struct ccs_io_buffer" structure. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + */ +static void ccs_set_slash(struct ccs_io_buffer *head) +{ + ccs_set_string(head, "/"); +} + +/** + * ccs_init_policy_namespace - Initialize namespace. + * + * @ns: Pointer to "struct ccs_policy_namespace". + * + * Returns nothing. + */ +static void ccs_init_policy_namespace(struct ccs_policy_namespace *ns) +{ + unsigned int idx; + for (idx = 0; idx < CCS_MAX_ACL_GROUPS; idx++) + INIT_LIST_HEAD(&ns->acl_group[idx]); + for (idx = 0; idx < CCS_MAX_GROUP; idx++) + INIT_LIST_HEAD(&ns->group_list[idx]); + for (idx = 0; idx < CCS_MAX_POLICY; idx++) + INIT_LIST_HEAD(&ns->policy_list[idx]); + ns->profile_version = 20150505; + ccs_namespace_enabled = !list_empty(&ccs_namespace_list); + list_add_tail_rcu(&ns->namespace_list, &ccs_namespace_list); +} + +/** + * ccs_print_namespace - Print namespace header. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + */ +static void ccs_print_namespace(struct ccs_io_buffer *head) +{ + if (!ccs_namespace_enabled) + return; + ccs_set_string(head, + container_of(head->r.ns, struct ccs_policy_namespace, + namespace_list)->name); + ccs_set_space(head); +} + +/** + * ccs_assign_profile - Create a new profile. + * + * @ns: Pointer to "struct ccs_policy_namespace". + * @profile: Profile number to create. + * + * Returns pointer to "struct ccs_profile" on success, NULL otherwise. + */ +static struct ccs_profile *ccs_assign_profile(struct ccs_policy_namespace *ns, + const unsigned int profile) +{ + struct ccs_profile *ptr; + struct ccs_profile *entry; + if (profile >= CCS_MAX_PROFILES) + return NULL; + ptr = ns->profile_ptr[profile]; + if (ptr) + return ptr; + entry = kzalloc(sizeof(*entry), CCS_GFP_FLAGS); + if (mutex_lock_interruptible(&ccs_policy_lock)) + goto out; + ptr = ns->profile_ptr[profile]; + if (!ptr && ccs_memory_ok(entry, sizeof(*entry))) { + ptr = entry; + ptr->default_config = CCS_CONFIG_DISABLED | + CCS_CONFIG_WANT_GRANT_LOG | CCS_CONFIG_WANT_REJECT_LOG; + memset(ptr->config, CCS_CONFIG_USE_DEFAULT, + sizeof(ptr->config)); + ptr->pref[CCS_PREF_MAX_AUDIT_LOG] = + CONFIG_CCSECURITY_MAX_AUDIT_LOG; + ptr->pref[CCS_PREF_MAX_LEARNING_ENTRY] = + CONFIG_CCSECURITY_MAX_ACCEPT_ENTRY; + mb(); /* Avoid out-of-order execution. */ + ns->profile_ptr[profile] = ptr; + entry = NULL; + } + mutex_unlock(&ccs_policy_lock); +out: + kfree(entry); + return ptr; +} + +/** + * ccs_check_profile - Check all profiles currently assigned to domains are defined. + * + * Returns nothing. + */ +static void ccs_check_profile(void) +{ + struct ccs_domain_info *domain; + const int idx = ccs_read_lock(); + ccs_policy_loaded = true; + printk(KERN_INFO "CCSecurity: 1.8.4 2015/05/05\n"); + list_for_each_entry_srcu(domain, &ccs_domain_list, list, &ccs_ss) { + const u8 profile = domain->profile; + struct ccs_policy_namespace *ns = domain->ns; + if (ns->profile_version == 20100903) { + static bool done; + if (!done) + printk(KERN_INFO "Converting profile version " + "from %u to %u.\n", 20100903, 20150505); + done = true; + ns->profile_version = 20150505; + } + if (ns->profile_version != 20150505) + printk(KERN_ERR + "Profile version %u is not supported.\n", + ns->profile_version); + else if (!ns->profile_ptr[profile]) + printk(KERN_ERR + "Profile %u (used by '%s') is not defined.\n", + profile, domain->domainname->name); + else + continue; + printk(KERN_ERR + "Userland tools for TOMOYO 1.8 must be installed and " + "policy must be initialized.\n"); + printk(KERN_ERR "Please see http://tomoyo.osdn.jp/1.8/ " + "for more information.\n"); + panic("STOP!"); + } + ccs_read_unlock(idx); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) && defined(CONFIG_SECURITY) + ccsecurity_exports.add_hooks(); +#endif + printk(KERN_INFO "Mandatory Access Control activated.\n"); +} + +/** + * ccs_profile - Find a profile. + * + * @profile: Profile number to find. + * + * Returns pointer to "struct ccs_profile". + */ +static struct ccs_profile *ccs_profile(const u8 profile) +{ + static struct ccs_profile ccs_null_profile; + struct ccs_profile *ptr = ccs_current_namespace()-> + profile_ptr[profile]; + if (!ptr) + ptr = &ccs_null_profile; + return ptr; +} + +/** + * ccs_get_config - Get config for specified profile's specified functionality. + * + * @profile: Profile number. + * @index: Index number of functionality. + * + * Returns config. + * + * First, check for CONFIG::category::functionality. + * If CONFIG::category::functionality is set to use default, then check + * CONFIG::category. If CONFIG::category is set to use default, then use + * CONFIG. CONFIG cannot be set to use default. + */ +u8 ccs_get_config(const u8 profile, const u8 index) +{ + u8 config; + const struct ccs_profile *p; + if (!ccs_policy_loaded) + return CCS_CONFIG_DISABLED; + p = ccs_profile(profile); + config = p->config[index]; + if (config == CCS_CONFIG_USE_DEFAULT) + config = p->config[ccs_index2category[index] + + CCS_MAX_MAC_INDEX]; + if (config == CCS_CONFIG_USE_DEFAULT) + config = p->default_config; + return config; +} + +/** + * ccs_find_yesno - Find values for specified keyword. + * + * @string: String to check. + * @find: Name of keyword. + * + * Returns 1 if "@find=yes" was found, 0 if "@find=no" was found, -1 otherwise. + */ +static s8 ccs_find_yesno(const char *string, const char *find) +{ + const char *cp = strstr(string, find); + if (cp) { + cp += strlen(find); + if (!strncmp(cp, "=yes", 4)) + return 1; + else if (!strncmp(cp, "=no", 3)) + return 0; + } + return -1; +} + +/** + * ccs_set_uint - Set value for specified preference. + * + * @i: Pointer to "unsigned int". + * @string: String to check. + * @find: Name of keyword. + * + * Returns nothing. + */ +static void ccs_set_uint(unsigned int *i, const char *string, const char *find) +{ + const char *cp = strstr(string, find); + if (cp) + sscanf(cp + strlen(find), "=%u", i); +} + +/** + * ccs_str_starts - Check whether the given string starts with the given keyword. + * + * @src: Pointer to pointer to the string. + * @find: Pointer to the keyword. + * + * Returns true if @src starts with @find, false otherwise. + * + * The @src is updated to point the first character after the @find + * if @src starts with @find. + */ +static bool ccs_str_starts(char **src, const char *find) +{ + const int len = strlen(find); + char *tmp = *src; + if (strncmp(tmp, find, len)) + return false; + tmp += len; + *src = tmp; + return true; +} + +/** + * ccs_print_group - Print group's name. + * + * @head: Pointer to "struct ccs_io_buffer". + * @group: Pointer to "struct ccsgroup". Maybe NULL. + * + * Returns true if @group is not NULL. false otherwise. + */ +static bool ccs_print_group(struct ccs_io_buffer *head, + const struct ccs_group *group) +{ + if (group) { + ccs_set_string(head, "@"); + ccs_set_string(head, group->group_name->name); + return true; + } + return false; +} + +/** + * ccs_set_mode - Set mode for specified profile. + * + * @name: Name of functionality. + * @value: Mode for @name. + * @profile: Pointer to "struct ccs_profile". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_set_mode(char *name, const char *value, + struct ccs_profile *profile) +{ + u8 i; + u8 config; + if (!strcmp(name, "CONFIG")) { + i = CCS_MAX_MAC_INDEX + CCS_MAX_MAC_CATEGORY_INDEX; + config = profile->default_config; + } else if (ccs_str_starts(&name, "CONFIG::")) { + config = 0; + for (i = 0; i < CCS_MAX_MAC_INDEX + CCS_MAX_MAC_CATEGORY_INDEX; + i++) { + int len = 0; + if (i < CCS_MAX_MAC_INDEX) { + const u8 c = ccs_index2category[i]; + const char *category = + ccs_category_keywords[c]; + len = strlen(category); + if (strncmp(name, category, len) || + name[len++] != ':' || name[len++] != ':') + continue; + } + if (strcmp(name + len, ccs_mac_keywords[i])) + continue; + config = profile->config[i]; + break; + } + if (i == CCS_MAX_MAC_INDEX + CCS_MAX_MAC_CATEGORY_INDEX) + return -EINVAL; + } else { + return -EINVAL; + } + if (strstr(value, "use_default")) { + config = CCS_CONFIG_USE_DEFAULT; + } else { + u8 mode; + for (mode = 0; mode < CCS_CONFIG_MAX_MODE; mode++) + if (strstr(value, ccs_mode[mode])) + /* + * Update lower 3 bits in order to distinguish + * 'config' from 'CCS_CONFIG_USE_DEAFULT'. + */ + config = (config & ~7) | mode; + if (config != CCS_CONFIG_USE_DEFAULT) { + switch (ccs_find_yesno(value, "grant_log")) { + case 1: + config |= CCS_CONFIG_WANT_GRANT_LOG; + break; + case 0: + config &= ~CCS_CONFIG_WANT_GRANT_LOG; + break; + } + switch (ccs_find_yesno(value, "reject_log")) { + case 1: + config |= CCS_CONFIG_WANT_REJECT_LOG; + break; + case 0: + config &= ~CCS_CONFIG_WANT_REJECT_LOG; + break; + } + } + } + if (i < CCS_MAX_MAC_INDEX + CCS_MAX_MAC_CATEGORY_INDEX) + profile->config[i] = config; + else if (config != CCS_CONFIG_USE_DEFAULT) + profile->default_config = config; + return 0; +} + +/** + * ccs_write_profile - Write profile table. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_write_profile(struct ccs_io_buffer *head) +{ + char *data = head->write_buf; + unsigned int i; + char *cp; + struct ccs_profile *profile; + if (sscanf(data, "PROFILE_VERSION=%u", &head->w.ns->profile_version) + == 1) + return 0; + i = simple_strtoul(data, &cp, 10); + if (*cp != '-') + return -EINVAL; + data = cp + 1; + profile = ccs_assign_profile(head->w.ns, i); + if (!profile) + return -EINVAL; + cp = strchr(data, '='); + if (!cp) + return -EINVAL; + *cp++ = '\0'; + if (!strcmp(data, "COMMENT")) { + static DEFINE_SPINLOCK(lock); + const struct ccs_path_info *new_comment = ccs_get_name(cp); + const struct ccs_path_info *old_comment; + if (!new_comment) + return -ENOMEM; + spin_lock(&lock); + old_comment = profile->comment; + profile->comment = new_comment; + spin_unlock(&lock); + ccs_put_name(old_comment); + return 0; + } + if (!strcmp(data, "PREFERENCE")) { + for (i = 0; i < CCS_MAX_PREF; i++) + ccs_set_uint(&profile->pref[i], cp, + ccs_pref_keywords[i]); + return 0; + } + return ccs_set_mode(data, cp, profile); +} + +/** + * ccs_print_config - Print mode for specified functionality. + * + * @head: Pointer to "struct ccs_io_buffer". + * @config: Mode for that functionality. + * + * Returns nothing. + * + * Caller prints functionality's name. + */ +static void ccs_print_config(struct ccs_io_buffer *head, const u8 config) +{ + ccs_io_printf(head, "={ mode=%s grant_log=%s reject_log=%s }\n", + ccs_mode[config & 3], + ccs_yesno(config & CCS_CONFIG_WANT_GRANT_LOG), + ccs_yesno(config & CCS_CONFIG_WANT_REJECT_LOG)); +} + +/** + * ccs_read_profile - Read profile table. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + */ +static void ccs_read_profile(struct ccs_io_buffer *head) +{ + u8 index; + struct ccs_policy_namespace *ns = container_of(head->r.ns, typeof(*ns), + namespace_list); + const struct ccs_profile *profile; + if (head->r.eof) + return; +next: + index = head->r.index; + profile = ns->profile_ptr[index]; + switch (head->r.step) { + case 0: + ccs_print_namespace(head); + ccs_io_printf(head, "PROFILE_VERSION=%u\n", + ns->profile_version); + head->r.step++; + break; + case 1: + for ( ; head->r.index < CCS_MAX_PROFILES; head->r.index++) + if (ns->profile_ptr[head->r.index]) + break; + if (head->r.index == CCS_MAX_PROFILES) { + head->r.eof = true; + return; + } + head->r.step++; + break; + case 2: + { + u8 i; + const struct ccs_path_info *comment = profile->comment; + ccs_print_namespace(head); + ccs_io_printf(head, "%u-COMMENT=", index); + ccs_set_string(head, comment ? comment->name : ""); + ccs_set_lf(head); + ccs_print_namespace(head); + ccs_io_printf(head, "%u-PREFERENCE={ ", index); + for (i = 0; i < CCS_MAX_PREF; i++) + ccs_io_printf(head, "%s=%u ", + ccs_pref_keywords[i], + profile->pref[i]); + ccs_set_string(head, "}\n"); + head->r.step++; + } + break; + case 3: + { + ccs_print_namespace(head); + ccs_io_printf(head, "%u-%s", index, "CONFIG"); + ccs_print_config(head, profile->default_config); + head->r.bit = 0; + head->r.step++; + } + break; + case 4: + for ( ; head->r.bit < CCS_MAX_MAC_INDEX + + CCS_MAX_MAC_CATEGORY_INDEX; head->r.bit++) { + const u8 i = head->r.bit; + const u8 config = profile->config[i]; + if (config == CCS_CONFIG_USE_DEFAULT) + continue; + ccs_print_namespace(head); + if (i < CCS_MAX_MAC_INDEX) + ccs_io_printf(head, "%u-CONFIG::%s::%s", index, + ccs_category_keywords + [ccs_index2category[i]], + ccs_mac_keywords[i]); + else + ccs_io_printf(head, "%u-CONFIG::%s", index, + ccs_mac_keywords[i]); + ccs_print_config(head, config); + head->r.bit++; + break; + } + if (head->r.bit == CCS_MAX_MAC_INDEX + + CCS_MAX_MAC_CATEGORY_INDEX) { + head->r.index++; + head->r.step = 1; + } + break; + } + if (ccs_flush(head)) + goto next; +} + +/** + * ccs_update_policy - Update an entry for exception policy. + * + * @size: Size of new entry in bytes. + * @param: Pointer to "struct ccs_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_update_policy(const int size, struct ccs_acl_param *param) +{ + struct ccs_acl_head *new_entry = ¶m->e.acl_head; + int error = param->is_delete ? -ENOENT : -ENOMEM; + struct ccs_acl_head *entry; + struct list_head *list = param->list; + BUG_ON(size < sizeof(*entry)); + if (mutex_lock_interruptible(&ccs_policy_lock)) + return -ENOMEM; + list_for_each_entry_srcu(entry, list, list, &ccs_ss) { + if (entry->is_deleted == CCS_GC_IN_PROGRESS) + continue; + if (memcmp(entry + 1, new_entry + 1, size - sizeof(*entry))) + continue; + entry->is_deleted = param->is_delete; + error = 0; + break; + } + if (error && !param->is_delete) { + entry = ccs_commit_ok(new_entry, size); + if (entry) { + list_add_tail_rcu(&entry->list, list); + error = 0; + } + } + mutex_unlock(&ccs_policy_lock); + return error; +} + +/** + * ccs_update_manager_entry - Add a manager entry. + * + * @manager: The path to manager or the domainnamme. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_update_manager_entry(const char *manager, + const bool is_delete) +{ + struct ccs_acl_param param = { + /* .ns = &ccs_kernel_namespace, */ + .is_delete = is_delete, + .list = &ccs_kernel_namespace.policy_list[CCS_ID_MANAGER], + }; + struct ccs_manager *e = ¶m.e.manager; + int error = is_delete ? -ENOENT : -ENOMEM; + /* Forced zero clear for using memcmp() at ccs_update_policy(). */ + memset(¶m.e, 0, sizeof(param.e)); + if (!ccs_correct_domain(manager) && !ccs_correct_word(manager)) + return -EINVAL; + e->manager = ccs_get_name(manager); + if (e->manager) { + error = ccs_update_policy(sizeof(*e), ¶m); + ccs_put_name(e->manager); + } + return error; +} + +/** + * ccs_write_manager - Write manager policy. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_write_manager(struct ccs_io_buffer *head) +{ + const char *data = head->write_buf; + if (!strcmp(data, "manage_by_non_root")) { + ccs_manage_by_non_root = !head->w.is_delete; + return 0; + } + return ccs_update_manager_entry(data, head->w.is_delete); +} + +/** + * ccs_read_manager - Read manager policy. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + * + * Caller holds ccs_read_lock(). + */ +static void ccs_read_manager(struct ccs_io_buffer *head) +{ + if (head->r.eof) + return; + list_for_each_cookie(head->r.acl, &ccs_kernel_namespace. + policy_list[CCS_ID_MANAGER]) { + struct ccs_manager *ptr = + list_entry(head->r.acl, typeof(*ptr), head.list); + if (ptr->head.is_deleted) + continue; + if (!ccs_flush(head)) + return; + ccs_set_string(head, ptr->manager->name); + ccs_set_lf(head); + } + head->r.eof = true; +} + +/** + * ccs_manager - Check whether the current process is a policy manager. + * + * Returns true if the current process is permitted to modify policy + * via /proc/ccs/ interface. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_manager(void) +{ + struct ccs_manager *ptr; + struct ccs_path_info exe; + struct ccs_security *task = ccs_current_security(); + const struct ccs_path_info *domainname + = ccs_current_domain()->domainname; + bool found = false; + if (!ccs_policy_loaded) + return true; + if (task->ccs_flags & CCS_TASK_IS_MANAGER) + return true; + if (!ccs_manage_by_non_root && + (!uid_eq(current_uid(), GLOBAL_ROOT_UID) || + !uid_eq(current_euid(), GLOBAL_ROOT_UID))) + return false; + exe.name = ccs_get_exe(); + if (!exe.name) + return false; + ccs_fill_path_info(&exe); + list_for_each_entry_srcu(ptr, &ccs_kernel_namespace. + policy_list[CCS_ID_MANAGER], head.list, + &ccs_ss) { + if (ptr->head.is_deleted) + continue; + if (ccs_pathcmp(domainname, ptr->manager) && + ccs_pathcmp(&exe, ptr->manager)) + continue; + /* Set manager flag. */ + task->ccs_flags |= CCS_TASK_IS_MANAGER; + found = true; + break; + } + if (!found) { /* Reduce error messages. */ + static pid_t ccs_last_pid; + const pid_t pid = current->pid; + if (ccs_last_pid != pid) { + printk(KERN_WARNING "%s ( %s ) is not permitted to " + "update policies.\n", domainname->name, + exe.name); + ccs_last_pid = pid; + } + } + kfree(exe.name); + return found; +} + +/** + * ccs_find_domain - Find a domain by the given name. + * + * @domainname: The domainname to find. + * + * Returns pointer to "struct ccs_domain_info" if found, NULL otherwise. + * + * Caller holds ccs_read_lock(). + */ +static struct ccs_domain_info *ccs_find_domain(const char *domainname) +{ + struct ccs_domain_info *domain; + struct ccs_path_info name; + name.name = domainname; + ccs_fill_path_info(&name); + list_for_each_entry_srcu(domain, &ccs_domain_list, list, &ccs_ss) { + if (!domain->is_deleted && + !ccs_pathcmp(&name, domain->domainname)) + return domain; + } + return NULL; +} + +/** + * ccs_select_domain - Parse select command. + * + * @head: Pointer to "struct ccs_io_buffer". + * @data: String to parse. + * + * Returns true on success, false otherwise. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_select_domain(struct ccs_io_buffer *head, const char *data) +{ + unsigned int pid; + struct ccs_domain_info *domain = NULL; + bool global_pid = false; + if (strncmp(data, "select ", 7)) + return false; + data += 7; + if (sscanf(data, "pid=%u", &pid) == 1 || + (global_pid = true, sscanf(data, "global-pid=%u", &pid) == 1)) { + struct task_struct *p; + ccs_tasklist_lock(); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + if (global_pid) + p = ccsecurity_exports.find_task_by_pid_ns(pid, + &init_pid_ns); + else + p = ccsecurity_exports.find_task_by_vpid(pid); +#else + p = find_task_by_pid(pid); +#endif + if (p) + domain = ccs_task_domain(p); + ccs_tasklist_unlock(); + } else if (!strncmp(data, "domain=", 7)) { + if (*(data + 7) == '<') + domain = ccs_find_domain(data + 7); + } else if (sscanf(data, "Q=%u", &pid) == 1) { + domain = ccs_find_domain_by_qid(pid); + } else + return false; + head->w.domain = domain; + /* Accessing read_buf is safe because head->io_sem is held. */ + if (!head->read_buf) + return true; /* Do nothing if open(O_WRONLY). */ + memset(&head->r, 0, sizeof(head->r)); + head->r.print_this_domain_only = true; + if (domain) + head->r.domain = &domain->list; + else + head->r.eof = true; + ccs_io_printf(head, "# select %s\n", data); + if (domain && domain->is_deleted) + ccs_set_string(head, "# This is a deleted domain.\n"); + return true; +} + +/** + * ccs_update_acl - Update "struct ccs_acl_info" entry. + * + * @size: Size of new entry in bytes. + * @param: Pointer to "struct ccs_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_update_acl(const int size, struct ccs_acl_param *param) +{ + struct ccs_acl_info *new_entry = ¶m->e.acl_info; + const bool is_delete = param->is_delete; + int error = is_delete ? -ENOENT : -ENOMEM; + struct ccs_acl_info *entry; + struct list_head * const list = param->list; + BUG_ON(size < sizeof(*entry)); + if (param->data[0]) { + new_entry->cond = ccs_get_condition(param); + if (!new_entry->cond) + return -EINVAL; + /* + * Domain transition preference is allowed for only + * "file execute"/"task auto_execute_handler"/ + * "task denied_auto_execute_handler" entries. + */ + if (new_entry->cond->exec_transit && + !(new_entry->type == CCS_TYPE_PATH_ACL && + new_entry->perm == 1 << CCS_TYPE_EXECUTE) +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + && new_entry->type != CCS_TYPE_AUTO_EXECUTE_HANDLER && + new_entry->type != CCS_TYPE_DENIED_EXECUTE_HANDLER +#endif + ) + return -EINVAL; + } + if (mutex_lock_interruptible(&ccs_policy_lock)) + return -ENOMEM; + list_for_each_entry_srcu(entry, list, list, &ccs_ss) { + if (entry->is_deleted == CCS_GC_IN_PROGRESS) + continue; + if (entry->type != new_entry->type || + entry->cond != new_entry->cond || + memcmp(entry + 1, new_entry + 1, size - sizeof(*entry))) + continue; + if (is_delete) + entry->perm &= ~new_entry->perm; + else + entry->perm |= new_entry->perm; + entry->is_deleted = !entry->perm; + error = 0; + break; + } + if (error && !is_delete) { + entry = ccs_commit_ok(new_entry, size); + if (entry) { + list_add_tail_rcu(&entry->list, list); + error = 0; + } + } + mutex_unlock(&ccs_policy_lock); + return error; +} + +/** + * ccs_permstr - Find permission keywords. + * + * @string: String representation for permissions in foo/bar/buz format. + * @keyword: Keyword to find from @string/ + * + * Returns ture if @keyword was found in @string, false otherwise. + * + * This function assumes that strncmp(w1, w2, strlen(w1)) != 0 if w1 != w2. + */ +static bool ccs_permstr(const char *string, const char *keyword) +{ + const char *cp = strstr(string, keyword); + if (cp) + return cp == string || *(cp - 1) == '/'; + return false; +} + +/** + * ccs_write_task - Update task related list. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_write_task(struct ccs_acl_param *param) +{ + int error; + const bool is_auto = ccs_str_starts(¶m->data, + "auto_domain_transition "); + if (!is_auto && !ccs_str_starts(¶m->data, + "manual_domain_transition ")) { +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + struct ccs_handler_acl *e = ¶m->e.handler_acl; + char *handler; + if (ccs_str_starts(¶m->data, "auto_execute_handler ")) + e->head.type = CCS_TYPE_AUTO_EXECUTE_HANDLER; + else if (ccs_str_starts(¶m->data, + "denied_execute_handler ")) + e->head.type = CCS_TYPE_DENIED_EXECUTE_HANDLER; + else + return -EINVAL; + handler = ccs_read_token(param); + if (!ccs_correct_path(handler)) + return -EINVAL; + e->handler = ccs_get_name(handler); + if (!e->handler) + return -ENOMEM; + if (e->handler->is_patterned) + return -EINVAL; /* No patterns allowed. */ + return ccs_update_acl(sizeof(*e), param); +#else + error = -EINVAL; +#endif + } else { +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + struct ccs_task_acl *e = ¶m->e.task_acl; + e->head.type = is_auto ? + CCS_TYPE_AUTO_TASK_ACL : CCS_TYPE_MANUAL_TASK_ACL; + e->domainname = ccs_get_domainname(param); + if (!e->domainname) + return -EINVAL; + return ccs_update_acl(sizeof(*e), param); +#else + error = -EINVAL; +#endif + } + return error; +} + +#ifdef CONFIG_CCSECURITY_NETWORK + +/** + * ccs_write_inet_network - Write "struct ccs_inet_acl" list. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_write_inet_network(struct ccs_acl_param *param) +{ + struct ccs_inet_acl *e = ¶m->e.inet_acl; + u8 type; + const char *protocol = ccs_read_token(param); + const char *operation = ccs_read_token(param); + e->head.type = CCS_TYPE_INET_ACL; + for (type = 0; type < CCS_SOCK_MAX; type++) + if (!strcmp(protocol, ccs_proto_keyword[type])) + break; + if (type == CCS_SOCK_MAX) + return -EINVAL; + e->protocol = type; + e->head.perm = 0; + for (type = 0; type < CCS_MAX_NETWORK_OPERATION; type++) + if (ccs_permstr(operation, ccs_socket_keyword[type])) + e->head.perm |= 1 << type; + if (!e->head.perm) + return -EINVAL; + if (param->data[0] == '@') { + param->data++; + e->address.group = ccs_get_group(param, CCS_ADDRESS_GROUP); + if (!e->address.group) + return -ENOMEM; + } else { + if (!ccs_parse_ipaddr_union(param, &e->address)) + return -EINVAL; + } + if (!ccs_parse_number_union(param, &e->port) || + e->port.values[1] > 65535) + return -EINVAL; + return ccs_update_acl(sizeof(*e), param); +} + +/** + * ccs_write_unix_network - Write "struct ccs_unix_acl" list. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_write_unix_network(struct ccs_acl_param *param) +{ + struct ccs_unix_acl *e = ¶m->e.unix_acl; + u8 type; + const char *protocol = ccs_read_token(param); + const char *operation = ccs_read_token(param); + e->head.type = CCS_TYPE_UNIX_ACL; + for (type = 0; type < CCS_SOCK_MAX; type++) + if (!strcmp(protocol, ccs_proto_keyword[type])) + break; + if (type == CCS_SOCK_MAX) + return -EINVAL; + e->protocol = type; + e->head.perm = 0; + for (type = 0; type < CCS_MAX_NETWORK_OPERATION; type++) + if (ccs_permstr(operation, ccs_socket_keyword[type])) + e->head.perm |= 1 << type; + if (!e->head.perm) + return -EINVAL; + if (!ccs_parse_name_union(param, &e->name)) + return -EINVAL; + return ccs_update_acl(sizeof(*e), param); +} + +#endif + +/** + * ccs_write_file - Update file related list. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_write_file(struct ccs_acl_param *param) +{ + u16 perm = 0; + u8 type; + const char *operation = ccs_read_token(param); + for (type = 0; type < CCS_MAX_PATH_OPERATION; type++) + if (ccs_permstr(operation, ccs_path_keyword[type])) + perm |= 1 << type; + if (perm) { + struct ccs_path_acl *e = ¶m->e.path_acl; + e->head.type = CCS_TYPE_PATH_ACL; + e->head.perm = perm; + if (!ccs_parse_name_union(param, &e->name)) + return -EINVAL; + return ccs_update_acl(sizeof(*e), param); + } + for (type = 0; type < CCS_MAX_PATH2_OPERATION; type++) + if (ccs_permstr(operation, ccs_mac_keywords[ccs_pp2mac[type]])) + perm |= 1 << type; + if (perm) { + struct ccs_path2_acl *e = ¶m->e.path2_acl; + e->head.type = CCS_TYPE_PATH2_ACL; + e->head.perm = perm; + if (!ccs_parse_name_union(param, &e->name1) || + !ccs_parse_name_union(param, &e->name2)) + return -EINVAL; + return ccs_update_acl(sizeof(*e), param); + } + for (type = 0; type < CCS_MAX_PATH_NUMBER_OPERATION; type++) + if (ccs_permstr(operation, ccs_mac_keywords[ccs_pn2mac[type]])) + perm |= 1 << type; + if (perm) { + struct ccs_path_number_acl *e = ¶m->e.path_number_acl; + e->head.type = CCS_TYPE_PATH_NUMBER_ACL; + e->head.perm = perm; + if (!ccs_parse_name_union(param, &e->name) || + !ccs_parse_number_union(param, &e->number)) + return -EINVAL; + return ccs_update_acl(sizeof(*e), param); + } + for (type = 0; type < CCS_MAX_MKDEV_OPERATION; type++) + if (ccs_permstr(operation, + ccs_mac_keywords[ccs_pnnn2mac[type]])) + perm |= 1 << type; + if (perm) { + struct ccs_mkdev_acl *e = ¶m->e.mkdev_acl; + e->head.type = CCS_TYPE_MKDEV_ACL; + e->head.perm = perm; + if (!ccs_parse_name_union(param, &e->name) || + !ccs_parse_number_union(param, &e->mode) || + !ccs_parse_number_union(param, &e->major) || + !ccs_parse_number_union(param, &e->minor)) + return -EINVAL; + return ccs_update_acl(sizeof(*e), param); + } + if (ccs_permstr(operation, ccs_mac_keywords[CCS_MAC_FILE_MOUNT])) { + struct ccs_mount_acl *e = ¶m->e.mount_acl; + e->head.type = CCS_TYPE_MOUNT_ACL; + if (!ccs_parse_name_union(param, &e->dev_name) || + !ccs_parse_name_union(param, &e->dir_name) || + !ccs_parse_name_union(param, &e->fs_type) || + !ccs_parse_number_union(param, &e->flags)) + return -EINVAL; + return ccs_update_acl(sizeof(*e), param); + } + return -EINVAL; +} + +#ifdef CONFIG_CCSECURITY_MISC + +/** + * ccs_write_misc - Update environment variable list. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_write_misc(struct ccs_acl_param *param) +{ + if (ccs_str_starts(¶m->data, "env ")) { + struct ccs_env_acl *e = ¶m->e.env_acl; + const char *data = ccs_read_token(param); + e->head.type = CCS_TYPE_ENV_ACL; + if (!ccs_correct_word(data) || strchr(data, '=')) + return -EINVAL; + e->env = ccs_get_name(data); + if (!e->env) + return -ENOMEM; + return ccs_update_acl(sizeof(*e), param); + } + return -EINVAL; +} + +#endif + +#ifdef CONFIG_CCSECURITY_IPC + +/** + * ccs_write_ipc - Update "struct ccs_signal_acl" list. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_write_ipc(struct ccs_acl_param *param) +{ + struct ccs_signal_acl *e = ¶m->e.signal_acl; + e->head.type = CCS_TYPE_SIGNAL_ACL; + if (!ccs_parse_number_union(param, &e->sig)) + return -EINVAL; + e->domainname = ccs_get_domainname(param); + if (!e->domainname) + return -EINVAL; + return ccs_update_acl(sizeof(*e), param); +} + +#endif + +#ifdef CONFIG_CCSECURITY_CAPABILITY + +/** + * ccs_write_capability - Write "struct ccs_capability_acl" list. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_write_capability(struct ccs_acl_param *param) +{ + struct ccs_capability_acl *e = ¶m->e.capability_acl; + const char *operation = ccs_read_token(param); + u8 type; + e->head.type = CCS_TYPE_CAPABILITY_ACL; + for (type = 0; type < CCS_MAX_CAPABILITY_INDEX; type++) { + if (strcmp(operation, ccs_mac_keywords[ccs_c2mac[type]])) + continue; + e->operation = type; + return ccs_update_acl(sizeof(*e), param); + } + return -EINVAL; +} + +#endif + +/** + * ccs_write_acl - Write "struct ccs_acl_info" list. + * + * @ns: Pointer to "struct ccs_policy_namespace". + * @list: Pointer to "struct list_head". + * @data: Policy to be interpreted. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_write_acl(struct ccs_policy_namespace *ns, + struct list_head *list, char *data, + const bool is_delete) +{ + struct ccs_acl_param param = { + .ns = ns, + .list = list, + .data = data, + .is_delete = is_delete, + }; + static const struct { + const char *keyword; + int (*write) (struct ccs_acl_param *); + } ccs_callback[] = { + { "file ", ccs_write_file }, +#ifdef CONFIG_CCSECURITY_NETWORK + { "network inet ", ccs_write_inet_network }, + { "network unix ", ccs_write_unix_network }, +#endif +#ifdef CONFIG_CCSECURITY_MISC + { "misc ", ccs_write_misc }, +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + { "capability ", ccs_write_capability }, +#endif +#ifdef CONFIG_CCSECURITY_IPC + { "ipc signal ", ccs_write_ipc }, +#endif + { "task ", ccs_write_task }, + }; + u8 i; + /* Forced zero clear for using memcmp() at ccs_update_acl(). */ + memset(¶m.e, 0, sizeof(param.e)); + param.e.acl_info.perm = 1; + for (i = 0; i < ARRAY_SIZE(ccs_callback); i++) { + int error; + if (!ccs_str_starts(¶m.data, ccs_callback[i].keyword)) + continue; + error = ccs_callback[i].write(¶m); + ccs_del_acl(¶m.e.acl_info.list); + return error; + } + return -EINVAL; +} + +/** + * ccs_delete_domain - Delete a domain. + * + * @domainname: The name of domain. + * + * Returns 0. + */ +static int ccs_delete_domain(char *domainname) +{ + struct ccs_domain_info *domain; + struct ccs_path_info name; + name.name = domainname; + ccs_fill_path_info(&name); + if (mutex_lock_interruptible(&ccs_policy_lock)) + return 0; + /* Is there an active domain? */ + list_for_each_entry_srcu(domain, &ccs_domain_list, list, &ccs_ss) { + /* Never delete ccs_kernel_domain. */ + if (domain == &ccs_kernel_domain) + continue; + if (domain->is_deleted || + ccs_pathcmp(domain->domainname, &name)) + continue; + domain->is_deleted = true; + break; + } + mutex_unlock(&ccs_policy_lock); + return 0; +} + +/** + * ccs_write_domain - Write domain policy. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_write_domain(struct ccs_io_buffer *head) +{ + char *data = head->write_buf; + struct ccs_policy_namespace *ns; + struct ccs_domain_info *domain = head->w.domain; + const bool is_delete = head->w.is_delete; + const bool is_select = !is_delete && ccs_str_starts(&data, "select "); + unsigned int idx; + if (*data == '<') { + domain = NULL; + if (is_delete) + ccs_delete_domain(data); + else if (is_select) + domain = ccs_find_domain(data); + else + domain = ccs_assign_domain(data, false); + head->w.domain = domain; + return 0; + } + if (!domain) + return -EINVAL; + ns = domain->ns; + if (sscanf(data, "use_profile %u\n", &idx) == 1 && + idx < CCS_MAX_PROFILES) { + if (!ccs_policy_loaded || ns->profile_ptr[(u8) idx]) + if (!is_delete) + domain->profile = (u8) idx; + return 0; + } + if (sscanf(data, "use_group %u\n", &idx) == 1 && + idx < CCS_MAX_ACL_GROUPS) { + if (!is_delete) + set_bit(idx, domain->group); + else + clear_bit(idx, domain->group); + return 0; + } + for (idx = 0; idx < CCS_MAX_DOMAIN_INFO_FLAGS; idx++) { + const char *cp = ccs_dif[idx]; + if (strncmp(data, cp, strlen(cp) - 1)) + continue; + domain->flags[idx] = !is_delete; + return 0; + } + return ccs_write_acl(ns, &domain->acl_info_list, data, is_delete); +} + +/** + * ccs_print_name_union - Print a ccs_name_union. + * + * @head: Pointer to "struct ccs_io_buffer". + * @ptr: Pointer to "struct ccs_name_union". + * + * Returns nothing. + */ +static void ccs_print_name_union(struct ccs_io_buffer *head, + const struct ccs_name_union *ptr) +{ + ccs_set_space(head); + if (!ccs_print_group(head, ptr->group)) + ccs_set_string(head, ptr->filename->name); +} + +/** + * ccs_print_name_union_quoted - Print a ccs_name_union with a quote. + * + * @head: Pointer to "struct ccs_io_buffer". + * @ptr: Pointer to "struct ccs_name_union". + * + * Returns nothing. + */ +static void ccs_print_name_union_quoted(struct ccs_io_buffer *head, + const struct ccs_name_union *ptr) +{ + if (!ccs_print_group(head, ptr->group)) { + ccs_set_string(head, "\""); + ccs_set_string(head, ptr->filename->name); + ccs_set_string(head, "\""); + } +} + +/** + * ccs_print_number_union_nospace - Print a ccs_number_union without a space. + * + * @head: Pointer to "struct ccs_io_buffer". + * @ptr: Pointer to "struct ccs_number_union". + * + * Returns nothing. + */ +static void ccs_print_number_union_nospace(struct ccs_io_buffer *head, + const struct ccs_number_union *ptr) +{ + if (!ccs_print_group(head, ptr->group)) { + int i; + unsigned long min = ptr->values[0]; + const unsigned long max = ptr->values[1]; + u8 min_type = ptr->value_type[0]; + const u8 max_type = ptr->value_type[1]; + char buffer[128]; + buffer[0] = '\0'; + for (i = 0; i < 2; i++) { + switch (min_type) { + case CCS_VALUE_TYPE_HEXADECIMAL: + ccs_addprintf(buffer, sizeof(buffer), "0x%lX", + min); + break; + case CCS_VALUE_TYPE_OCTAL: + ccs_addprintf(buffer, sizeof(buffer), "0%lo", + min); + break; + default: + ccs_addprintf(buffer, sizeof(buffer), "%lu", + min); + break; + } + if (min == max && min_type == max_type) + break; + ccs_addprintf(buffer, sizeof(buffer), "-"); + min_type = max_type; + min = max; + } + ccs_io_printf(head, "%s", buffer); + } +} + +/** + * ccs_print_number_union - Print a ccs_number_union. + * + * @head: Pointer to "struct ccs_io_buffer". + * @ptr: Pointer to "struct ccs_number_union". + * + * Returns nothing. + */ +static void ccs_print_number_union(struct ccs_io_buffer *head, + const struct ccs_number_union *ptr) +{ + ccs_set_space(head); + ccs_print_number_union_nospace(head, ptr); +} + +/** + * ccs_print_condition - Print condition part. + * + * @head: Pointer to "struct ccs_io_buffer". + * @cond: Pointer to "struct ccs_condition". + * + * Returns true on success, false otherwise. + */ +static bool ccs_print_condition(struct ccs_io_buffer *head, + const struct ccs_condition *cond) +{ + switch (head->r.cond_step) { + case 0: + head->r.cond_index = 0; + head->r.cond_step++; + if (cond->transit && cond->exec_transit) { + ccs_set_space(head); + ccs_set_string(head, cond->transit->name); + } + /* fall through */ + case 1: + { + const u16 condc = cond->condc; + const struct ccs_condition_element *condp = + (typeof(condp)) (cond + 1); + const struct ccs_number_union *numbers_p = + (typeof(numbers_p)) (condp + condc); + const struct ccs_name_union *names_p = + (typeof(names_p)) + (numbers_p + cond->numbers_count); + const struct ccs_argv *argv = + (typeof(argv)) (names_p + cond->names_count); + const struct ccs_envp *envp = + (typeof(envp)) (argv + cond->argc); + u16 skip; + for (skip = 0; skip < head->r.cond_index; skip++) { + const u8 left = condp->left; + const u8 right = condp->right; + condp++; + switch (left) { + case CCS_ARGV_ENTRY: + argv++; + continue; + case CCS_ENVP_ENTRY: + envp++; + continue; + case CCS_NUMBER_UNION: + numbers_p++; + break; + } + switch (right) { + case CCS_NAME_UNION: + names_p++; + break; + case CCS_NUMBER_UNION: + numbers_p++; + break; + } + } + while (head->r.cond_index < condc) { + const u8 match = condp->equals; + const u8 left = condp->left; + const u8 right = condp->right; + if (!ccs_flush(head)) + return false; + condp++; + head->r.cond_index++; + ccs_set_space(head); + switch (left) { + case CCS_ARGV_ENTRY: + ccs_io_printf(head, + "exec.argv[%lu]%s=\"", + argv->index, + argv->is_not ? "!" : ""); + ccs_set_string(head, + argv->value->name); + ccs_set_string(head, "\""); + argv++; + continue; + case CCS_ENVP_ENTRY: + ccs_set_string(head, "exec.envp[\""); + ccs_set_string(head, envp->name->name); + ccs_io_printf(head, "\"]%s=", + envp->is_not ? "!" : ""); + if (envp->value) { + ccs_set_string(head, "\""); + ccs_set_string(head, envp-> + value->name); + ccs_set_string(head, "\""); + } else { + ccs_set_string(head, "NULL"); + } + envp++; + continue; + case CCS_NUMBER_UNION: + ccs_print_number_union_nospace + (head, numbers_p++); + break; + default: + ccs_set_string(head, + ccs_condition_keyword[left]); + break; + } + ccs_set_string(head, match ? "=" : "!="); + switch (right) { + case CCS_NAME_UNION: + ccs_print_name_union_quoted + (head, names_p++); + break; + case CCS_NUMBER_UNION: + ccs_print_number_union_nospace + (head, numbers_p++); + break; + default: + ccs_set_string(head, + ccs_condition_keyword[right]); + break; + } + } + } + head->r.cond_step++; + /* fall through */ + case 2: + if (!ccs_flush(head)) + break; + head->r.cond_step++; + /* fall through */ + case 3: + if (cond->grant_log != CCS_GRANTLOG_AUTO) + ccs_io_printf(head, " grant_log=%s", + ccs_yesno(cond->grant_log == + CCS_GRANTLOG_YES)); + if (cond->transit && !cond->exec_transit) { + const char *name = cond->transit->name; + ccs_set_string(head, " auto_domain_transition=\""); + ccs_set_string(head, name); + ccs_set_string(head, "\""); + } + ccs_set_lf(head); + return true; + } + return false; +} + +/** + * ccs_set_group - Print "acl_group " header keyword and category name. + * + * @head: Pointer to "struct ccs_io_buffer". + * @category: Category name. + * + * Returns nothing. + */ +static void ccs_set_group(struct ccs_io_buffer *head, const char *category) +{ + if (head->type == CCS_EXCEPTION_POLICY) { + ccs_print_namespace(head); + ccs_io_printf(head, "acl_group %u ", head->r.acl_group_index); + } + ccs_set_string(head, category); +} + +/** + * ccs_print_entry - Print an ACL entry. + * + * @head: Pointer to "struct ccs_io_buffer". + * @acl: Pointer to an ACL entry. + * + * Returns true on success, false otherwise. + */ +static bool ccs_print_entry(struct ccs_io_buffer *head, + const struct ccs_acl_info *acl) +{ + const u8 acl_type = acl->type; + const bool may_trigger_transition = acl->cond && acl->cond->transit; + bool first = true; + u8 bit; + if (head->r.print_cond_part) + goto print_cond_part; + if (acl->is_deleted) + return true; + if (!ccs_flush(head)) + return false; + else if (acl_type == CCS_TYPE_PATH_ACL) { + struct ccs_path_acl *ptr + = container_of(acl, typeof(*ptr), head); + for (bit = 0; bit < CCS_MAX_PATH_OPERATION; bit++) { + if (!(acl->perm & (1 << bit))) + continue; + if (head->r.print_transition_related_only && + bit != CCS_TYPE_EXECUTE && !may_trigger_transition) + continue; + if (first) { + ccs_set_group(head, "file "); + first = false; + } else { + ccs_set_slash(head); + } + ccs_set_string(head, ccs_path_keyword[bit]); + } + if (first) + return true; + ccs_print_name_union(head, &ptr->name); +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + } else if (acl_type == CCS_TYPE_AUTO_EXECUTE_HANDLER || + acl_type == CCS_TYPE_DENIED_EXECUTE_HANDLER) { + struct ccs_handler_acl *ptr + = container_of(acl, typeof(*ptr), head); + ccs_set_group(head, "task "); + ccs_set_string(head, acl_type == CCS_TYPE_AUTO_EXECUTE_HANDLER + ? "auto_execute_handler " : + "denied_execute_handler "); + ccs_set_string(head, ptr->handler->name); +#endif +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + } else if (acl_type == CCS_TYPE_AUTO_TASK_ACL || + acl_type == CCS_TYPE_MANUAL_TASK_ACL) { + struct ccs_task_acl *ptr = + container_of(acl, typeof(*ptr), head); + ccs_set_group(head, "task "); + ccs_set_string(head, acl_type == CCS_TYPE_AUTO_TASK_ACL ? + "auto_domain_transition " : + "manual_domain_transition "); + ccs_set_string(head, ptr->domainname->name); +#endif + } else if (head->r.print_transition_related_only && + !may_trigger_transition) { + return true; + } else if (acl_type == CCS_TYPE_MKDEV_ACL) { + struct ccs_mkdev_acl *ptr = + container_of(acl, typeof(*ptr), head); + for (bit = 0; bit < CCS_MAX_MKDEV_OPERATION; bit++) { + if (!(acl->perm & (1 << bit))) + continue; + if (first) { + ccs_set_group(head, "file "); + first = false; + } else { + ccs_set_slash(head); + } + ccs_set_string(head, ccs_mac_keywords + [ccs_pnnn2mac[bit]]); + } + if (first) + return true; + ccs_print_name_union(head, &ptr->name); + ccs_print_number_union(head, &ptr->mode); + ccs_print_number_union(head, &ptr->major); + ccs_print_number_union(head, &ptr->minor); + } else if (acl_type == CCS_TYPE_PATH2_ACL) { + struct ccs_path2_acl *ptr = + container_of(acl, typeof(*ptr), head); + for (bit = 0; bit < CCS_MAX_PATH2_OPERATION; bit++) { + if (!(acl->perm & (1 << bit))) + continue; + if (first) { + ccs_set_group(head, "file "); + first = false; + } else { + ccs_set_slash(head); + } + ccs_set_string(head, ccs_mac_keywords + [ccs_pp2mac[bit]]); + } + if (first) + return true; + ccs_print_name_union(head, &ptr->name1); + ccs_print_name_union(head, &ptr->name2); + } else if (acl_type == CCS_TYPE_PATH_NUMBER_ACL) { + struct ccs_path_number_acl *ptr = + container_of(acl, typeof(*ptr), head); + for (bit = 0; bit < CCS_MAX_PATH_NUMBER_OPERATION; bit++) { + if (!(acl->perm & (1 << bit))) + continue; + if (first) { + ccs_set_group(head, "file "); + first = false; + } else { + ccs_set_slash(head); + } + ccs_set_string(head, ccs_mac_keywords + [ccs_pn2mac[bit]]); + } + if (first) + return true; + ccs_print_name_union(head, &ptr->name); + ccs_print_number_union(head, &ptr->number); +#ifdef CONFIG_CCSECURITY_MISC + } else if (acl_type == CCS_TYPE_ENV_ACL) { + struct ccs_env_acl *ptr = + container_of(acl, typeof(*ptr), head); + ccs_set_group(head, "misc env "); + ccs_set_string(head, ptr->env->name); +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + } else if (acl_type == CCS_TYPE_CAPABILITY_ACL) { + struct ccs_capability_acl *ptr = + container_of(acl, typeof(*ptr), head); + ccs_set_group(head, "capability "); + ccs_set_string(head, ccs_mac_keywords + [ccs_c2mac[ptr->operation]]); +#endif +#ifdef CONFIG_CCSECURITY_NETWORK + } else if (acl_type == CCS_TYPE_INET_ACL) { + struct ccs_inet_acl *ptr = + container_of(acl, typeof(*ptr), head); + for (bit = 0; bit < CCS_MAX_NETWORK_OPERATION; bit++) { + if (!(acl->perm & (1 << bit))) + continue; + if (first) { + ccs_set_group(head, "network inet "); + ccs_set_string(head, ccs_proto_keyword + [ptr->protocol]); + ccs_set_space(head); + first = false; + } else { + ccs_set_slash(head); + } + ccs_set_string(head, ccs_socket_keyword[bit]); + } + if (first) + return true; + ccs_set_space(head); + if (!ccs_print_group(head, ptr->address.group)) { + char buf[128]; + ccs_print_ip(buf, sizeof(buf), &ptr->address); + ccs_io_printf(head, "%s", buf); + } + ccs_print_number_union(head, &ptr->port); + } else if (acl_type == CCS_TYPE_UNIX_ACL) { + struct ccs_unix_acl *ptr = + container_of(acl, typeof(*ptr), head); + for (bit = 0; bit < CCS_MAX_NETWORK_OPERATION; bit++) { + if (!(acl->perm & (1 << bit))) + continue; + if (first) { + ccs_set_group(head, "network unix "); + ccs_set_string(head, ccs_proto_keyword + [ptr->protocol]); + ccs_set_space(head); + first = false; + } else { + ccs_set_slash(head); + } + ccs_set_string(head, ccs_socket_keyword[bit]); + } + if (first) + return true; + ccs_print_name_union(head, &ptr->name); +#endif +#ifdef CONFIG_CCSECURITY_IPC + } else if (acl_type == CCS_TYPE_SIGNAL_ACL) { + struct ccs_signal_acl *ptr = + container_of(acl, typeof(*ptr), head); + ccs_set_group(head, "ipc signal "); + ccs_print_number_union_nospace(head, &ptr->sig); + ccs_set_space(head); + ccs_set_string(head, ptr->domainname->name); +#endif + } else if (acl_type == CCS_TYPE_MOUNT_ACL) { + struct ccs_mount_acl *ptr = + container_of(acl, typeof(*ptr), head); + ccs_set_group(head, "file mount"); + ccs_print_name_union(head, &ptr->dev_name); + ccs_print_name_union(head, &ptr->dir_name); + ccs_print_name_union(head, &ptr->fs_type); + ccs_print_number_union(head, &ptr->flags); + } + if (acl->cond) { + head->r.print_cond_part = true; + head->r.cond_step = 0; + if (!ccs_flush(head)) + return false; +print_cond_part: + if (!ccs_print_condition(head, acl->cond)) + return false; + head->r.print_cond_part = false; + } else { + ccs_set_lf(head); + } + return true; +} + +/** + * ccs_read_acl - Read "struct ccs_acl_info" list. + * + * @head: Pointer to "struct ccs_io_buffer". + * @list: Pointer to "struct list_head". + * + * Returns true on success, false otherwise. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_read_acl(struct ccs_io_buffer *head, struct list_head *list) +{ + list_for_each_cookie(head->r.acl, list) { + struct ccs_acl_info *ptr = + list_entry(head->r.acl, typeof(*ptr), list); + if (!ccs_print_entry(head, ptr)) + return false; + } + head->r.acl = NULL; + return true; +} + +/** + * ccs_read_domain - Read domain policy. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + * + * Caller holds ccs_read_lock(). + */ +static void ccs_read_domain(struct ccs_io_buffer *head) +{ + if (head->r.eof) + return; + list_for_each_cookie(head->r.domain, &ccs_domain_list) { + struct ccs_domain_info *domain = + list_entry(head->r.domain, typeof(*domain), list); + switch (head->r.step) { + u8 i; + case 0: + if (domain->is_deleted && + !head->r.print_this_domain_only) + continue; + /* Print domainname and flags. */ + ccs_set_string(head, domain->domainname->name); + ccs_set_lf(head); + ccs_io_printf(head, "use_profile %u\n", + domain->profile); + for (i = 0; i < CCS_MAX_DOMAIN_INFO_FLAGS; i++) + if (domain->flags[i]) + ccs_set_string(head, ccs_dif[i]); + head->r.index = 0; + head->r.step++; + /* fall through */ + case 1: + while (head->r.index < CCS_MAX_ACL_GROUPS) { + i = head->r.index++; + if (!test_bit(i, domain->group)) + continue; + ccs_io_printf(head, "use_group %u\n", i); + if (!ccs_flush(head)) + return; + } + head->r.index = 0; + head->r.step++; + ccs_set_lf(head); + /* fall through */ + case 2: + if (!ccs_read_acl(head, &domain->acl_info_list)) + return; + head->r.step++; + if (!ccs_set_lf(head)) + return; + /* fall through */ + case 3: + head->r.step = 0; + if (head->r.print_this_domain_only) + goto done; + } + } +done: + head->r.eof = true; +} + +/** + * ccs_write_pid - Specify PID to obtain domainname. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns 0. + */ +static int ccs_write_pid(struct ccs_io_buffer *head) +{ + head->r.eof = false; + return 0; +} + +/** + * ccs_read_pid - Read information of a process. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns the domainname which the specified PID is in or + * process information of the specified PID on success, + * empty string otherwise. + * + * Caller holds ccs_read_lock(). + */ +static void ccs_read_pid(struct ccs_io_buffer *head) +{ + char *buf = head->write_buf; + bool task_info = false; + bool global_pid = false; + unsigned int pid; + struct task_struct *p; + struct ccs_domain_info *domain = NULL; + u32 ccs_flags = 0; + /* Accessing write_buf is safe because head->io_sem is held. */ + if (!buf) { + head->r.eof = true; + return; /* Do nothing if open(O_RDONLY). */ + } + if (head->r.w_pos || head->r.eof) + return; + head->r.eof = true; + if (ccs_str_starts(&buf, "info ")) + task_info = true; + if (ccs_str_starts(&buf, "global-pid ")) + global_pid = true; + pid = (unsigned int) simple_strtoul(buf, NULL, 10); + ccs_tasklist_lock(); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + if (global_pid) + p = ccsecurity_exports.find_task_by_pid_ns(pid, &init_pid_ns); + else + p = ccsecurity_exports.find_task_by_vpid(pid); +#else + p = find_task_by_pid(pid); +#endif + if (p) { + domain = ccs_task_domain(p); + ccs_flags = ccs_task_flags(p); + } + ccs_tasklist_unlock(); + if (!domain) + return; + if (!task_info) { + ccs_io_printf(head, "%u %u ", pid, domain->profile); + ccs_set_string(head, domain->domainname->name); + } else { + ccs_io_printf(head, "%u manager=%s execute_handler=%s ", pid, + ccs_yesno(ccs_flags & + CCS_TASK_IS_MANAGER), + ccs_yesno(ccs_flags & + CCS_TASK_IS_EXECUTE_HANDLER)); + } +} + +/** + * ccs_write_group - Write "struct ccs_path_group"/"struct ccs_number_group"/"struct ccs_address_group" list. + * + * @param: Pointer to "struct ccs_acl_param". + * @type: Type of this group. + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_write_group(struct ccs_acl_param *param, const u8 type) +{ + struct ccs_group *group = ccs_get_group(param, type); + int error = -EINVAL; + if (!group) + return -ENOMEM; + param->list = &group->member_list; + if (type == CCS_PATH_GROUP) { + struct ccs_path_group *e = ¶m->e.path_group; + e->member_name = ccs_get_name(ccs_read_token(param)); + if (!e->member_name) { + error = -ENOMEM; + goto out; + } + error = ccs_update_policy(sizeof(*e), param); + ccs_put_name(e->member_name); + } else if (type == CCS_NUMBER_GROUP) { + struct ccs_number_group *e = ¶m->e.number_group; + if (param->data[0] == '@' || + !ccs_parse_number_union(param, &e->number)) + goto out; + error = ccs_update_policy(sizeof(*e), param); +#ifdef CONFIG_CCSECURITY_NETWORK + } else { + struct ccs_address_group *e = ¶m->e.address_group; + if (param->data[0] == '@' || + !ccs_parse_ipaddr_union(param, &e->address)) + goto out; + error = ccs_update_policy(sizeof(*e), param); +#endif + } +out: + ccs_put_group(group); + return error; +} + +#ifdef CONFIG_CCSECURITY_PORTRESERVE +/** + * ccs_lport_reserved - Check whether local port is reserved or not. + * + * @port: Port number. + * + * Returns true if local port is reserved, false otherwise. + */ +static bool __ccs_lport_reserved(const u16 port) +{ + return ccs_reserved_port_map[port >> 3] & (1 << (port & 7)) + ? true : false; +} + +/** + * ccs_write_reserved_port - Update "struct ccs_reserved" list. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_write_reserved_port(struct ccs_acl_param *param) +{ + struct ccs_reserved *e = ¶m->e.reserved; + struct ccs_policy_namespace *ns = param->ns; + int error; + u8 *tmp; + if (param->data[0] == '@' || + !ccs_parse_number_union(param, &e->port) || + e->port.values[1] > 65535 || param->data[0]) + return -EINVAL; + param->list = &ns->policy_list[CCS_ID_RESERVEDPORT]; + error = ccs_update_policy(sizeof(*e), param); + if (error) + return error; + tmp = kzalloc(sizeof(ccs_reserved_port_map), CCS_GFP_FLAGS); + if (!tmp) + return -ENOMEM; + list_for_each_entry_srcu(ns, &ccs_namespace_list, namespace_list, + &ccs_ss) { + struct ccs_reserved *ptr; + struct list_head *list = &ns->policy_list[CCS_ID_RESERVEDPORT]; + list_for_each_entry_srcu(ptr, list, head.list, &ccs_ss) { + unsigned int port; + if (ptr->head.is_deleted) + continue; + for (port = ptr->port.values[0]; + port <= ptr->port.values[1]; port++) + tmp[port >> 3] |= 1 << (port & 7); + } + } + memmove(ccs_reserved_port_map, tmp, sizeof(ccs_reserved_port_map)); + kfree(tmp); + /* + * Since this feature is no-op by default, we don't need to register + * this callback hook unless the first entry is added. + */ + ccsecurity_ops.lport_reserved = __ccs_lport_reserved; + return 0; +} +#endif + +/** + * ccs_write_aggregator - Write "struct ccs_aggregator" list. + * + * @param: Pointer to "struct ccs_acl_param". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_write_aggregator(struct ccs_acl_param *param) +{ + struct ccs_aggregator *e = ¶m->e.aggregator; + int error = param->is_delete ? -ENOENT : -ENOMEM; + const char *original_name = ccs_read_token(param); + const char *aggregated_name = ccs_read_token(param); + if (!ccs_correct_word(original_name) || + !ccs_correct_path(aggregated_name)) + return -EINVAL; + e->original_name = ccs_get_name(original_name); + e->aggregated_name = ccs_get_name(aggregated_name); + if (!e->original_name || !e->aggregated_name || + e->aggregated_name->is_patterned) /* No patterns allowed. */ + goto out; + param->list = ¶m->ns->policy_list[CCS_ID_AGGREGATOR]; + error = ccs_update_policy(sizeof(*e), param); +out: + ccs_put_name(e->original_name); + ccs_put_name(e->aggregated_name); + return error; +} + +/** + * ccs_write_transition_control - Write "struct ccs_transition_control" list. + * + * @param: Pointer to "struct ccs_acl_param". + * @type: Type of this entry. + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_write_transition_control(struct ccs_acl_param *param, + const u8 type) +{ + struct ccs_transition_control *e = ¶m->e.transition_control; + int error = param->is_delete ? -ENOENT : -ENOMEM; + char *program = param->data; + char *domainname = strstr(program, " from "); + e->type = type; + if (domainname) { + *domainname = '\0'; + domainname += 6; + } else if (type == CCS_TRANSITION_CONTROL_NO_KEEP || + type == CCS_TRANSITION_CONTROL_KEEP) { + domainname = program; + program = NULL; + } + if (program && strcmp(program, "any")) { + if (!ccs_correct_path(program)) + return -EINVAL; + e->program = ccs_get_name(program); + if (!e->program) + goto out; + } + if (domainname && strcmp(domainname, "any")) { + if (!ccs_correct_domain(domainname)) { + if (!ccs_correct_path(domainname)) + goto out; + e->is_last_name = true; + } + e->domainname = ccs_get_name(domainname); + if (!e->domainname) + goto out; + } + param->list = ¶m->ns->policy_list[CCS_ID_TRANSITION_CONTROL]; + error = ccs_update_policy(sizeof(*e), param); +out: + ccs_put_name(e->domainname); + ccs_put_name(e->program); + return error; +} + +/** + * ccs_write_exception - Write exception policy. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_write_exception(struct ccs_io_buffer *head) +{ + const bool is_delete = head->w.is_delete; + struct ccs_acl_param param = { + .ns = head->w.ns, + .is_delete = is_delete, + .data = head->write_buf, + }; + u8 i; + /* Forced zero clear for using memcmp() at ccs_update_policy(). */ + memset(¶m.e, 0, sizeof(param.e)); + if (ccs_str_starts(¶m.data, "aggregator ")) + return ccs_write_aggregator(¶m); +#ifdef CONFIG_CCSECURITY_PORTRESERVE + if (ccs_str_starts(¶m.data, "deny_autobind ")) + return ccs_write_reserved_port(¶m); +#endif + for (i = 0; i < CCS_MAX_TRANSITION_TYPE; i++) + if (ccs_str_starts(¶m.data, ccs_transition_type[i])) + return ccs_write_transition_control(¶m, i); + for (i = 0; i < CCS_MAX_GROUP; i++) + if (ccs_str_starts(¶m.data, ccs_group_name[i])) + return ccs_write_group(¶m, i); + if (ccs_str_starts(¶m.data, "acl_group ")) { + unsigned int group; + char *data; + group = simple_strtoul(param.data, &data, 10); + if (group < CCS_MAX_ACL_GROUPS && *data++ == ' ') + return ccs_write_acl(head->w.ns, + &head->w.ns->acl_group[group], + data, is_delete); + } + return -EINVAL; +} + +/** + * ccs_read_group - Read "struct ccs_path_group"/"struct ccs_number_group"/"struct ccs_address_group" list. + * + * @head: Pointer to "struct ccs_io_buffer". + * @idx: Index number. + * + * Returns true on success, false otherwise. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_read_group(struct ccs_io_buffer *head, const int idx) +{ + struct ccs_policy_namespace *ns = container_of(head->r.ns, typeof(*ns), + namespace_list); + struct list_head *list = &ns->group_list[idx]; + list_for_each_cookie(head->r.group, list) { + struct ccs_group *group = + list_entry(head->r.group, typeof(*group), head.list); + list_for_each_cookie(head->r.acl, &group->member_list) { + struct ccs_acl_head *ptr = + list_entry(head->r.acl, typeof(*ptr), list); + if (ptr->is_deleted) + continue; + if (!ccs_flush(head)) + return false; + ccs_print_namespace(head); + ccs_set_string(head, ccs_group_name[idx]); + ccs_set_string(head, group->group_name->name); + if (idx == CCS_PATH_GROUP) { + ccs_set_space(head); + ccs_set_string(head, container_of + (ptr, struct ccs_path_group, + head)->member_name->name); + } else if (idx == CCS_NUMBER_GROUP) { + ccs_print_number_union(head, &container_of + (ptr, struct ccs_number_group, + head)->number); +#ifdef CONFIG_CCSECURITY_NETWORK + } else if (idx == CCS_ADDRESS_GROUP) { + char buffer[128]; + struct ccs_address_group *member = + container_of(ptr, typeof(*member), + head); + ccs_print_ip(buffer, sizeof(buffer), + &member->address); + ccs_io_printf(head, " %s", buffer); +#endif + } + ccs_set_lf(head); + } + head->r.acl = NULL; + } + head->r.group = NULL; + return true; +} + +/** + * ccs_read_policy - Read "struct ccs_..._entry" list. + * + * @head: Pointer to "struct ccs_io_buffer". + * @idx: Index number. + * + * Returns true on success, false otherwise. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_read_policy(struct ccs_io_buffer *head, const int idx) +{ + struct ccs_policy_namespace *ns = container_of(head->r.ns, typeof(*ns), + namespace_list); + struct list_head *list = &ns->policy_list[idx]; + list_for_each_cookie(head->r.acl, list) { + struct ccs_acl_head *acl = + container_of(head->r.acl, typeof(*acl), list); + if (acl->is_deleted) + continue; + if (head->r.print_transition_related_only && + idx != CCS_ID_TRANSITION_CONTROL) + continue; + if (!ccs_flush(head)) + return false; + switch (idx) { + case CCS_ID_TRANSITION_CONTROL: + { + struct ccs_transition_control *ptr = + container_of(acl, typeof(*ptr), head); + ccs_print_namespace(head); + ccs_set_string(head, + ccs_transition_type[ptr->type]); + ccs_set_string(head, ptr->program ? + ptr->program->name : "any"); + ccs_set_string(head, " from "); + ccs_set_string(head, ptr->domainname ? + ptr->domainname->name : "any"); + } + break; + case CCS_ID_AGGREGATOR: + { + struct ccs_aggregator *ptr = + container_of(acl, typeof(*ptr), head); + ccs_print_namespace(head); + ccs_set_string(head, "aggregator "); + ccs_set_string(head, ptr->original_name->name); + ccs_set_space(head); + ccs_set_string(head, + ptr->aggregated_name->name); + } + break; +#ifdef CONFIG_CCSECURITY_PORTRESERVE + case CCS_ID_RESERVEDPORT: + { + struct ccs_reserved *ptr = + container_of(acl, typeof(*ptr), head); + ccs_print_namespace(head); + ccs_set_string(head, "deny_autobind "); + ccs_print_number_union_nospace(head, + &ptr->port); + } + break; +#endif + default: + continue; + } + ccs_set_lf(head); + } + head->r.acl = NULL; + return true; +} + +/** + * ccs_read_exception - Read exception policy. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + * + * Caller holds ccs_read_lock(). + */ +static void ccs_read_exception(struct ccs_io_buffer *head) +{ + struct ccs_policy_namespace *ns = container_of(head->r.ns, typeof(*ns), + namespace_list); + if (head->r.eof) + return; + while (head->r.step < CCS_MAX_POLICY && + ccs_read_policy(head, head->r.step)) + head->r.step++; + if (head->r.step < CCS_MAX_POLICY) + return; + while (head->r.step < CCS_MAX_POLICY + CCS_MAX_GROUP && + ccs_read_group(head, head->r.step - CCS_MAX_POLICY)) + head->r.step++; + if (head->r.step < CCS_MAX_POLICY + CCS_MAX_GROUP) + return; + while (head->r.step < CCS_MAX_POLICY + CCS_MAX_GROUP + + CCS_MAX_ACL_GROUPS) { + head->r.acl_group_index = + head->r.step - CCS_MAX_POLICY - CCS_MAX_GROUP; + if (!ccs_read_acl(head, &ns->acl_group + [head->r.acl_group_index])) + return; + head->r.step++; + } + head->r.eof = true; +} + +/** + * ccs_truncate - Truncate a line. + * + * @str: String to truncate. + * + * Returns length of truncated @str. + */ +static int ccs_truncate(char *str) +{ + char *start = str; + while (*(unsigned char *) str > (unsigned char) ' ') + str++; + *str = '\0'; + return strlen(start) + 1; +} + +/** + * ccs_add_entry - Add an ACL to current thread's domain. Used by learning mode. + * + * @header: Lines containing ACL. + * + * Returns nothing. + */ +static void ccs_add_entry(char *header) +{ + char *buffer; + char *realpath = NULL; + char *argv0 = NULL; + char *symlink = NULL; +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + char *handler; +#endif + char *cp = strchr(header, '\n'); + int len; + if (!cp) + return; + cp = strchr(cp + 1, '\n'); + if (!cp) + return; + *cp++ = '\0'; + len = strlen(cp) + 1; + /* strstr() will return NULL if ordering is wrong. */ + if (*cp == 'f') { + argv0 = strstr(header, " argv[]={ \""); + if (argv0) { + argv0 += 10; + len += ccs_truncate(argv0) + 14; + } + realpath = strstr(header, " exec={ realpath=\""); + if (realpath) { + realpath += 8; + len += ccs_truncate(realpath) + 6; + } + symlink = strstr(header, " symlink.target=\""); + if (symlink) + len += ccs_truncate(symlink + 1) + 1; + } +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + handler = strstr(header, "type=execute_handler"); + if (handler) + len += ccs_truncate(handler) + 6; +#endif + buffer = kmalloc(len, CCS_GFP_FLAGS); + if (!buffer) + return; + snprintf(buffer, len - 1, "%s", cp); +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + if (handler) + ccs_addprintf(buffer, len, " task.%s", handler); +#endif + if (realpath) + ccs_addprintf(buffer, len, " exec.%s", realpath); + if (argv0) + ccs_addprintf(buffer, len, " exec.argv[0]=%s", argv0); + if (symlink) + ccs_addprintf(buffer, len, "%s", symlink); + ccs_normalize_line(buffer); + { + struct ccs_domain_info *domain = ccs_current_domain(); + if (!ccs_write_acl(domain->ns, &domain->acl_info_list, + buffer, false)) + ccs_update_stat(CCS_STAT_POLICY_UPDATES); + } + kfree(buffer); +} + +/** + * ccs_domain_quota_ok - Check for domain's quota. + * + * @r: Pointer to "struct ccs_request_info". + * + * Returns true if the domain is not exceeded quota, false otherwise. + * + * Caller holds ccs_read_lock(). + */ +static bool ccs_domain_quota_ok(struct ccs_request_info *r) +{ + unsigned int count = 0; + struct ccs_domain_info * const domain = ccs_current_domain(); + struct ccs_acl_info *ptr; + if (r->mode != CCS_CONFIG_LEARNING) + return false; + if (!domain) + return true; + list_for_each_entry_srcu(ptr, &domain->acl_info_list, list, &ccs_ss) { + u16 perm; + u8 i; + if (ptr->is_deleted) + continue; + switch (ptr->type) { + case CCS_TYPE_PATH_ACL: + case CCS_TYPE_PATH2_ACL: + case CCS_TYPE_PATH_NUMBER_ACL: + case CCS_TYPE_MKDEV_ACL: +#ifdef CONFIG_CCSECURITY_NETWORK + case CCS_TYPE_INET_ACL: + case CCS_TYPE_UNIX_ACL: +#endif + perm = ptr->perm; + break; +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + case CCS_TYPE_AUTO_EXECUTE_HANDLER: + case CCS_TYPE_DENIED_EXECUTE_HANDLER: +#endif +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + case CCS_TYPE_AUTO_TASK_ACL: + case CCS_TYPE_MANUAL_TASK_ACL: +#endif + perm = 0; + break; + default: + perm = 1; + } + for (i = 0; i < 16; i++) + if (perm & (1 << i)) + count++; + } + if (count < ccs_profile(r->profile)->pref[CCS_PREF_MAX_LEARNING_ENTRY]) + return true; + if (!domain->flags[CCS_DIF_QUOTA_WARNED]) { + domain->flags[CCS_DIF_QUOTA_WARNED] = true; + /* r->granted = false; */ + ccs_write_log(r, "%s", ccs_dif[CCS_DIF_QUOTA_WARNED]); + printk(KERN_WARNING "WARNING: " + "Domain '%s' has too many ACLs to hold. " + "Stopped learning mode.\n", domain->domainname->name); + } + return false; +} + +/** + * ccs_supervisor - Ask for the supervisor's decision. + * + * @r: Pointer to "struct ccs_request_info". + * @fmt: The printf()'s format string, followed by parameters. + * + * Returns 0 if the supervisor decided to permit the access request which + * violated the policy in enforcing mode, CCS_RETRY_REQUEST if the supervisor + * decided to retry the access request which violated the policy in enforcing + * mode, 0 if it is not in enforcing mode, -EPERM otherwise. + */ +static int ccs_supervisor(struct ccs_request_info *r, const char *fmt, ...) +{ + va_list args; + int error; + int len; + static unsigned int ccs_serial; + struct ccs_query entry = { }; + bool quota_exceeded = false; + va_start(args, fmt); + len = vsnprintf((char *) &len, 1, fmt, args) + 1; + va_end(args); + /* Write /proc/ccs/audit. */ + va_start(args, fmt); + ccs_write_log2(r, len, fmt, args); + va_end(args); + /* Nothing more to do if granted. */ + if (r->granted) + return 0; + if (r->mode) + ccs_update_stat(r->mode); + switch (r->mode) { + int i; + struct ccs_profile *p; + case CCS_CONFIG_ENFORCING: + error = -EPERM; + if (atomic_read(&ccs_query_observers)) + break; + if (r->dont_sleep_on_enforce_error) + goto out; + p = ccs_profile(r->profile); + /* Check enforcing_penalty parameter. */ + for (i = 0; i < p->pref[CCS_PREF_ENFORCING_PENALTY]; i++) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ / 10); + } + goto out; + case CCS_CONFIG_LEARNING: + error = 0; + /* Check max_learning_entry parameter. */ + if (ccs_domain_quota_ok(r)) + break; + /* fall through */ + default: + return 0; + } + /* Get message. */ + va_start(args, fmt); + entry.query = ccs_init_log(r, len, fmt, args); + va_end(args); + if (!entry.query) + goto out; + entry.query_len = strlen(entry.query) + 1; + if (!error) { + ccs_add_entry(entry.query); + goto out; + } + len = ccs_round2(entry.query_len); + entry.domain = ccs_current_domain(); + spin_lock(&ccs_query_list_lock); + if (ccs_memory_quota[CCS_MEMORY_QUERY] && + ccs_memory_used[CCS_MEMORY_QUERY] + len + >= ccs_memory_quota[CCS_MEMORY_QUERY]) { + quota_exceeded = true; + } else { + entry.serial = ccs_serial++; + entry.retry = r->retry; + ccs_memory_used[CCS_MEMORY_QUERY] += len; + list_add_tail(&entry.list, &ccs_query_list); + } + spin_unlock(&ccs_query_list_lock); + if (quota_exceeded) + goto out; + /* Give 10 seconds for supervisor's opinion. */ + while (entry.timer < 10) { + wake_up_all(&ccs_query_wait); + if (wait_event_interruptible_timeout + (ccs_answer_wait, entry.answer || + !atomic_read(&ccs_query_observers), HZ)) + break; + else + entry.timer++; + } + spin_lock(&ccs_query_list_lock); + list_del(&entry.list); + ccs_memory_used[CCS_MEMORY_QUERY] -= len; + spin_unlock(&ccs_query_list_lock); + switch (entry.answer) { + case 3: /* Asked to retry by administrator. */ + error = CCS_RETRY_REQUEST; + r->retry++; + break; + case 1: + /* Granted by administrator. */ + error = 0; + break; + default: + /* Timed out or rejected by administrator. */ + break; + } +out: + kfree(entry.query); + return error; +} + +/** + * ccs_audit_log - Audit permission check log. + * + * @r: Pointer to "struct ccs_request_info". + * + * Returns return value of ccs_supervisor(). + */ +int ccs_audit_log(struct ccs_request_info *r) +{ + switch (r->param_type) { + u8 type; + char buf[48]; +#ifdef CONFIG_CCSECURITY_NETWORK + const u32 *address; +#endif + case CCS_TYPE_PATH_ACL: + return ccs_supervisor(r, "file %s %s\n", ccs_path_keyword + [r->param.path.operation], + r->param.path.filename->name); + case CCS_TYPE_PATH2_ACL: + return ccs_supervisor(r, "file %s %s %s\n", ccs_mac_keywords + [ccs_pp2mac[r->param.path2.operation]], + r->param.path2.filename1->name, + r->param.path2.filename2->name); + case CCS_TYPE_PATH_NUMBER_ACL: + type = r->param.path_number.operation; + switch (type) { + case CCS_TYPE_CREATE: + case CCS_TYPE_MKDIR: + case CCS_TYPE_MKFIFO: + case CCS_TYPE_MKSOCK: + case CCS_TYPE_CHMOD: + snprintf(buf, sizeof(buf), "0%lo", + r->param.path_number.number); + break; + case CCS_TYPE_IOCTL: + snprintf(buf, sizeof(buf), "0x%lX", + r->param.path_number.number); + break; + default: + snprintf(buf, sizeof(buf), "%lu", + r->param.path_number.number); + break; + } + return ccs_supervisor(r, "file %s %s %s\n", ccs_mac_keywords + [ccs_pn2mac[type]], + r->param.path_number.filename->name, + buf); + case CCS_TYPE_MKDEV_ACL: + return ccs_supervisor(r, "file %s %s 0%o %u %u\n", + ccs_mac_keywords + [ccs_pnnn2mac[r->param.mkdev.operation]], + r->param.mkdev.filename->name, + r->param.mkdev.mode, + r->param.mkdev.major, + r->param.mkdev.minor); + case CCS_TYPE_MOUNT_ACL: + return ccs_supervisor(r, "file mount %s %s %s 0x%lX\n", + r->param.mount.dev->name, + r->param.mount.dir->name, + r->param.mount.type->name, + r->param.mount.flags); +#ifdef CONFIG_CCSECURITY_MISC + case CCS_TYPE_ENV_ACL: + return ccs_supervisor(r, "misc env %s\n", + r->param.environ.name->name); +#endif +#ifdef CONFIG_CCSECURITY_CAPABILITY + case CCS_TYPE_CAPABILITY_ACL: + return ccs_supervisor(r, "capability %s\n", ccs_mac_keywords + [ccs_c2mac[r->param.capability. + operation]]); +#endif +#ifdef CONFIG_CCSECURITY_NETWORK + case CCS_TYPE_INET_ACL: + address = r->param.inet_network.address; + if (r->param.inet_network.is_ipv6) + ccs_print_ipv6(buf, sizeof(buf), + (const struct in6_addr *) address); + else + ccs_print_ipv4(buf, sizeof(buf), address); + return ccs_supervisor(r, "network inet %s %s %s %u\n", + ccs_proto_keyword[r->param.inet_network. + protocol], + ccs_socket_keyword[r->param.inet_network. + operation], + buf, r->param.inet_network.port); + case CCS_TYPE_UNIX_ACL: + return ccs_supervisor(r, "network unix %s %s %s\n", + ccs_proto_keyword[r->param. + unix_network.protocol], + ccs_socket_keyword[r->param.unix_network. + operation], + r->param.unix_network.address->name); +#endif +#ifdef CONFIG_CCSECURITY_IPC + case CCS_TYPE_SIGNAL_ACL: + return ccs_supervisor(r, "ipc signal %d %s\n", + r->param.signal.sig, + r->param.signal.dest_pattern); +#endif + } + return 0; +} + +/** + * ccs_find_domain_by_qid - Get domain by query id. + * + * @serial: Query ID assigned by ccs_supervisor(). + * + * Returns pointer to "struct ccs_domain_info" if found, NULL otherwise. + */ +static struct ccs_domain_info *ccs_find_domain_by_qid(unsigned int serial) +{ + struct ccs_query *ptr; + struct ccs_domain_info *domain = NULL; + spin_lock(&ccs_query_list_lock); + list_for_each_entry(ptr, &ccs_query_list, list) { + if (ptr->serial != serial) + continue; + domain = ptr->domain; + break; + } + spin_unlock(&ccs_query_list_lock); + return domain; +} + +/** + * ccs_read_query - Read access requests which violated policy in enforcing mode. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + */ +static void ccs_read_query(struct ccs_io_buffer *head) +{ + struct list_head *tmp; + unsigned int pos = 0; + size_t len = 0; + char *buf; + if (head->r.w_pos) + return; + kfree(head->read_buf); + head->read_buf = NULL; + spin_lock(&ccs_query_list_lock); + list_for_each(tmp, &ccs_query_list) { + struct ccs_query *ptr = list_entry(tmp, typeof(*ptr), list); + if (pos++ != head->r.query_index) + continue; + len = ptr->query_len; + break; + } + spin_unlock(&ccs_query_list_lock); + if (!len) { + head->r.query_index = 0; + return; + } + buf = kzalloc(len + 32, CCS_GFP_FLAGS); + if (!buf) + return; + pos = 0; + spin_lock(&ccs_query_list_lock); + list_for_each(tmp, &ccs_query_list) { + struct ccs_query *ptr = list_entry(tmp, typeof(*ptr), list); + if (pos++ != head->r.query_index) + continue; + /* + * Some query can be skipped because ccs_query_list + * can change, but I don't care. + */ + if (len == ptr->query_len) + snprintf(buf, len + 31, "Q%u-%hu\n%s", ptr->serial, + ptr->retry, ptr->query); + break; + } + spin_unlock(&ccs_query_list_lock); + if (buf[0]) { + head->read_buf = buf; + head->r.w[head->r.w_pos++] = buf; + head->r.query_index++; + } else { + kfree(buf); + } +} + +/** + * ccs_write_answer - Write the supervisor's decision. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns 0 on success, -EINVAL otherwise. + */ +static int ccs_write_answer(struct ccs_io_buffer *head) +{ + char *data = head->write_buf; + struct list_head *tmp; + unsigned int serial; + unsigned int answer; + spin_lock(&ccs_query_list_lock); + list_for_each(tmp, &ccs_query_list) { + struct ccs_query *ptr = list_entry(tmp, typeof(*ptr), list); + ptr->timer = 0; + } + spin_unlock(&ccs_query_list_lock); + if (sscanf(data, "A%u=%u", &serial, &answer) != 2) + return -EINVAL; + spin_lock(&ccs_query_list_lock); + list_for_each(tmp, &ccs_query_list) { + struct ccs_query *ptr = list_entry(tmp, typeof(*ptr), list); + if (ptr->serial != serial) + continue; + ptr->answer = (u8) answer; + /* Remove from ccs_query_list. */ + if (ptr->answer) { + list_del(&ptr->list); + INIT_LIST_HEAD(&ptr->list); + } + break; + } + spin_unlock(&ccs_query_list_lock); + wake_up_all(&ccs_answer_wait); + return 0; +} + +/** + * ccs_read_version - Get version. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + */ +static void ccs_read_version(struct ccs_io_buffer *head) +{ + if (head->r.eof) + return; + ccs_set_string(head, "1.8.4"); + head->r.eof = true; +} + +/** + * ccs_update_stat - Update statistic counters. + * + * @index: Index for policy type. + * + * Returns nothing. + */ +static void ccs_update_stat(const u8 index) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) + struct timeval tv; + do_gettimeofday(&tv); + /* + * I don't use atomic operations because race condition is not fatal. + */ + ccs_stat_updated[index]++; + ccs_stat_modified[index] = tv.tv_sec; +#else + /* + * I don't use atomic operations because race condition is not fatal. + */ + ccs_stat_updated[index]++; + ccs_stat_modified[index] = get_seconds(); +#endif +} + +/** + * ccs_read_stat - Read statistic data. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + */ +static void ccs_read_stat(struct ccs_io_buffer *head) +{ + u8 i; + unsigned int total = 0; + if (head->r.eof) + return; + for (i = 0; i < CCS_MAX_POLICY_STAT; i++) { + ccs_io_printf(head, "Policy %-30s %10u", ccs_policy_headers[i], + ccs_stat_updated[i]); + if (ccs_stat_modified[i]) { + struct ccs_time stamp; + ccs_convert_time(ccs_stat_modified[i], &stamp); + ccs_io_printf(head, " (Last: %04u/%02u/%02u " + "%02u:%02u:%02u)", + stamp.year, stamp.month, stamp.day, + stamp.hour, stamp.min, stamp.sec); + } + ccs_set_lf(head); + } + for (i = 0; i < CCS_MAX_MEMORY_STAT; i++) { + unsigned int used = ccs_memory_used[i]; + total += used; + ccs_io_printf(head, "Memory used by %-22s %10u", + ccs_memory_headers[i], used); + used = ccs_memory_quota[i]; + if (used) + ccs_io_printf(head, " (Quota: %10u)", used); + ccs_set_lf(head); + } + ccs_io_printf(head, "Total memory used: %10u\n", + total); + head->r.eof = true; +} + +/** + * ccs_write_stat - Set memory quota. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns 0. + */ +static int ccs_write_stat(struct ccs_io_buffer *head) +{ + char *data = head->write_buf; + u8 i; + if (ccs_str_starts(&data, "Memory used by ")) + for (i = 0; i < CCS_MAX_MEMORY_STAT; i++) + if (ccs_str_starts(&data, ccs_memory_headers[i])) { + if (*data == ' ') + data++; + ccs_memory_quota[i] = + simple_strtoul(data, NULL, 10); + } + return 0; +} + +/** + * ccs_print_bprm - Print "struct linux_binprm" for auditing. + * + * @bprm: Pointer to "struct linux_binprm". + * @dump: Pointer to "struct ccs_page_dump". + * + * Returns the contents of @bprm on success, NULL otherwise. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +static char *ccs_print_bprm(struct linux_binprm *bprm, + struct ccs_page_dump *dump) +{ + static const int ccs_buffer_len = 4096 * 2; + char *buffer = kzalloc(ccs_buffer_len, CCS_GFP_FLAGS); + char *cp; + char *last_start; + int len; + unsigned long pos = bprm->p; + int offset = pos % PAGE_SIZE; + int argv_count = bprm->argc; + int envp_count = bprm->envc; + bool truncated = false; + if (!buffer) + return NULL; + len = snprintf(buffer, ccs_buffer_len - 1, "argv[]={ "); + cp = buffer + len; + if (!argv_count) { + memmove(cp, "} envp[]={ ", 11); + cp += 11; + } + last_start = cp; + while (argv_count || envp_count) { + if (!ccs_dump_page(bprm, pos, dump)) + goto out; + pos += PAGE_SIZE - offset; + /* Read. */ + while (offset < PAGE_SIZE) { + const char *kaddr = dump->data; + const unsigned char c = kaddr[offset++]; + if (cp == last_start) + *cp++ = '"'; + if (cp >= buffer + ccs_buffer_len - 32) { + /* Reserve some room for "..." string. */ + truncated = true; + } else if (c == '\\') { + *cp++ = '\\'; + *cp++ = '\\'; + } else if (c > ' ' && c < 127) { + *cp++ = c; + } else if (!c) { + *cp++ = '"'; + *cp++ = ' '; + last_start = cp; + } else { + *cp++ = '\\'; + *cp++ = (c >> 6) + '0'; + *cp++ = ((c >> 3) & 7) + '0'; + *cp++ = (c & 7) + '0'; + } + if (c) + continue; + if (argv_count) { + if (--argv_count == 0) { + if (truncated) { + cp = last_start; + memmove(cp, "... ", 4); + cp += 4; + } + memmove(cp, "} envp[]={ ", 11); + cp += 11; + last_start = cp; + truncated = false; + } + } else if (envp_count) { + if (--envp_count == 0) { + if (truncated) { + cp = last_start; + memmove(cp, "... ", 4); + cp += 4; + } + } + } + if (!argv_count && !envp_count) + break; + } + offset = 0; + } + *cp++ = '}'; + *cp = '\0'; + return buffer; +out: + snprintf(buffer, ccs_buffer_len - 1, "argv[]={ ... } envp[]= { ... }"); + return buffer; +} + +/** + * ccs_filetype - Get string representation of file type. + * + * @mode: Mode value for stat(). + * + * Returns file type string. + */ +static inline const char *ccs_filetype(const umode_t mode) +{ + switch (mode & S_IFMT) { + case S_IFREG: + case 0: + return ccs_condition_keyword[CCS_TYPE_IS_FILE]; + case S_IFDIR: + return ccs_condition_keyword[CCS_TYPE_IS_DIRECTORY]; + case S_IFLNK: + return ccs_condition_keyword[CCS_TYPE_IS_SYMLINK]; + case S_IFIFO: + return ccs_condition_keyword[CCS_TYPE_IS_FIFO]; + case S_IFSOCK: + return ccs_condition_keyword[CCS_TYPE_IS_SOCKET]; + case S_IFBLK: + return ccs_condition_keyword[CCS_TYPE_IS_BLOCK_DEV]; + case S_IFCHR: + return ccs_condition_keyword[CCS_TYPE_IS_CHAR_DEV]; + } + return "unknown"; /* This should not happen. */ +} + +/** + * ccs_print_header - Get header line of audit log. + * + * @r: Pointer to "struct ccs_request_info". + * + * Returns string representation. + * + * This function uses kmalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +static char *ccs_print_header(struct ccs_request_info *r) +{ + struct ccs_time stamp; + struct ccs_obj_info *obj = r->obj; + const u32 ccs_flags = ccs_current_flags(); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) + const pid_t gpid = ccs_sys_getpid(); +#else + const pid_t gpid = task_pid_nr(current); +#endif + static const int ccs_buffer_len = 4096; + char *buffer = kmalloc(ccs_buffer_len, CCS_GFP_FLAGS); + int pos; + u8 i; + if (!buffer) + return NULL; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) + { + struct timeval tv; + do_gettimeofday(&tv); + ccs_convert_time(tv.tv_sec, &stamp); + } +#else + ccs_convert_time(get_seconds(), &stamp); +#endif + pos = snprintf(buffer, ccs_buffer_len - 1, + "#%04u/%02u/%02u %02u:%02u:%02u# profile=%u mode=%s " + "granted=%s (global-pid=%u) task={ pid=%u ppid=%u " + "uid=%u gid=%u euid=%u egid=%u suid=%u sgid=%u " + "fsuid=%u fsgid=%u type%s=execute_handler }", + stamp.year, stamp.month, stamp.day, stamp.hour, + stamp.min, stamp.sec, r->profile, ccs_mode[r->mode], + ccs_yesno(r->granted), gpid, ccs_sys_getpid(), + ccs_sys_getppid(), + from_kuid(&init_user_ns, current_uid()), + from_kgid(&init_user_ns, current_gid()), + from_kuid(&init_user_ns, current_euid()), + from_kgid(&init_user_ns, current_egid()), + from_kuid(&init_user_ns, current_suid()), + from_kgid(&init_user_ns, current_sgid()), + from_kuid(&init_user_ns, current_fsuid()), + from_kgid(&init_user_ns, current_fsgid()), + ccs_flags & CCS_TASK_IS_EXECUTE_HANDLER ? "" : "!"); + if (!obj) + goto no_obj_info; + if (!obj->validate_done) { + ccs_get_attributes(obj); + obj->validate_done = true; + } + for (i = 0; i < CCS_MAX_PATH_STAT; i++) { + struct ccs_mini_stat *stat; + unsigned int dev; + umode_t mode; + if (!obj->stat_valid[i]) + continue; + stat = &obj->stat[i]; + dev = stat->dev; + mode = stat->mode; + if (i & 1) { + pos += snprintf(buffer + pos, ccs_buffer_len - 1 - pos, + " path%u.parent={ uid=%u gid=%u " + "ino=%lu perm=0%o }", (i >> 1) + 1, + from_kuid(&init_user_ns, stat->uid), + from_kgid(&init_user_ns, stat->gid), + (unsigned long) stat->ino, + stat->mode & S_IALLUGO); + continue; + } + pos += snprintf(buffer + pos, ccs_buffer_len - 1 - pos, + " path%u={ uid=%u gid=%u ino=%lu major=%u" + " minor=%u perm=0%o type=%s", (i >> 1) + 1, + from_kuid(&init_user_ns, stat->uid), + from_kgid(&init_user_ns, stat->gid), + (unsigned long) stat->ino, MAJOR(dev), + MINOR(dev), mode & S_IALLUGO, + ccs_filetype(mode)); + if (S_ISCHR(mode) || S_ISBLK(mode)) { + dev = stat->rdev; + pos += snprintf(buffer + pos, ccs_buffer_len - 1 - pos, + " dev_major=%u dev_minor=%u", + MAJOR(dev), MINOR(dev)); + } + pos += snprintf(buffer + pos, ccs_buffer_len - 1 - pos, " }"); + } +no_obj_info: + if (pos < ccs_buffer_len - 1) + return buffer; + kfree(buffer); + return NULL; +} + +/** + * ccs_init_log - Allocate buffer for audit logs. + * + * @r: Pointer to "struct ccs_request_info". + * @len: Buffer size needed for @fmt and @args. + * @fmt: The printf()'s format string. + * @args: va_list structure for @fmt. + * + * Returns pointer to allocated memory. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +static char *ccs_init_log(struct ccs_request_info *r, int len, const char *fmt, + va_list args) +{ + char *buf = NULL; + char *bprm_info = NULL; + char *realpath = NULL; + const char *symlink = NULL; + const char *header = NULL; + int pos; + const char *domainname = ccs_current_domain()->domainname->name; + header = ccs_print_header(r); + if (!header) + return NULL; + /* +10 is for '\n' etc. and '\0'. */ + len += strlen(domainname) + strlen(header) + 10; + if (r->ee) { + struct file *file = r->ee->bprm->file; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) + struct path path = { file->f_vfsmnt, file->f_dentry }; + realpath = ccs_realpath(&path); +#else + realpath = ccs_realpath(&file->f_path); +#endif + bprm_info = ccs_print_bprm(r->ee->bprm, &r->ee->dump); + if (!realpath || !bprm_info) + goto out; + /* +80 is for " exec={ realpath=\"%s\" argc=%d envc=%d %s }" */ + len += strlen(realpath) + 80 + strlen(bprm_info); + } else if (r->obj && r->obj->symlink_target) { + symlink = r->obj->symlink_target->name; + /* +18 is for " symlink.target=\"%s\"" */ + len += 18 + strlen(symlink); + } + len = ccs_round2(len); + buf = kzalloc(len, CCS_GFP_FLAGS); + if (!buf) + goto out; + len--; + pos = snprintf(buf, len, "%s", header); + if (realpath) { + struct linux_binprm *bprm = r->ee->bprm; + pos += snprintf(buf + pos, len - pos, + " exec={ realpath=\"%s\" argc=%d envc=%d %s }", + realpath, bprm->argc, bprm->envc, bprm_info); + } else if (symlink) + pos += snprintf(buf + pos, len - pos, " symlink.target=\"%s\"", + symlink); + pos += snprintf(buf + pos, len - pos, "\n%s\n", domainname); + vsnprintf(buf + pos, len - pos, fmt, args); +out: + kfree(realpath); + kfree(bprm_info); + kfree(header); + return buf; +} + +/** + * ccs_transition_failed - Print waning message and send signal when domain transition failed. + * + * @domainname: Name of domain to transit. + * + * Returns nothing. + * + * Note that if current->pid == 1, sending SIGKILL won't work. + */ +void ccs_transition_failed(const char *domainname) +{ + printk(KERN_WARNING + "ERROR: Unable to transit to '%s' domain.\n", domainname); + force_sig(SIGKILL, current); +} + +/** + * ccs_update_task_domain - Update task's domain. + * + * @r: Pointer to "struct ccs_request_info". + * + * Returns nothing. + * + * The task will retry as hard as possible. But if domain transition failed, + * the task will be killed by SIGKILL. + */ +static void ccs_update_task_domain(struct ccs_request_info *r) +{ + char *buf; + const char *cp; + const struct ccs_acl_info *acl = r->matched_acl; + r->matched_acl = NULL; + if (!acl || !acl->cond || !acl->cond->transit || + acl->cond->exec_transit) + return; + while (1) { + buf = kzalloc(CCS_EXEC_TMPSIZE, CCS_GFP_FLAGS); + if (buf) + break; + ssleep(1); + if (fatal_signal_pending(current)) + return; + } + cp = acl->cond->transit->name; + if (*cp == '/') + snprintf(buf, CCS_EXEC_TMPSIZE - 1, "%s %s", + ccs_current_domain()->domainname->name, cp); + else + strncpy(buf, cp, CCS_EXEC_TMPSIZE - 1); + if (!ccs_assign_domain(buf, true)) + ccs_transition_failed(buf); + kfree(buf); +} + +/** + * ccs_get_audit - Get audit mode. + * + * @r: Pointer to "struct ccs_request_info". + * + * Returns true if this request should be audited, false otherwise. + */ +static bool ccs_get_audit(const struct ccs_request_info *r) +{ + const struct ccs_acl_info *matched_acl = r->matched_acl; + const u8 profile = r->profile; + const u8 index = r->type; + const bool is_granted = r->granted; + u8 mode; + struct ccs_profile *p; + if (!ccs_policy_loaded) + return false; + p = ccs_profile(profile); + if (ccs_log_count >= p->pref[CCS_PREF_MAX_AUDIT_LOG]) + return false; + if (is_granted && matched_acl && matched_acl->cond && + matched_acl->cond->grant_log != CCS_GRANTLOG_AUTO) + return matched_acl->cond->grant_log == CCS_GRANTLOG_YES; + mode = p->config[index]; + if (mode == CCS_CONFIG_USE_DEFAULT) + mode = p->config + [ccs_index2category[index] + CCS_MAX_MAC_INDEX]; + if (mode == CCS_CONFIG_USE_DEFAULT) + mode = p->default_config; + if (is_granted) + return mode & CCS_CONFIG_WANT_GRANT_LOG; + return mode & CCS_CONFIG_WANT_REJECT_LOG; +} + +/** + * ccs_write_log2 - Write an audit log. + * + * @r: Pointer to "struct ccs_request_info". + * @len: Buffer size needed for @fmt and @args. + * @fmt: The printf()'s format string. + * @args: va_list structure for @fmt. + * + * Returns nothing. + */ +static void ccs_write_log2(struct ccs_request_info *r, int len, + const char *fmt, va_list args) +{ + char *buf; + struct ccs_log *entry; + bool quota_exceeded = false; + if (!ccs_get_audit(r)) + goto out; + buf = ccs_init_log(r, len, fmt, args); + if (!buf) + goto out; + entry = kzalloc(sizeof(*entry), CCS_GFP_FLAGS); + if (!entry) { + kfree(buf); + goto out; + } + entry->log = buf; + len = ccs_round2(strlen(buf) + 1); + /* + * The entry->size is used for memory quota checks. + * Don't go beyond strlen(entry->log). + */ + entry->size = len + ccs_round2(sizeof(*entry)); + spin_lock(&ccs_log_lock); + if (ccs_memory_quota[CCS_MEMORY_AUDIT] && + ccs_memory_used[CCS_MEMORY_AUDIT] + entry->size >= + ccs_memory_quota[CCS_MEMORY_AUDIT]) { + quota_exceeded = true; + } else { + ccs_memory_used[CCS_MEMORY_AUDIT] += entry->size; + list_add_tail(&entry->list, &ccs_log); + ccs_log_count++; + } + spin_unlock(&ccs_log_lock); + if (quota_exceeded) { + kfree(buf); + kfree(entry); + goto out; + } + wake_up(&ccs_log_wait); +out: + ccs_update_task_domain(r); +} + +/** + * ccs_write_log - Write an audit log. + * + * @r: Pointer to "struct ccs_request_info". + * @fmt: The printf()'s format string, followed by parameters. + * + * Returns nothing. + */ +void ccs_write_log(struct ccs_request_info *r, const char *fmt, ...) +{ + va_list args; + int len; + va_start(args, fmt); + len = vsnprintf((char *) &len, 1, fmt, args) + 1; + va_end(args); + va_start(args, fmt); + ccs_write_log2(r, len, fmt, args); + va_end(args); +} + +/** + * ccs_read_log - Read an audit log. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + */ +static void ccs_read_log(struct ccs_io_buffer *head) +{ + struct ccs_log *ptr = NULL; + if (head->r.w_pos) + return; + kfree(head->read_buf); + head->read_buf = NULL; + spin_lock(&ccs_log_lock); + if (!list_empty(&ccs_log)) { + ptr = list_entry(ccs_log.next, typeof(*ptr), list); + list_del(&ptr->list); + ccs_log_count--; + ccs_memory_used[CCS_MEMORY_AUDIT] -= ptr->size; + } + spin_unlock(&ccs_log_lock); + if (ptr) { + head->read_buf = ptr->log; + head->r.w[head->r.w_pos++] = head->read_buf; + kfree(ptr); + } +} + +/** + * ccs_set_namespace_cursor - Set namespace to read. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns nothing. + */ +static void ccs_set_namespace_cursor(struct ccs_io_buffer *head) +{ + struct list_head *ns; + if (head->type != CCS_EXCEPTION_POLICY && head->type != CCS_PROFILE) + return; + /* + * If this is the first read, or reading previous namespace finished + * and has more namespaces to read, update the namespace cursor. + */ + ns = head->r.ns; + if (!ns || (head->r.eof && ns->next != &ccs_namespace_list)) { + /* Clearing is OK because ccs_flush() returned true. */ + memset(&head->r, 0, sizeof(head->r)); + head->r.ns = ns ? ns->next : ccs_namespace_list.next; + } +} + +/** + * ccs_has_more_namespace - Check for unread namespaces. + * + * @head: Pointer to "struct ccs_io_buffer". + * + * Returns true if we have more entries to print, false otherwise. + */ +static bool ccs_has_more_namespace(struct ccs_io_buffer *head) +{ + return (head->type == CCS_EXCEPTION_POLICY || + head->type == CCS_PROFILE) && head->r.eof && + head->r.ns->next != &ccs_namespace_list; +} + +/** + * ccs_find_namespace - Find specified namespace. + * + * @name: Name of namespace to find. + * @len: Length of @name. + * + * Returns pointer to "struct ccs_policy_namespace" if found, NULL otherwise. + * + * Caller holds ccs_read_lock(). + */ +static struct ccs_policy_namespace *ccs_find_namespace(const char *name, + const unsigned int len) +{ + struct ccs_policy_namespace *ns; + list_for_each_entry_srcu(ns, &ccs_namespace_list, namespace_list, + &ccs_ss) { + if (strncmp(name, ns->name, len) || + (name[len] && name[len] != ' ')) + continue; + return ns; + } + return NULL; +} + +/** + * ccs_assign_namespace - Create a new namespace. + * + * @domainname: Name of namespace to create. + * + * Returns pointer to "struct ccs_policy_namespace" on success, NULL otherwise. + * + * Caller holds ccs_read_lock(). + */ +static struct ccs_policy_namespace *ccs_assign_namespace +(const char *domainname) +{ + struct ccs_policy_namespace *ptr; + struct ccs_policy_namespace *entry; + const char *cp = domainname; + unsigned int len = 0; + while (*cp && *cp++ != ' ') + len++; + ptr = ccs_find_namespace(domainname, len); + if (ptr) + return ptr; + if (len >= CCS_EXEC_TMPSIZE - 10 || !ccs_domain_def(domainname)) + return NULL; + entry = kzalloc(sizeof(*entry) + len + 1, CCS_GFP_FLAGS); + if (!entry) + return NULL; + if (mutex_lock_interruptible(&ccs_policy_lock)) + goto out; + ptr = ccs_find_namespace(domainname, len); + if (!ptr && ccs_memory_ok(entry, sizeof(*entry) + len + 1)) { + char *name = (char *) (entry + 1); + ptr = entry; + memmove(name, domainname, len); + name[len] = '\0'; + entry->name = name; + ccs_init_policy_namespace(entry); + entry = NULL; + } + mutex_unlock(&ccs_policy_lock); +out: + kfree(entry); + return ptr; +} + +/** + * ccs_namespace_jump - Check for namespace jump. + * + * @domainname: Name of domain. + * + * Returns true if namespace differs, false otherwise. + */ +static bool ccs_namespace_jump(const char *domainname) +{ + const char *namespace = ccs_current_namespace()->name; + const int len = strlen(namespace); + return strncmp(domainname, namespace, len) || + (domainname[len] && domainname[len] != ' '); +} + +/** + * ccs_assign_domain - Create a domain or a namespace. + * + * @domainname: The name of domain. + * @transit: True if transit to domain found or created. + * + * Returns pointer to "struct ccs_domain_info" on success, NULL otherwise. + * + * Caller holds ccs_read_lock(). + */ +struct ccs_domain_info *ccs_assign_domain(const char *domainname, + const bool transit) +{ + struct ccs_security *security = ccs_current_security(); + struct ccs_domain_info e = { }; + struct ccs_domain_info *entry = ccs_find_domain(domainname); + bool created = false; + if (entry) { + if (transit) { + /* + * Since namespace is created at runtime, profiles may + * not be created by the moment the process transits to + * that domain. Do not perform domain transition if + * profile for that domain is not yet created. + */ + if (ccs_policy_loaded && + !entry->ns->profile_ptr[entry->profile]) + return NULL; + security->ccs_domain_info = entry; + } + return entry; + } + /* Requested domain does not exist. */ + /* Don't create requested domain if domainname is invalid. */ + if (strlen(domainname) >= CCS_EXEC_TMPSIZE - 10 || + !ccs_correct_domain(domainname)) + return NULL; + /* + * Since definition of profiles and acl_groups may differ across + * namespaces, do not inherit "use_profile" and "use_group" settings + * by automatically creating requested domain upon domain transition. + */ + if (transit && ccs_namespace_jump(domainname)) + return NULL; + e.ns = ccs_assign_namespace(domainname); + if (!e.ns) + return NULL; + /* + * "use_profile" and "use_group" settings for automatically created + * domains are inherited from current domain. These are 0 for manually + * created domains. + */ + if (transit) { + const struct ccs_domain_info *domain = + security->ccs_domain_info; + e.profile = domain->profile; + memcpy(e.group, domain->group, sizeof(e.group)); + } + e.domainname = ccs_get_name(domainname); + if (!e.domainname) + return NULL; + if (mutex_lock_interruptible(&ccs_policy_lock)) + goto out; + entry = ccs_find_domain(domainname); + if (!entry) { + entry = ccs_commit_ok(&e, sizeof(e)); + if (entry) { + INIT_LIST_HEAD(&entry->acl_info_list); + list_add_tail_rcu(&entry->list, &ccs_domain_list); + created = true; + } + } + mutex_unlock(&ccs_policy_lock); +out: + ccs_put_name(e.domainname); + if (entry && transit) { + security->ccs_domain_info = entry; + if (created) { + struct ccs_request_info r; + int i; + ccs_init_request_info(&r, CCS_MAC_FILE_EXECUTE); + r.granted = false; + ccs_write_log(&r, "use_profile %u\n", entry->profile); + for (i = 0; i < CCS_MAX_ACL_GROUPS; i++) + if (test_bit(i, entry->group)) + ccs_write_log(&r, "use_group %u\n", i); + ccs_update_stat(CCS_STAT_POLICY_UPDATES); + } + } + return entry; +} + +/** + * ccs_parse_policy - Parse a policy line. + * + * @head: Poiter to "struct ccs_io_buffer". + * @line: Line to parse. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds ccs_read_lock(). + */ +static int ccs_parse_policy(struct ccs_io_buffer *head, char *line) +{ + /* Delete request? */ + head->w.is_delete = !strncmp(line, "delete ", 7); + if (head->w.is_delete) + memmove(line, line + 7, strlen(line + 7) + 1); + /* Selecting namespace to update. */ + if (head->type == CCS_EXCEPTION_POLICY || head->type == CCS_PROFILE) { + if (*line == '<') { + char *cp = strchr(line, ' '); + if (cp) { + *cp++ = '\0'; + head->w.ns = ccs_assign_namespace(line); + memmove(line, cp, strlen(cp) + 1); + } else + head->w.ns = NULL; + } else + head->w.ns = &ccs_kernel_namespace; + /* Don't allow updating if namespace is invalid. */ + if (!head->w.ns) + return -ENOENT; + } + /* Do the update. */ + switch (head->type) { + case CCS_DOMAIN_POLICY: + return ccs_write_domain(head); + case CCS_EXCEPTION_POLICY: + return ccs_write_exception(head); +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + case CCS_EXECUTE_HANDLER: +#endif + case CCS_PROCESS_STATUS: + return ccs_write_pid(head); + case CCS_STAT: + return ccs_write_stat(head); + case CCS_PROFILE: + return ccs_write_profile(head); + case CCS_QUERY: + return ccs_write_answer(head); + case CCS_MANAGER: + return ccs_write_manager(head); + default: + return -ENOSYS; + } +} + +/** + * ccs_policy_io_init - Register hooks for policy I/O. + * + * Returns nothing. + */ +static void __init ccs_policy_io_init(void) +{ + ccsecurity_ops.check_profile = ccs_check_profile; +} + +/** + * ccs_load_builtin_policy - Load built-in policy. + * + * Returns nothing. + */ +static void __init ccs_load_builtin_policy(void) +{ + /* + * This include file is manually created and contains built-in policy + * named "ccs_builtin_profile", "ccs_builtin_exception_policy", + * "ccs_builtin_domain_policy", "ccs_builtin_manager", + * "ccs_builtin_stat" in the form of "static char [] __initdata". + */ +#include "builtin-policy.h" + u8 i; + const int idx = ccs_read_lock(); + for (i = 0; i < 5; i++) { + struct ccs_io_buffer head = { }; + char *start = ""; + switch (i) { + case 0: + start = ccs_builtin_profile; + head.type = CCS_PROFILE; + break; + case 1: + start = ccs_builtin_exception_policy; + head.type = CCS_EXCEPTION_POLICY; + break; + case 2: + start = ccs_builtin_domain_policy; + head.type = CCS_DOMAIN_POLICY; + break; + case 3: + start = ccs_builtin_manager; + head.type = CCS_MANAGER; + break; + case 4: + start = ccs_builtin_stat; + head.type = CCS_STAT; + break; + } + while (1) { + char *end = strchr(start, '\n'); + if (!end) + break; + *end = '\0'; + ccs_normalize_line(start); + head.write_buf = start; + ccs_parse_policy(&head, start); + start = end + 1; + } + } + ccs_read_unlock(idx); +#ifdef CONFIG_CCSECURITY_OMIT_USERSPACE_LOADER + ccs_check_profile(); +#endif +} + +/** + * ccs_read_self - read() for /proc/ccs/self_domain interface. + * + * @file: Pointer to "struct file". + * @buf: Domainname which current thread belongs to. + * @count: Size of @buf. + * @ppos: Bytes read by now. + * + * Returns read size on success, negative value otherwise. + */ +static ssize_t ccs_read_self(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + const char *domain = ccs_current_domain()->domainname->name; + loff_t len = strlen(domain); + loff_t pos = *ppos; + if (pos >= len || !count) + return 0; + len -= pos; + if (count < len) + len = count; + if (copy_to_user(buf, domain + pos, len)) + return -EFAULT; + *ppos += len; + return len; +} + +/** + * ccs_open - open() for /proc/ccs/ interface. + * + * @inode: Pointer to "struct inode". + * @file: Pointer to "struct file". + * + * Returns 0 on success, negative value otherwise. + */ +static int ccs_open(struct inode *inode, struct file *file) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + const u8 type = (unsigned long) PDE_DATA(inode); +#else + const u8 type = (unsigned long) PDE(inode)->data; +#endif + struct ccs_io_buffer *head = kzalloc(sizeof(*head), CCS_GFP_FLAGS); + if (!head) + return -ENOMEM; + mutex_init(&head->io_sem); + head->type = type; +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + if (type == CCS_EXECUTE_HANDLER) { + /* Allow execute_handler to read process's status. */ + if (!(ccs_current_flags() & CCS_TASK_IS_EXECUTE_HANDLER)) { + kfree(head); + return -EPERM; + } + } +#endif + if ((file->f_mode & FMODE_READ) && type != CCS_AUDIT && + type != CCS_QUERY) { + /* Don't allocate read_buf for poll() access. */ + head->readbuf_size = 4096; + head->read_buf = kzalloc(head->readbuf_size, CCS_GFP_FLAGS); + if (!head->read_buf) { + kfree(head); + return -ENOMEM; + } + } + if (file->f_mode & FMODE_WRITE) { + head->writebuf_size = 4096; + head->write_buf = kzalloc(head->writebuf_size, CCS_GFP_FLAGS); + if (!head->write_buf) { + kfree(head->read_buf); + kfree(head); + return -ENOMEM; + } + } + /* + * If the file is /proc/ccs/query, increment the observer counter. + * The obserber counter is used by ccs_supervisor() to see if + * there is some process monitoring /proc/ccs/query. + */ + if (type == CCS_QUERY) + atomic_inc(&ccs_query_observers); + file->private_data = head; + ccs_notify_gc(head, true); + return 0; +} + +/** + * ccs_release - close() for /proc/ccs/ interface. + * + * @inode: Pointer to "struct inode". + * @file: Pointer to "struct file". + * + * Returns 0. + */ +static int ccs_release(struct inode *inode, struct file *file) +{ + struct ccs_io_buffer *head = file->private_data; + /* + * If the file is /proc/ccs/query, decrement the observer counter. + */ + if (head->type == CCS_QUERY && + atomic_dec_and_test(&ccs_query_observers)) + wake_up_all(&ccs_answer_wait); + ccs_notify_gc(head, false); + return 0; +} + +/** + * ccs_poll - poll() for /proc/ccs/ interface. + * + * @file: Pointer to "struct file". + * @wait: Pointer to "poll_table". Maybe NULL. + * + * Returns POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM if ready to read/write, + * POLLOUT | POLLWRNORM otherwise. + */ +static unsigned int ccs_poll(struct file *file, poll_table *wait) +{ + struct ccs_io_buffer *head = file->private_data; + if (head->type == CCS_AUDIT) { + if (!ccs_memory_used[CCS_MEMORY_AUDIT]) { + poll_wait(file, &ccs_log_wait, wait); + if (!ccs_memory_used[CCS_MEMORY_AUDIT]) + return POLLOUT | POLLWRNORM; + } + } else if (head->type == CCS_QUERY) { + if (list_empty(&ccs_query_list)) { + poll_wait(file, &ccs_query_wait, wait); + if (list_empty(&ccs_query_list)) + return POLLOUT | POLLWRNORM; + } + } + return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM; +} + +/** + * ccs_read - read() for /proc/ccs/ interface. + * + * @file: Pointer to "struct file". + * @buf: Pointer to buffer. + * @count: Size of @buf. + * @ppos: Unused. + * + * Returns bytes read on success, negative value otherwise. + */ +static ssize_t ccs_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + struct ccs_io_buffer *head = file->private_data; + int len; + int idx; + if (mutex_lock_interruptible(&head->io_sem)) + return -EINTR; + head->read_user_buf = buf; + head->read_user_buf_avail = count; + idx = ccs_read_lock(); + if (ccs_flush(head)) + /* Call the policy handler. */ + do { + ccs_set_namespace_cursor(head); + switch (head->type) { + case CCS_DOMAIN_POLICY: + ccs_read_domain(head); + break; + case CCS_EXCEPTION_POLICY: + ccs_read_exception(head); + break; + case CCS_AUDIT: + ccs_read_log(head); + break; +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + case CCS_EXECUTE_HANDLER: +#endif + case CCS_PROCESS_STATUS: + ccs_read_pid(head); + break; + case CCS_VERSION: + ccs_read_version(head); + break; + case CCS_STAT: + ccs_read_stat(head); + break; + case CCS_PROFILE: + ccs_read_profile(head); + break; + case CCS_QUERY: + ccs_read_query(head); + break; + case CCS_MANAGER: + ccs_read_manager(head); + break; + } + } while (ccs_flush(head) && ccs_has_more_namespace(head)); + ccs_read_unlock(idx); + len = head->read_user_buf - buf; + mutex_unlock(&head->io_sem); + return len; +} + +#ifdef CONFIG_CCSECURITY_TASK_DOMAIN_TRANSITION + +/** + * ccs_write_self - write() for /proc/ccs/self_domain interface. + * + * @file: Pointer to "struct file". + * @buf: Domainname to transit to. + * @count: Size of @buf. + * @ppos: Unused. + * + * Returns @count on success, negative value otherwise. + * + * If domain transition was permitted but the domain transition failed, this + * function returns error rather than terminating current thread with SIGKILL. + */ +static ssize_t ccs_write_self(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char *data; + int error; + if (!count || count >= CCS_EXEC_TMPSIZE - 10) + return -ENOMEM; + data = kzalloc(count + 1, CCS_GFP_FLAGS); + if (!data) + return -ENOMEM; + if (copy_from_user(data, buf, count)) { + error = -EFAULT; + goto out; + } + ccs_normalize_line(data); + if (ccs_correct_domain(data)) { + const int idx = ccs_read_lock(); + struct ccs_path_info name; + struct ccs_request_info r; + name.name = data; + ccs_fill_path_info(&name); + /* Check "task manual_domain_transition" permission. */ + ccs_init_request_info(&r, CCS_MAC_FILE_EXECUTE); + r.param_type = CCS_TYPE_MANUAL_TASK_ACL; + r.param.task.domainname = &name; + ccs_check_acl(&r); + if (!r.granted) + error = -EPERM; + else + error = ccs_assign_domain(data, true) ? 0 : -ENOENT; + ccs_read_unlock(idx); + } else + error = -EINVAL; +out: + kfree(data); + return error ? error : count; +} + +#endif + +/** + * ccs_write - write() for /proc/ccs/ interface. + * + * @file: Pointer to "struct file". + * @buf: Pointer to buffer. + * @count: Size of @buf. + * @ppos: Unused. + * + * Returns @count on success, negative value otherwise. + */ +static ssize_t ccs_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct ccs_io_buffer *head = file->private_data; + int error = count; + char *cp0 = head->write_buf; + int idx; + if (mutex_lock_interruptible(&head->io_sem)) + return -EINTR; + head->read_user_buf_avail = 0; + idx = ccs_read_lock(); + /* Read a line and dispatch it to the policy handler. */ + while (count) { + char c; + if (head->w.avail >= head->writebuf_size - 1) { + const int len = head->writebuf_size * 2; + char *cp = kzalloc(len, CCS_GFP_FLAGS); + if (!cp) { + error = -ENOMEM; + break; + } + memmove(cp, cp0, head->w.avail); + kfree(cp0); + head->write_buf = cp; + cp0 = cp; + head->writebuf_size = len; + } + if (get_user(c, buf)) { + error = -EFAULT; + break; + } + buf++; + count--; + cp0[head->w.avail++] = c; + if (c != '\n') + continue; + cp0[head->w.avail - 1] = '\0'; + head->w.avail = 0; + ccs_normalize_line(cp0); + if (!strcmp(cp0, "reset")) { + head->w.ns = &ccs_kernel_namespace; + head->w.domain = NULL; + memset(&head->r, 0, sizeof(head->r)); + continue; + } + /* Don't allow updating policies by non manager programs. */ + switch (head->type) { + case CCS_PROCESS_STATUS: + /* This does not write anything. */ + break; + case CCS_DOMAIN_POLICY: + if (ccs_select_domain(head, cp0)) + continue; + /* fall through */ + case CCS_EXCEPTION_POLICY: + if (!strcmp(cp0, "select transition_only")) { + head->r.print_transition_related_only = true; + continue; + } + /* fall through */ + default: + if (!ccs_manager()) { + error = -EPERM; + goto out; + } + } + switch (ccs_parse_policy(head, cp0)) { + case -EPERM: + error = -EPERM; + goto out; + case 0: + /* Update statistics. */ + switch (head->type) { + case CCS_DOMAIN_POLICY: + case CCS_EXCEPTION_POLICY: + case CCS_STAT: + case CCS_PROFILE: + case CCS_MANAGER: + ccs_update_stat(CCS_STAT_POLICY_UPDATES); + break; + default: + break; + } + break; + } + } +out: + ccs_read_unlock(idx); + mutex_unlock(&head->io_sem); + return error; +} + +/** + * ccs_create_entry - Create interface files under /proc/ccs/ directory. + * + * @name: The name of the interface file. + * @mode: The permission of the interface file. + * @parent: The parent directory. + * @key: Type of interface. + * + * Returns nothing. + */ +static void __init ccs_create_entry(const char *name, const umode_t mode, + struct proc_dir_entry *parent, + const u8 key) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26) + proc_create_data(name, mode, parent, &ccs_operations, + ((u8 *) NULL) + key); +#else + struct proc_dir_entry *entry = create_proc_entry(name, mode, parent); + if (entry) { + entry->proc_fops = &ccs_operations; + entry->data = ((u8 *) NULL) + key; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + if (entry->proc_iops) + ccs_file_inode_operations = *entry->proc_iops; + if (!ccs_file_inode_operations.setattr) + ccs_file_inode_operations.setattr = proc_notify_change; + entry->proc_iops = &ccs_file_inode_operations; +#endif + } +#endif +} + +/** + * ccs_proc_init - Initialize /proc/ccs/ interface. + * + * Returns nothing. + */ +static void __init ccs_proc_init(void) +{ + struct proc_dir_entry *ccs_dir = proc_mkdir("ccs", NULL); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + if (ccs_dir->proc_iops) + ccs_dir_inode_operations = *ccs_dir->proc_iops; + if (!ccs_dir_inode_operations.setattr) + ccs_dir_inode_operations.setattr = proc_notify_change; + ccs_dir->proc_iops = &ccs_dir_inode_operations; +#endif + ccs_create_entry("query", 0600, ccs_dir, CCS_QUERY); + ccs_create_entry("domain_policy", 0600, ccs_dir, CCS_DOMAIN_POLICY); + ccs_create_entry("exception_policy", 0600, ccs_dir, + CCS_EXCEPTION_POLICY); + ccs_create_entry("audit", 0400, ccs_dir, CCS_AUDIT); + ccs_create_entry(".process_status", 0600, ccs_dir, + CCS_PROCESS_STATUS); + ccs_create_entry("stat", 0644, ccs_dir, CCS_STAT); + ccs_create_entry("profile", 0600, ccs_dir, CCS_PROFILE); + ccs_create_entry("manager", 0600, ccs_dir, CCS_MANAGER); + ccs_create_entry("version", 0400, ccs_dir, CCS_VERSION); +#ifdef CONFIG_CCSECURITY_TASK_EXECUTE_HANDLER + ccs_create_entry(".execute_handler", 0666, ccs_dir, + CCS_EXECUTE_HANDLER); +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26) + proc_create("self_domain", 0666, ccs_dir, &ccs_self_operations); +#else + { + struct proc_dir_entry *e = create_proc_entry("self_domain", + 0666, ccs_dir); + if (e) + e->proc_fops = &ccs_self_operations; + } +#endif +} + +/** + * ccs_init_module - Initialize this module. + * + * Returns 0 on success, negative value otherwise. + */ +static int __init ccs_init_module(void) +{ + if (ccsecurity_ops.disabled) + return -EINVAL; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) + MOD_INC_USE_COUNT; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) + if (init_srcu_struct(&ccs_ss)) + panic("Out of memory."); +#endif + ccs_kernel_namespace.name = ""; + ccs_init_policy_namespace(&ccs_kernel_namespace); + ccs_kernel_domain.ns = &ccs_kernel_namespace; + INIT_LIST_HEAD(&ccs_kernel_domain.acl_info_list); + ccs_mm_init(); + ccs_policy_io_init(); + ccs_permission_init(); + ccs_proc_init(); + ccs_load_builtin_policy(); + return 0; +} + +MODULE_LICENSE("GPL"); +module_init(ccs_init_module); diff --git a/security/ccsecurity/realpath.c b/security/ccsecurity/realpath.c new file mode 100644 index 0000000..df821ed --- /dev/null +++ b/security/ccsecurity/realpath.c @@ -0,0 +1,767 @@ +/* + * security/ccsecurity/realpath.c + * + * Copyright (C) 2005-2012 NTT DATA CORPORATION + * + * Version: 1.8.4 2015/05/05 + */ + +#include "internal.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) && LINUX_VERSION_CODE < KERNEL_VERSION(3, 2, 0) +#include +#include +#endif + +/***** SECTION1: Constants definition *****/ + +#define SOCKFS_MAGIC 0x534F434B + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) +#define s_fs_info u.generic_sbp +#endif + +/***** SECTION2: Structure definition *****/ + +/***** SECTION3: Prototype definition section *****/ + +char *ccs_encode(const char *str); +char *ccs_encode2(const char *str, int str_len); +char *ccs_realpath(const struct path *path); +const char *ccs_get_exe(void); +void ccs_fill_path_info(struct ccs_path_info *ptr); + +static char *ccs_get_absolute_path(const struct path *path, + char * const buffer, const int buflen); +static char *ccs_get_dentry_path(struct dentry *dentry, char * const buffer, + const int buflen); +static char *ccs_get_local_path(struct dentry *dentry, char * const buffer, + const int buflen); +static char *ccs_get_socket_name(const struct path *path, char * const buffer, + const int buflen); +static int ccs_const_part_length(const char *filename); + +/***** SECTION4: Standalone functions section *****/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + +/** + * SOCKET_I - Get "struct socket". + * + * @inode: Pointer to "struct inode". + * + * Returns pointer to "struct socket". + * + * This is for compatibility with older kernels. + */ +static inline struct socket *SOCKET_I(struct inode *inode) +{ + return inode->i_sock ? &inode->u.socket_i : NULL; +} + +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) + +/** + * ccs_realpath_lock - Take locks for __d_path(). + * + * Returns nothing. + */ +static inline void ccs_realpath_lock(void) +{ + /* dcache_lock is locked by __d_path(). */ + /* vfsmount_lock is locked by __d_path(). */ +} + +/** + * ccs_realpath_unlock - Release locks for __d_path(). + * + * Returns nothing. + */ +static inline void ccs_realpath_unlock(void) +{ + /* vfsmount_lock is unlocked by __d_path(). */ + /* dcache_lock is unlocked by __d_path(). */ +} + +#elif LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 36) + +/** + * ccs_realpath_lock - Take locks for __d_path(). + * + * Returns nothing. + */ +static inline void ccs_realpath_lock(void) +{ + spin_lock(&dcache_lock); + /* vfsmount_lock is locked by __d_path(). */ +} + +/** + * ccs_realpath_unlock - Release locks for __d_path(). + * + * Returns nothing. + */ +static inline void ccs_realpath_unlock(void) +{ + /* vfsmount_lock is unlocked by __d_path(). */ + spin_unlock(&dcache_lock); +} + +#elif defined(D_PATH_DISCONNECT) && !defined(CONFIG_SUSE_KERNEL) + +/** + * ccs_realpath_lock - Take locks for __d_path(). + * + * Returns nothing. + * + * Original unambiguous-__d_path.diff in patches.apparmor.tar.bz2 inversed the + * order of holding dcache_lock and vfsmount_lock. That patch was applied on + * (at least) SUSE 11.1 and Ubuntu 8.10 and Ubuntu 9.04 kernels. + * + * However, that patch was updated to use original order and the updated patch + * is applied to (as far as I know) only SUSE kernels. + * + * Therefore, I need to use original order for SUSE 11.1 kernels and inversed + * order for other kernels. I detect it by checking D_PATH_DISCONNECT and + * CONFIG_SUSE_KERNEL. I don't know whether other distributions are using the + * updated patch or not. If you got deadlock, check fs/dcache.c for locking + * order, and add " && 0" to this "#elif " block if fs/dcache.c uses original + * order. + */ +static inline void ccs_realpath_lock(void) +{ + spin_lock(ccsecurity_exports.vfsmount_lock); + spin_lock(&dcache_lock); +} + +/** + * ccs_realpath_unlock - Release locks for __d_path(). + * + * Returns nothing. + */ +static inline void ccs_realpath_unlock(void) +{ + spin_unlock(&dcache_lock); + spin_unlock(ccsecurity_exports.vfsmount_lock); +} + +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) + +/** + * ccs_realpath_lock - Take locks for __d_path(). + * + * Returns nothing. + */ +static inline void ccs_realpath_lock(void) +{ + spin_lock(&dcache_lock); + spin_lock(ccsecurity_exports.vfsmount_lock); +} + +/** + * ccs_realpath_unlock - Release locks for __d_path(). + * + * Returns nothing. + */ +static inline void ccs_realpath_unlock(void) +{ + spin_unlock(ccsecurity_exports.vfsmount_lock); + spin_unlock(&dcache_lock); +} + +#else + +/** + * ccs_realpath_lock - Take locks for __d_path(). + * + * Returns nothing. + */ +static inline void ccs_realpath_lock(void) +{ + spin_lock(&dcache_lock); +} + +/** + * ccs_realpath_unlock - Release locks for __d_path(). + * + * Returns nothing. + */ +static inline void ccs_realpath_unlock(void) +{ + spin_unlock(&dcache_lock); +} + +#endif + +/***** SECTION5: Variables definition section *****/ + +/***** SECTION6: Dependent functions section *****/ + +/** + * ccs_get_absolute_path - Get the path of a dentry but ignores chroot'ed root. + * + * @path: Pointer to "struct path". + * @buffer: Pointer to buffer to return value in. + * @buflen: Sizeof @buffer. + * + * Returns the buffer on success, an error code otherwise. + * + * Caller holds the dcache_lock and vfsmount_lock. + * Based on __d_path() in fs/dcache.c + * + * If dentry is a directory, trailing '/' is appended. + */ +static char *ccs_get_absolute_path(const struct path *path, + char * const buffer, const int buflen) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0) + char *pos = ERR_PTR(-ENOMEM); + if (buflen >= 256) { + pos = ccsecurity_exports.d_absolute_path(path, buffer, + buflen - 1); + if (!IS_ERR(pos) && *pos == '/' && pos[1]) { + struct inode *inode = path->dentry->d_inode; + if (inode && S_ISDIR(inode->i_mode)) { + buffer[buflen - 2] = '/'; + buffer[buflen - 1] = '\0'; + } + } + } + return pos; +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) + /* + * __d_path() will start returning NULL by backporting commit 02125a82 + * "fix apparmor dereferencing potentially freed dentry, sanitize + * __d_path() API". + * + * Unfortunately, __d_path() after applying that commit always returns + * NULL when root is empty. d_absolute_path() is provided for TOMOYO + * 2.x and AppArmor but TOMOYO 1.x does not use it, for TOMOYO 1.x + * might be built as a loadable kernel module and there is no warrantee + * that TOMOYO 1.x is recompiled after applying that commit. Also, + * I don't want to search /proc/kallsyms for d_absolute_path() because + * I want to keep TOMOYO 1.x architecture independent. Thus, supply + * non empty root like AppArmor's d_namespace_path() did. + */ + char *pos = ERR_PTR(-ENOMEM); + if (buflen >= 256) { + static bool ccs_no_empty; + if (!ccs_no_empty) { + struct path root = { }; + pos = ccsecurity_exports.__d_path(path, &root, buffer, + buflen - 1); + } else { + pos = NULL; + } + if (!pos) { + struct task_struct *task = current; + struct path root; + struct path tmp; + spin_lock(&task->fs->lock); + root.mnt = task->nsproxy->mnt_ns->root; + root.dentry = root.mnt->mnt_root; + path_get(&root); + spin_unlock(&task->fs->lock); + tmp = root; + pos = ccsecurity_exports.__d_path(path, &tmp, buffer, + buflen - 1); + path_put(&root); + if (!pos) + return ERR_PTR(-EINVAL); + /* Remember if __d_path() needs non empty root. */ + ccs_no_empty = true; + } + if (!IS_ERR(pos) && *pos == '/' && pos[1]) { + struct inode *inode = path->dentry->d_inode; + if (inode && S_ISDIR(inode->i_mode)) { + buffer[buflen - 2] = '/'; + buffer[buflen - 1] = '\0'; + } + } + } + return pos; +#else + char *pos = buffer + buflen - 1; + struct dentry *dentry = path->dentry; + struct vfsmount *vfsmnt = path->mnt; + const char *name; + int len; + + if (buflen < 256) + goto out; + + *pos = '\0'; + if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) + *--pos = '/'; + for (;;) { + struct dentry *parent; + if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { + if (vfsmnt->mnt_parent == vfsmnt) + break; + dentry = vfsmnt->mnt_mountpoint; + vfsmnt = vfsmnt->mnt_parent; + continue; + } + parent = dentry->d_parent; + name = dentry->d_name.name; + len = dentry->d_name.len; + pos -= len; + if (pos <= buffer) + goto out; + memmove(pos, name, len); + *--pos = '/'; + dentry = parent; + } + if (*pos == '/') + pos++; + len = dentry->d_name.len; + pos -= len; + if (pos < buffer) + goto out; + memmove(pos, dentry->d_name.name, len); + return pos; +out: + return ERR_PTR(-ENOMEM); +#endif +} + +/** + * ccs_get_dentry_path - Get the path of a dentry. + * + * @dentry: Pointer to "struct dentry". + * @buffer: Pointer to buffer to return value in. + * @buflen: Sizeof @buffer. + * + * Returns the buffer on success, an error code otherwise. + * + * Based on dentry_path() in fs/dcache.c + * + * If dentry is a directory, trailing '/' is appended. + */ +static char *ccs_get_dentry_path(struct dentry *dentry, char * const buffer, + const int buflen) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) + char *pos = ERR_PTR(-ENOMEM); + if (buflen >= 256) { + pos = dentry_path_raw(dentry, buffer, buflen - 1); + if (!IS_ERR(pos) && *pos == '/' && pos[1] && + d_is_dir(dentry)) { + buffer[buflen - 2] = '/'; + buffer[buflen - 1] = '\0'; + } + } + return pos; +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 38) + char *pos = ERR_PTR(-ENOMEM); + if (buflen >= 256) { + /* rename_lock is locked/unlocked by dentry_path_raw(). */ + pos = dentry_path_raw(dentry, buffer, buflen - 1); + if (!IS_ERR(pos) && *pos == '/' && pos[1]) { + struct inode *inode = dentry->d_inode; + if (inode && S_ISDIR(inode->i_mode)) { + buffer[buflen - 2] = '/'; + buffer[buflen - 1] = '\0'; + } + } + } + return pos; +#else + char *pos = buffer + buflen - 1; + if (buflen < 256) + return ERR_PTR(-ENOMEM); + *pos = '\0'; + if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) + *--pos = '/'; + spin_lock(&dcache_lock); + while (!IS_ROOT(dentry)) { + struct dentry *parent = dentry->d_parent; + const char *name = dentry->d_name.name; + const int len = dentry->d_name.len; + pos -= len; + if (pos <= buffer) { + pos = ERR_PTR(-ENOMEM); + break; + } + memmove(pos, name, len); + *--pos = '/'; + dentry = parent; + } + spin_unlock(&dcache_lock); + return pos; +#endif +} + +/** + * ccs_get_local_path - Get the path of a dentry. + * + * @dentry: Pointer to "struct dentry". + * @buffer: Pointer to buffer to return value in. + * @buflen: Sizeof @buffer. + * + * Returns the buffer on success, an error code otherwise. + */ +static char *ccs_get_local_path(struct dentry *dentry, char * const buffer, + const int buflen) +{ + struct super_block *sb = dentry->d_sb; + char *pos = ccs_get_dentry_path(dentry, buffer, buflen); + if (IS_ERR(pos)) + return pos; + /* Convert from $PID to self if $PID is current thread. */ + if (sb->s_magic == PROC_SUPER_MAGIC && *pos == '/') { + char *ep; + const pid_t pid = (pid_t) simple_strtoul(pos + 1, &ep, 10); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + if (*ep == '/' && pid && pid == + task_tgid_nr_ns(current, sb->s_fs_info)) { + pos = ep - 5; + if (pos < buffer) + goto out; + memmove(pos, "/self", 5); + } +#else + if (*ep == '/' && pid == ccs_sys_getpid()) { + pos = ep - 5; + if (pos < buffer) + goto out; + memmove(pos, "/self", 5); + } +#endif + goto prepend_filesystem_name; + } + /* Use filesystem name for unnamed devices. */ + if (!MAJOR(sb->s_dev)) + goto prepend_filesystem_name; + { + struct inode *inode = sb->s_root->d_inode; + /* + * Use filesystem name if filesystems does not support rename() + * operation. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) + if (inode->i_op && !inode->i_op->rename) + goto prepend_filesystem_name; +#else + if (!inode->i_op->rename && !inode->i_op->rename2) + goto prepend_filesystem_name; +#endif + } + /* Prepend device name. */ + { + char name[64]; + int name_len; + const dev_t dev = sb->s_dev; + name[sizeof(name) - 1] = '\0'; + snprintf(name, sizeof(name) - 1, "dev(%u,%u):", MAJOR(dev), + MINOR(dev)); + name_len = strlen(name); + pos -= name_len; + if (pos < buffer) + goto out; + memmove(pos, name, name_len); + return pos; + } + /* Prepend filesystem name. */ +prepend_filesystem_name: + { + const char *name = sb->s_type->name; + const int name_len = strlen(name); + pos -= name_len + 1; + if (pos < buffer) + goto out; + memmove(pos, name, name_len); + pos[name_len] = ':'; + } + return pos; +out: + return ERR_PTR(-ENOMEM); +} + +/** + * ccs_get_socket_name - Get the name of a socket. + * + * @path: Pointer to "struct path". + * @buffer: Pointer to buffer to return value in. + * @buflen: Sizeof @buffer. + * + * Returns the buffer. + */ +static char *ccs_get_socket_name(const struct path *path, char * const buffer, + const int buflen) +{ + struct inode *inode = path->dentry->d_inode; + struct socket *sock = inode ? SOCKET_I(inode) : NULL; + struct sock *sk = sock ? sock->sk : NULL; + if (sk) { + snprintf(buffer, buflen, "socket:[family=%u:type=%u:" + "protocol=%u]", sk->sk_family, sk->sk_type, + sk->sk_protocol); + } else { + snprintf(buffer, buflen, "socket:[unknown]"); + } + return buffer; +} + +#define SOCKFS_MAGIC 0x534F434B + +/** + * ccs_realpath - Returns realpath(3) of the given pathname but ignores chroot'ed root. + * + * @path: Pointer to "struct path". + * + * Returns the realpath of the given @path on success, NULL otherwise. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +char *ccs_realpath(const struct path *path) +{ + char *buf = NULL; + char *name = NULL; + unsigned int buf_len = PAGE_SIZE / 2; + struct dentry *dentry = path->dentry; + struct super_block *sb; + if (!dentry) + return NULL; + sb = dentry->d_sb; + while (1) { + char *pos; + struct inode *inode; + buf_len <<= 1; + kfree(buf); + buf = kmalloc(buf_len, CCS_GFP_FLAGS); + if (!buf) + break; + /* To make sure that pos is '\0' terminated. */ + buf[buf_len - 1] = '\0'; + /* Get better name for socket. */ + if (sb->s_magic == SOCKFS_MAGIC) { + pos = ccs_get_socket_name(path, buf, buf_len - 1); + goto encode; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) + /* For "pipe:[\$]". */ + if (dentry->d_op && dentry->d_op->d_dname) { + pos = dentry->d_op->d_dname(dentry, buf, buf_len - 1); + goto encode; + } +#endif + inode = sb->s_root->d_inode; + /* + * Use local name for "filesystems without rename() operation" + * or "path without vfsmount" or "absolute name is unavailable" + * cases. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) + if (!path->mnt || (inode->i_op && !inode->i_op->rename)) + pos = ERR_PTR(-EINVAL); + else { + /* Get absolute name for the rest. */ + ccs_realpath_lock(); + pos = ccs_get_absolute_path(path, buf, buf_len - 1); + ccs_realpath_unlock(); + } + if (pos == ERR_PTR(-EINVAL)) + pos = ccs_get_local_path(path->dentry, buf, + buf_len - 1); +#else + if (!path->mnt || + (!inode->i_op->rename && !inode->i_op->rename2)) + pos = ccs_get_local_path(path->dentry, buf, + buf_len - 1); + else + pos = ccs_get_absolute_path(path, buf, buf_len - 1); +#endif +encode: + if (IS_ERR(pos)) + continue; + name = ccs_encode(pos); + break; + } + kfree(buf); + if (!name) + ccs_warn_oom(__func__); + return name; +} + +/** + * ccs_encode2 - Encode binary string to ascii string. + * + * @str: String in binary format. + * @str_len: Size of @str in byte. + * + * Returns pointer to @str in ascii format on success, NULL otherwise. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +char *ccs_encode2(const char *str, int str_len) +{ + int i; + int len = 0; + const char *p = str; + char *cp; + char *cp0; + if (!p) + return NULL; + for (i = 0; i < str_len; i++) { + const unsigned char c = p[i]; + if (c == '\\') + len += 2; + else if (c > ' ' && c < 127) + len++; + else + len += 4; + } + len++; + /* Reserve space for appending "/". */ + cp = kzalloc(len + 10, CCS_GFP_FLAGS); + if (!cp) + return NULL; + cp0 = cp; + p = str; + for (i = 0; i < str_len; i++) { + const unsigned char c = p[i]; + if (c == '\\') { + *cp++ = '\\'; + *cp++ = '\\'; + } else if (c > ' ' && c < 127) { + *cp++ = c; + } else { + *cp++ = '\\'; + *cp++ = (c >> 6) + '0'; + *cp++ = ((c >> 3) & 7) + '0'; + *cp++ = (c & 7) + '0'; + } + } + return cp0; +} + +/** + * ccs_encode - Encode binary string to ascii string. + * + * @str: String in binary format. + * + * Returns pointer to @str in ascii format on success, NULL otherwise. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +char *ccs_encode(const char *str) +{ + return str ? ccs_encode2(str, strlen(str)) : NULL; +} + +/** + * ccs_const_part_length - Evaluate the initial length without a pattern in a token. + * + * @filename: The string to evaluate. + * + * Returns the initial length without a pattern in @filename. + */ +static int ccs_const_part_length(const char *filename) +{ + char c; + int len = 0; + if (!filename) + return 0; + while (1) { + c = *filename++; + if (!c) + break; + if (c != '\\') { + len++; + continue; + } + c = *filename++; + switch (c) { + case '\\': /* "\\" */ + len += 2; + continue; + case '0': /* "\ooo" */ + case '1': + case '2': + case '3': + c = *filename++; + if (c < '0' || c > '7') + break; + c = *filename++; + if (c < '0' || c > '7') + break; + len += 4; + continue; + } + break; + } + return len; +} + +/** + * ccs_fill_path_info - Fill in "struct ccs_path_info" members. + * + * @ptr: Pointer to "struct ccs_path_info" to fill in. + * + * The caller sets "struct ccs_path_info"->name. + */ +void ccs_fill_path_info(struct ccs_path_info *ptr) +{ + const char *name = ptr->name; + const int len = strlen(name); + ptr->total_len = len; + ptr->const_len = ccs_const_part_length(name); + ptr->is_dir = len && (name[len - 1] == '/'); + ptr->is_patterned = (ptr->const_len < len); + ptr->hash = full_name_hash(name, len); +} + +/** + * ccs_get_exe - Get ccs_realpath() of current process. + * + * Returns the ccs_realpath() of current process on success, NULL otherwise. + * + * This function uses kzalloc(), so the caller must kfree() + * if this function didn't return NULL. + */ +const char *ccs_get_exe(void) +{ + struct mm_struct *mm = current->mm; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26) + struct vm_area_struct *vma; +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) + struct path path; +#endif + struct file *exe_file = NULL; + const char *cp; + if (!mm) + return NULL; + down_read(&mm->mmap_sem); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26) + /* Not using get_mm_exe_file() as it is not exported. */ + exe_file = mm->exe_file; +#else + for (vma = mm->mmap; vma; vma = vma->vm_next) { + if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) { + exe_file = vma->vm_file; + break; + } + } +#endif + if (exe_file) + get_file(exe_file); + up_read(&mm->mmap_sem); + if (!exe_file) + return NULL; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) + cp = ccs_realpath(&exe_file->f_path); +#else + path.mnt = exe_file->f_vfsmnt; + path.dentry = exe_file->f_dentry; + cp = ccs_realpath(&path); +#endif + fput(exe_file); + return cp; +} -- 1.9.1