aboutsummaryrefslogtreecommitdiff
path: root/security/selinux/hooks.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /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.c4565
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);
+}