diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /security/selinux/hooks.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'security/selinux/hooks.c')
-rw-r--r-- | security/selinux/hooks.c | 4565 |
1 files changed, 4565 insertions, 0 deletions
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c new file mode 100644 index 00000000000..8a2cc75b394 --- /dev/null +++ b/security/selinux/hooks.c @@ -0,0 +1,4565 @@ +/* + * NSA Security-Enhanced Linux (SELinux) security module + * + * This file contains the SELinux hook function implementations. + * + * Authors: Stephen Smalley, <sds@epoch.ncsc.mil> + * Chris Vance, <cvance@nai.com> + * Wayne Salamon, <wsalamon@nai.com> + * James Morris <jmorris@redhat.com> + * + * Copyright (C) 2001,2002 Networks Associates Technology, Inc. + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * <dgoeddel@trustedcs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/security.h> +#include <linux/xattr.h> +#include <linux/capability.h> +#include <linux/unistd.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/slab.h> +#include <linux/pagemap.h> +#include <linux/swap.h> +#include <linux/smp_lock.h> +#include <linux/spinlock.h> +#include <linux/syscalls.h> +#include <linux/file.h> +#include <linux/namei.h> +#include <linux/mount.h> +#include <linux/ext2_fs.h> +#include <linux/proc_fs.h> +#include <linux/kd.h> +#include <linux/netfilter_ipv4.h> +#include <linux/netfilter_ipv6.h> +#include <linux/tty.h> +#include <net/icmp.h> +#include <net/ip.h> /* for sysctl_local_port_range[] */ +#include <net/tcp.h> /* struct or_callable used in sock_rcv_skb */ +#include <asm/uaccess.h> +#include <asm/semaphore.h> +#include <asm/ioctls.h> +#include <linux/bitops.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> /* for network interface checks */ +#include <linux/netlink.h> +#include <linux/tcp.h> +#include <linux/udp.h> +#include <linux/quota.h> +#include <linux/un.h> /* for Unix socket types */ +#include <net/af_unix.h> /* for Unix socket types */ +#include <linux/parser.h> +#include <linux/nfs_mount.h> +#include <net/ipv6.h> +#include <linux/hugetlb.h> +#include <linux/personality.h> +#include <linux/sysctl.h> +#include <linux/audit.h> + +#include "avc.h" +#include "objsec.h" +#include "netif.h" + +#define XATTR_SELINUX_SUFFIX "selinux" +#define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX + +extern unsigned int policydb_loaded_version; +extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); + +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +int selinux_enforcing = 0; + +static int __init enforcing_setup(char *str) +{ + selinux_enforcing = simple_strtol(str,NULL,0); + return 1; +} +__setup("enforcing=", enforcing_setup); +#endif + +#ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM +int selinux_enabled = CONFIG_SECURITY_SELINUX_BOOTPARAM_VALUE; + +static int __init selinux_enabled_setup(char *str) +{ + selinux_enabled = simple_strtol(str, NULL, 0); + return 1; +} +__setup("selinux=", selinux_enabled_setup); +#endif + +/* Original (dummy) security module. */ +static struct security_operations *original_ops = NULL; + +/* Minimal support for a secondary security module, + just to allow the use of the dummy or capability modules. + The owlsm module can alternatively be used as a secondary + module as long as CONFIG_OWLSM_FD is not enabled. */ +static struct security_operations *secondary_ops = NULL; + +/* Lists of inode and superblock security structures initialized + before the policy was loaded. */ +static LIST_HEAD(superblock_security_head); +static DEFINE_SPINLOCK(sb_security_lock); + +/* Allocate and free functions for each kind of security blob. */ + +static int task_alloc_security(struct task_struct *task) +{ + struct task_security_struct *tsec; + + tsec = kmalloc(sizeof(struct task_security_struct), GFP_KERNEL); + if (!tsec) + return -ENOMEM; + + memset(tsec, 0, sizeof(struct task_security_struct)); + tsec->magic = SELINUX_MAGIC; + tsec->task = task; + tsec->osid = tsec->sid = tsec->ptrace_sid = SECINITSID_UNLABELED; + task->security = tsec; + + return 0; +} + +static void task_free_security(struct task_struct *task) +{ + struct task_security_struct *tsec = task->security; + + if (!tsec || tsec->magic != SELINUX_MAGIC) + return; + + task->security = NULL; + kfree(tsec); +} + +static int inode_alloc_security(struct inode *inode) +{ + struct task_security_struct *tsec = current->security; + struct inode_security_struct *isec; + + isec = kmalloc(sizeof(struct inode_security_struct), GFP_KERNEL); + if (!isec) + return -ENOMEM; + + memset(isec, 0, sizeof(struct inode_security_struct)); + init_MUTEX(&isec->sem); + INIT_LIST_HEAD(&isec->list); + isec->magic = SELINUX_MAGIC; + isec->inode = inode; + isec->sid = SECINITSID_UNLABELED; + isec->sclass = SECCLASS_FILE; + if (tsec && tsec->magic == SELINUX_MAGIC) + isec->task_sid = tsec->sid; + else + isec->task_sid = SECINITSID_UNLABELED; + inode->i_security = isec; + + return 0; +} + +static void inode_free_security(struct inode *inode) +{ + struct inode_security_struct *isec = inode->i_security; + struct superblock_security_struct *sbsec = inode->i_sb->s_security; + + if (!isec || isec->magic != SELINUX_MAGIC) + return; + + spin_lock(&sbsec->isec_lock); + if (!list_empty(&isec->list)) + list_del_init(&isec->list); + spin_unlock(&sbsec->isec_lock); + + inode->i_security = NULL; + kfree(isec); +} + +static int file_alloc_security(struct file *file) +{ + struct task_security_struct *tsec = current->security; + struct file_security_struct *fsec; + + fsec = kmalloc(sizeof(struct file_security_struct), GFP_ATOMIC); + if (!fsec) + return -ENOMEM; + + memset(fsec, 0, sizeof(struct file_security_struct)); + fsec->magic = SELINUX_MAGIC; + fsec->file = file; + if (tsec && tsec->magic == SELINUX_MAGIC) { + fsec->sid = tsec->sid; + fsec->fown_sid = tsec->sid; + } else { + fsec->sid = SECINITSID_UNLABELED; + fsec->fown_sid = SECINITSID_UNLABELED; + } + file->f_security = fsec; + + return 0; +} + +static void file_free_security(struct file *file) +{ + struct file_security_struct *fsec = file->f_security; + + if (!fsec || fsec->magic != SELINUX_MAGIC) + return; + + file->f_security = NULL; + kfree(fsec); +} + +static int superblock_alloc_security(struct super_block *sb) +{ + struct superblock_security_struct *sbsec; + + sbsec = kmalloc(sizeof(struct superblock_security_struct), GFP_KERNEL); + if (!sbsec) + return -ENOMEM; + + memset(sbsec, 0, sizeof(struct superblock_security_struct)); + init_MUTEX(&sbsec->sem); + INIT_LIST_HEAD(&sbsec->list); + INIT_LIST_HEAD(&sbsec->isec_head); + spin_lock_init(&sbsec->isec_lock); + sbsec->magic = SELINUX_MAGIC; + sbsec->sb = sb; + sbsec->sid = SECINITSID_UNLABELED; + sbsec->def_sid = SECINITSID_FILE; + sb->s_security = sbsec; + + return 0; +} + +static void superblock_free_security(struct super_block *sb) +{ + struct superblock_security_struct *sbsec = sb->s_security; + + if (!sbsec || sbsec->magic != SELINUX_MAGIC) + return; + + spin_lock(&sb_security_lock); + if (!list_empty(&sbsec->list)) + list_del_init(&sbsec->list); + spin_unlock(&sb_security_lock); + + sb->s_security = NULL; + kfree(sbsec); +} + +#ifdef CONFIG_SECURITY_NETWORK +static int sk_alloc_security(struct sock *sk, int family, int priority) +{ + struct sk_security_struct *ssec; + + if (family != PF_UNIX) + return 0; + + ssec = kmalloc(sizeof(*ssec), priority); + if (!ssec) + return -ENOMEM; + + memset(ssec, 0, sizeof(*ssec)); + ssec->magic = SELINUX_MAGIC; + ssec->sk = sk; + ssec->peer_sid = SECINITSID_UNLABELED; + sk->sk_security = ssec; + + return 0; +} + +static void sk_free_security(struct sock *sk) +{ + struct sk_security_struct *ssec = sk->sk_security; + + if (sk->sk_family != PF_UNIX || ssec->magic != SELINUX_MAGIC) + return; + + sk->sk_security = NULL; + kfree(ssec); +} +#endif /* CONFIG_SECURITY_NETWORK */ + +/* The security server must be initialized before + any labeling or access decisions can be provided. */ +extern int ss_initialized; + +/* The file system's label must be initialized prior to use. */ + +static char *labeling_behaviors[6] = { + "uses xattr", + "uses transition SIDs", + "uses task SIDs", + "uses genfs_contexts", + "not configured for labeling", + "uses mountpoint labeling", +}; + +static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dentry); + +static inline int inode_doinit(struct inode *inode) +{ + return inode_doinit_with_dentry(inode, NULL); +} + +enum { + Opt_context = 1, + Opt_fscontext = 2, + Opt_defcontext = 4, +}; + +static match_table_t tokens = { + {Opt_context, "context=%s"}, + {Opt_fscontext, "fscontext=%s"}, + {Opt_defcontext, "defcontext=%s"}, +}; + +#define SEL_MOUNT_FAIL_MSG "SELinux: duplicate or incompatible mount options\n" + +static int try_context_mount(struct super_block *sb, void *data) +{ + char *context = NULL, *defcontext = NULL; + const char *name; + u32 sid; + int alloc = 0, rc = 0, seen = 0; + struct task_security_struct *tsec = current->security; + struct superblock_security_struct *sbsec = sb->s_security; + + if (!data) + goto out; + + name = sb->s_type->name; + + if (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) { + + /* NFS we understand. */ + if (!strcmp(name, "nfs")) { + struct nfs_mount_data *d = data; + + if (d->version < NFS_MOUNT_VERSION) + goto out; + + if (d->context[0]) { + context = d->context; + seen |= Opt_context; + } + } else + goto out; + + } else { + /* Standard string-based options. */ + char *p, *options = data; + + while ((p = strsep(&options, ",")) != NULL) { + int token; + substring_t args[MAX_OPT_ARGS]; + + if (!*p) + continue; + + token = match_token(p, tokens, args); + + switch (token) { + case Opt_context: + if (seen) { + rc = -EINVAL; + printk(KERN_WARNING SEL_MOUNT_FAIL_MSG); + goto out_free; + } + context = match_strdup(&args[0]); + if (!context) { + rc = -ENOMEM; + goto out_free; + } + if (!alloc) + alloc = 1; + seen |= Opt_context; + break; + + case Opt_fscontext: + if (seen & (Opt_context|Opt_fscontext)) { + rc = -EINVAL; + printk(KERN_WARNING SEL_MOUNT_FAIL_MSG); + goto out_free; + } + context = match_strdup(&args[0]); + if (!context) { + rc = -ENOMEM; + goto out_free; + } + if (!alloc) + alloc = 1; + seen |= Opt_fscontext; + break; + + case Opt_defcontext: + if (sbsec->behavior != SECURITY_FS_USE_XATTR) { + rc = -EINVAL; + printk(KERN_WARNING "SELinux: " + "defcontext option is invalid " + "for this filesystem type\n"); + goto out_free; + } + if (seen & (Opt_context|Opt_defcontext)) { + rc = -EINVAL; + printk(KERN_WARNING SEL_MOUNT_FAIL_MSG); + goto out_free; + } + defcontext = match_strdup(&args[0]); + if (!defcontext) { + rc = -ENOMEM; + goto out_free; + } + if (!alloc) + alloc = 1; + seen |= Opt_defcontext; + break; + + default: + rc = -EINVAL; + printk(KERN_WARNING "SELinux: unknown mount " + "option\n"); + goto out_free; + + } + } + } + + if (!seen) + goto out; + + if (context) { + rc = security_context_to_sid(context, strlen(context), &sid); + if (rc) { + printk(KERN_WARNING "SELinux: security_context_to_sid" + "(%s) failed for (dev %s, type %s) errno=%d\n", + context, sb->s_id, name, rc); + goto out_free; + } + + rc = avc_has_perm(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, + FILESYSTEM__RELABELFROM, NULL); + if (rc) + goto out_free; + + rc = avc_has_perm(tsec->sid, sid, SECCLASS_FILESYSTEM, + FILESYSTEM__RELABELTO, NULL); + if (rc) + goto out_free; + + sbsec->sid = sid; + + if (seen & Opt_context) + sbsec->behavior = SECURITY_FS_USE_MNTPOINT; + } + + if (defcontext) { + rc = security_context_to_sid(defcontext, strlen(defcontext), &sid); + if (rc) { + printk(KERN_WARNING "SELinux: security_context_to_sid" + "(%s) failed for (dev %s, type %s) errno=%d\n", + defcontext, sb->s_id, name, rc); + goto out_free; + } + + if (sid == sbsec->def_sid) + goto out_free; + + rc = avc_has_perm(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, + FILESYSTEM__RELABELFROM, NULL); + if (rc) + goto out_free; + + rc = avc_has_perm(sid, sbsec->sid, SECCLASS_FILESYSTEM, + FILESYSTEM__ASSOCIATE, NULL); + if (rc) + goto out_free; + + sbsec->def_sid = sid; + } + +out_free: + if (alloc) { + kfree(context); + kfree(defcontext); + } +out: + return rc; +} + +static int superblock_doinit(struct super_block *sb, void *data) +{ + struct superblock_security_struct *sbsec = sb->s_security; + struct dentry *root = sb->s_root; + struct inode *inode = root->d_inode; + int rc = 0; + + down(&sbsec->sem); + if (sbsec->initialized) + goto out; + + if (!ss_initialized) { + /* Defer initialization until selinux_complete_init, + after the initial policy is loaded and the security + server is ready to handle calls. */ + spin_lock(&sb_security_lock); + if (list_empty(&sbsec->list)) + list_add(&sbsec->list, &superblock_security_head); + spin_unlock(&sb_security_lock); + goto out; + } + + /* Determine the labeling behavior to use for this filesystem type. */ + rc = security_fs_use(sb->s_type->name, &sbsec->behavior, &sbsec->sid); + if (rc) { + printk(KERN_WARNING "%s: security_fs_use(%s) returned %d\n", + __FUNCTION__, sb->s_type->name, rc); + goto out; + } + + rc = try_context_mount(sb, data); + if (rc) + goto out; + + if (sbsec->behavior == SECURITY_FS_USE_XATTR) { + /* Make sure that the xattr handler exists and that no + error other than -ENODATA is returned by getxattr on + the root directory. -ENODATA is ok, as this may be + the first boot of the SELinux kernel before we have + assigned xattr values to the filesystem. */ + if (!inode->i_op->getxattr) { + printk(KERN_WARNING "SELinux: (dev %s, type %s) has no " + "xattr support\n", sb->s_id, sb->s_type->name); + rc = -EOPNOTSUPP; + goto out; + } + rc = inode->i_op->getxattr(root, XATTR_NAME_SELINUX, NULL, 0); + if (rc < 0 && rc != -ENODATA) { + if (rc == -EOPNOTSUPP) + printk(KERN_WARNING "SELinux: (dev %s, type " + "%s) has no security xattr handler\n", + sb->s_id, sb->s_type->name); + else + printk(KERN_WARNING "SELinux: (dev %s, type " + "%s) getxattr errno %d\n", sb->s_id, + sb->s_type->name, -rc); + goto out; + } + } + + if (strcmp(sb->s_type->name, "proc") == 0) + sbsec->proc = 1; + + sbsec->initialized = 1; + + if (sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) { + printk(KERN_INFO "SELinux: initialized (dev %s, type %s), unknown behavior\n", + sb->s_id, sb->s_type->name); + } + else { + printk(KERN_INFO "SELinux: initialized (dev %s, type %s), %s\n", + sb->s_id, sb->s_type->name, + labeling_behaviors[sbsec->behavior-1]); + } + + /* Initialize the root inode. */ + rc = inode_doinit_with_dentry(sb->s_root->d_inode, sb->s_root); + + /* Initialize any other inodes associated with the superblock, e.g. + inodes created prior to initial policy load or inodes created + during get_sb by a pseudo filesystem that directly + populates itself. */ + spin_lock(&sbsec->isec_lock); +next_inode: + if (!list_empty(&sbsec->isec_head)) { + struct inode_security_struct *isec = + list_entry(sbsec->isec_head.next, + struct inode_security_struct, list); + struct inode *inode = isec->inode; + spin_unlock(&sbsec->isec_lock); + inode = igrab(inode); + if (inode) { + if (!IS_PRIVATE (inode)) + inode_doinit(inode); + iput(inode); + } + spin_lock(&sbsec->isec_lock); + list_del_init(&isec->list); + goto next_inode; + } + spin_unlock(&sbsec->isec_lock); +out: + up(&sbsec->sem); + return rc; +} + +static inline u16 inode_mode_to_security_class(umode_t mode) +{ + switch (mode & S_IFMT) { + case S_IFSOCK: + return SECCLASS_SOCK_FILE; + case S_IFLNK: + return SECCLASS_LNK_FILE; + case S_IFREG: + return SECCLASS_FILE; + case S_IFBLK: + return SECCLASS_BLK_FILE; + case S_IFDIR: + return SECCLASS_DIR; + case S_IFCHR: + return SECCLASS_CHR_FILE; + case S_IFIFO: + return SECCLASS_FIFO_FILE; + + } + + return SECCLASS_FILE; +} + +static inline u16 socket_type_to_security_class(int family, int type, int protocol) +{ + switch (family) { + case PF_UNIX: + switch (type) { + case SOCK_STREAM: + case SOCK_SEQPACKET: + return SECCLASS_UNIX_STREAM_SOCKET; + case SOCK_DGRAM: + return SECCLASS_UNIX_DGRAM_SOCKET; + } + break; + case PF_INET: + case PF_INET6: + switch (type) { + case SOCK_STREAM: + return SECCLASS_TCP_SOCKET; + case SOCK_DGRAM: + return SECCLASS_UDP_SOCKET; + case SOCK_RAW: + return SECCLASS_RAWIP_SOCKET; + } + break; + case PF_NETLINK: + switch (protocol) { + case NETLINK_ROUTE: + return SECCLASS_NETLINK_ROUTE_SOCKET; + case NETLINK_FIREWALL: + return SECCLASS_NETLINK_FIREWALL_SOCKET; + case NETLINK_TCPDIAG: + return SECCLASS_NETLINK_TCPDIAG_SOCKET; + case NETLINK_NFLOG: + return SECCLASS_NETLINK_NFLOG_SOCKET; + case NETLINK_XFRM: + return SECCLASS_NETLINK_XFRM_SOCKET; + case NETLINK_SELINUX: + return SECCLASS_NETLINK_SELINUX_SOCKET; + case NETLINK_AUDIT: + return SECCLASS_NETLINK_AUDIT_SOCKET; + case NETLINK_IP6_FW: + return SECCLASS_NETLINK_IP6FW_SOCKET; + case NETLINK_DNRTMSG: + return SECCLASS_NETLINK_DNRT_SOCKET; + default: + return SECCLASS_NETLINK_SOCKET; + } + case PF_PACKET: + return SECCLASS_PACKET_SOCKET; + case PF_KEY: + return SECCLASS_KEY_SOCKET; + } + + return SECCLASS_SOCKET; +} + +#ifdef CONFIG_PROC_FS +static int selinux_proc_get_sid(struct proc_dir_entry *de, + u16 tclass, + u32 *sid) +{ + int buflen, rc; + char *buffer, *path, *end; + + buffer = (char*)__get_free_page(GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + buflen = PAGE_SIZE; + end = buffer+buflen; + *--end = '\0'; + buflen--; + path = end-1; + *path = '/'; + while (de && de != de->parent) { + buflen -= de->namelen + 1; + if (buflen < 0) + break; + end -= de->namelen; + memcpy(end, de->name, de->namelen); + *--end = '/'; + path = end; + de = de->parent; + } + rc = security_genfs_sid("proc", path, tclass, sid); + free_page((unsigned long)buffer); + return rc; +} +#else +static int selinux_proc_get_sid(struct proc_dir_entry *de, + u16 tclass, + u32 *sid) +{ + return -EINVAL; +} +#endif + +/* The inode's security attributes must be initialized before first use. */ +static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dentry) +{ + struct superblock_security_struct *sbsec = NULL; + struct inode_security_struct *isec = inode->i_security; + u32 sid; + struct dentry *dentry; +#define INITCONTEXTLEN 255 + char *context = NULL; + unsigned len = 0; + int rc = 0; + int hold_sem = 0; + + if (isec->initialized) + goto out; + + down(&isec->sem); + hold_sem = 1; + if (isec->initialized) + goto out; + + sbsec = inode->i_sb->s_security; + if (!sbsec->initialized) { + /* Defer initialization until selinux_complete_init, + after the initial policy is loaded and the security + server is ready to handle calls. */ + spin_lock(&sbsec->isec_lock); + if (list_empty(&isec->list)) + list_add(&isec->list, &sbsec->isec_head); + spin_unlock(&sbsec->isec_lock); + goto out; + } + + switch (sbsec->behavior) { + case SECURITY_FS_USE_XATTR: + if (!inode->i_op->getxattr) { + isec->sid = sbsec->def_sid; + break; + } + + /* Need a dentry, since the xattr API requires one. + Life would be simpler if we could just pass the inode. */ + if (opt_dentry) { + /* Called from d_instantiate or d_splice_alias. */ + dentry = dget(opt_dentry); + } else { + /* Called from selinux_complete_init, try to find a dentry. */ + dentry = d_find_alias(inode); + } + if (!dentry) { + printk(KERN_WARNING "%s: no dentry for dev=%s " + "ino=%ld\n", __FUNCTION__, inode->i_sb->s_id, + inode->i_ino); + goto out; + } + + len = INITCONTEXTLEN; + context = kmalloc(len, GFP_KERNEL); + if (!context) { + rc = -ENOMEM; + dput(dentry); + goto out; + } + rc = inode->i_op->getxattr(dentry, XATTR_NAME_SELINUX, + context, len); + if (rc == -ERANGE) { + /* Need a larger buffer. Query for the right size. */ + rc = inode->i_op->getxattr(dentry, XATTR_NAME_SELINUX, + NULL, 0); + if (rc < 0) { + dput(dentry); + goto out; + } + kfree(context); + len = rc; + context = kmalloc(len, GFP_KERNEL); + if (!context) { + rc = -ENOMEM; + dput(dentry); + goto out; + } + rc = inode->i_op->getxattr(dentry, + XATTR_NAME_SELINUX, + context, len); + } + dput(dentry); + if (rc < 0) { + if (rc != -ENODATA) { + printk(KERN_WARNING "%s: getxattr returned " + "%d for dev=%s ino=%ld\n", __FUNCTION__, + -rc, inode->i_sb->s_id, inode->i_ino); + kfree(context); + goto out; + } + /* Map ENODATA to the default file SID */ + sid = sbsec->def_sid; + rc = 0; + } else { + rc = security_context_to_sid(context, rc, &sid); + if (rc) { + printk(KERN_WARNING "%s: context_to_sid(%s) " + "returned %d for dev=%s ino=%ld\n", + __FUNCTION__, context, -rc, + inode->i_sb->s_id, inode->i_ino); + kfree(context); + /* Leave with the unlabeled SID */ + rc = 0; + break; + } + } + kfree(context); + isec->sid = sid; + break; + case SECURITY_FS_USE_TASK: + isec->sid = isec->task_sid; + break; + case SECURITY_FS_USE_TRANS: + /* Default to the fs SID. */ + isec->sid = sbsec->sid; + + /* Try to obtain a transition SID. */ + isec->sclass = inode_mode_to_security_class(inode->i_mode); + rc = security_transition_sid(isec->task_sid, + sbsec->sid, + isec->sclass, + &sid); + if (rc) + goto out; + isec->sid = sid; + break; + default: + /* Default to the fs SID. */ + isec->sid = sbsec->sid; + + if (sbsec->proc) { + struct proc_inode *proci = PROC_I(inode); + if (proci->pde) { + isec->sclass = inode_mode_to_security_class(inode->i_mode); + rc = selinux_proc_get_sid(proci->pde, + isec->sclass, + &sid); + if (rc) + goto out; + isec->sid = sid; + } + } + break; + } + + isec->initialized = 1; + +out: + if (isec->sclass == SECCLASS_FILE) + isec->sclass = inode_mode_to_security_class(inode->i_mode); + + if (hold_sem) + up(&isec->sem); + return rc; +} + +/* Convert a Linux signal to an access vector. */ +static inline u32 signal_to_av(int sig) +{ + u32 perm = 0; + + switch (sig) { + case SIGCHLD: + /* Commonly granted from child to parent. */ + perm = PROCESS__SIGCHLD; + break; + case SIGKILL: + /* Cannot be caught or ignored */ + perm = PROCESS__SIGKILL; + break; + case SIGSTOP: + /* Cannot be caught or ignored */ + perm = PROCESS__SIGSTOP; + break; + default: + /* All other signals. */ + perm = PROCESS__SIGNAL; + break; + } + + return perm; +} + +/* Check permission betweeen a pair of tasks, e.g. signal checks, + fork check, ptrace check, etc. */ +static int task_has_perm(struct task_struct *tsk1, + struct task_struct *tsk2, + u32 perms) +{ + struct task_security_struct *tsec1, *tsec2; + + tsec1 = tsk1->security; + tsec2 = tsk2->security; + return avc_has_perm(tsec1->sid, tsec2->sid, + SECCLASS_PROCESS, perms, NULL); +} + +/* Check whether a task is allowed to use a capability. */ +static int task_has_capability(struct task_struct *tsk, + int cap) +{ + struct task_security_struct *tsec; + struct avc_audit_data ad; + + tsec = tsk->security; + + AVC_AUDIT_DATA_INIT(&ad,CAP); + ad.tsk = tsk; + ad.u.cap = cap; + + return avc_has_perm(tsec->sid, tsec->sid, + SECCLASS_CAPABILITY, CAP_TO_MASK(cap), &ad); +} + +/* Check whether a task is allowed to use a system operation. */ +static int task_has_system(struct task_struct *tsk, + u32 perms) +{ + struct task_security_struct *tsec; + + tsec = tsk->security; + + return avc_has_perm(tsec->sid, SECINITSID_KERNEL, + SECCLASS_SYSTEM, perms, NULL); +} + +/* Check whether a task has a particular permission to an inode. + The 'adp' parameter is optional and allows other audit + data to be passed (e.g. the dentry). */ +static int inode_has_perm(struct task_struct *tsk, + struct inode *inode, + u32 perms, + struct avc_audit_data *adp) +{ + struct task_security_struct *tsec; + struct inode_security_struct *isec; + struct avc_audit_data ad; + + tsec = tsk->security; + isec = inode->i_security; + + if (!adp) { + adp = &ad; + AVC_AUDIT_DATA_INIT(&ad, FS); + ad.u.fs.inode = inode; + } + + return avc_has_perm(tsec->sid, isec->sid, isec->sclass, perms, adp); +} + +/* Same as inode_has_perm, but pass explicit audit data containing + the dentry to help the auditing code to more easily generate the + pathname if needed. */ +static inline int dentry_has_perm(struct task_struct *tsk, + struct vfsmount *mnt, + struct dentry *dentry, + u32 av) +{ + struct inode *inode = dentry->d_inode; + struct avc_audit_data ad; + AVC_AUDIT_DATA_INIT(&ad,FS); + ad.u.fs.mnt = mnt; + ad.u.fs.dentry = dentry; + return inode_has_perm(tsk, inode, av, &ad); +} + +/* Check whether a task can use an open file descriptor to + access an inode in a given way. Check access to the + descriptor itself, and then use dentry_has_perm to + check a particular permission to the file. + Access to the descriptor is implicitly granted if it + has the same SID as the process. If av is zero, then + access to the file is not checked, e.g. for cases + where only the descriptor is affected like seek. */ +static inline int file_has_perm(struct task_struct *tsk, + struct file *file, + u32 av) +{ + struct task_security_struct *tsec = tsk->security; + struct file_security_struct *fsec = file->f_security; + struct vfsmount *mnt = file->f_vfsmnt; + struct dentry *dentry = file->f_dentry; + struct inode *inode = dentry->d_inode; + struct avc_audit_data ad; + int rc; + + AVC_AUDIT_DATA_INIT(&ad, FS); + ad.u.fs.mnt = mnt; + ad.u.fs.dentry = dentry; + + if (tsec->sid != fsec->sid) { + rc = avc_has_perm(tsec->sid, fsec->sid, + SECCLASS_FD, + FD__USE, + &ad); + if (rc) + return rc; + } + + /* av is zero if only checking access to the descriptor. */ + if (av) + return inode_has_perm(tsk, inode, av, &ad); + + return 0; +} + +/* Check whether a task can create a file. */ +static int may_create(struct inode *dir, + struct dentry *dentry, + u16 tclass) +{ + struct task_security_struct *tsec; + struct inode_security_struct *dsec; + struct superblock_security_struct *sbsec; + u32 newsid; + struct avc_audit_data ad; + int rc; + + tsec = current->security; + dsec = dir->i_security; + sbsec = dir->i_sb->s_security; + + AVC_AUDIT_DATA_INIT(&ad, FS); + ad.u.fs.dentry = dentry; + + rc = avc_has_perm(tsec->sid, dsec->sid, SECCLASS_DIR, + DIR__ADD_NAME | DIR__SEARCH, + &ad); + if (rc) + return rc; + + if (tsec->create_sid && sbsec->behavior != SECURITY_FS_USE_MNTPOINT) { + newsid = tsec->create_sid; + } else { + rc = security_transition_sid(tsec->sid, dsec->sid, tclass, + &newsid); + if (rc) + return rc; + } + + rc = avc_has_perm(tsec->sid, newsid, tclass, FILE__CREATE, &ad); + if (rc) + return rc; + + return avc_has_perm(newsid, sbsec->sid, + SECCLASS_FILESYSTEM, + FILESYSTEM__ASSOCIATE, &ad); +} + +#define MAY_LINK 0 +#define MAY_UNLINK 1 +#define MAY_RMDIR 2 + +/* Check whether a task can link, unlink, or rmdir a file/directory. */ +static int may_link(struct inode *dir, + struct dentry *dentry, + int kind) + +{ + struct task_security_struct *tsec; + struct inode_security_struct *dsec, *isec; + struct avc_audit_data ad; + u32 av; + int rc; + + tsec = current->security; + dsec = dir->i_security; + isec = dentry->d_inode->i_security; + + AVC_AUDIT_DATA_INIT(&ad, FS); + ad.u.fs.dentry = dentry; + + av = DIR__SEARCH; + av |= (kind ? DIR__REMOVE_NAME : DIR__ADD_NAME); + rc = avc_has_perm(tsec->sid, dsec->sid, SECCLASS_DIR, av, &ad); + if (rc) + return rc; + + switch (kind) { + case MAY_LINK: + av = FILE__LINK; + break; + case MAY_UNLINK: + av = FILE__UNLINK; + break; + case MAY_RMDIR: + av = DIR__RMDIR; + break; + default: + printk(KERN_WARNING "may_link: unrecognized kind %d\n", kind); + return 0; + } + + rc = avc_has_perm(tsec->sid, isec->sid, isec->sclass, av, &ad); + return rc; +} + +static inline int may_rename(struct inode *old_dir, + struct dentry *old_dentry, + struct inode *new_dir, + struct dentry *new_dentry) +{ + struct task_security_struct *tsec; + struct inode_security_struct *old_dsec, *new_dsec, *old_isec, *new_isec; + struct avc_audit_data ad; + u32 av; + int old_is_dir, new_is_dir; + int rc; + + tsec = current->security; + old_dsec = old_dir->i_security; + old_isec = old_dentry->d_inode->i_security; + old_is_dir = S_ISDIR(old_dentry->d_inode->i_mode); + new_dsec = new_dir->i_security; + + AVC_AUDIT_DATA_INIT(&ad, FS); + + ad.u.fs.dentry = old_dentry; + rc = avc_has_perm(tsec->sid, old_dsec->sid, SECCLASS_DIR, + DIR__REMOVE_NAME | DIR__SEARCH, &ad); + if (rc) + return rc; + rc = avc_has_perm(tsec->sid, old_isec->sid, + old_isec->sclass, FILE__RENAME, &ad); + if (rc) + return rc; + if (old_is_dir && new_dir != old_dir) { + rc = avc_has_perm(tsec->sid, old_isec->sid, + old_isec->sclass, DIR__REPARENT, &ad); + if (rc) + return rc; + } + + ad.u.fs.dentry = new_dentry; + av = DIR__ADD_NAME | DIR__SEARCH; + if (new_dentry->d_inode) + av |= DIR__REMOVE_NAME; + rc = avc_has_perm(tsec->sid, new_dsec->sid, SECCLASS_DIR, av, &ad); + if (rc) + return rc; + if (new_dentry->d_inode) { + new_isec = new_dentry->d_inode->i_security; + new_is_dir = S_ISDIR(new_dentry->d_inode->i_mode); + rc = avc_has_perm(tsec->sid, new_isec->sid, + new_isec->sclass, + (new_is_dir ? DIR__RMDIR : FILE__UNLINK), &ad); + if (rc) + return rc; + } + + return 0; +} + +/* Check whether a task can perform a filesystem operation. */ +static int superblock_has_perm(struct task_struct *tsk, + struct super_block *sb, + u32 perms, + struct avc_audit_data *ad) +{ + struct task_security_struct *tsec; + struct superblock_security_struct *sbsec; + + tsec = tsk->security; + sbsec = sb->s_security; + return avc_has_perm(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, + perms, ad); +} |