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 |
Linux-2.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')
61 files changed, 23351 insertions, 0 deletions
diff --git a/security/Kconfig b/security/Kconfig new file mode 100644 index 000000000000..dcf04a09185d --- /dev/null +++ b/security/Kconfig @@ -0,0 +1,91 @@ +# +# Security configuration +# + +menu "Security options" + +config KEYS + bool "Enable access key retention support" + help + This option provides support for retaining authentication tokens and + access keys in the kernel. + + It also includes provision of methods by which such keys might be + associated with a process so that network filesystems, encryption + support and the like can find them. + + Furthermore, a special type of key is available that acts as keyring: + a searchable sequence of keys. Each process is equipped with access + to five standard keyrings: UID-specific, GID-specific, session, + process and thread. + + If you are unsure as to whether this is required, answer N. + +config KEYS_DEBUG_PROC_KEYS + bool "Enable the /proc/keys file by which all keys may be viewed" + depends on KEYS + help + This option turns on support for the /proc/keys file through which + all the keys on the system can be listed. + + This option is a slight security risk in that it makes it possible + for anyone to see all the keys on the system. Normally the manager + pretends keys that are inaccessible to a process don't exist as far + as that process is concerned. + +config SECURITY + bool "Enable different security models" + help + This allows you to choose different security modules to be + configured into your kernel. + + If this option is not selected, the default Linux security + model will be used. + + If you are unsure how to answer this question, answer N. + +config SECURITY_NETWORK + bool "Socket and Networking Security Hooks" + depends on SECURITY + help + This enables the socket and networking security hooks. + If enabled, a security module can use these hooks to + implement socket and networking access controls. + If you are unsure how to answer this question, answer N. + +config SECURITY_CAPABILITIES + tristate "Default Linux Capabilities" + depends on SECURITY + help + This enables the "default" Linux capabilities functionality. + If you are unsure how to answer this question, answer Y. + +config SECURITY_ROOTPLUG + tristate "Root Plug Support" + depends on USB && SECURITY + help + This is a sample LSM module that should only be used as such. + It prevents any programs running with egid == 0 if a specific + USB device is not present in the system. + + See <http://www.linuxjournal.com/article.php?sid=6279> for + more information about this module. + + If you are unsure how to answer this question, answer N. + +config SECURITY_SECLVL + tristate "BSD Secure Levels" + depends on SECURITY + select CRYPTO + select CRYPTO_SHA1 + help + Implements BSD Secure Levels as an LSM. See + <file:Documentation/seclvl.txt> for instructions on how to use this + module. + + If you are unsure how to answer this question, answer N. + +source security/selinux/Kconfig + +endmenu + diff --git a/security/Makefile b/security/Makefile new file mode 100644 index 000000000000..197cc2f3f1ec --- /dev/null +++ b/security/Makefile @@ -0,0 +1,19 @@ +# +# Makefile for the kernel security code +# + +obj-$(CONFIG_KEYS) += keys/ +subdir-$(CONFIG_SECURITY_SELINUX) += selinux + +# if we don't select a security model, use the default capabilities +ifneq ($(CONFIG_SECURITY),y) +obj-y += commoncap.o +endif + +# Object file lists +obj-$(CONFIG_SECURITY) += security.o dummy.o +# Must precede capability.o in order to stack properly. +obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o +obj-$(CONFIG_SECURITY_CAPABILITIES) += commoncap.o capability.o +obj-$(CONFIG_SECURITY_ROOTPLUG) += commoncap.o root_plug.o +obj-$(CONFIG_SECURITY_SECLVL) += seclvl.o diff --git a/security/capability.c b/security/capability.c new file mode 100644 index 000000000000..ec18d6075625 --- /dev/null +++ b/security/capability.c @@ -0,0 +1,104 @@ +/* + * Capabilities Linux Security Module + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/security.h> +#include <linux/file.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/pagemap.h> +#include <linux/swap.h> +#include <linux/smp_lock.h> +#include <linux/skbuff.h> +#include <linux/netlink.h> +#include <linux/ptrace.h> +#include <linux/moduleparam.h> + +static struct security_operations capability_ops = { + .ptrace = cap_ptrace, + .capget = cap_capget, + .capset_check = cap_capset_check, + .capset_set = cap_capset_set, + .capable = cap_capable, + .settime = cap_settime, + .netlink_send = cap_netlink_send, + .netlink_recv = cap_netlink_recv, + + .bprm_apply_creds = cap_bprm_apply_creds, + .bprm_set_security = cap_bprm_set_security, + .bprm_secureexec = cap_bprm_secureexec, + + .inode_setxattr = cap_inode_setxattr, + .inode_removexattr = cap_inode_removexattr, + + .task_post_setuid = cap_task_post_setuid, + .task_reparent_to_init = cap_task_reparent_to_init, + + .syslog = cap_syslog, + + .vm_enough_memory = cap_vm_enough_memory, +}; + +#define MY_NAME __stringify(KBUILD_MODNAME) + +/* flag to keep track of how we were registered */ +static int secondary; + +static int capability_disable; +module_param_named(disable, capability_disable, int, 0); +MODULE_PARM_DESC(disable, "To disable capabilities module set disable = 1"); + +static int __init capability_init (void) +{ + if (capability_disable) { + printk(KERN_INFO "Capabilities disabled at initialization\n"); + return 0; + } + /* register ourselves with the security framework */ + if (register_security (&capability_ops)) { + /* try registering with primary module */ + if (mod_reg_security (MY_NAME, &capability_ops)) { + printk (KERN_INFO "Failure registering capabilities " + "with primary security module.\n"); + return -EINVAL; + } + secondary = 1; + } + printk (KERN_INFO "Capability LSM initialized%s\n", + secondary ? " as secondary" : ""); + return 0; +} + +static void __exit capability_exit (void) +{ + if (capability_disable) + return; + /* remove ourselves from the security framework */ + if (secondary) { + if (mod_unreg_security (MY_NAME, &capability_ops)) + printk (KERN_INFO "Failure unregistering capabilities " + "with primary module.\n"); + return; + } + + if (unregister_security (&capability_ops)) { + printk (KERN_INFO + "Failure unregistering capabilities with the kernel\n"); + } +} + +security_initcall (capability_init); +module_exit (capability_exit); + +MODULE_DESCRIPTION("Standard Linux Capabilities Security Module"); +MODULE_LICENSE("GPL"); diff --git a/security/commoncap.c b/security/commoncap.c new file mode 100644 index 000000000000..849b8c338ee8 --- /dev/null +++ b/security/commoncap.c @@ -0,0 +1,345 @@ +/* Common capabilities, needed by capability.o and root_plug.o + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/security.h> +#include <linux/file.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/pagemap.h> +#include <linux/swap.h> +#include <linux/smp_lock.h> +#include <linux/skbuff.h> +#include <linux/netlink.h> +#include <linux/ptrace.h> +#include <linux/xattr.h> +#include <linux/hugetlb.h> + +int cap_netlink_send(struct sock *sk, struct sk_buff *skb) +{ + NETLINK_CB(skb).eff_cap = current->cap_effective; + return 0; +} + +EXPORT_SYMBOL(cap_netlink_send); + +int cap_netlink_recv(struct sk_buff *skb) +{ + if (!cap_raised(NETLINK_CB(skb).eff_cap, CAP_NET_ADMIN)) + return -EPERM; + return 0; +} + +EXPORT_SYMBOL(cap_netlink_recv); + +int cap_capable (struct task_struct *tsk, int cap) +{ + /* Derived from include/linux/sched.h:capable. */ + if (cap_raised(tsk->cap_effective, cap)) + return 0; + return -EPERM; +} + +int cap_settime(struct timespec *ts, struct timezone *tz) +{ + if (!capable(CAP_SYS_TIME)) + return -EPERM; + return 0; +} + +int cap_ptrace (struct task_struct *parent, struct task_struct *child) +{ + /* Derived from arch/i386/kernel/ptrace.c:sys_ptrace. */ + if (!cap_issubset (child->cap_permitted, current->cap_permitted) && + !capable(CAP_SYS_PTRACE)) + return -EPERM; + return 0; +} + +int cap_capget (struct task_struct *target, kernel_cap_t *effective, + kernel_cap_t *inheritable, kernel_cap_t *permitted) +{ + /* Derived from kernel/capability.c:sys_capget. */ + *effective = cap_t (target->cap_effective); + *inheritable = cap_t (target->cap_inheritable); + *permitted = cap_t (target->cap_permitted); + return 0; +} + +int cap_capset_check (struct task_struct *target, kernel_cap_t *effective, + kernel_cap_t *inheritable, kernel_cap_t *permitted) +{ + /* Derived from kernel/capability.c:sys_capset. */ + /* verify restrictions on target's new Inheritable set */ + if (!cap_issubset (*inheritable, + cap_combine (target->cap_inheritable, + current->cap_permitted))) { + return -EPERM; + } + + /* verify restrictions on target's new Permitted set */ + if (!cap_issubset (*permitted, + cap_combine (target->cap_permitted, + current->cap_permitted))) { + return -EPERM; + } + + /* verify the _new_Effective_ is a subset of the _new_Permitted_ */ + if (!cap_issubset (*effective, *permitted)) { + return -EPERM; + } + + return 0; +} + +void cap_capset_set (struct task_struct *target, kernel_cap_t *effective, + kernel_cap_t *inheritable, kernel_cap_t *permitted) +{ + target->cap_effective = *effective; + target->cap_inheritable = *inheritable; + target->cap_permitted = *permitted; +} + +int cap_bprm_set_security (struct linux_binprm *bprm) +{ + /* Copied from fs/exec.c:prepare_binprm. */ + + /* We don't have VFS support for capabilities yet */ + cap_clear (bprm->cap_inheritable); + cap_clear (bprm->cap_permitted); + cap_clear (bprm->cap_effective); + + /* To support inheritance of root-permissions and suid-root + * executables under compatibility mode, we raise all three + * capability sets for the file. + * + * If only the real uid is 0, we only raise the inheritable + * and permitted sets of the executable file. + */ + + if (!issecure (SECURE_NOROOT)) { + if (bprm->e_uid == 0 || current->uid == 0) { + cap_set_full (bprm->cap_inheritable); + cap_set_full (bprm->cap_permitted); + } + if (bprm->e_uid == 0) + cap_set_full (bprm->cap_effective); + } + return 0; +} + +void cap_bprm_apply_creds (struct linux_binprm *bprm, int unsafe) +{ + /* Derived from fs/exec.c:compute_creds. */ + kernel_cap_t new_permitted, working; + + new_permitted = cap_intersect (bprm->cap_permitted, cap_bset); + working = cap_intersect (bprm->cap_inheritable, + current->cap_inheritable); + new_permitted = cap_combine (new_permitted, working); + + if (bprm->e_uid != current->uid || bprm->e_gid != current->gid || + !cap_issubset (new_permitted, current->cap_permitted)) { + current->mm->dumpable = 0; + + if (unsafe & ~LSM_UNSAFE_PTRACE_CAP) { + if (!capable(CAP_SETUID)) { + bprm->e_uid = current->uid; + bprm->e_gid = current->gid; + } + if (!capable (CAP_SETPCAP)) { + new_permitted = cap_intersect (new_permitted, + current->cap_permitted); + } + } + } + + current->suid = current->euid = current->fsuid = bprm->e_uid; + current->sgid = current->egid = current->fsgid = bprm->e_gid; + + /* For init, we want to retain the capabilities set + * in the init_task struct. Thus we skip the usual + * capability rules */ + if (current->pid != 1) { + current->cap_permitted = new_permitted; + current->cap_effective = + cap_intersect (new_permitted, bprm->cap_effective); + } + + /* AUD: Audit candidate if current->cap_effective is set */ + + current->keep_capabilities = 0; +} + +int cap_bprm_secureexec (struct linux_binprm *bprm) +{ + /* If/when this module is enhanced to incorporate capability + bits on files, the test below should be extended to also perform a + test between the old and new capability sets. For now, + it simply preserves the legacy decision algorithm used by + the old userland. */ + return (current->euid != current->uid || + current->egid != current->gid); +} + +int cap_inode_setxattr(struct dentry *dentry, char *name, void *value, + size_t size, int flags) +{ + if (!strncmp(name, XATTR_SECURITY_PREFIX, + sizeof(XATTR_SECURITY_PREFIX) - 1) && + !capable(CAP_SYS_ADMIN)) + return -EPERM; + return 0; +} + +int cap_inode_removexattr(struct dentry *dentry, char *name) +{ + if (!strncmp(name, XATTR_SECURITY_PREFIX, + sizeof(XATTR_SECURITY_PREFIX) - 1) && + !capable(CAP_SYS_ADMIN)) + return -EPERM; + return 0; +} + +/* moved from kernel/sys.c. */ +/* + * cap_emulate_setxuid() fixes the effective / permitted capabilities of + * a process after a call to setuid, setreuid, or setresuid. + * + * 1) When set*uiding _from_ one of {r,e,s}uid == 0 _to_ all of + * {r,e,s}uid != 0, the permitted and effective capabilities are + * cleared. + * + * 2) When set*uiding _from_ euid == 0 _to_ euid != 0, the effective + * capabilities of the process are cleared. + * + * 3) When set*uiding _from_ euid != 0 _to_ euid == 0, the effective + * capabilities are set to the permitted capabilities. + * + * fsuid is handled elsewhere. fsuid == 0 and {r,e,s}uid!= 0 should + * never happen. + * + * -astor + * + * cevans - New behaviour, Oct '99 + * A process may, via prctl(), elect to keep its capabilities when it + * calls setuid() and switches away from uid==0. Both permitted and + * effective sets will be retained. + * Without this change, it was impossible for a daemon to drop only some + * of its privilege. The call to setuid(!=0) would drop all privileges! + * Keeping uid 0 is not an option because uid 0 owns too many vital + * files.. + * Thanks to Olaf Kirch and Peter Benie for spotting this. + */ +static inline void cap_emulate_setxuid (int old_ruid, int old_euid, + int old_suid) +{ + if ((old_ruid == 0 || old_euid == 0 || old_suid == 0) && + (current->uid != 0 && current->euid != 0 && current->suid != 0) && + !current->keep_capabilities) { + cap_clear (current->cap_permitted); + cap_clear (current->cap_effective); + } + if (old_euid == 0 && current->euid != 0) { + cap_clear (current->cap_effective); + } + if (old_euid != 0 && current->euid == 0) { + current->cap_effective = current->cap_permitted; + } +} + +int cap_task_post_setuid (uid_t old_ruid, uid_t old_euid, uid_t old_suid, + int flags) +{ + switch (flags) { + case LSM_SETID_RE: + case LSM_SETID_ID: + case LSM_SETID_RES: + /* Copied from kernel/sys.c:setreuid/setuid/setresuid. */ + if (!issecure (SECURE_NO_SETUID_FIXUP)) { + cap_emulate_setxuid (old_ruid, old_euid, old_suid); + } + break; + case LSM_SETID_FS: + { + uid_t old_fsuid = old_ruid; + + /* Copied from kernel/sys.c:setfsuid. */ + + /* + * FIXME - is fsuser used for all CAP_FS_MASK capabilities? + * if not, we might be a bit too harsh here. + */ + + if (!issecure (SECURE_NO_SETUID_FIXUP)) { + if (old_fsuid == 0 && current->fsuid != 0) { + cap_t (current->cap_effective) &= + ~CAP_FS_MASK; + } + if (old_fsuid != 0 && current->fsuid == 0) { + cap_t (current->cap_effective) |= + (cap_t (current->cap_permitted) & + CAP_FS_MASK); + } + } + break; + } + default: + return -EINVAL; + } + + return 0; +} + +void cap_task_reparent_to_init (struct task_struct *p) +{ + p->cap_effective = CAP_INIT_EFF_SET; + p->cap_inheritable = CAP_INIT_INH_SET; + p->cap_permitted = CAP_FULL_SET; + p->keep_capabilities = 0; + return; +} + +int cap_syslog (int type) +{ + if ((type != 3 && type != 10) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + return 0; +} + +int cap_vm_enough_memory(long pages) +{ + int cap_sys_admin = 0; + + if (cap_capable(current, CAP_SYS_ADMIN) == 0) + cap_sys_admin = 1; + return __vm_enough_memory(pages, cap_sys_admin); +} + +EXPORT_SYMBOL(cap_capable); +EXPORT_SYMBOL(cap_settime); +EXPORT_SYMBOL(cap_ptrace); +EXPORT_SYMBOL(cap_capget); +EXPORT_SYMBOL(cap_capset_check); +EXPORT_SYMBOL(cap_capset_set); +EXPORT_SYMBOL(cap_bprm_set_security); +EXPORT_SYMBOL(cap_bprm_apply_creds); +EXPORT_SYMBOL(cap_bprm_secureexec); +EXPORT_SYMBOL(cap_inode_setxattr); +EXPORT_SYMBOL(cap_inode_removexattr); +EXPORT_SYMBOL(cap_task_post_setuid); +EXPORT_SYMBOL(cap_task_reparent_to_init); +EXPORT_SYMBOL(cap_syslog); +EXPORT_SYMBOL(cap_vm_enough_memory); + +MODULE_DESCRIPTION("Standard Linux Common Capabilities Security Module"); +MODULE_LICENSE("GPL"); diff --git a/security/dummy.c b/security/dummy.c new file mode 100644 index 000000000000..b32eff146547 --- /dev/null +++ b/security/dummy.c @@ -0,0 +1,996 @@ +/* + * Stub functions for the default security function pointers in case no + * security model is loaded. + * + * Copyright (C) 2001 WireX Communications, Inc <chris@wirex.com> + * Copyright (C) 2001-2002 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2001 Networks Associates Technology, Inc <ssmalley@nai.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#undef DEBUG + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/mman.h> +#include <linux/pagemap.h> +#include <linux/swap.h> +#include <linux/security.h> +#include <linux/skbuff.h> +#include <linux/netlink.h> +#include <net/sock.h> +#include <linux/xattr.h> +#include <linux/hugetlb.h> +#include <linux/ptrace.h> +#include <linux/file.h> + +static int dummy_ptrace (struct task_struct *parent, struct task_struct *child) +{ + return 0; +} + +static int dummy_capget (struct task_struct *target, kernel_cap_t * effective, + kernel_cap_t * inheritable, kernel_cap_t * permitted) +{ + *effective = *inheritable = *permitted = 0; + if (!issecure(SECURE_NOROOT)) { + if (target->euid == 0) { + *permitted |= (~0 & ~CAP_FS_MASK); + *effective |= (~0 & ~CAP_TO_MASK(CAP_SETPCAP) & ~CAP_FS_MASK); + } + if (target->fsuid == 0) { + *permitted |= CAP_FS_MASK; + *effective |= CAP_FS_MASK; + } + } + return 0; +} + +static int dummy_capset_check (struct task_struct *target, + kernel_cap_t * effective, + kernel_cap_t * inheritable, + kernel_cap_t * permitted) +{ + return -EPERM; +} + +static void dummy_capset_set (struct task_struct *target, + kernel_cap_t * effective, + kernel_cap_t * inheritable, + kernel_cap_t * permitted) +{ + return; +} + +static int dummy_acct (struct file *file) +{ + return 0; +} + +static int dummy_capable (struct task_struct *tsk, int cap) +{ + if (cap_raised (tsk->cap_effective, cap)) + return 0; + return -EPERM; +} + +static int dummy_sysctl (ctl_table * table, int op) +{ + return 0; +} + +static int dummy_quotactl (int cmds, int type, int id, struct super_block *sb) +{ + return 0; +} + +static int dummy_quota_on (struct dentry *dentry) +{ + return 0; +} + +static int dummy_syslog (int type) +{ + if ((type != 3 && type != 10) && current->euid) + return -EPERM; + return 0; +} + +static int dummy_settime(struct timespec *ts, struct timezone *tz) +{ + if (!capable(CAP_SYS_TIME)) + return -EPERM; + return 0; +} + +static int dummy_vm_enough_memory(long pages) +{ + int cap_sys_admin = 0; + + if (dummy_capable(current, CAP_SYS_ADMIN) == 0) + cap_sys_admin = 1; + return __vm_enough_memory(pages, cap_sys_admin); +} + +static int dummy_bprm_alloc_security (struct linux_binprm *bprm) +{ + return 0; +} + +static void dummy_bprm_free_security (struct linux_binprm *bprm) +{ + return; +} + +static void dummy_bprm_apply_creds (struct linux_binprm *bprm, int unsafe) +{ + if (bprm->e_uid != current->uid || bprm->e_gid != current->gid) { + current->mm->dumpable = 0; + + if ((unsafe & ~LSM_UNSAFE_PTRACE_CAP) && !capable(CAP_SETUID)) { + bprm->e_uid = current->uid; + bprm->e_gid = current->gid; + } + } + + current->suid = current->euid = current->fsuid = bprm->e_uid; + current->sgid = current->egid = current->fsgid = bprm->e_gid; + + dummy_capget(current, ¤t->cap_effective, ¤t->cap_inheritable, ¤t->cap_permitted); +} + +static void dummy_bprm_post_apply_creds (struct linux_binprm *bprm) +{ + return; +} + +static int dummy_bprm_set_security (struct linux_binprm *bprm) +{ + return 0; +} + +static int dummy_bprm_check_security (struct linux_binprm *bprm) +{ + return 0; +} + +static int dummy_bprm_secureexec (struct linux_binprm *bprm) +{ + /* The new userland will simply use the value provided + in the AT_SECURE field to decide whether secure mode + is required. Hence, this logic is required to preserve + the legacy decision algorithm used by the old userland. */ + return (current->euid != current->uid || + current->egid != current->gid); +} + +static int dummy_sb_alloc_security (struct super_block *sb) +{ + return 0; +} + +static void dummy_sb_free_security (struct super_block *sb) +{ + return; +} + +static int dummy_sb_copy_data (struct file_system_type *type, + void *orig, void *copy) +{ + return 0; +} + +static int dummy_sb_kern_mount (struct super_block *sb, void *data) +{ + return 0; +} + +static int dummy_sb_statfs (struct super_block *sb) +{ + return 0; +} + +static int dummy_sb_mount (char *dev_name, struct nameidata *nd, char *type, + unsigned long flags, void *data) +{ + return 0; +} + +static int dummy_sb_check_sb (struct vfsmount *mnt, struct nameidata *nd) +{ + return 0; +} + +static int dummy_sb_umount (struct vfsmount *mnt, int flags) +{ + return 0; +} + +static void dummy_sb_umount_close (struct vfsmount *mnt) +{ + return; +} + +static void dummy_sb_umount_busy (struct vfsmount *mnt) +{ + return; +} + +static void dummy_sb_post_remount (struct vfsmount *mnt, unsigned long flags, + void *data) +{ + return; +} + + +static void dummy_sb_post_mountroot (void) +{ + return; +} + +static void dummy_sb_post_addmount (struct vfsmount *mnt, struct nameidata *nd) +{ + return; +} + +static int dummy_sb_pivotroot (struct nameidata *old_nd, struct nameidata *new_nd) +{ + return 0; +} + +static void dummy_sb_post_pivotroot (struct nameidata *old_nd, struct nameidata *new_nd) +{ + return; +} + +static int dummy_inode_alloc_security (struct inode *inode) +{ + return 0; +} + +static void dummy_inode_free_security (struct inode *inode) +{ + return; +} + +static int dummy_inode_create (struct inode *inode, struct dentry *dentry, + int mask) +{ + return 0; +} + +static void dummy_inode_post_create (struct inode *inode, struct dentry *dentry, + int mask) +{ + return; +} + +static int dummy_inode_link (struct dentry *old_dentry, struct inode *inode, + struct dentry *new_dentry) +{ + return 0; +} + +static void dummy_inode_post_link (struct dentry *old_dentry, + struct inode *inode, + struct dentry *new_dentry) +{ + return; +} + +static int dummy_inode_unlink (struct inode *inode, struct dentry *dentry) +{ + return 0; +} + +static int dummy_inode_symlink (struct inode *inode, struct dentry *dentry, + const char *name) +{ + return 0; +} + +static void dummy_inode_post_symlink (struct inode *inode, + struct dentry *dentry, const char *name) +{ + return; +} + +static int dummy_inode_mkdir (struct inode *inode, struct dentry *dentry, + int mask) +{ + return 0; +} + +static void dummy_inode_post_mkdir (struct inode *inode, struct dentry *dentry, + int mask) +{ + return; +} + +static int dummy_inode_rmdir (struct inode *inode, struct dentry *dentry) +{ + return 0; +} + +static int dummy_inode_mknod (struct inode *inode, struct dentry *dentry, + int mode, dev_t dev) +{ + return 0; +} + +static void dummy_inode_post_mknod (struct inode *inode, struct dentry *dentry, + int mode, dev_t dev) +{ + return; +} + +static int dummy_inode_rename (struct inode *old_inode, + struct dentry *old_dentry, + struct inode *new_inode, + struct dentry *new_dentry) +{ + return 0; +} + +static void dummy_inode_post_rename (struct inode *old_inode, + struct dentry *old_dentry, + struct inode *new_inode, + struct dentry *new_dentry) +{ + return; +} + +static int dummy_inode_readlink (struct dentry *dentry) +{ + return 0; +} + +static int dummy_inode_follow_link (struct dentry *dentry, + struct nameidata *nameidata) +{ + return 0; +} + +static int dummy_inode_permission (struct inode *inode, int mask, struct nameidata *nd) +{ + return 0; +} + +static int dummy_inode_setattr (struct dentry *dentry, struct iattr *iattr) +{ + return 0; +} + +static int dummy_inode_getattr (struct vfsmount *mnt, struct dentry *dentry) +{ + return 0; +} + +static void dummy_inode_delete (struct inode *ino) +{ + return; +} + +static int dummy_inode_setxattr (struct dentry *dentry, char *name, void *value, + size_t size, int flags) +{ + if (!strncmp(name, XATTR_SECURITY_PREFIX, + sizeof(XATTR_SECURITY_PREFIX) - 1) && + !capable(CAP_SYS_ADMIN)) + return -EPERM; + return 0; +} + +static void dummy_inode_post_setxattr (struct dentry *dentry, char *name, void *value, + size_t size, int flags) +{ +} + +static int dummy_inode_getxattr (struct dentry *dentry, char *name) +{ + return 0; +} + +static int dummy_inode_listxattr (struct dentry *dentry) +{ + return 0; +} + +static int dummy_inode_removexattr (struct dentry *dentry, char *name) +{ + if (!strncmp(name, XATTR_SECURITY_PREFIX, + sizeof(XATTR_SECURITY_PREFIX) - 1) && + !capable(CAP_SYS_ADMIN)) + return -EPERM; + return 0; +} + +static int dummy_inode_getsecurity(struct inode *inode, const char *name, void *buffer, size_t size) +{ + return -EOPNOTSUPP; +} + +static int dummy_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags) +{ + return -EOPNOTSUPP; +} + +static int dummy_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size) +{ + return 0; +} + +static int dummy_file_permission (struct file *file, int mask) +{ + return 0; +} + +static int dummy_file_alloc_security (struct file *file) +{ + return 0; +} + +static void dummy_file_free_security (struct file *file) +{ + return; +} + +static int dummy_file_ioctl (struct file *file, unsigned int command, + unsigned long arg) +{ + return 0; +} + +static int dummy_file_mmap (struct file *file, unsigned long reqprot, + unsigned long prot, + unsigned long flags) +{ + return 0; +} + +static int dummy_file_mprotect (struct vm_area_struct *vma, + unsigned long reqprot, + unsigned long prot) +{ + return 0; +} + +static int dummy_file_lock (struct file *file, unsigned int cmd) +{ + return 0; +} + +static int dummy_file_fcntl (struct file *file, unsigned int cmd, + unsigned long arg) +{ + return 0; +} + +static int dummy_file_set_fowner (struct file *file) +{ + return 0; +} + +static int dummy_file_send_sigiotask (struct task_struct *tsk, + struct fown_struct *fown, int sig) +{ + return 0; +} + +static int dummy_file_receive (struct file *file) +{ + return 0; +} + +static int dummy_task_create (unsigned long clone_flags) +{ + return 0; +} + +static int dummy_task_alloc_security (struct task_struct *p) +{ + return 0; +} + +static void dummy_task_free_security (struct task_struct *p) +{ + return; +} + +static int dummy_task_setuid (uid_t id0, uid_t id1, uid_t id2, int flags) +{ + return 0; +} + +static int dummy_task_post_setuid (uid_t id0, uid_t id1, uid_t id2, int flags) +{ + dummy_capget(current, ¤t->cap_effective, ¤t->cap_inheritable, ¤t->cap_permitted); + return 0; +} + +static int dummy_task_setgid (gid_t id0, gid_t id1, gid_t id2, int flags) +{ + return 0; +} + +static int dummy_task_setpgid (struct task_struct *p, pid_t pgid) +{ + return 0; +} + +static int dummy_task_getpgid (struct task_struct *p) +{ + return 0; +} + +static int dummy_task_getsid (struct task_struct *p) +{ + return 0; +} + +static int dummy_task_setgroups (struct group_info *group_info) +{ + return 0; +} + +static int dummy_task_setnice (struct task_struct *p, int nice) +{ + return 0; +} + +static int dummy_task_setrlimit (unsigned int resource, struct rlimit *new_rlim) +{ + return 0; +} + +static int dummy_task_setscheduler (struct task_struct *p, int policy, + struct sched_param *lp) +{ + return 0; +} + +static int dummy_task_getscheduler (struct task_struct *p) +{ + return 0; +} + +static int dummy_task_wait (struct task_struct *p) +{ + return 0; +} + +static int dummy_task_kill (struct task_struct *p, struct siginfo *info, + int sig) +{ + return 0; +} + +static int dummy_task_prctl (int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + return 0; +} + +static void dummy_task_reparent_to_init (struct task_struct *p) +{ + p->euid = p->fsuid = 0; + return; +} + +static void dummy_task_to_inode(struct task_struct *p, struct inode *inode) +{ } + +static int dummy_ipc_permission (struct kern_ipc_perm *ipcp, short flag) +{ + return 0; +} + +static int dummy_msg_msg_alloc_security (struct msg_msg *msg) +{ + return 0; +} + +static void dummy_msg_msg_free_security (struct msg_msg *msg) +{ + return; +} + +static int dummy_msg_queue_alloc_security (struct msg_queue *msq) +{ + return 0; +} + +static void dummy_msg_queue_free_security (struct msg_queue *msq) +{ + return; +} + +static int dummy_msg_queue_associate (struct msg_queue *msq, + int msqflg) +{ + return 0; +} + +static int dummy_msg_queue_msgctl (struct msg_queue *msq, int cmd) +{ + return 0; +} + +static int dummy_msg_queue_msgsnd (struct msg_queue *msq, struct msg_msg *msg, + int msgflg) +{ + return 0; +} + +static int dummy_msg_queue_msgrcv (struct msg_queue *msq, struct msg_msg *msg, + struct task_struct *target, long type, + int mode) +{ + return 0; +} + +static int dummy_shm_alloc_security (struct shmid_kernel *shp) +{ + return 0; +} + +static void dummy_shm_free_security (struct shmid_kernel *shp) +{ + return; +} + +static int dummy_shm_associate (struct shmid_kernel *shp, int shmflg) +{ + return 0; +} + +static int dummy_shm_shmctl (struct shmid_kernel *shp, int cmd) +{ + return 0; +} + +static int dummy_shm_shmat (struct shmid_kernel *shp, char __user *shmaddr, + int shmflg) +{ + return 0; +} + +static int dummy_sem_alloc_security (struct sem_array *sma) +{ + return 0; +} + +static void dummy_sem_free_security (struct sem_array *sma) +{ + return; +} + +static int dummy_sem_associate (struct sem_array *sma, int semflg) +{ + return 0; +} + +static int dummy_sem_semctl (struct sem_array *sma, int cmd) +{ + return 0; +} + +static int dummy_sem_semop (struct sem_array *sma, + struct sembuf *sops, unsigned nsops, int alter) +{ + return 0; +} + +static int dummy_netlink_send (struct sock *sk, struct sk_buff *skb) +{ + NETLINK_CB(skb).eff_cap = current->cap_effective; + return 0; +} + +static int dummy_netlink_recv (struct sk_buff *skb) +{ + if (!cap_raised (NETLINK_CB (skb).eff_cap, CAP_NET_ADMIN)) + return -EPERM; + return 0; +} + +#ifdef CONFIG_SECURITY_NETWORK +static int dummy_unix_stream_connect (struct socket *sock, + struct socket *other, + struct sock *newsk) +{ + return 0; +} + +static int dummy_unix_may_send (struct socket *sock, + struct socket *other) +{ + return 0; +} + +static int dummy_socket_create (int family, int type, + int protocol, int kern) +{ + return 0; +} + +static void dummy_socket_post_create (struct socket *sock, int family, int type, + int protocol, int kern) +{ + return; +} + +static int dummy_socket_bind (struct socket *sock, struct sockaddr *address, + int addrlen) +{ + return 0; +} + +static int dummy_socket_connect (struct socket *sock, struct sockaddr *address, + int addrlen) +{ + return 0; +} + +static int dummy_socket_listen (struct socket *sock, int backlog) +{ + return 0; +} + +static int dummy_socket_accept (struct socket *sock, struct socket *newsock) +{ + return 0; +} + +static void dummy_socket_post_accept (struct socket *sock, + struct socket *newsock) +{ + return; +} + +static int dummy_socket_sendmsg (struct socket *sock, struct msghdr *msg, + int size) +{ + return 0; +} + +static int dummy_socket_recvmsg (struct socket *sock, struct msghdr *msg, + int size, int flags) +{ + return 0; +} + +static int dummy_socket_getsockname (struct socket *sock) +{ + return 0; +} + +static int dummy_socket_getpeername (struct socket *sock) +{ + return 0; +} + +static int dummy_socket_setsockopt (struct socket *sock, int level, int optname) +{ + return 0; +} + +static int dummy_socket_getsockopt (struct socket *sock, int level, int optname) +{ + return 0; +} + +static int dummy_socket_shutdown (struct socket *sock, int how) +{ + return 0; +} + +static int dummy_socket_sock_rcv_skb (struct sock *sk, struct sk_buff *skb) +{ + return 0; +} + +static int dummy_socket_getpeersec(struct socket *sock, char __user *optval, + int __user *optlen, unsigned len) +{ + return -ENOPROTOOPT; +} + +static inline int dummy_sk_alloc_security (struct sock *sk, int family, int priority) +{ + return 0; +} + +static inline void dummy_sk_free_security (struct sock *sk) +{ +} +#endif /* CONFIG_SECURITY_NETWORK */ + +static int dummy_register_security (const char *name, struct security_operations *ops) +{ + return -EINVAL; +} + +static int dummy_unregister_security (const char *name, struct security_operations *ops) +{ + return -EINVAL; +} + +static void dummy_d_instantiate (struct dentry *dentry, struct inode *inode) +{ + return; +} + +static int dummy_getprocattr(struct task_struct *p, char *name, void *value, size_t size) +{ + return -EINVAL; +} + +static int dummy_setprocattr(struct task_struct *p, char *name, void *value, size_t size) +{ + return -EINVAL; +} + + +struct security_operations dummy_security_ops; + +#define set_to_dummy_if_null(ops, function) \ + do { \ + if (!ops->function) { \ + ops->function = dummy_##function; \ + pr_debug("Had to override the " #function \ + " security operation with the dummy one.\n");\ + } \ + } while (0) + +void security_fixup_ops (struct security_operations *ops) +{ + set_to_dummy_if_null(ops, ptrace); + set_to_dummy_if_null(ops, capget); + set_to_dummy_if_null(ops, capset_check); + set_to_dummy_if_null(ops, capset_set); + set_to_dummy_if_null(ops, acct); + set_to_dummy_if_null(ops, capable); + set_to_dummy_if_null(ops, quotactl); + set_to_dummy_if_null(ops, quota_on); + set_to_dummy_if_null(ops, sysctl); + set_to_dummy_if_null(ops, syslog); + set_to_dummy_if_null(ops, settime); + set_to_dummy_if_null(ops, vm_enough_memory); + set_to_dummy_if_null(ops, bprm_alloc_security); + set_to_dummy_if_null(ops, bprm_free_security); + set_to_dummy_if_null(ops, bprm_apply_creds); + set_to_dummy_if_null(ops, bprm_post_apply_creds); + set_to_dummy_if_null(ops, bprm_set_security); + set_to_dummy_if_null(ops, bprm_check_security); + set_to_dummy_if_null(ops, bprm_secureexec); + set_to_dummy_if_null(ops, sb_alloc_security); + set_to_dummy_if_null(ops, sb_free_security); + set_to_dummy_if_null(ops, sb_copy_data); + set_to_dummy_if_null(ops, sb_kern_mount); + set_to_dummy_if_null(ops, sb_statfs); + set_to_dummy_if_null(ops, sb_mount); + set_to_dummy_if_null(ops, sb_check_sb); + set_to_dummy_if_null(ops, sb_umount); + set_to_dummy_if_null(ops, sb_umount_close); + set_to_dummy_if_null(ops, sb_umount_busy); + set_to_dummy_if_null(ops, sb_post_remount); + set_to_dummy_if_null(ops, sb_post_mountroot); + set_to_dummy_if_null(ops, sb_post_addmount); + set_to_dummy_if_null(ops, sb_pivotroot); + set_to_dummy_if_null(ops, sb_post_pivotroot); + set_to_dummy_if_null(ops, inode_alloc_security); + set_to_dummy_if_null(ops, inode_free_security); + set_to_dummy_if_null(ops, inode_create); + set_to_dummy_if_null(ops, inode_post_create); + set_to_dummy_if_null(ops, inode_link); + set_to_dummy_if_null(ops, inode_post_link); + set_to_dummy_if_null(ops, inode_unlink); + set_to_dummy_if_null(ops, inode_symlink); + set_to_dummy_if_null(ops, inode_post_symlink); + set_to_dummy_if_null(ops, inode_mkdir); + set_to_dummy_if_null(ops, inode_post_mkdir); + set_to_dummy_if_null(ops, inode_rmdir); + set_to_dummy_if_null(ops, inode_mknod); + set_to_dummy_if_null(ops, inode_post_mknod); + set_to_dummy_if_null(ops, inode_rename); + set_to_dummy_if_null(ops, inode_post_rename); + set_to_dummy_if_null(ops, inode_readlink); + set_to_dummy_if_null(ops, inode_follow_link); + set_to_dummy_if_null(ops, inode_permission); + set_to_dummy_if_null(ops, inode_setattr); + set_to_dummy_if_null(ops, inode_getattr); + set_to_dummy_if_null(ops, inode_delete); + set_to_dummy_if_null(ops, inode_setxattr); + set_to_dummy_if_null(ops, inode_post_setxattr); + set_to_dummy_if_null(ops, inode_getxattr); + set_to_dummy_if_null(ops, inode_listxattr); + set_to_dummy_if_null(ops, inode_removexattr); + set_to_dummy_if_null(ops, inode_getsecurity); + set_to_dummy_if_null(ops, inode_setsecurity); + set_to_dummy_if_null(ops, inode_listsecurity); + set_to_dummy_if_null(ops, file_permission); + set_to_dummy_if_null(ops, file_alloc_security); + set_to_dummy_if_null(ops, file_free_security); + set_to_dummy_if_null(ops, file_ioctl); + set_to_dummy_if_null(ops, file_mmap); + set_to_dummy_if_null(ops, file_mprotect); + set_to_dummy_if_null(ops, file_lock); + set_to_dummy_if_null(ops, file_fcntl); + set_to_dummy_if_null(ops, file_set_fowner); + set_to_dummy_if_null(ops, file_send_sigiotask); + set_to_dummy_if_null(ops, file_receive); + set_to_dummy_if_null(ops, task_create); + set_to_dummy_if_null(ops, task_alloc_security); + set_to_dummy_if_null(ops, task_free_security); + set_to_dummy_if_null(ops, task_setuid); + set_to_dummy_if_null(ops, task_post_setuid); + set_to_dummy_if_null(ops, task_setgid); + set_to_dummy_if_null(ops, task_setpgid); + set_to_dummy_if_null(ops, task_getpgid); + set_to_dummy_if_null(ops, task_getsid); + set_to_dummy_if_null(ops, task_setgroups); + set_to_dummy_if_null(ops, task_setnice); + set_to_dummy_if_null(ops, task_setrlimit); + set_to_dummy_if_null(ops, task_setscheduler); + set_to_dummy_if_null(ops, task_getscheduler); + set_to_dummy_if_null(ops, task_wait); + set_to_dummy_if_null(ops, task_kill); + set_to_dummy_if_null(ops, task_prctl); + set_to_dummy_if_null(ops, task_reparent_to_init); + set_to_dummy_if_null(ops, task_to_inode); + set_to_dummy_if_null(ops, ipc_permission); + set_to_dummy_if_null(ops, msg_msg_alloc_security); + set_to_dummy_if_null(ops, msg_msg_free_security); + set_to_dummy_if_null(ops, msg_queue_alloc_security); + set_to_dummy_if_null(ops, msg_queue_free_security); + set_to_dummy_if_null(ops, msg_queue_associate); + set_to_dummy_if_null(ops, msg_queue_msgctl); + set_to_dummy_if_null(ops, msg_queue_msgsnd); + set_to_dummy_if_null(ops, msg_queue_msgrcv); + set_to_dummy_if_null(ops, shm_alloc_security); + set_to_dummy_if_null(ops, shm_free_security); + set_to_dummy_if_null(ops, shm_associate); + set_to_dummy_if_null(ops, shm_shmctl); + set_to_dummy_if_null(ops, shm_shmat); + set_to_dummy_if_null(ops, sem_alloc_security); + set_to_dummy_if_null(ops, sem_free_security); + set_to_dummy_if_null(ops, sem_associate); + set_to_dummy_if_null(ops, sem_semctl); + set_to_dummy_if_null(ops, sem_semop); + set_to_dummy_if_null(ops, netlink_send); + set_to_dummy_if_null(ops, netlink_recv); + set_to_dummy_if_null(ops, register_security); + set_to_dummy_if_null(ops, unregister_security); + set_to_dummy_if_null(ops, d_instantiate); + set_to_dummy_if_null(ops, getprocattr); + set_to_dummy_if_null(ops, setprocattr); +#ifdef CONFIG_SECURITY_NETWORK + set_to_dummy_if_null(ops, unix_stream_connect); + set_to_dummy_if_null(ops, unix_may_send); + set_to_dummy_if_null(ops, socket_create); + set_to_dummy_if_null(ops, socket_post_create); + set_to_dummy_if_null(ops, socket_bind); + set_to_dummy_if_null(ops, socket_connect); + set_to_dummy_if_null(ops, socket_listen); + set_to_dummy_if_null(ops, socket_accept); + set_to_dummy_if_null(ops, socket_post_accept); + set_to_dummy_if_null(ops, socket_sendmsg); + set_to_dummy_if_null(ops, socket_recvmsg); + set_to_dummy_if_null(ops, socket_getsockname); + set_to_dummy_if_null(ops, socket_getpeername); + set_to_dummy_if_null(ops, socket_setsockopt); + set_to_dummy_if_null(ops, socket_getsockopt); + set_to_dummy_if_null(ops, socket_shutdown); + set_to_dummy_if_null(ops, socket_sock_rcv_skb); + set_to_dummy_if_null(ops, socket_getpeersec); + set_to_dummy_if_null(ops, sk_alloc_security); + set_to_dummy_if_null(ops, sk_free_security); +#endif /* CONFIG_SECURITY_NETWORK */ +} + diff --git a/security/keys/Makefile b/security/keys/Makefile new file mode 100644 index 000000000000..ddb495d65062 --- /dev/null +++ b/security/keys/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for key management +# + +obj-y := \ + key.o \ + keyring.o \ + keyctl.o \ + process_keys.o \ + user_defined.o \ + request_key.o + +obj-$(CONFIG_KEYS_COMPAT) += compat.o +obj-$(CONFIG_PROC_FS) += proc.o diff --git a/security/keys/compat.c b/security/keys/compat.c new file mode 100644 index 000000000000..aff8b22dcb5c --- /dev/null +++ b/security/keys/compat.c @@ -0,0 +1,78 @@ +/* compat.c: 32-bit compatibility syscall for 64-bit systems + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/sched.h> +#include <linux/syscalls.h> +#include <linux/keyctl.h> +#include <linux/compat.h> +#include "internal.h" + +/*****************************************************************************/ +/* + * the key control system call, 32-bit compatibility version for 64-bit archs + * - this should only be called if the 64-bit arch uses weird pointers in + * 32-bit mode or doesn't guarantee that the top 32-bits of the argument + * registers on taking a 32-bit syscall are zero + * - if you can, you should call sys_keyctl directly + */ +asmlinkage long compat_sys_keyctl(u32 option, + u32 arg2, u32 arg3, u32 arg4, u32 arg5) +{ + switch (option) { + case KEYCTL_GET_KEYRING_ID: + return keyctl_get_keyring_ID(arg2, arg3); + + case KEYCTL_JOIN_SESSION_KEYRING: + return keyctl_join_session_keyring(compat_ptr(arg2)); + + case KEYCTL_UPDATE: + return keyctl_update_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_REVOKE: + return keyctl_revoke_key(arg2); + + case KEYCTL_DESCRIBE: + return keyctl_describe_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_CLEAR: + return keyctl_keyring_clear(arg2); + + case KEYCTL_LINK: + return keyctl_keyring_link(arg2, arg3); + + case KEYCTL_UNLINK: + return keyctl_keyring_unlink(arg2, arg3); + + case KEYCTL_SEARCH: + return keyctl_keyring_search(arg2, compat_ptr(arg3), + compat_ptr(arg4), arg5); + + case KEYCTL_READ: + return keyctl_read_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_CHOWN: + return keyctl_chown_key(arg2, arg3, arg4); + + case KEYCTL_SETPERM: + return keyctl_setperm_key(arg2, arg3); + + case KEYCTL_INSTANTIATE: + return keyctl_instantiate_key(arg2, compat_ptr(arg3), arg4, + arg5); + + case KEYCTL_NEGATE: + return keyctl_negate_key(arg2, arg3, arg4); + + default: + return -EOPNOTSUPP; + } + +} /* end compat_sys_keyctl() */ diff --git a/security/keys/internal.h b/security/keys/internal.h new file mode 100644 index 000000000000..67b2b93a7489 --- /dev/null +++ b/security/keys/internal.h @@ -0,0 +1,123 @@ +/* internal.h: authentication token and access key management internal defs + * + * Copyright (C) 2003 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _INTERNAL_H +#define _INTERNAL_H + +#include <linux/key.h> +#include <linux/key-ui.h> + +extern struct key_type key_type_dead; +extern struct key_type key_type_user; + +/*****************************************************************************/ +/* + * keep track of keys for a user + * - this needs to be separate to user_struct to avoid a refcount-loop + * (user_struct pins some keyrings which pin this struct) + * - this also keeps track of keys under request from userspace for this UID + */ +struct key_user { + struct rb_node node; + struct list_head consq; /* construction queue */ + spinlock_t lock; + atomic_t usage; /* for accessing qnkeys & qnbytes */ + atomic_t nkeys; /* number of keys */ + atomic_t nikeys; /* number of instantiated keys */ + uid_t uid; + int qnkeys; /* number of keys allocated to this user */ + int qnbytes; /* number of bytes allocated to this user */ +}; + +#define KEYQUOTA_MAX_KEYS 100 +#define KEYQUOTA_MAX_BYTES 10000 +#define KEYQUOTA_LINK_BYTES 4 /* a link in a keyring is worth 4 bytes */ + +extern struct rb_root key_user_tree; +extern spinlock_t key_user_lock; +extern struct key_user root_key_user; + +extern struct key_user *key_user_lookup(uid_t uid); +extern void key_user_put(struct key_user *user); + + + +extern struct rb_root key_serial_tree; +extern spinlock_t key_serial_lock; +extern struct semaphore key_alloc_sem; +extern struct rw_semaphore key_construction_sem; +extern wait_queue_head_t request_key_conswq; + + +extern void keyring_publish_name(struct key *keyring); + +extern int __key_link(struct key *keyring, struct key *key); + +extern struct key *__keyring_search_one(struct key *keyring, + const struct key_type *type, + const char *description, + key_perm_t perm); + +typedef int (*key_match_func_t)(const struct key *, const void *); + +extern struct key *keyring_search_aux(struct key *keyring, + struct key_type *type, + const void *description, + key_match_func_t match); + +extern struct key *search_process_keyrings_aux(struct key_type *type, + const void *description, + key_match_func_t match); + +extern struct key *find_keyring_by_name(const char *name, key_serial_t bound); + +extern int install_thread_keyring(struct task_struct *tsk); + +/* + * keyctl functions + */ +extern long keyctl_get_keyring_ID(key_serial_t, int); +extern long keyctl_join_session_keyring(const char __user *); +extern long keyctl_update_key(key_serial_t, const void __user *, size_t); +extern long keyctl_revoke_key(key_serial_t); +extern long keyctl_keyring_clear(key_serial_t); +extern long keyctl_keyring_link(key_serial_t, key_serial_t); +extern long keyctl_keyring_unlink(key_serial_t, key_serial_t); +extern long keyctl_describe_key(key_serial_t, char __user *, size_t); +extern long keyctl_keyring_search(key_serial_t, const char __user *, + const char __user *, key_serial_t); +extern long keyctl_read_key(key_serial_t, char __user *, size_t); +extern long keyctl_chown_key(key_serial_t, uid_t, gid_t); +extern long keyctl_setperm_key(key_serial_t, key_perm_t); +extern long keyctl_instantiate_key(key_serial_t, const void __user *, + size_t, key_serial_t); +extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t); + + +/* + * debugging key validation + */ +#ifdef KEY_DEBUGGING +extern void __key_check(const struct key *); + +static inline void key_check(const struct key *key) +{ + if (key && (IS_ERR(key) || key->magic != KEY_DEBUG_MAGIC)) + __key_check(key); +} + +#else + +#define key_check(key) do {} while(0) + +#endif + +#endif /* _INTERNAL_H */ diff --git a/security/keys/key.c b/security/keys/key.c new file mode 100644 index 000000000000..59402c843203 --- /dev/null +++ b/security/keys/key.c @@ -0,0 +1,1040 @@ +/* key.c: basic authentication token and access key management + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/err.h> +#include "internal.h" + +static kmem_cache_t *key_jar; +static key_serial_t key_serial_next = 3; +struct rb_root key_serial_tree; /* tree of keys indexed by serial */ +DEFINE_SPINLOCK(key_serial_lock); + +struct rb_root key_user_tree; /* tree of quota records indexed by UID */ +DEFINE_SPINLOCK(key_user_lock); + +static LIST_HEAD(key_types_list); +static DECLARE_RWSEM(key_types_sem); + +static void key_cleanup(void *data); +static DECLARE_WORK(key_cleanup_task, key_cleanup, NULL); + +/* we serialise key instantiation and link */ +DECLARE_RWSEM(key_construction_sem); + +/* any key who's type gets unegistered will be re-typed to this */ +struct key_type key_type_dead = { + .name = "dead", +}; + +#ifdef KEY_DEBUGGING +void __key_check(const struct key *key) +{ + printk("__key_check: key %p {%08x} should be {%08x}\n", + key, key->magic, KEY_DEBUG_MAGIC); + BUG(); +} +#endif + +/*****************************************************************************/ +/* + * get the key quota record for a user, allocating a new record if one doesn't + * already exist + */ +struct key_user *key_user_lookup(uid_t uid) +{ + struct key_user *candidate = NULL, *user; + struct rb_node *parent = NULL; + struct rb_node **p; + + try_again: + p = &key_user_tree.rb_node; + spin_lock(&key_user_lock); + + /* search the tree for a user record with a matching UID */ + while (*p) { + parent = *p; + user = rb_entry(parent, struct key_user, node); + + if (uid < user->uid) + p = &(*p)->rb_left; + else if (uid > user->uid) + p = &(*p)->rb_right; + else + goto found; + } + + /* if we get here, we failed to find a match in the tree */ + if (!candidate) { + /* allocate a candidate user record if we don't already have + * one */ + spin_unlock(&key_user_lock); + + user = NULL; + candidate = kmalloc(sizeof(struct key_user), GFP_KERNEL); + if (unlikely(!candidate)) + goto out; + + /* the allocation may have scheduled, so we need to repeat the + * search lest someone else added the record whilst we were + * asleep */ + goto try_again; + } + + /* if we get here, then the user record still hadn't appeared on the + * second pass - so we use the candidate record */ + atomic_set(&candidate->usage, 1); + atomic_set(&candidate->nkeys, 0); + atomic_set(&candidate->nikeys, 0); + candidate->uid = uid; + candidate->qnkeys = 0; + candidate->qnbytes = 0; + spin_lock_init(&candidate->lock); + INIT_LIST_HEAD(&candidate->consq); + + rb_link_node(&candidate->node, parent, p); + rb_insert_color(&candidate->node, &key_user_tree); + spin_unlock(&key_user_lock); + user = candidate; + goto out; + + /* okay - we found a user record for this UID */ + found: + atomic_inc(&user->usage); + spin_unlock(&key_user_lock); + if (candidate) + kfree(candidate); + out: + return user; + +} /* end key_user_lookup() */ + +/*****************************************************************************/ +/* + * dispose of a user structure + */ +void key_user_put(struct key_user *user) +{ + if (atomic_dec_and_lock(&user->usage, &key_user_lock)) { + rb_erase(&user->node, &key_user_tree); + spin_unlock(&key_user_lock); + + kfree(user); + } + +} /* end key_user_put() */ + +/*****************************************************************************/ +/* + * insert a key with a fixed serial number + */ +static void __init __key_insert_serial(struct key *key) +{ + struct rb_node *parent, **p; + struct key *xkey; + + parent = NULL; + p = &key_serial_tree.rb_node; + + while (*p) { + parent = *p; + xkey = rb_entry(parent, struct key, serial_node); + + if (key->serial < xkey->serial) + p = &(*p)->rb_left; + else if (key->serial > xkey->serial) + p = &(*p)->rb_right; + else + BUG(); + } + + /* we've found a suitable hole - arrange for this key to occupy it */ + rb_link_node(&key->serial_node, parent, p); + rb_insert_color(&key->serial_node, &key_serial_tree); + +} /* end __key_insert_serial() */ + +/*****************************************************************************/ +/* + * assign a key the next unique serial number + * - we work through all the serial numbers between 2 and 2^31-1 in turn and + * then wrap + */ +static inline void key_alloc_serial(struct key *key) +{ + struct rb_node *parent, **p; + struct key *xkey; + + spin_lock(&key_serial_lock); + + /* propose a likely serial number and look for a hole for it in the + * serial number tree */ + key->serial = key_serial_next; + if (key->serial < 3) + key->serial = 3; + key_serial_next = key->serial + 1; + + parent = NULL; + p = &key_serial_tree.rb_node; + + while (*p) { + parent = *p; + xkey = rb_entry(parent, struct key, serial_node); + + if (key->serial < xkey->serial) + p = &(*p)->rb_left; + else if (key->serial > xkey->serial) + p = &(*p)->rb_right; + else + goto serial_exists; + } + goto insert_here; + + /* we found a key with the proposed serial number - walk the tree from + * that point looking for the next unused serial number */ + serial_exists: + for (;;) { + key->serial = key_serial_next; + if (key->serial < 2) + key->serial = 2; + key_serial_next = key->serial + 1; + + if (!parent->rb_parent) + p = &key_serial_tree.rb_node; + else if (parent->rb_parent->rb_left == parent) + p = &parent->rb_parent->rb_left; + else + p = &parent->rb_parent->rb_right; + + parent = rb_next(parent); + if (!parent) + break; + + xkey = rb_entry(parent, struct key, serial_node); + if (key->serial < xkey->serial) + goto insert_here; + } + + /* we've found a suitable hole - arrange for this key to occupy it */ + insert_here: + rb_link_node(&key->serial_node, parent, p); + rb_insert_color(&key->serial_node, &key_serial_tree); + + spin_unlock(&key_serial_lock); + +} /* end key_alloc_serial() */ + +/*****************************************************************************/ +/* + * allocate a key of the specified type + * - update the user's quota to reflect the existence of the key + * - called from a key-type operation with key_types_sem read-locked by either + * key_create_or_update() or by key_duplicate(); this prevents unregistration + * of the key type + * - upon return the key is as yet uninstantiated; the caller needs to either + * instantiate the key or discard it before returning + */ +struct key *key_alloc(struct key_type *type, const char *desc, + uid_t uid, gid_t gid, key_perm_t perm, + int not_in_quota) +{ + struct key_user *user = NULL; + struct key *key; + size_t desclen, quotalen; + + key = ERR_PTR(-EINVAL); + if (!desc || !*desc) + goto error; + + desclen = strlen(desc) + 1; + quotalen = desclen + type->def_datalen; + + /* get hold of the key tracking for this user */ + user = key_user_lookup(uid); + if (!user) + goto no_memory_1; + + /* check that the user's quota permits allocation of another key and + * its description */ + if (!not_in_quota) { + spin_lock(&user->lock); + if (user->qnkeys + 1 >= KEYQUOTA_MAX_KEYS && + user->qnbytes + quotalen >= KEYQUOTA_MAX_BYTES + ) + goto no_quota; + + user->qnkeys++; + user->qnbytes += quotalen; + spin_unlock(&user->lock); + } + + /* allocate and initialise the key and its description */ + key = kmem_cache_alloc(key_jar, SLAB_KERNEL); + if (!key) + goto no_memory_2; + + if (desc) { + key->description = kmalloc(desclen, GFP_KERNEL); + if (!key->description) + goto no_memory_3; + + memcpy(key->description, desc, desclen); + } + + atomic_set(&key->usage, 1); + rwlock_init(&key->lock); + init_rwsem(&key->sem); + key->type = type; + key->user = user; + key->quotalen = quotalen; + key->datalen = type->def_datalen; + key->uid = uid; + key->gid = gid; + key->perm = perm; + key->flags = 0; + key->expiry = 0; + key->payload.data = NULL; + + if (!not_in_quota) + key->flags |= KEY_FLAG_IN_QUOTA; + + memset(&key->type_data, 0, sizeof(key->type_data)); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC; +#endif + + /* publish the key by giving it a serial number */ + atomic_inc(&user->nkeys); + key_alloc_serial(key); + + error: + return key; + + no_memory_3: + kmem_cache_free(key_jar, key); + no_memory_2: + if (!not_in_quota) { + spin_lock(&user->lock); + user->qnkeys--; + user->qnbytes -= quotalen; + spin_unlock(&user->lock); + } + key_user_put(user); + no_memory_1: + key = ERR_PTR(-ENOMEM); + goto error; + + no_quota: + spin_unlock(&user->lock); + key_user_put(user); + key = ERR_PTR(-EDQUOT); + goto error; + +} /* end key_alloc() */ + +EXPORT_SYMBOL(key_alloc); + +/*****************************************************************************/ +/* + * reserve an amount of quota for the key's payload + */ +int key_payload_reserve(struct key *key, size_t datalen) +{ + int delta = (int) datalen - key->datalen; + int ret = 0; + + key_check(key); + + /* contemplate the quota adjustment */ + if (delta != 0 && key->flags & KEY_FLAG_IN_QUOTA) { + spin_lock(&key->user->lock); + + if (delta > 0 && + key->user->qnbytes + delta > KEYQUOTA_MAX_BYTES + ) { + ret = -EDQUOT; + } + else { + key->user->qnbytes += delta; + key->quotalen += delta; + } + spin_unlock(&key->user->lock); + } + + /* change the recorded data length if that didn't generate an error */ + if (ret == 0) + key->datalen = datalen; + + return ret; + +} /* end key_payload_reserve() */ + +EXPORT_SYMBOL(key_payload_reserve); + +/*****************************************************************************/ +/* + * instantiate a key and link it into the target keyring atomically + * - called with the target keyring's semaphore writelocked + */ +static int __key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring) +{ + int ret, awaken; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + down_write(&key_construction_sem); + + /* can't instantiate twice */ + if (!(key->flags & KEY_FLAG_INSTANTIATED)) { + /* instantiate the key */ + ret = key->type->instantiate(key, data, datalen); + + if (ret == 0) { + /* mark the key as being instantiated */ + write_lock(&key->lock); + + atomic_inc(&key->user->nikeys); + key->flags |= KEY_FLAG_INSTANTIATED; + + if (key->flags & KEY_FLAG_USER_CONSTRUCT) { + key->flags &= ~KEY_FLAG_USER_CONSTRUCT; + awaken = 1; + } + + write_unlock(&key->lock); + + /* and link it into the destination keyring */ + if (keyring) + ret = __key_link(keyring, key); + } + } + + up_write(&key_construction_sem); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_all(&request_key_conswq); + + return ret; + +} /* end __key_instantiate_and_link() */ + +/*****************************************************************************/ +/* + * instantiate a key and link it into the target keyring atomically + */ +int key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring) +{ + int ret; + + if (keyring) + down_write(&keyring->sem); + + ret = __key_instantiate_and_link(key, data, datalen, keyring); + + if (keyring) + up_write(&keyring->sem); + + return ret; +} /* end key_instantiate_and_link() */ + +EXPORT_SYMBOL(key_instantiate_and_link); + +/*****************************************************************************/ +/* + * negatively instantiate a key and link it into the target keyring atomically + */ +int key_negate_and_link(struct key *key, + unsigned timeout, + struct key *keyring) +{ + struct timespec now; + int ret, awaken; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + if (keyring) + down_write(&keyring->sem); + + down_write(&key_construction_sem); + + /* can't instantiate twice */ + if (!(key->flags & KEY_FLAG_INSTANTIATED)) { + /* mark the key as being negatively instantiated */ + write_lock(&key->lock); + + atomic_inc(&key->user->nikeys); + key->flags |= KEY_FLAG_INSTANTIATED | KEY_FLAG_NEGATIVE; + now = current_kernel_time(); + key->expiry = now.tv_sec + timeout; + + if (key->flags & KEY_FLAG_USER_CONSTRUCT) { + key->flags &= ~KEY_FLAG_USER_CONSTRUCT; + awaken = 1; + } + + write_unlock(&key->lock); + ret = 0; + + /* and link it into the destination keyring */ + if (keyring) + ret = __key_link(keyring, key); + } + + up_write(&key_construction_sem); + + if (keyring) + up_write(&keyring->sem); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_all(&request_key_conswq); + + return ret; + +} /* end key_negate_and_link() */ + +EXPORT_SYMBOL(key_negate_and_link); + +/*****************************************************************************/ +/* + * do cleaning up in process context so that we don't have to disable + * interrupts all over the place + */ +static void key_cleanup(void *data) +{ + struct rb_node *_n; + struct key *key; + + go_again: + /* look for a dead key in the tree */ + spin_lock(&key_serial_lock); + + for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { + key = rb_entry(_n, struct key, serial_node); + + if (atomic_read(&key->usage) == 0) + goto found_dead_key; + } + + spin_unlock(&key_serial_lock); + return; + + found_dead_key: + /* we found a dead key - once we've removed it from the tree, we can + * drop the lock */ + rb_erase(&key->serial_node, &key_serial_tree); + spin_unlock(&key_serial_lock); + + /* deal with the user's key tracking and quota */ + if (key->flags & KEY_FLAG_IN_QUOTA) { + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } + + atomic_dec(&key->user->nkeys); + if (key->flags & KEY_FLAG_INSTANTIATED) + atomic_dec(&key->user->nikeys); + + key_user_put(key->user); + + /* now throw away the key memory */ + if (key->type->destroy) + key->type->destroy(key); + + kfree(key->description); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC_X; +#endif + kmem_cache_free(key_jar, key); + + /* there may, of course, be more than one key to destroy */ + goto go_again; + +} /* end key_cleanup() */ + +/*****************************************************************************/ +/* + * dispose of a reference to a key + * - when all the references are gone, we schedule the cleanup task to come and + * pull it out of the tree in definite process context + */ +void key_put(struct key *key) +{ + if (key) { + key_check(key); + + if (atomic_dec_and_test(&key->usage)) + schedule_work(&key_cleanup_task); + } + +} /* end key_put() */ + +EXPORT_SYMBOL(key_put); + +/*****************************************************************************/ +/* + * find a key by its serial number + */ +struct key *key_lookup(key_serial_t id) +{ + struct rb_node *n; + struct key *key; + + spin_lock(&key_serial_lock); + + /* search the tree for the specified key */ + n = key_serial_tree.rb_node; + while (n) { + key = rb_entry(n, struct key, serial_node); + + if (id < key->serial) + n = n->rb_left; + else if (id > key->serial) + n = n->rb_right; + else + goto found; + } + + not_found: + key = ERR_PTR(-ENOKEY); + goto error; + + found: + /* pretent doesn't exist if it's dead */ + if (atomic_read(&key->usage) == 0 || + (key->flags & KEY_FLAG_DEAD) || + key->type == &key_type_dead) + goto not_found; + + /* this races with key_put(), but that doesn't matter since key_put() + * doesn't actually change the key + */ + atomic_inc(&key->usage); + + error: + spin_unlock(&key_serial_lock); + return key; + +} /* end key_lookup() */ + +/*****************************************************************************/ +/* + * find and lock the specified key type against removal + * - we return with the sem readlocked + */ +struct key_type *key_type_lookup(const char *type) +{ + struct key_type *ktype; + + down_read(&key_types_sem); + + /* look up the key type to see if it's one of the registered kernel + * types */ + list_for_each_entry(ktype, &key_types_list, link) { + if (strcmp(ktype->name, type) == 0) + goto found_kernel_type; + } + + up_read(&key_types_sem); + ktype = ERR_PTR(-ENOKEY); + + found_kernel_type: + return ktype; + +} /* end key_type_lookup() */ + +/*****************************************************************************/ +/* + * unlock a key type + */ +void key_type_put(struct key_type *ktype) +{ + up_read(&key_types_sem); + +} /* end key_type_put() */ + +/*****************************************************************************/ +/* + * attempt to update an existing key + * - the key has an incremented refcount + * - we need to put the key if we get an error + */ +static inline struct key *__key_update(struct key *key, const void *payload, + size_t plen) +{ + int ret; + + /* need write permission on the key to update it */ + ret = -EACCES; + if (!key_permission(key, KEY_WRITE)) + goto error; + + ret = -EEXIST; + if (!key->type->update) + goto error; + + down_write(&key->sem); + + ret = key->type->update(key, payload, plen); + + if (ret == 0) { + /* updating a negative key instantiates it */ + write_lock(&key->lock); + key->flags &= ~KEY_FLAG_NEGATIVE; + write_unlock(&key->lock); + } + + up_write(&key->sem); + + if (ret < 0) + goto error; + out: + return key; + + error: + key_put(key); + key = ERR_PTR(ret); + goto out; + +} /* end __key_update() */ + +/*****************************************************************************/ +/* + * search the specified keyring for a key of the same description; if one is + * found, update it, otherwise add a new one + */ +struct key *key_create_or_update(struct key *keyring, + const char *type, + const char *description, + const void *payload, + size_t plen, + int not_in_quota) +{ + struct key_type *ktype; + struct key *key = NULL; + key_perm_t perm; + int ret; + + key_check(keyring); + + /* look up the key type to see if it's one of the registered kernel + * types */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + key = ERR_PTR(-ENODEV); + goto error; + } + + ret = -EINVAL; + if (!ktype->match || !ktype->instantiate) + goto error_2; + + /* search for an existing key of the same type and description in the + * destination keyring + */ + down_write(&keyring->sem); + + key = __keyring_search_one(keyring, ktype, description, 0); + if (!IS_ERR(key)) + goto found_matching_key; + + /* if we're going to allocate a new key, we're going to have to modify + * the keyring */ + ret = -EACCES; + if (!key_permission(keyring, KEY_WRITE)) + goto error_3; + + /* decide on the permissions we want */ + perm = KEY_USR_VIEW | KEY_USR_SEARCH | KEY_USR_LINK; + + if (ktype->read) + perm |= KEY_USR_READ; + + if (ktype == &key_type_keyring || ktype->update) + perm |= KEY_USR_WRITE; + + /* allocate a new key */ + key = key_alloc(ktype, description, current->fsuid, current->fsgid, + perm, not_in_quota); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error_3; + } + + /* instantiate it and link it into the target keyring */ + ret = __key_instantiate_and_link(key, payload, plen, keyring); + if (ret < 0) { + key_put(key); + key = ERR_PTR(ret); + } + + error_3: + up_write(&keyring->sem); + error_2: + key_type_put(ktype); + error: + return key; + + found_matching_key: + /* we found a matching key, so we're going to try to update it + * - we can drop the locks first as we have the key pinned + */ + up_write(&keyring->sem); + key_type_put(ktype); + + key = __key_update(key, payload, plen); + goto error; + +} /* end key_create_or_update() */ + +EXPORT_SYMBOL(key_create_or_update); + +/*****************************************************************************/ +/* + * update a key + */ +int key_update(struct key *key, const void *payload, size_t plen) +{ + int ret; + + key_check(key); + + /* the key must be writable */ + ret = -EACCES; + if (!key_permission(key, KEY_WRITE)) + goto error; + + /* attempt to update it if supported */ + ret = -EOPNOTSUPP; + if (key->type->update) { + down_write(&key->sem); + ret = key->type->update(key, payload, plen); + + if (ret == 0) { + /* updating a negative key instantiates it */ + write_lock(&key->lock); + key->flags &= ~KEY_FLAG_NEGATIVE; + write_unlock(&key->lock); + } + + up_write(&key->sem); + } + + error: + return ret; + +} /* end key_update() */ + +EXPORT_SYMBOL(key_update); + +/*****************************************************************************/ +/* + * duplicate a key, potentially with a revised description + * - must be supported by the keytype (keyrings for instance can be duplicated) + */ +struct key *key_duplicate(struct key *source, const char *desc) +{ + struct key *key; + int ret; + + key_check(source); + + if (!desc) + desc = source->description; + + down_read(&key_types_sem); + + ret = -EINVAL; + if (!source->type->duplicate) + goto error; + + /* allocate and instantiate a key */ + key = key_alloc(source->type, desc, current->fsuid, current->fsgid, + source->perm, 0); + if (IS_ERR(key)) + goto error_k; + + down_read(&source->sem); + ret = key->type->duplicate(key, source); + up_read(&source->sem); + if (ret < 0) + goto error2; + + atomic_inc(&key->user->nikeys); + + write_lock(&key->lock); + key->flags |= KEY_FLAG_INSTANTIATED; + write_unlock(&key->lock); + + error_k: + up_read(&key_types_sem); + out: + return key; + + error2: + key_put(key); + error: + up_read(&key_types_sem); + key = ERR_PTR(ret); + goto out; + +} /* end key_duplicate() */ + +/*****************************************************************************/ +/* + * revoke a key + */ +void key_revoke(struct key *key) +{ + key_check(key); + + /* make sure no one's trying to change or use the key when we mark + * it */ + down_write(&key->sem); + write_lock(&key->lock); + key->flags |= KEY_FLAG_REVOKED; + write_unlock(&key->lock); + up_write(&key->sem); + +} /* end key_revoke() */ + +EXPORT_SYMBOL(key_revoke); + +/*****************************************************************************/ +/* + * register a type of key + */ +int register_key_type(struct key_type *ktype) +{ + struct key_type *p; + int ret; + + ret = -EEXIST; + down_write(&key_types_sem); + + /* disallow key types with the same name */ + list_for_each_entry(p, &key_types_list, link) { + if (strcmp(p->name, ktype->name) == 0) + goto out; + } + + /* store the type */ + list_add(&ktype->link, &key_types_list); + ret = 0; + + out: + up_write(&key_types_sem); + return ret; + +} /* end register_key_type() */ + +EXPORT_SYMBOL(register_key_type); + +/*****************************************************************************/ +/* + * unregister a type of key + */ +void unregister_key_type(struct key_type *ktype) +{ + struct rb_node *_n; + struct key *key; + + down_write(&key_types_sem); + + /* withdraw the key type */ + list_del_init(&ktype->link); + + /* need to withdraw all keys of this type */ + spin_lock(&key_serial_lock); + + for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { + key = rb_entry(_n, struct key, serial_node); + + if (key->type != ktype) + continue; + + write_lock(&key->lock); + key->type = &key_type_dead; + write_unlock(&key->lock); + + /* there shouldn't be anyone looking at the description or + * payload now */ + if (ktype->destroy) + ktype->destroy(key); + memset(&key->payload, 0xbd, sizeof(key->payload)); + } + + spin_unlock(&key_serial_lock); + up_write(&key_types_sem); + +} /* end unregister_key_type() */ + +EXPORT_SYMBOL(unregister_key_type); + +/*****************************************************************************/ +/* + * initialise the key management stuff + */ +void __init key_init(void) +{ + /* allocate a slab in which we can store keys */ + key_jar = kmem_cache_create("key_jar", sizeof(struct key), + 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL); + + /* add the special key types */ + list_add_tail(&key_type_keyring.link, &key_types_list); + list_add_tail(&key_type_dead.link, &key_types_list); + list_add_tail(&key_type_user.link, &key_types_list); + + /* record the root user tracking */ + rb_link_node(&root_key_user.node, + NULL, + &key_user_tree.rb_node); + + rb_insert_color(&root_key_user.node, + &key_user_tree); + + /* record root's user standard keyrings */ + key_check(&root_user_keyring); + key_check(&root_session_keyring); + + __key_insert_serial(&root_user_keyring); + __key_insert_serial(&root_session_keyring); + + keyring_publish_name(&root_user_keyring); + keyring_publish_name(&root_session_keyring); + + /* link the two root keyrings together */ + key_link(&root_session_keyring, &root_user_keyring); +} /* end key_init() */ diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c new file mode 100644 index 000000000000..dc0011b3fac9 --- /dev/null +++ b/security/keys/keyctl.c @@ -0,0 +1,987 @@ +/* keyctl.c: userspace keyctl operations + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/syscalls.h> +#include <linux/keyctl.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <asm/uaccess.h> +#include "internal.h" + +/*****************************************************************************/ +/* + * extract the description of a new key from userspace and either add it as a + * new key to the specified keyring or update a matching key in that keyring + * - the keyring must be writable + * - returns the new key's serial number + * - implements add_key() + */ +asmlinkage long sys_add_key(const char __user *_type, + const char __user *_description, + const void __user *_payload, + size_t plen, + key_serial_t ringid) +{ + struct key *keyring, *key; + char type[32], *description; + void *payload; + long dlen, ret; + + ret = -EINVAL; + if (plen > 32767) + goto error; + + /* draw all the data into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* pull the payload in if one was supplied */ + payload = NULL; + + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error2; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error3; + } + + /* find the target keyring (which must be writable) */ + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error3; + } + + /* create or update the requested key and add it to the target + * keyring */ + key = key_create_or_update(keyring, type, description, + payload, plen, 0); + if (!IS_ERR(key)) { + ret = key->serial; + key_put(key); + } + else { + ret = PTR_ERR(key); + } + + key_put(keyring); + error3: + kfree(payload); + error2: + kfree(description); + error: + return ret; + +} /* end sys_add_key() */ + +/*****************************************************************************/ +/* + * search the process keyrings for a matching key + * - nested keyrings may also be searched if they have Search permission + * - if a key is found, it will be attached to the destination keyring if + * there's one specified + * - /sbin/request-key will be invoked if _callout_info is non-NULL + * - the _callout_info string will be passed to /sbin/request-key + * - if the _callout_info string is empty, it will be rendered as "-" + * - implements request_key() + */ +asmlinkage long sys_request_key(const char __user *_type, + const char __user *_description, + const char __user *_callout_info, + key_serial_t destringid) +{ + struct key_type *ktype; + struct key *key, *dest; + char type[32], *description, *callout_info; + long dlen, ret; + + /* pull the type into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + /* pull the description into kernel space */ + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* pull the callout info into kernel space */ + callout_info = NULL; + if (_callout_info) { + ret = -EFAULT; + dlen = strnlen_user(_callout_info, PAGE_SIZE - 1); + if (dlen <= 0) + goto error2; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error2; + + ret = -ENOMEM; + callout_info = kmalloc(dlen + 1, GFP_KERNEL); + if (!callout_info) + goto error2; + + ret = -EFAULT; + if (copy_from_user(callout_info, _callout_info, dlen + 1) != 0) + goto error3; + } + + /* get the destination keyring if specified */ + dest = NULL; + if (destringid) { + dest = lookup_user_key(destringid, 1, 0, KEY_WRITE); + if (IS_ERR(dest)) { + ret = PTR_ERR(dest); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key = request_key(ktype, description, callout_info); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error5; + } + + /* link the resulting key to the destination keyring */ + if (dest) { + ret = key_link(dest, key); + if (ret < 0) + goto error6; + } + + ret = key->serial; + + error6: + key_put(key); + error5: + key_type_put(ktype); + error4: + key_put(dest); + error3: + kfree(callout_info); + error2: + kfree(description); + error: + return ret; + +} /* end sys_request_key() */ + +/*****************************************************************************/ +/* + * get the ID of the specified process keyring + * - the keyring must have search permission to be found + * - implements keyctl(KEYCTL_GET_KEYRING_ID) + */ +long keyctl_get_keyring_ID(key_serial_t id, int create) +{ + struct key *key; + long ret; + + key = lookup_user_key(id, create, 0, KEY_SEARCH); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + ret = key->serial; + key_put(key); + error: + return ret; + +} /* end keyctl_get_keyring_ID() */ + +/*****************************************************************************/ +/* + * join the session keyring + * - implements keyctl(KEYCTL_JOIN_SESSION_KEYRING) + */ +long keyctl_join_session_keyring(const char __user *_name) +{ + char *name; + long nlen, ret; + + /* fetch the name from userspace */ + name = NULL; + if (_name) { + ret = -EFAULT; + nlen = strnlen_user(_name, PAGE_SIZE - 1); + if (nlen <= 0) + goto error; + + ret = -EINVAL; + if (nlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + name = kmalloc(nlen + 1, GFP_KERNEL); + if (!name) + goto error; + + ret = -EFAULT; + if (copy_from_user(name, _name, nlen + 1) != 0) + goto error2; + } + + /* join the session */ + ret = join_session_keyring(name); + + error2: + kfree(name); + error: + return ret; + +} /* end keyctl_join_session_keyring() */ + +/*****************************************************************************/ +/* + * update a key's data payload + * - the key must be writable + * - implements keyctl(KEYCTL_UPDATE) + */ +long keyctl_update_key(key_serial_t id, + const void __user *_payload, + size_t plen) +{ + struct key *key; + void *payload; + long ret; + + ret = -EINVAL; + if (plen > PAGE_SIZE) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + } + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 0, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + /* update the key */ + ret = key_update(key, payload, plen); + + key_put(key); + error2: + kfree(payload); + error: + return ret; + +} /* end keyctl_update_key() */ + +/*****************************************************************************/ +/* + * revoke a key + * - the key must be writable + * - implements keyctl(KEYCTL_REVOKE) + */ +long keyctl_revoke_key(key_serial_t id) +{ + struct key *key; + long ret; + + key = lookup_user_key(id, 0, 0, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + key_revoke(key); + ret = 0; + + key_put(key); + error: + return 0; + +} /* end keyctl_revoke_key() */ + +/*****************************************************************************/ +/* + * clear the specified process keyring + * - the keyring must be writable + * - implements keyctl(KEYCTL_CLEAR) + */ +long keyctl_keyring_clear(key_serial_t ringid) +{ + struct key *keyring; + long ret; + + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + ret = keyring_clear(keyring); + + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_clear() */ + +/*****************************************************************************/ +/* + * link a key into a keyring + * - the keyring must be writable + * - the key must be linkable + * - implements keyctl(KEYCTL_LINK) + */ +long keyctl_keyring_link(key_serial_t id, key_serial_t ringid) +{ + struct key *keyring, *key; + long ret; + + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + key = lookup_user_key(id, 1, 0, KEY_LINK); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + ret = key_link(keyring, key); + + key_put(key); + error2: + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_link() */ + +/*****************************************************************************/ +/* + * unlink the first attachment of a key from a keyring + * - the keyring must be writable + * - we don't need any permissions on the key + * - implements keyctl(KEYCTL_UNLINK) + */ +long keyctl_keyring_unlink(key_serial_t id, key_serial_t ringid) +{ + struct key *keyring, *key; + long ret; + + keyring = lookup_user_key(ringid, 0, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + key = lookup_user_key(id, 0, 0, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + ret = key_unlink(keyring, key); + + key_put(key); + error2: + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_unlink() */ + +/*****************************************************************************/ +/* + * describe a user key + * - the key must have view permission + * - if there's a buffer, we place up to buflen bytes of data into it + * - unless there's an error, we return the amount of description available, + * irrespective of how much we may have copied + * - the description is formatted thus: + * type;uid;gid;perm;description<NUL> + * - implements keyctl(KEYCTL_DESCRIBE) + */ +long keyctl_describe_key(key_serial_t keyid, + char __user *buffer, + size_t buflen) +{ + struct key *key; + char *tmpbuf; + long ret; + + key = lookup_user_key(keyid, 0, 1, KEY_VIEW); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* calculate how much description we're going to return */ + ret = -ENOMEM; + tmpbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!tmpbuf) + goto error2; + + ret = snprintf(tmpbuf, PAGE_SIZE - 1, + "%s;%d;%d;%06x;%s", + key->type->name, + key->uid, + key->gid, + key->perm, + key->description ? key->description :"" + ); + + /* include a NUL char at the end of the data */ + if (ret > PAGE_SIZE - 1) + ret = PAGE_SIZE - 1; + tmpbuf[ret] = 0; + ret++; + + /* consider returning the data */ + if (buffer && buflen > 0) { + if (buflen > ret) + buflen = ret; + + if (copy_to_user(buffer, tmpbuf, buflen) != 0) + ret = -EFAULT; + } + + kfree(tmpbuf); + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_describe_key() */ + +/*****************************************************************************/ +/* + * search the specified keyring for a matching key + * - the start keyring must be searchable + * - nested keyrings may also be searched if they are searchable + * - only keys with search permission may be found + * - if a key is found, it will be attached to the destination keyring if + * there's one specified + * - implements keyctl(KEYCTL_SEARCH) + */ +long keyctl_keyring_search(key_serial_t ringid, + const char __user *_type, + const char __user *_description, + key_serial_t destringid) +{ + struct key_type *ktype; + struct key *keyring, *key, *dest; + char type[32], *description; + long dlen, ret; + + /* pull the type and description into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* get the keyring at which to begin the search */ + keyring = lookup_user_key(ringid, 0, 0, KEY_SEARCH); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + + /* get the destination keyring if specified */ + dest = NULL; + if (destringid) { + dest = lookup_user_key(destringid, 1, 0, KEY_WRITE); + if (IS_ERR(dest)) { + ret = PTR_ERR(dest); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key = keyring_search(keyring, ktype, description); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + + /* treat lack or presence of a negative key the same */ + if (ret == -EAGAIN) + ret = -ENOKEY; + goto error5; + } + + /* link the resulting key to the destination keyring if we can */ + if (dest) { + ret = -EACCES; + if (!key_permission(key, KEY_LINK)) + goto error6; + + ret = key_link(dest, key); + if (ret < 0) + goto error6; + } + + ret = key->serial; + + error6: + key_put(key); + error5: + key_type_put(ktype); + error4: + key_put(dest); + error3: + key_put(keyring); + error2: + kfree(description); + error: + return ret; + +} /* end keyctl_keyring_search() */ + +/*****************************************************************************/ +/* + * see if the key we're looking at is the target key + */ +static int keyctl_read_key_same(const struct key *key, const void *target) +{ + return key == target; + +} /* end keyctl_read_key_same() */ + +/*****************************************************************************/ +/* + * read a user key's payload + * - the keyring must be readable or the key must be searchable from the + * process's keyrings + * - if there's a buffer, we place up to buflen bytes of data into it + * - unless there's an error, we return the amount of data in the key, + * irrespective of how much we may have copied + * - implements keyctl(KEYCTL_READ) + */ +long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen) +{ + struct key *key, *skey; + long ret; + + /* find the key first */ + key = lookup_user_key(keyid, 0, 0, 0); + if (!IS_ERR(key)) { + /* see if we can read it directly */ + if (key_permission(key, KEY_READ)) + goto can_read_key; + + /* can't; see if it's searchable from this process's + * keyrings */ + ret = -ENOKEY; + if (key_permission(key, KEY_SEARCH)) { + /* okay - we do have search permission on the key + * itself, but do we have the key? */ + skey = search_process_keyrings_aux(key->type, key, + keyctl_read_key_same); + if (!IS_ERR(skey)) + goto can_read_key2; + } + + goto error2; + } + + ret = -ENOKEY; + goto error; + + /* the key is probably readable - now try to read it */ + can_read_key2: + key_put(skey); + can_read_key: + ret = key_validate(key); + if (ret == 0) { + ret = -EOPNOTSUPP; + if (key->type->read) { + /* read the data with the semaphore held (since we + * might sleep) */ + down_read(&key->sem); + ret = key->type->read(key, buffer, buflen); + up_read(&key->sem); + } + } + + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_read_key() */ + +/*****************************************************************************/ +/* + * change the ownership of a key + * - the keyring owned by the changer + * - if the uid or gid is -1, then that parameter is not changed + * - implements keyctl(KEYCTL_CHOWN) + */ +long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) +{ + struct key *key; + long ret; + + ret = 0; + if (uid == (uid_t) -1 && gid == (gid_t) -1) + goto error; + + key = lookup_user_key(id, 1, 1, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* make the changes with the locks held to prevent chown/chown races */ + ret = -EACCES; + down_write(&key->sem); + write_lock(&key->lock); + + if (!capable(CAP_SYS_ADMIN)) { + /* only the sysadmin can chown a key to some other UID */ + if (uid != (uid_t) -1 && key->uid != uid) + goto no_access; + + /* only the sysadmin can set the key's GID to a group other + * than one of those that the current process subscribes to */ + if (gid != (gid_t) -1 && gid != key->gid && !in_group_p(gid)) + goto no_access; + } + + /* change the UID (have to update the quotas) */ + if (uid != (uid_t) -1 && uid != key->uid) { + /* don't support UID changing yet */ + ret = -EOPNOTSUPP; + goto no_access; + } + + /* change the GID */ + if (gid != (gid_t) -1) + key->gid = gid; + + ret = 0; + + no_access: + write_unlock(&key->lock); + up_write(&key->sem); + key_put(key); + error: + return ret; + +} /* end keyctl_chown_key() */ + +/*****************************************************************************/ +/* + * change the permission mask on a key + * - the keyring owned by the changer + * - implements keyctl(KEYCTL_SETPERM) + */ +long keyctl_setperm_key(key_serial_t id, key_perm_t perm) +{ + struct key *key; + long ret; + + ret = -EINVAL; + if (perm & ~(KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL)) + goto error; + + key = lookup_user_key(id, 1, 1, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* make the changes with the locks held to prevent chown/chmod + * races */ + ret = -EACCES; + down_write(&key->sem); + write_lock(&key->lock); + + /* if we're not the sysadmin, we can only chmod a key that we + * own */ + if (!capable(CAP_SYS_ADMIN) && key->uid != current->fsuid) + goto no_access; + + /* changing the permissions mask */ + key->perm = perm; + ret = 0; + + no_access: + write_unlock(&key->lock); + up_write(&key->sem); + key_put(key); + error: + return ret; + +} /* end keyctl_setperm_key() */ + +/*****************************************************************************/ +/* + * instantiate the key with the specified payload, and, if one is given, link + * the key into the keyring + */ +long keyctl_instantiate_key(key_serial_t id, + const void __user *_payload, + size_t plen, + key_serial_t ringid) +{ + struct key *key, *keyring; + void *payload; + long ret; + + ret = -EINVAL; + if (plen > 32767) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + } + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 1, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + /* find the destination keyring if present (which must also be + * writable) */ + keyring = NULL; + if (ringid) { + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error3; + } + } + + /* instantiate the key and link it into a keyring */ + ret = key_instantiate_and_link(key, payload, plen, keyring); + + key_put(keyring); + error3: + key_put(key); + error2: + kfree(payload); + error: + return ret; + +} /* end keyctl_instantiate_key() */ + +/*****************************************************************************/ +/* + * negatively instantiate the key with the given timeout (in seconds), and, if + * one is given, link the key into the keyring + */ +long keyctl_negate_key(key_serial_t id, unsigned timeout, key_serial_t ringid) +{ + struct key *key, *keyring; + long ret; + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 1, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* find the destination keyring if present (which must also be + * writable) */ + keyring = NULL; + if (ringid) { + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + } + + /* instantiate the key and link it into a keyring */ + ret = key_negate_and_link(key, timeout, keyring); + + key_put(keyring); + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_negate_key() */ + +/*****************************************************************************/ +/* + * the key control system call + */ +asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + switch (option) { + case KEYCTL_GET_KEYRING_ID: + return keyctl_get_keyring_ID((key_serial_t) arg2, + (int) arg3); + + case KEYCTL_JOIN_SESSION_KEYRING: + return keyctl_join_session_keyring((const char __user *) arg2); + + case KEYCTL_UPDATE: + return keyctl_update_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4); + + case KEYCTL_REVOKE: + return keyctl_revoke_key((key_serial_t) arg2); + + case KEYCTL_DESCRIBE: + return keyctl_describe_key((key_serial_t) arg2, + (char __user *) arg3, + (unsigned) arg4); + + case KEYCTL_CLEAR: + return keyctl_keyring_clear((key_serial_t) arg2); + + case KEYCTL_LINK: + return keyctl_keyring_link((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_UNLINK: + return keyctl_keyring_unlink((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_SEARCH: + return keyctl_keyring_search((key_serial_t) arg2, + (const char __user *) arg3, + (const char __user *) arg4, + (key_serial_t) arg5); + + case KEYCTL_READ: + return keyctl_read_key((key_serial_t) arg2, + (char __user *) arg3, + (size_t) arg4); + + case KEYCTL_CHOWN: + return keyctl_chown_key((key_serial_t) arg2, + (uid_t) arg3, + (gid_t) arg4); + + case KEYCTL_SETPERM: + return keyctl_setperm_key((key_serial_t) arg2, + (key_perm_t) arg3); + + case KEYCTL_INSTANTIATE: + return keyctl_instantiate_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4, + (key_serial_t) arg5); + + case KEYCTL_NEGATE: + return keyctl_negate_key((key_serial_t) arg2, + (unsigned) arg3, + (key_serial_t) arg4); + + default: + return -EOPNOTSUPP; + } + +} /* end sys_keyctl() */ diff --git a/security/keys/keyring.c b/security/keys/keyring.c new file mode 100644 index 000000000000..e2ab4f8e7481 --- /dev/null +++ b/security/keys/keyring.c @@ -0,0 +1,895 @@ +/* keyring.c: keyring handling + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <asm/uaccess.h> +#include "internal.h" + +/* + * when plumbing the depths of the key tree, this sets a hard limit set on how + * deep we're willing to go + */ +#define KEYRING_SEARCH_MAX_DEPTH 6 + +/* + * we keep all named keyrings in a hash to speed looking them up + */ +#define KEYRING_NAME_HASH_SIZE (1 << 5) + +static struct list_head keyring_name_hash[KEYRING_NAME_HASH_SIZE]; +static DEFINE_RWLOCK(keyring_name_lock); + +static inline unsigned keyring_hash(const char *desc) +{ + unsigned bucket = 0; + + for (; *desc; desc++) + bucket += (unsigned char) *desc; + + return bucket & (KEYRING_NAME_HASH_SIZE - 1); +} + +/* + * the keyring type definition + */ +static int keyring_instantiate(struct key *keyring, + const void *data, size_t datalen); +static int keyring_duplicate(struct key *keyring, const struct key *source); +static int keyring_match(const struct key *keyring, const void *criterion); +static void keyring_destroy(struct key *keyring); +static void keyring_describe(const struct key *keyring, struct seq_file *m); +static long keyring_read(const struct key *keyring, + char __user *buffer, size_t buflen); + +struct key_type key_type_keyring = { + .name = "keyring", + .def_datalen = sizeof(struct keyring_list), + .instantiate = keyring_instantiate, + .duplicate = keyring_duplicate, + .match = keyring_match, + .destroy = keyring_destroy, + .describe = keyring_describe, + .read = keyring_read, +}; + +/* + * semaphore to serialise link/link calls to prevent two link calls in parallel + * introducing a cycle + */ +DECLARE_RWSEM(keyring_serialise_link_sem); + +/*****************************************************************************/ +/* + * publish the name of a keyring so that it can be found by name (if it has + * one) + */ +void keyring_publish_name(struct key *keyring) +{ + int bucket; + + if (keyring->description) { + bucket = keyring_hash(keyring->description); + + write_lock(&keyring_name_lock); + + if (!keyring_name_hash[bucket].next) + INIT_LIST_HEAD(&keyring_name_hash[bucket]); + + list_add_tail(&keyring->type_data.link, + &keyring_name_hash[bucket]); + + write_unlock(&keyring_name_lock); + } + +} /* end keyring_publish_name() */ + +/*****************************************************************************/ +/* + * initialise a keyring + * - we object if we were given any data + */ +static int keyring_instantiate(struct key *keyring, + const void *data, size_t datalen) +{ + int ret; + + ret = -EINVAL; + if (datalen == 0) { + /* make the keyring available by name if it has one */ + keyring_publish_name(keyring); + ret = 0; + } + + return ret; + +} /* end keyring_instantiate() */ + +/*****************************************************************************/ +/* + * duplicate the list of subscribed keys from a source keyring into this one + */ +static int keyring_duplicate(struct key *keyring, const struct key *source) +{ + struct keyring_list *sklist, *klist; + unsigned max; + size_t size; + int loop, ret; + + const unsigned limit = + (PAGE_SIZE - sizeof(*klist)) / sizeof(struct key); + + ret = 0; + sklist = source->payload.subscriptions; + + if (sklist && sklist->nkeys > 0) { + max = sklist->nkeys; + BUG_ON(max > limit); + + max = (max + 3) & ~3; + if (max > limit) + max = limit; + + ret = -ENOMEM; + size = sizeof(*klist) + sizeof(struct key) * max; + klist = kmalloc(size, GFP_KERNEL); + if (!klist) + goto error; + + klist->maxkeys = max; + klist->nkeys = sklist->nkeys; + memcpy(klist->keys, + sklist->keys, + sklist->nkeys * sizeof(struct key)); + + for (loop = klist->nkeys - 1; loop >= 0; loop--) + atomic_inc(&klist->keys[loop]->usage); + + keyring->payload.subscriptions = klist; + ret = 0; + } + + error: + return ret; + +} /* end keyring_duplicate() */ + +/*****************************************************************************/ +/* + * match keyrings on their name + */ +static int keyring_match(const struct key *keyring, const void *description) +{ + return keyring->description && + strcmp(keyring->description, description) == 0; + +} /* end keyring_match() */ + +/*****************************************************************************/ +/* + * dispose of the data dangling from the corpse of a keyring + */ +static void keyring_destroy(struct key *keyring) +{ + struct keyring_list *klist; + int loop; + + if (keyring->description) { + write_lock(&keyring_name_lock); + list_del(&keyring->type_data.link); + write_unlock(&keyring_name_lock); + } + + klist = keyring->payload.subscriptions; + if (klist) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + kfree(klist); + } + +} /* end keyring_destroy() */ + +/*****************************************************************************/ +/* + * describe the keyring + */ +static void keyring_describe(const struct key *keyring, struct seq_file *m) +{ + struct keyring_list *klist; + + if (keyring->description) { + seq_puts(m, keyring->description); + } + else { + seq_puts(m, "[anon]"); + } + + klist = keyring->payload.subscriptions; + if (klist) + seq_printf(m, ": %u/%u", klist->nkeys, klist->maxkeys); + else + seq_puts(m, ": empty"); + +} /* end keyring_describe() */ + +/*****************************************************************************/ +/* + * read a list of key IDs from the keyring's contents + */ +static long keyring_read(const struct key *keyring, + char __user *buffer, size_t buflen) +{ + struct keyring_list *klist; + struct key *key; + size_t qty, tmp; + int loop, ret; + + ret = 0; + klist = keyring->payload.subscriptions; + + if (klist) { + /* calculate how much data we could return */ + qty = klist->nkeys * sizeof(key_serial_t); + + if (buffer && buflen > 0) { + if (buflen > qty) + buflen = qty; + + /* copy the IDs of the subscribed keys into the + * buffer */ + ret = -EFAULT; + + for (loop = 0; loop < klist->nkeys; loop++) { + key = klist->keys[loop]; + + tmp = sizeof(key_serial_t); + if (tmp > buflen) + tmp = buflen; + + if (copy_to_user(buffer, + &key->serial, + tmp) != 0) + goto error; + + buflen -= tmp; + if (buflen == 0) + break; + buffer += tmp; + } + } + + ret = qty; + } + + error: + return ret; + +} /* end keyring_read() */ + +/*****************************************************************************/ +/* + * allocate a keyring and link into the destination keyring + */ +struct key *keyring_alloc(const char *description, uid_t uid, gid_t gid, + int not_in_quota, struct key *dest) +{ + struct key *keyring; + int ret; + + keyring = key_alloc(&key_type_keyring, description, + uid, gid, KEY_USR_ALL, not_in_quota); + + if (!IS_ERR(keyring)) { + ret = key_instantiate_and_link(keyring, NULL, 0, dest); + if (ret < 0) { + key_put(keyring); + keyring = ERR_PTR(ret); + } + } + + return keyring; + +} /* end keyring_alloc() */ + +/*****************************************************************************/ +/* + * search the supplied keyring tree for a key that matches the criterion + * - perform a breadth-then-depth search up to the prescribed limit + * - we only find keys on which we have search permission + * - we use the supplied match function to see if the description (or other + * feature of interest) matches + * - we readlock the keyrings as we search down the tree + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we only found negative matching keys + */ +struct key *keyring_search_aux(struct key *keyring, + struct key_type *type, + const void *description, + key_match_func_t match) +{ + struct { + struct key *keyring; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct timespec now; + struct key *key; + long err; + int sp, psp, kix; + + key_check(keyring); + + /* top keyring must have search permission to begin the search */ + key = ERR_PTR(-EACCES); + if (!key_permission(keyring, KEY_SEARCH)) + goto error; + + key = ERR_PTR(-ENOTDIR); + if (keyring->type != &key_type_keyring) + goto error; + + now = current_kernel_time(); + err = -EAGAIN; + sp = 0; + + /* start processing a new keyring */ + descend: + read_lock(&keyring->lock); + if (keyring->flags & KEY_FLAG_REVOKED) + goto not_this_keyring; + + keylist = keyring->payload.subscriptions; + if (!keylist) + goto not_this_keyring; + + /* iterate through the keys in this keyring first */ + for (kix = 0; kix < keylist->nkeys; kix++) { + key = keylist->keys[kix]; + + /* ignore keys not of this type */ + if (key->type != type) + continue; + + /* skip revoked keys and expired keys */ + if (key->flags & KEY_FLAG_REVOKED) + continue; + + if (key->expiry && now.tv_sec >= key->expiry) + continue; + + /* keys that don't match */ + if (!match(key, description)) + continue; + + /* key must have search permissions */ + if (!key_permission(key, KEY_SEARCH)) + continue; + + /* we set a different error code if we find a negative key */ + if (key->flags & KEY_FLAG_NEGATIVE) { + err = -ENOKEY; + continue; + } + + goto found; + } + + /* search through the keyrings nested in this one */ + kix = 0; + ascend: + while (kix < keylist->nkeys) { + key = keylist->keys[kix]; + if (key->type != &key_type_keyring) + goto next; + + /* recursively search nested keyrings + * - only search keyrings for which we have search permission + */ + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + goto next; + + if (!key_permission(key, KEY_SEARCH)) + goto next; + + /* evade loops in the keyring tree */ + for (psp = 0; psp < sp; psp++) + if (stack[psp].keyring == keyring) + goto next; + + /* stack the current position */ + stack[sp].keyring = keyring; + stack[sp].kix = kix; + sp++; + + /* begin again with the new keyring */ + keyring = key; + goto descend; + + next: + kix++; + } + + /* the keyring we're looking at was disqualified or didn't contain a + * matching key */ + not_this_keyring: + read_unlock(&keyring->lock); + + if (sp > 0) { + /* resume the processing of a keyring higher up in the tree */ + sp--; + keyring = stack[sp].keyring; + keylist = keyring->payload.subscriptions; + kix = stack[sp].kix + 1; + goto ascend; + } + + key = ERR_PTR(err); + goto error; + + /* we found a viable match */ + found: + atomic_inc(&key->usage); + read_unlock(&keyring->lock); + + /* unwind the keyring stack */ + while (sp > 0) { + sp--; + read_unlock(&stack[sp].keyring->lock); + } + + key_check(key); + error: + return key; + +} /* end keyring_search_aux() */ + +/*****************************************************************************/ +/* + * search the supplied keyring tree for a key that matches the criterion + * - perform a breadth-then-depth search up to the prescribed limit + * - we only find keys on which we have search permission + * - we readlock the keyrings as we search down the tree + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we only found negative matching keys + */ +struct key *keyring_search(struct key *keyring, + struct key_type *type, + const char *description) +{ + return keyring_search_aux(keyring, type, description, type->match); + +} /* end keyring_search() */ + +EXPORT_SYMBOL(keyring_search); + +/*****************************************************************************/ +/* + * search the given keyring only (no recursion) + * - keyring must be locked by caller + */ +struct key *__keyring_search_one(struct key *keyring, + const struct key_type *ktype, + const char *description, + key_perm_t perm) +{ + struct keyring_list *klist; + struct key *key; + int loop; + + klist = keyring->payload.subscriptions; + if (klist) { + for (loop = 0; loop < klist->nkeys; loop++) { + key = klist->keys[loop]; + + if (key->type == ktype && + key->type->match(key, description) && + key_permission(key, perm) && + !(key->flags & KEY_FLAG_REVOKED) + ) + goto found; + } + } + + key = ERR_PTR(-ENOKEY); + goto error; + + found: + atomic_inc(&key->usage); + error: + return key; + +} /* end __keyring_search_one() */ + +/*****************************************************************************/ +/* + * find a keyring with the specified name + * - all named keyrings are searched + * - only find keyrings with search permission for the process + * - only find keyrings with a serial number greater than the one specified + */ +struct key *find_keyring_by_name(const char *name, key_serial_t bound) +{ + struct key *keyring; + int bucket; + + keyring = ERR_PTR(-EINVAL); + if (!name) + goto error; + + bucket = keyring_hash(name); + + read_lock(&keyring_name_lock); + + if (keyring_name_hash[bucket].next) { + /* search this hash bucket for a keyring with a matching name + * that's readable and that hasn't been revoked */ + list_for_each_entry(keyring, + &keyring_name_hash[bucket], + type_data.link + ) { + if (keyring->flags & KEY_FLAG_REVOKED) + continue; + + if (strcmp(keyring->description, name) != 0) + continue; + + if (!key_permission(keyring, KEY_SEARCH)) + continue; + + /* found a potential candidate, but we still need to + * check the serial number */ + if (keyring->serial <= bound) + continue; + + /* we've got a match */ + atomic_inc(&keyring->usage); + read_unlock(&keyring_name_lock); + goto error; + } + } + + read_unlock(&keyring_name_lock); + keyring = ERR_PTR(-ENOKEY); + + error: + return keyring; + +} /* end find_keyring_by_name() */ + +/*****************************************************************************/ +/* + * see if a cycle will will be created by inserting acyclic tree B in acyclic + * tree A at the topmost level (ie: as a direct child of A) + * - since we are adding B to A at the top level, checking for cycles should + * just be a matter of seeing if node A is somewhere in tree B + */ +static int keyring_detect_cycle(struct key *A, struct key *B) +{ + struct { + struct key *subtree; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct key *subtree, *key; + int sp, kix, ret; + + ret = -EDEADLK; + if (A == B) + goto error; + + subtree = B; + sp = 0; + + /* start processing a new keyring */ + descend: + read_lock(&subtree->lock); + if (subtree->flags & KEY_FLAG_REVOKED) + goto not_this_keyring; + + keylist = subtree->payload.subscriptions; + if (!keylist) + goto not_this_keyring; + kix = 0; + + ascend: + /* iterate through the remaining keys in this keyring */ + for (; kix < keylist->nkeys; kix++) { + key = keylist->keys[kix]; + + if (key == A) + goto cycle_detected; + + /* recursively check nested keyrings */ + if (key->type == &key_type_keyring) { + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + goto too_deep; + + /* stack the current position */ + stack[sp].subtree = subtree; + stack[sp].kix = kix; + sp++; + + /* begin again with the new keyring */ + subtree = key; + goto descend; + } + } + + /* the keyring we're looking at was disqualified or didn't contain a + * matching key */ + not_this_keyring: + read_unlock(&subtree->lock); + + if (sp > 0) { + /* resume the checking of a keyring higher up in the tree */ + sp--; + subtree = stack[sp].subtree; + keylist = subtree->payload.subscriptions; + kix = stack[sp].kix + 1; + goto ascend; + } + + ret = 0; /* no cycles detected */ + + error: + return ret; + + too_deep: + ret = -ELOOP; + goto error_unwind; + cycle_detected: + ret = -EDEADLK; + error_unwind: + read_unlock(&subtree->lock); + + /* unwind the keyring stack */ + while (sp > 0) { + sp--; + read_unlock(&stack[sp].subtree->lock); + } + + goto error; + +} /* end keyring_detect_cycle() */ + +/*****************************************************************************/ +/* + * link a key into to a keyring + * - must be called with the keyring's semaphore held + */ +int __key_link(struct key *keyring, struct key *key) +{ + struct keyring_list *klist, *nklist; + unsigned max; + size_t size; + int ret; + + ret = -EKEYREVOKED; + if (keyring->flags & KEY_FLAG_REVOKED) + goto error; + + ret = -ENOTDIR; + if (keyring->type != &key_type_keyring) + goto error; + + /* serialise link/link calls to prevent parallel calls causing a + * cycle when applied to two keyring in opposite orders */ + down_write(&keyring_serialise_link_sem); + + /* check that we aren't going to create a cycle adding one keyring to + * another */ + if (key->type == &key_type_keyring) { + ret = keyring_detect_cycle(keyring, key); + if (ret < 0) + goto error2; + } + + /* check that we aren't going to overrun the user's quota */ + ret = key_payload_reserve(keyring, + keyring->datalen + KEYQUOTA_LINK_BYTES); + if (ret < 0) + goto error2; + + klist = keyring->payload.subscriptions; + + if (klist && klist->nkeys < klist->maxkeys) { + /* there's sufficient slack space to add directly */ + atomic_inc(&key->usage); + + write_lock(&keyring->lock); + klist->keys[klist->nkeys++] = key; + write_unlock(&keyring->lock); + + ret = 0; + } + else { + /* grow the key list */ + max = 4; + if (klist) + max += klist->maxkeys; + + ret = -ENFILE; + size = sizeof(*klist) + sizeof(*key) * max; + if (size > PAGE_SIZE) + goto error3; + + ret = -ENOMEM; + nklist = kmalloc(size, GFP_KERNEL); + if (!nklist) + goto error3; + nklist->maxkeys = max; + nklist->nkeys = 0; + + if (klist) { + nklist->nkeys = klist->nkeys; + memcpy(nklist->keys, + klist->keys, + sizeof(struct key *) * klist->nkeys); + } + + /* add the key into the new space */ + atomic_inc(&key->usage); + + write_lock(&keyring->lock); + keyring->payload.subscriptions = nklist; + nklist->keys[nklist->nkeys++] = key; + write_unlock(&keyring->lock); + + /* dispose of the old keyring list */ + kfree(klist); + + ret = 0; + } + + error2: + up_write(&keyring_serialise_link_sem); + error: + return ret; + + error3: + /* undo the quota changes */ + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); + goto error2; + +} /* end __key_link() */ + +/*****************************************************************************/ +/* + * link a key to a keyring + */ +int key_link(struct key *keyring, struct key *key) +{ + int ret; + + key_check(keyring); + key_check(key); + + down_write(&keyring->sem); + ret = __key_link(keyring, key); + up_write(&keyring->sem); + + return ret; + +} /* end key_link() */ + +EXPORT_SYMBOL(key_link); + +/*****************************************************************************/ +/* + * unlink the first link to a key from a keyring + */ +int key_unlink(struct key *keyring, struct key *key) +{ + struct keyring_list *klist; + int loop, ret; + + key_check(keyring); + key_check(key); + + ret = -ENOTDIR; + if (keyring->type != &key_type_keyring) + goto error; + + down_write(&keyring->sem); + + klist = keyring->payload.subscriptions; + if (klist) { + /* search the keyring for the key */ + for (loop = 0; loop < klist->nkeys; loop++) + if (klist->keys[loop] == key) + goto key_is_present; + } + + up_write(&keyring->sem); + ret = -ENOENT; + goto error; + + key_is_present: + /* adjust the user's quota */ + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); + + /* shuffle down the key pointers + * - it might be worth shrinking the allocated memory, but that runs + * the risk of ENOMEM as we would have to copy + */ + write_lock(&keyring->lock); + + klist->nkeys--; + if (loop < klist->nkeys) + memcpy(&klist->keys[loop], + &klist->keys[loop + 1], + (klist->nkeys - loop) * sizeof(struct key *)); + + write_unlock(&keyring->lock); + + up_write(&keyring->sem); + key_put(key); + ret = 0; + + error: + return ret; + +} /* end key_unlink() */ + +EXPORT_SYMBOL(key_unlink); + +/*****************************************************************************/ +/* + * clear the specified process keyring + * - implements keyctl(KEYCTL_CLEAR) + */ +int keyring_clear(struct key *keyring) +{ + struct keyring_list *klist; + int loop, ret; + + ret = -ENOTDIR; + if (keyring->type == &key_type_keyring) { + /* detach the pointer block with the locks held */ + down_write(&keyring->sem); + + klist = keyring->payload.subscriptions; + if (klist) { + /* adjust the quota */ + key_payload_reserve(keyring, + sizeof(struct keyring_list)); + + write_lock(&keyring->lock); + keyring->payload.subscriptions = NULL; + write_unlock(&keyring->lock); + } + + up_write(&keyring->sem); + + /* free the keys after the locks have been dropped */ + if (klist) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + + kfree(klist); + } + + ret = 0; + } + + return ret; + +} /* end keyring_clear() */ + +EXPORT_SYMBOL(keyring_clear); diff --git a/security/keys/proc.c b/security/keys/proc.c new file mode 100644 index 000000000000..91343b85c39c --- /dev/null +++ b/security/keys/proc.c @@ -0,0 +1,251 @@ +/* proc.c: proc files for key database enumeration + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <asm/errno.h> +#include "internal.h" + +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS +static int proc_keys_open(struct inode *inode, struct file *file); +static void *proc_keys_start(struct seq_file *p, loff_t *_pos); +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos); +static void proc_keys_stop(struct seq_file *p, void *v); +static int proc_keys_show(struct seq_file *m, void *v); + +static struct seq_operations proc_keys_ops = { + .start = proc_keys_start, + .next = proc_keys_next, + .stop = proc_keys_stop, + .show = proc_keys_show, +}; + +static struct file_operations proc_keys_fops = { + .open = proc_keys_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif + +static int proc_key_users_open(struct inode *inode, struct file *file); +static void *proc_key_users_start(struct seq_file *p, loff_t *_pos); +static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos); +static void proc_key_users_stop(struct seq_file *p, void *v); +static int proc_key_users_show(struct seq_file *m, void *v); + +static struct seq_operations proc_key_users_ops = { + .start = proc_key_users_start, + .next = proc_key_users_next, + .stop = proc_key_users_stop, + .show = proc_key_users_show, +}; + +static struct file_operations proc_key_users_fops = { + .open = proc_key_users_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +/*****************************************************************************/ +/* + * declare the /proc files + */ +static int __init key_proc_init(void) +{ + struct proc_dir_entry *p; + +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS + p = create_proc_entry("keys", 0, NULL); + if (!p) + panic("Cannot create /proc/keys\n"); + + p->proc_fops = &proc_keys_fops; +#endif + + p = create_proc_entry("key-users", 0, NULL); + if (!p) + panic("Cannot create /proc/key-users\n"); + + p->proc_fops = &proc_key_users_fops; + + return 0; + +} /* end key_proc_init() */ + +__initcall(key_proc_init); + +/*****************************************************************************/ +/* + * implement "/proc/keys" to provides a list of the keys on the system + */ +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS + +static int proc_keys_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_keys_ops); + +} + +static void *proc_keys_start(struct seq_file *p, loff_t *_pos) +{ + struct rb_node *_p; + loff_t pos = *_pos; + + spin_lock(&key_serial_lock); + + _p = rb_first(&key_serial_tree); + while (pos > 0 && _p) { + pos--; + _p = rb_next(_p); + } + + return _p; + +} + +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos) +{ + (*_pos)++; + return rb_next((struct rb_node *) v); + +} + +static void proc_keys_stop(struct seq_file *p, void *v) +{ + spin_unlock(&key_serial_lock); +} + +static int proc_keys_show(struct seq_file *m, void *v) +{ + struct rb_node *_p = v; + struct key *key = rb_entry(_p, struct key, serial_node); + struct timespec now; + unsigned long timo; + char xbuf[12]; + + now = current_kernel_time(); + + read_lock(&key->lock); + + /* come up with a suitable timeout value */ + if (key->expiry == 0) { + memcpy(xbuf, "perm", 5); + } + else if (now.tv_sec >= key->expiry) { + memcpy(xbuf, "expd", 5); + } + else { + timo = key->expiry - now.tv_sec; + + if (timo < 60) + sprintf(xbuf, "%lus", timo); + else if (timo < 60*60) + sprintf(xbuf, "%lum", timo / 60); + else if (timo < 60*60*24) + sprintf(xbuf, "%luh", timo / (60*60)); + else if (timo < 60*60*24*7) + sprintf(xbuf, "%lud", timo / (60*60*24)); + else + sprintf(xbuf, "%luw", timo / (60*60*24*7)); + } + + seq_printf(m, "%08x %c%c%c%c%c%c %5d %4s %06x %5d %5d %-9.9s ", + key->serial, + key->flags & KEY_FLAG_INSTANTIATED ? 'I' : '-', + key->flags & KEY_FLAG_REVOKED ? 'R' : '-', + key->flags & KEY_FLAG_DEAD ? 'D' : '-', + key->flags & KEY_FLAG_IN_QUOTA ? 'Q' : '-', + key->flags & KEY_FLAG_USER_CONSTRUCT ? 'U' : '-', + key->flags & KEY_FLAG_NEGATIVE ? 'N' : '-', + atomic_read(&key->usage), + xbuf, + key->perm, + key->uid, + key->gid, + key->type->name); + + if (key->type->describe) + key->type->describe(key, m); + seq_putc(m, '\n'); + + read_unlock(&key->lock); + + return 0; + +} + +#endif /* CONFIG_KEYS_DEBUG_PROC_KEYS */ + +/*****************************************************************************/ +/* + * implement "/proc/key-users" to provides a list of the key users + */ +static int proc_key_users_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_key_users_ops); + +} + +static void *proc_key_users_start(struct seq_file *p, loff_t *_pos) +{ + struct rb_node *_p; + loff_t pos = *_pos; + + spin_lock(&key_user_lock); + + _p = rb_first(&key_user_tree); + while (pos > 0 && _p) { + pos--; + _p = rb_next(_p); + } + + return _p; + +} + +static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos) +{ + (*_pos)++; + return rb_next((struct rb_node *) v); + +} + +static void proc_key_users_stop(struct seq_file *p, void *v) +{ + spin_unlock(&key_user_lock); +} + +static int proc_key_users_show(struct seq_file *m, void *v) +{ + struct rb_node *_p = v; + struct key_user *user = rb_entry(_p, struct key_user, node); + + seq_printf(m, "%5u: %5d %d/%d %d/%d %d/%d\n", + user->uid, + atomic_read(&user->usage), + atomic_read(&user->nkeys), + atomic_read(&user->nikeys), + user->qnkeys, + KEYQUOTA_MAX_KEYS, + user->qnbytes, + KEYQUOTA_MAX_BYTES + ); + + return 0; + +} diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c new file mode 100644 index 000000000000..2eb0e471cd40 --- /dev/null +++ b/security/keys/process_keys.c @@ -0,0 +1,665 @@ +/* process_keys.c: management of a process's keyrings + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/keyctl.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <asm/uaccess.h> +#include "internal.h" + +/* session keyring create vs join semaphore */ +static DECLARE_MUTEX(key_session_sem); + +/* the root user's tracking struct */ +struct key_user root_key_user = { + .usage = ATOMIC_INIT(3), + .consq = LIST_HEAD_INIT(root_key_user.consq), + .lock = SPIN_LOCK_UNLOCKED, + .nkeys = ATOMIC_INIT(2), + .nikeys = ATOMIC_INIT(2), + .uid = 0, +}; + +/* the root user's UID keyring */ +struct key root_user_keyring = { + .usage = ATOMIC_INIT(1), + .serial = 2, + .type = &key_type_keyring, + .user = &root_key_user, + .lock = RW_LOCK_UNLOCKED, + .sem = __RWSEM_INITIALIZER(root_user_keyring.sem), + .perm = KEY_USR_ALL, + .flags = KEY_FLAG_INSTANTIATED, + .description = "_uid.0", +#ifdef KEY_DEBUGGING + .magic = KEY_DEBUG_MAGIC, +#endif +}; + +/* the root user's default session keyring */ +struct key root_session_keyring = { + .usage = ATOMIC_INIT(1), + .serial = 1, + .type = &key_type_keyring, + .user = &root_key_user, + .lock = RW_LOCK_UNLOCKED, + .sem = __RWSEM_INITIALIZER(root_session_keyring.sem), + .perm = KEY_USR_ALL, + .flags = KEY_FLAG_INSTANTIATED, + .description = "_uid_ses.0", +#ifdef KEY_DEBUGGING + .magic = KEY_DEBUG_MAGIC, +#endif +}; + +/*****************************************************************************/ +/* + * allocate the keyrings to be associated with a UID + */ +int alloc_uid_keyring(struct user_struct *user) +{ + struct key *uid_keyring, *session_keyring; + char buf[20]; + int ret; + + /* concoct a default session keyring */ + sprintf(buf, "_uid_ses.%u", user->uid); + + session_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, 0, NULL); + if (IS_ERR(session_keyring)) { + ret = PTR_ERR(session_keyring); + goto error; + } + + /* and a UID specific keyring, pointed to by the default session + * keyring */ + sprintf(buf, "_uid.%u", user->uid); + + uid_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, 0, + session_keyring); + if (IS_ERR(uid_keyring)) { + key_put(session_keyring); + ret = PTR_ERR(uid_keyring); + goto error; + } + + /* install the keyrings */ + user->uid_keyring = uid_keyring; + user->session_keyring = session_keyring; + ret = 0; + + error: + return ret; + +} /* end alloc_uid_keyring() */ + +/*****************************************************************************/ +/* + * deal with the UID changing + */ +void switch_uid_keyring(struct user_struct *new_user) +{ +#if 0 /* do nothing for now */ + struct key *old; + + /* switch to the new user's session keyring if we were running under + * root's default session keyring */ + if (new_user->uid != 0 && + current->session_keyring == &root_session_keyring + ) { + atomic_inc(&new_user->session_keyring->usage); + + task_lock(current); + old = current->session_keyring; + current->session_keyring = new_user->session_keyring; + task_unlock(current); + + key_put(old); + } +#endif + +} /* end switch_uid_keyring() */ + +/*****************************************************************************/ +/* + * install a fresh thread keyring, discarding the old one + */ +int install_thread_keyring(struct task_struct *tsk) +{ + struct key *keyring, *old; + char buf[20]; + int ret; + + sprintf(buf, "_tid.%u", tsk->pid); + + keyring = keyring_alloc(buf, tsk->uid, tsk->gid, 1, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + task_lock(tsk); + old = tsk->thread_keyring; + tsk->thread_keyring = keyring; + task_unlock(tsk); + + ret = 0; + + key_put(old); + error: + return ret; + +} /* end install_thread_keyring() */ + +/*****************************************************************************/ +/* + * make sure a process keyring is installed + */ +static int install_process_keyring(struct task_struct *tsk) +{ + unsigned long flags; + struct key *keyring; + char buf[20]; + int ret; + + if (!tsk->signal->process_keyring) { + sprintf(buf, "_pid.%u", tsk->tgid); + + keyring = keyring_alloc(buf, tsk->uid, tsk->gid, 1, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + /* attach or swap keyrings */ + spin_lock_irqsave(&tsk->sighand->siglock, flags); + if (!tsk->signal->process_keyring) { + tsk->signal->process_keyring = keyring; + keyring = NULL; + } + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + + key_put(keyring); + } + + ret = 0; + error: + return ret; + +} /* end install_process_keyring() */ + +/*****************************************************************************/ +/* + * install a session keyring, discarding the old one + * - if a keyring is not supplied, an empty one is invented + */ +static int install_session_keyring(struct task_struct *tsk, + struct key *keyring) +{ + unsigned long flags; + struct key *old; + char buf[20]; + int ret; + + /* create an empty session keyring */ + if (!keyring) { + sprintf(buf, "_ses.%u", tsk->tgid); + + keyring = keyring_alloc(buf, tsk->uid, tsk->gid, 1, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + } + else { + atomic_inc(&keyring->usage); + } + + /* install the keyring */ + spin_lock_irqsave(&tsk->sighand->siglock, flags); + old = tsk->signal->session_keyring; + tsk->signal->session_keyring = keyring; + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + + ret = 0; + + key_put(old); + error: + return ret; + +} /* end install_session_keyring() */ + +/*****************************************************************************/ +/* + * copy the keys in a thread group for fork without CLONE_THREAD + */ +int copy_thread_group_keys(struct task_struct *tsk) +{ + unsigned long flags; + + key_check(current->thread_group->session_keyring); + key_check(current->thread_group->process_keyring); + + /* no process keyring yet */ + tsk->signal->process_keyring = NULL; + + /* same session keyring */ + spin_lock_irqsave(¤t->sighand->siglock, flags); + tsk->signal->session_keyring = + key_get(current->signal->session_keyring); + spin_unlock_irqrestore(¤t->sighand->siglock, flags); + + return 0; + +} /* end copy_thread_group_keys() */ + +/*****************************************************************************/ +/* + * copy the keys for fork + */ +int copy_keys(unsigned long clone_flags, struct task_struct *tsk) +{ + key_check(tsk->thread_keyring); + + /* no thread keyring yet */ + tsk->thread_keyring = NULL; + return 0; + +} /* end copy_keys() */ + +/*****************************************************************************/ +/* + * dispose of thread group keys upon thread group destruction + */ +void exit_thread_group_keys(struct signal_struct *tg) +{ + key_put(tg->session_keyring); + key_put(tg->process_keyring); + +} /* end exit_thread_group_keys() */ + +/*****************************************************************************/ +/* + * dispose of keys upon thread exit + */ +void exit_keys(struct task_struct *tsk) +{ + key_put(tsk->thread_keyring); + +} /* end exit_keys() */ + +/*****************************************************************************/ +/* + * deal with execve() + */ +int exec_keys(struct task_struct *tsk) +{ + unsigned long flags; + struct key *old; + + /* newly exec'd tasks don't get a thread keyring */ + task_lock(tsk); + old = tsk->thread_keyring; + tsk->thread_keyring = NULL; + task_unlock(tsk); + + key_put(old); + + /* discard the process keyring from a newly exec'd task */ + spin_lock_irqsave(&tsk->sighand->siglock, flags); + old = tsk->signal->process_keyring; + tsk->signal->process_keyring = NULL; + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + + key_put(old); + + return 0; + +} /* end exec_keys() */ + +/*****************************************************************************/ +/* + * deal with SUID programs + * - we might want to make this invent a new session keyring + */ +int suid_keys(struct task_struct *tsk) +{ + return 0; + +} /* end suid_keys() */ + +/*****************************************************************************/ +/* + * the filesystem user ID changed + */ +void key_fsuid_changed(struct task_struct *tsk) +{ + /* update the ownership of the thread keyring */ + if (tsk->thread_keyring) { + down_write(&tsk->thread_keyring->sem); + write_lock(&tsk->thread_keyring->lock); + tsk->thread_keyring->uid = tsk->fsuid; + write_unlock(&tsk->thread_keyring->lock); + up_write(&tsk->thread_keyring->sem); + } + +} /* end key_fsuid_changed() */ + +/*****************************************************************************/ +/* + * the filesystem group ID changed + */ +void key_fsgid_changed(struct task_struct *tsk) +{ + /* update the ownership of the thread keyring */ + if (tsk->thread_keyring) { + down_write(&tsk->thread_keyring->sem); + write_lock(&tsk->thread_keyring->lock); + tsk->thread_keyring->gid = tsk->fsgid; + write_unlock(&tsk->thread_keyring->lock); + up_write(&tsk->thread_keyring->sem); + } + +} /* end key_fsgid_changed() */ + +/*****************************************************************************/ +/* + * search the process keyrings for the first matching key + * - we use the supplied match function to see if the description (or other + * feature of interest) matches + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we found only negative matching keys + */ +struct key *search_process_keyrings_aux(struct key_type *type, + const void *description, + key_match_func_t match) +{ + struct task_struct *tsk = current; + unsigned long flags; + struct key *key, *ret, *err, *tmp; + + /* we want to return -EAGAIN or -ENOKEY if any of the keyrings were + * searchable, but we failed to find a key or we found a negative key; + * otherwise we want to return a sample error (probably -EACCES) if + * none of the keyrings were searchable + * + * in terms of priority: success > -ENOKEY > -EAGAIN > other error + */ + key = NULL; + ret = NULL; + err = ERR_PTR(-EAGAIN); + + /* search the thread keyring first */ + if (tsk->thread_keyring) { + key = keyring_search_aux(tsk->thread_keyring, type, + description, match); + if (!IS_ERR(key)) + goto found; + + switch (PTR_ERR(key)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key; + break; + default: + err = key; + break; + } + } + + /* search the process keyring second */ + if (tsk->signal->process_keyring) { + key = keyring_search_aux(tsk->signal->process_keyring, + type, description, match); + if (!IS_ERR(key)) + goto found; + + switch (PTR_ERR(key)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key; + break; + default: + err = key; + break; + } + } + + /* search the session keyring last */ + spin_lock_irqsave(&tsk->sighand->siglock, flags); + + tmp = tsk->signal->session_keyring; + if (!tmp) + tmp = tsk->user->session_keyring; + atomic_inc(&tmp->usage); + + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + + key = keyring_search_aux(tmp, type, description, match); + key_put(tmp); + if (!IS_ERR(key)) + goto found; + + switch (PTR_ERR(key)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key; + break; + default: + err = key; + break; + } + + /* no key - decide on the error we're going to go for */ + key = ret ? ret : err; + + found: + return key; + +} /* end search_process_keyrings_aux() */ + +/*****************************************************************************/ +/* + * search the process keyrings for the first matching key + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we found only negative matching keys + */ +struct key *search_process_keyrings(struct key_type *type, + const char *description) +{ + return search_process_keyrings_aux(type, description, type->match); + +} /* end search_process_keyrings() */ + +/*****************************************************************************/ +/* + * lookup a key given a key ID from userspace with a given permissions mask + * - don't create special keyrings unless so requested + * - partially constructed keys aren't found unless requested + */ +struct key *lookup_user_key(key_serial_t id, int create, int partial, + key_perm_t perm) +{ + struct task_struct *tsk = current; + unsigned long flags; + struct key *key; + int ret; + + key = ERR_PTR(-ENOKEY); + + switch (id) { + case KEY_SPEC_THREAD_KEYRING: + if (!tsk->thread_keyring) { + if (!create) + goto error; + + ret = install_thread_keyring(tsk); + if (ret < 0) { + key = ERR_PTR(ret); + goto error; + } + } + + key = tsk->thread_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_PROCESS_KEYRING: + if (!tsk->signal->process_keyring) { + if (!create) + goto error; + + ret = install_process_keyring(tsk); + if (ret < 0) { + key = ERR_PTR(ret); + goto error; + } + } + + key = tsk->signal->process_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_SESSION_KEYRING: + if (!tsk->signal->session_keyring) { + /* always install a session keyring upon access if one + * doesn't exist yet */ + ret = install_session_keyring( + tsk, tsk->user->session_keyring); + if (ret < 0) + goto error; + } + + spin_lock_irqsave(&tsk->sighand->siglock, flags); + key = tsk->signal->session_keyring; + atomic_inc(&key->usage); + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + break; + + case KEY_SPEC_USER_KEYRING: + key = tsk->user->uid_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_USER_SESSION_KEYRING: + key = tsk->user->session_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_GROUP_KEYRING: + /* group keyrings are not yet supported */ + key = ERR_PTR(-EINVAL); + goto error; + + default: + key = ERR_PTR(-EINVAL); + if (id < 1) + goto error; + + key = key_lookup(id); + if (IS_ERR(key)) + goto error; + break; + } + + /* check the status and permissions */ + if (perm) { + ret = key_validate(key); + if (ret < 0) + goto invalid_key; + } + + ret = -EIO; + if (!partial && !(key->flags & KEY_FLAG_INSTANTIATED)) + goto invalid_key; + + ret = -EACCES; + if (!key_permission(key, perm)) + goto invalid_key; + + error: + return key; + + invalid_key: + key_put(key); + key = ERR_PTR(ret); + goto error; + +} /* end lookup_user_key() */ + +/*****************************************************************************/ +/* + * join the named keyring as the session keyring if possible, or attempt to + * create a new one of that name if not + * - if the name is NULL, an empty anonymous keyring is installed instead + * - named session keyring joining is done with a semaphore held + */ +long join_session_keyring(const char *name) +{ + struct task_struct *tsk = current; + unsigned long flags; + struct key *keyring; + long ret; + + /* if no name is provided, install an anonymous keyring */ + if (!name) { + ret = install_session_keyring(tsk, NULL); + if (ret < 0) + goto error; + + spin_lock_irqsave(&tsk->sighand->siglock, flags); + ret = tsk->signal->session_keyring->serial; + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + goto error; + } + + /* allow the user to join or create a named keyring */ + down(&key_session_sem); + + /* look for an existing keyring of this name */ + keyring = find_keyring_by_name(name, 0); + if (PTR_ERR(keyring) == -ENOKEY) { + /* not found - try and create a new one */ + keyring = keyring_alloc(name, tsk->uid, tsk->gid, 0, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + } + else if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + + /* we've got a keyring - now to install it */ + ret = install_session_keyring(tsk, keyring); + if (ret < 0) + goto error2; + + ret = keyring->serial; + key_put(keyring); + + error2: + up(&key_session_sem); + error: + return ret; + +} /* end join_session_keyring() */ diff --git a/security/keys/request_key.c b/security/keys/request_key.c new file mode 100644 index 000000000000..9705b1aeba5d --- /dev/null +++ b/security/keys/request_key.c @@ -0,0 +1,359 @@ +/* request_key.c: request a key from userspace + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/kmod.h> +#include <linux/err.h> +#include "internal.h" + +struct key_construction { + struct list_head link; /* link in construction queue */ + struct key *key; /* key being constructed */ +}; + +/* when waiting for someone else's keys, you get added to this */ +DECLARE_WAIT_QUEUE_HEAD(request_key_conswq); + +/*****************************************************************************/ +/* + * request userspace finish the construction of a key + * - execute "/sbin/request-key <op> <key> <uid> <gid> <keyring> <keyring> <keyring> <info>" + * - if callout_info is an empty string, it'll be rendered as a "-" instead + */ +static int call_request_key(struct key *key, + const char *op, + const char *callout_info) +{ + struct task_struct *tsk = current; + unsigned long flags; + key_serial_t prkey, sskey; + char *argv[10], *envp[3], uid_str[12], gid_str[12]; + char key_str[12], keyring_str[3][12]; + int i; + + /* record the UID and GID */ + sprintf(uid_str, "%d", current->fsuid); + sprintf(gid_str, "%d", current->fsgid); + + /* we say which key is under construction */ + sprintf(key_str, "%d", key->serial); + + /* we specify the process's default keyrings */ + sprintf(keyring_str[0], "%d", + tsk->thread_keyring ? tsk->thread_keyring->serial : 0); + + prkey = 0; + if (tsk->signal->process_keyring) + prkey = tsk->signal->process_keyring->serial; + + sskey = 0; + spin_lock_irqsave(&tsk->sighand->siglock, flags); + if (tsk->signal->session_keyring) + sskey = tsk->signal->session_keyring->serial; + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + + + if (!sskey) + sskey = tsk->user->session_keyring->serial; + + sprintf(keyring_str[1], "%d", prkey); + sprintf(keyring_str[2], "%d", sskey); + + /* set up a minimal environment */ + i = 0; + envp[i++] = "HOME=/"; + envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + envp[i] = NULL; + + /* set up the argument list */ + i = 0; + argv[i++] = "/sbin/request-key"; + argv[i++] = (char *) op; + argv[i++] = key_str; + argv[i++] = uid_str; + argv[i++] = gid_str; + argv[i++] = keyring_str[0]; + argv[i++] = keyring_str[1]; + argv[i++] = keyring_str[2]; + argv[i++] = callout_info[0] ? (char *) callout_info : "-"; + argv[i] = NULL; + + /* do it */ + return call_usermodehelper(argv[0], argv, envp, 1); + +} /* end call_request_key() */ + +/*****************************************************************************/ +/* + * call out to userspace for the key + * - called with the construction sem held, but the sem is dropped here + * - we ignore program failure and go on key status instead + */ +static struct key *__request_key_construction(struct key_type *type, + const char *description, + const char *callout_info) +{ + struct key_construction cons; + struct timespec now; + struct key *key; + int ret, negative; + + /* create a key and add it to the queue */ + key = key_alloc(type, description, + current->fsuid, current->fsgid, KEY_USR_ALL, 0); + if (IS_ERR(key)) + goto alloc_failed; + + write_lock(&key->lock); + key->flags |= KEY_FLAG_USER_CONSTRUCT; + write_unlock(&key->lock); + + cons.key = key; + list_add_tail(&cons.link, &key->user->consq); + + /* we drop the construction sem here on behalf of the caller */ + up_write(&key_construction_sem); + + /* make the call */ + ret = call_request_key(key, "create", callout_info); + if (ret < 0) + goto request_failed; + + /* if the key wasn't instantiated, then we want to give an error */ + ret = -ENOKEY; + if (!(key->flags & KEY_FLAG_INSTANTIATED)) + goto request_failed; + + down_write(&key_construction_sem); + list_del(&cons.link); + up_write(&key_construction_sem); + + /* also give an error if the key was negatively instantiated */ + check_not_negative: + if (key->flags & KEY_FLAG_NEGATIVE) { + key_put(key); + key = ERR_PTR(-ENOKEY); + } + + out: + return key; + + request_failed: + /* it wasn't instantiated + * - remove from construction queue + * - mark the key as dead + */ + negative = 0; + down_write(&key_construction_sem); + + list_del(&cons.link); + + write_lock(&key->lock); + key->flags &= ~KEY_FLAG_USER_CONSTRUCT; + + /* check it didn't get instantiated between the check and the down */ + if (!(key->flags & KEY_FLAG_INSTANTIATED)) { + key->flags |= KEY_FLAG_INSTANTIATED | KEY_FLAG_NEGATIVE; + negative = 1; + } + + write_unlock(&key->lock); + up_write(&key_construction_sem); + + if (!negative) + goto check_not_negative; /* surprisingly, the key got + * instantiated */ + + /* set the timeout and store in the session keyring if we can */ + now = current_kernel_time(); + key->expiry = now.tv_sec + key_negative_timeout; + + if (current->signal->session_keyring) { + unsigned long flags; + struct key *keyring; + + spin_lock_irqsave(¤t->sighand->siglock, flags); + keyring = current->signal->session_keyring; + atomic_inc(&keyring->usage); + spin_unlock_irqrestore(¤t->sighand->siglock, flags); + + key_link(keyring, key); + key_put(keyring); + } + + key_put(key); + + /* notify anyone who was waiting */ + wake_up_all(&request_key_conswq); + + key = ERR_PTR(ret); + goto out; + + alloc_failed: + up_write(&key_construction_sem); + goto out; + +} /* end __request_key_construction() */ + +/*****************************************************************************/ +/* + * call out to userspace to request the key + * - we check the construction queue first to see if an appropriate key is + * already being constructed by userspace + */ +static struct key *request_key_construction(struct key_type *type, + const char *description, + struct key_user *user, + const char *callout_info) +{ + struct key_construction *pcons; + struct key *key, *ckey; + + DECLARE_WAITQUEUE(myself, current); + + /* see if there's such a key under construction already */ + down_write(&key_construction_sem); + + list_for_each_entry(pcons, &user->consq, link) { + ckey = pcons->key; + + if (ckey->type != type) + continue; + + if (type->match(ckey, description)) + goto found_key_under_construction; + } + + /* see about getting userspace to construct the key */ + key = __request_key_construction(type, description, callout_info); + error: + return key; + + /* someone else has the same key under construction + * - we want to keep an eye on their key + */ + found_key_under_construction: + atomic_inc(&ckey->usage); + up_write(&key_construction_sem); + + /* wait for the key to be completed one way or another */ + add_wait_queue(&request_key_conswq, &myself); + + for (;;) { + set_current_state(TASK_UNINTERRUPTIBLE); + if (!(ckey->flags & KEY_FLAG_USER_CONSTRUCT)) + break; + schedule(); + } + + set_current_state(TASK_RUNNING); + remove_wait_queue(&request_key_conswq, &myself); + + /* we'll need to search this process's keyrings to see if the key is + * now there since we can't automatically assume it's also available + * there */ + key_put(ckey); + ckey = NULL; + + key = NULL; /* request a retry */ + goto error; + +} /* end request_key_construction() */ + +/*****************************************************************************/ +/* + * request a key + * - search the process's keyrings + * - check the list of keys being created or updated + * - call out to userspace for a key if requested (supplementary info can be + * passed) + */ +struct key *request_key(struct key_type *type, + const char *description, + const char *callout_info) +{ + struct key_user *user; + struct key *key; + + /* search all the process keyrings for a key */ + key = search_process_keyrings_aux(type, description, type->match); + + if (PTR_ERR(key) == -EAGAIN) { + /* the search failed, but the keyrings were searchable, so we + * should consult userspace if we can */ + key = ERR_PTR(-ENOKEY); + if (!callout_info) + goto error; + + /* - get hold of the user's construction queue */ + user = key_user_lookup(current->fsuid); + if (!user) { + key = ERR_PTR(-ENOMEM); + goto error; + } + + for (;;) { + /* ask userspace (returns NULL if it waited on a key + * being constructed) */ + key = request_key_construction(type, description, + user, callout_info); + if (key) + break; + + /* someone else made the key we want, so we need to + * search again as it might now be available to us */ + key = search_process_keyrings_aux(type, description, + type->match); + if (PTR_ERR(key) != -EAGAIN) + break; + } + + key_user_put(user); + } + + error: + return key; + +} /* end request_key() */ + +EXPORT_SYMBOL(request_key); + +/*****************************************************************************/ +/* + * validate a key + */ +int key_validate(struct key *key) +{ + struct timespec now; + int ret = 0; + + if (key) { + /* check it's still accessible */ + ret = -EKEYREVOKED; + if (key->flags & (KEY_FLAG_REVOKED | KEY_FLAG_DEAD)) + goto error; + + /* check it hasn't expired */ + ret = 0; + if (key->expiry) { + now = current_kernel_time(); + if (now.tv_sec >= key->expiry) + ret = -EKEYEXPIRED; + } + } + + error: + return ret; + +} /* end key_validate() */ + +EXPORT_SYMBOL(key_validate); diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c new file mode 100644 index 000000000000..8d65b3a28129 --- /dev/null +++ b/security/keys/user_defined.c @@ -0,0 +1,191 @@ +/* user_defined.c: user defined key type + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <asm/uaccess.h> +#include "internal.h" + +static int user_instantiate(struct key *key, const void *data, size_t datalen); +static int user_duplicate(struct key *key, const struct key *source); +static int user_update(struct key *key, const void *data, size_t datalen); +static int user_match(const struct key *key, const void *criterion); +static void user_destroy(struct key *key); +static void user_describe(const struct key *user, struct seq_file *m); +static long user_read(const struct key *key, + char __user *buffer, size_t buflen); + +/* + * user defined keys take an arbitrary string as the description and an + * arbitrary blob of data as the payload + */ +struct key_type key_type_user = { + .name = "user", + .instantiate = user_instantiate, + .duplicate = user_duplicate, + .update = user_update, + .match = user_match, + .destroy = user_destroy, + .describe = user_describe, + .read = user_read, +}; + +/*****************************************************************************/ +/* + * instantiate a user defined key + */ +static int user_instantiate(struct key *key, const void *data, size_t datalen) +{ + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + ret = key_payload_reserve(key, datalen); + if (ret < 0) + goto error; + + /* attach the data */ + ret = -ENOMEM; + key->payload.data = kmalloc(datalen, GFP_KERNEL); + if (!key->payload.data) + goto error; + + memcpy(key->payload.data, data, datalen); + ret = 0; + + error: + return ret; + +} /* end user_instantiate() */ + +/*****************************************************************************/ +/* + * duplicate a user defined key + */ +static int user_duplicate(struct key *key, const struct key *source) +{ + int ret; + + /* just copy the payload */ + ret = -ENOMEM; + key->payload.data = kmalloc(source->datalen, GFP_KERNEL); + + if (key->payload.data) { + key->datalen = source->datalen; + memcpy(key->payload.data, source->payload.data, source->datalen); + ret = 0; + } + + return ret; + +} /* end user_duplicate() */ + +/*****************************************************************************/ +/* + * update a user defined key + */ +static int user_update(struct key *key, const void *data, size_t datalen) +{ + void *new, *zap; + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + /* copy the data */ + ret = -ENOMEM; + new = kmalloc(datalen, GFP_KERNEL); + if (!new) + goto error; + + memcpy(new, data, datalen); + + /* check the quota and attach the new data */ + zap = new; + write_lock(&key->lock); + + ret = key_payload_reserve(key, datalen); + + if (ret == 0) { + /* attach the new data, displacing the old */ + zap = key->payload.data; + key->payload.data = new; + key->expiry = 0; + } + + write_unlock(&key->lock); + kfree(zap); + + error: + return ret; + +} /* end user_update() */ + +/*****************************************************************************/ +/* + * match users on their name + */ +static int user_match(const struct key *key, const void *description) +{ + return strcmp(key->description, description) == 0; + +} /* end user_match() */ + +/*****************************************************************************/ +/* + * dispose of the data dangling from the corpse of a user + */ +static void user_destroy(struct key *key) +{ + kfree(key->payload.data); + +} /* end user_destroy() */ + +/*****************************************************************************/ +/* + * describe the user + */ +static void user_describe(const struct key *key, struct seq_file *m) +{ + seq_puts(m, key->description); + + seq_printf(m, ": %u", key->datalen); + +} /* end user_describe() */ + +/*****************************************************************************/ +/* + * read the key data + */ +static long user_read(const struct key *key, + char __user *buffer, size_t buflen) +{ + long ret = key->datalen; + + /* we can return the data as is */ + if (buffer && buflen > 0) { + if (buflen > key->datalen) + buflen = key->datalen; + + if (copy_to_user(buffer, key->payload.data, buflen) != 0) + ret = -EFAULT; + } + + return ret; + +} /* end user_read() */ diff --git a/security/root_plug.c b/security/root_plug.c new file mode 100644 index 000000000000..07651def2f78 --- /dev/null +++ b/security/root_plug.c @@ -0,0 +1,142 @@ +/* + * Root Plug sample LSM module + * + * Originally written for a Linux Journal. + * + * Copyright (C) 2002 Greg Kroah-Hartman <greg@kroah.com> + * + * Prevents any programs running with egid == 0 if a specific USB device + * is not present in the system. Yes, it can be gotten around, but is a + * nice starting point for people to play with, and learn the LSM + * interface. + * + * If you want to turn this into something with a semblance of security, + * you need to hook the task_* functions also. + * + * See http://www.linuxjournal.com/article.php?sid=6279 for more information + * about this code. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/security.h> +#include <linux/usb.h> + +/* flag to keep track of how we were registered */ +static int secondary; + +/* default is a generic type of usb to serial converter */ +static int vendor_id = 0x0557; +static int product_id = 0x2008; + +module_param(vendor_id, uint, 0400); +MODULE_PARM_DESC(vendor_id, "USB Vendor ID of device to look for"); + +module_param(product_id, uint, 0400); +MODULE_PARM_DESC(product_id, "USB Product ID of device to look for"); + +/* should we print out debug messages */ +static int debug = 0; + +module_param(debug, bool, 0600); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +#if defined(CONFIG_SECURITY_ROOTPLUG_MODULE) +#define MY_NAME THIS_MODULE->name +#else +#define MY_NAME "root_plug" +#endif + +#define root_dbg(fmt, arg...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "%s: %s: " fmt , \ + MY_NAME , __FUNCTION__ , \ + ## arg); \ + } while (0) + +static int rootplug_bprm_check_security (struct linux_binprm *bprm) +{ + struct usb_device *dev; + + root_dbg("file %s, e_uid = %d, e_gid = %d\n", + bprm->filename, bprm->e_uid, bprm->e_gid); + + if (bprm->e_gid == 0) { + dev = usb_find_device(vendor_id, product_id); + if (!dev) { + root_dbg("e_gid = 0, and device not found, " + "task not allowed to run...\n"); + return -EPERM; + } + usb_put_dev(dev); + } + + return 0; +} + +static struct security_operations rootplug_security_ops = { + /* Use the capability functions for some of the hooks */ + .ptrace = cap_ptrace, + .capget = cap_capget, + .capset_check = cap_capset_check, + .capset_set = cap_capset_set, + .capable = cap_capable, + + .bprm_apply_creds = cap_bprm_apply_creds, + .bprm_set_security = cap_bprm_set_security, + + .task_post_setuid = cap_task_post_setuid, + .task_reparent_to_init = cap_task_reparent_to_init, + + .bprm_check_security = rootplug_bprm_check_security, +}; + +static int __init rootplug_init (void) +{ + /* register ourselves with the security framework */ + if (register_security (&rootplug_security_ops)) { + printk (KERN_INFO + "Failure registering Root Plug module with the kernel\n"); + /* try registering with primary module */ + if (mod_reg_security (MY_NAME, &rootplug_security_ops)) { + printk (KERN_INFO "Failure registering Root Plug " + " module with primary security module.\n"); + return -EINVAL; + } + secondary = 1; + } + printk (KERN_INFO "Root Plug module initialized, " + "vendor_id = %4.4x, product id = %4.4x\n", vendor_id, product_id); + return 0; +} + +static void __exit rootplug_exit (void) +{ + /* remove ourselves from the security framework */ + if (secondary) { + if (mod_unreg_security (MY_NAME, &rootplug_security_ops)) + printk (KERN_INFO "Failure unregistering Root Plug " + " module with primary module.\n"); + } else { + if (unregister_security (&rootplug_security_ops)) { + printk (KERN_INFO "Failure unregistering Root Plug " + "module with the kernel\n"); + } + } + printk (KERN_INFO "Root Plug module removed\n"); +} + +security_initcall (rootplug_init); +module_exit (rootplug_exit); + +MODULE_DESCRIPTION("Root Plug sample LSM module, written for Linux Journal article"); +MODULE_LICENSE("GPL"); + diff --git a/security/seclvl.c b/security/seclvl.c new file mode 100644 index 000000000000..8a0ab0d7949e --- /dev/null +++ b/security/seclvl.c @@ -0,0 +1,747 @@ +/** + * BSD Secure Levels LSM + * + * Maintainers: + * Michael A. Halcrow <mike@halcrow.us> + * Serge Hallyn <hallyn@cs.wm.edu> + * + * Copyright (c) 2001 WireX Communications, Inc <chris@wirex.com> + * Copyright (c) 2001 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (c) 2002 International Business Machines <robb@austin.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/security.h> +#include <linux/netlink.h> +#include <linux/fs.h> +#include <linux/namei.h> +#include <linux/mount.h> +#include <linux/capability.h> +#include <linux/time.h> +#include <linux/proc_fs.h> +#include <linux/kobject.h> +#include <linux/crypto.h> +#include <asm/scatterlist.h> +#include <linux/gfp.h> +#include <linux/sysfs.h> + +#define SHA1_DIGEST_SIZE 20 + +/** + * Module parameter that defines the initial secure level. + * + * When built as a module, it defaults to seclvl 1, which is the + * behavior of BSD secure levels. Note that this default behavior + * wrecks havoc on a machine when the seclvl module is compiled into + * the kernel. In that case, we default to seclvl 0. + */ +#ifdef CONFIG_SECURITY_SECLVL_MODULE +static int initlvl = 1; +#else +static int initlvl; +#endif +module_param(initlvl, int, 0); +MODULE_PARM_DESC(initlvl, "Initial secure level (defaults to 1)"); + +/* Module parameter that defines the verbosity level */ +static int verbosity; +module_param(verbosity, int, 0); +MODULE_PARM_DESC(verbosity, "Initial verbosity level (0 or 1; defaults to " + "0, which is Quiet)"); + +/** + * Optional password which can be passed in to bring seclvl to 0 + * (i.e., for halt/reboot). Defaults to NULL (the passwd attribute + * file will not be registered in sysfs). + * + * This gets converted to its SHA1 hash when stored. It's probably + * not a good idea to use this parameter when loading seclvl from a + * script; use sha1_passwd instead. + */ + +#define MAX_PASSWD_SIZE 32 +static char passwd[MAX_PASSWD_SIZE]; +module_param_string(passwd, passwd, sizeof(passwd), 0); +MODULE_PARM_DESC(passwd, + "Plaintext of password that sets seclvl=0 when written to " + "(sysfs mount point)/seclvl/passwd\n"); + +/** + * SHA1 hashed version of the optional password which can be passed in + * to bring seclvl to 0 (i.e., for halt/reboot). Must be in + * hexadecimal format (40 characters). Defaults to NULL (the passwd + * attribute file will not be registered in sysfs). + * + * Use the sha1sum utility to generate the SHA1 hash of a password: + * + * echo -n "secret" | sha1sum + */ +#define MAX_SHA1_PASSWD 41 +static char sha1_passwd[MAX_SHA1_PASSWD]; +module_param_string(sha1_passwd, sha1_passwd, sizeof(sha1_passwd), 0); +MODULE_PARM_DESC(sha1_passwd, + "SHA1 hash (40 hexadecimal characters) of password that " + "sets seclvl=0 when plaintext password is written to " + "(sysfs mount point)/seclvl/passwd\n"); + +static int hideHash = 1; +module_param(hideHash, int, 0); +MODULE_PARM_DESC(hideHash, "When set to 0, reading seclvl/passwd from sysfs " + "will return the SHA1-hashed value of the password that " + "lowers the secure level to 0.\n"); + +#define MY_NAME "seclvl" + +/** + * This time-limits log writes to one per second. + */ +#define seclvl_printk(verb, type, fmt, arg...) \ + do { \ + if (verbosity >= verb) { \ + static unsigned long _prior; \ + unsigned long _now = jiffies; \ + if ((_now - _prior) > HZ) { \ + printk(type "%s: %s: " fmt, \ + MY_NAME, __FUNCTION__ , \ + ## arg); \ + _prior = _now; \ + } \ + } \ + } while (0) + +/** + * kobject stuff + */ + +struct subsystem seclvl_subsys; + +struct seclvl_obj { + char *name; + struct list_head slot_list; + struct kobject kobj; +}; + +/** + * There is a seclvl_attribute struct for each file in sysfs. + * + * In our case, we have one of these structs for "passwd" and another + * for "seclvl". + */ +struct seclvl_attribute { + struct attribute attr; + ssize_t(*show) (struct seclvl_obj *, char *); + ssize_t(*store) (struct seclvl_obj *, const char *, size_t); +}; + +/** + * When this function is called, one of the files in sysfs is being + * written to. attribute->store is a function pointer to whatever the + * struct seclvl_attribute store function pointer points to. It is + * unique for "passwd" and "seclvl". + */ +static ssize_t +seclvl_attr_store(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t len) +{ + struct seclvl_obj *obj = container_of(kobj, struct seclvl_obj, kobj); + struct seclvl_attribute *attribute = + container_of(attr, struct seclvl_attribute, attr); + return (attribute->store ? attribute->store(obj, buf, len) : 0); +} + +static ssize_t +seclvl_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) +{ + struct seclvl_obj *obj = container_of(kobj, struct seclvl_obj, kobj); + struct seclvl_attribute *attribute = + container_of(attr, struct seclvl_attribute, attr); + return (attribute->show ? attribute->show(obj, buf) : 0); +} + +/** + * Callback function pointers for show and store + */ +static struct sysfs_ops seclvlfs_sysfs_ops = { + .show = seclvl_attr_show, + .store = seclvl_attr_store, +}; + +static struct kobj_type seclvl_ktype = { + .sysfs_ops = &seclvlfs_sysfs_ops +}; + +decl_subsys(seclvl, &seclvl_ktype, NULL); + +/** + * The actual security level. Ranges between -1 and 2 inclusive. + */ +static int seclvl; + +/** + * flag to keep track of how we were registered + */ +static int secondary; + +/** + * Verifies that the requested secure level is valid, given the current + * secure level. + */ +static int seclvl_sanity(int reqlvl) +{ + if ((reqlvl < -1) || (reqlvl > 2)) { + seclvl_printk(1, KERN_WARNING, "Attempt to set seclvl out of " + "range: [%d]\n", reqlvl); + return -EINVAL; + } + if ((seclvl == 0) && (reqlvl == -1)) + return 0; + if (reqlvl < seclvl) { + seclvl_printk(1, KERN_WARNING, "Attempt to lower seclvl to " + "[%d]\n", reqlvl); + return -EPERM; + } + return 0; +} + +/** + * Called whenever the user reads the sysfs handle to this kernel + * object + */ +static ssize_t seclvl_read_file(struct seclvl_obj *obj, char *buff) +{ + return snprintf(buff, PAGE_SIZE, "%d\n", seclvl); +} + +/** + * security level advancement rules: + * Valid levels are -1 through 2, inclusive. + * From -1, stuck. [ in case compiled into kernel ] + * From 0 or above, can only increment. + */ +static int do_seclvl_advance(int newlvl) +{ + if (newlvl <= seclvl) { + seclvl_printk(1, KERN_WARNING, "Cannot advance to seclvl " + "[%d]\n", newlvl); + return -EINVAL; + } + if (newlvl > 2) { + seclvl_printk(1, KERN_WARNING, "Cannot advance to seclvl " + "[%d]\n", newlvl); + return -EINVAL; + } + if (seclvl == -1) { + seclvl_printk(1, KERN_WARNING, "Not allowed to advance to " + "seclvl [%d]\n", seclvl); + return -EPERM; + } + seclvl = newlvl; + return 0; +} + +/** + * Called whenever the user writes to the sysfs handle to this kernel + * object (seclvl/seclvl). It expects a single-digit number. + */ +static ssize_t +seclvl_write_file(struct seclvl_obj *obj, const char *buff, size_t count) +{ + unsigned long val; + if (count > 2 || (count == 2 && buff[1] != '\n')) { + seclvl_printk(1, KERN_WARNING, "Invalid value passed to " + "seclvl: [%s]\n", buff); + return -EINVAL; + } + val = buff[0] - 48; + if (seclvl_sanity(val)) { + seclvl_printk(1, KERN_WARNING, "Illegal secure level " + "requested: [%d]\n", (int)val); + return -EPERM; + } + if (do_seclvl_advance(val)) { + seclvl_printk(0, KERN_ERR, "Failure advancing security level " + "to %lu\n", val); + } + return count; +} + +/* Generate sysfs_attr_seclvl */ +static struct seclvl_attribute sysfs_attr_seclvl = +__ATTR(seclvl, (S_IFREG | S_IRUGO | S_IWUSR), seclvl_read_file, + seclvl_write_file); + +static unsigned char hashedPassword[SHA1_DIGEST_SIZE]; + +/** + * Called whenever the user reads the sysfs passwd handle. + */ +static ssize_t seclvl_read_passwd(struct seclvl_obj *obj, char *buff) +{ + /* So just how good *is* your password? :-) */ + char tmp[3]; + int i = 0; + buff[0] = '\0'; + if (hideHash) { + /* Security through obscurity */ + return 0; + } + while (i < SHA1_DIGEST_SIZE) { + snprintf(tmp, 3, "%02x", hashedPassword[i]); + strncat(buff, tmp, 2); + i++; + } + strcat(buff, "\n"); + return ((SHA1_DIGEST_SIZE * 2) + 1); +} + +/** + * Converts a block of plaintext of into its SHA1 hashed value. + * + * It would be nice if crypto had a wrapper to do this for us linear + * people... + */ +static int +plaintext_to_sha1(unsigned char *hash, const char *plaintext, int len) +{ + char *pgVirtAddr; + struct crypto_tfm *tfm; + struct scatterlist sg[1]; + if (len > PAGE_SIZE) { + seclvl_printk(0, KERN_ERR, "Plaintext password too large (%d " + "characters). Largest possible is %lu " + "bytes.\n", len, PAGE_SIZE); + return -ENOMEM; + } + tfm = crypto_alloc_tfm("sha1", 0); + if (tfm == NULL) { + seclvl_printk(0, KERN_ERR, + "Failed to load transform for SHA1\n"); + return -ENOSYS; + } + // Just get a new page; don't play around with page boundaries + // and scatterlists. + pgVirtAddr = (char *)__get_free_page(GFP_KERNEL); + sg[0].page = virt_to_page(pgVirtAddr); + sg[0].offset = 0; + sg[0].length = len; + strncpy(pgVirtAddr, plaintext, len); + crypto_digest_init(tfm); + crypto_digest_update(tfm, sg, 1); + crypto_digest_final(tfm, hash); + crypto_free_tfm(tfm); + free_page((unsigned long)pgVirtAddr); + return 0; +} + +/** + * Called whenever the user writes to the sysfs passwd handle to this kernel + * object. It hashes the password and compares the hashed results. + */ +static ssize_t +seclvl_write_passwd(struct seclvl_obj *obj, const char *buff, size_t count) +{ + int i; + unsigned char tmp[SHA1_DIGEST_SIZE]; + int rc; + int len; + if (!*passwd && !*sha1_passwd) { + seclvl_printk(0, KERN_ERR, "Attempt to password-unlock the " + "seclvl module, but neither a plain text " + "password nor a SHA1 hashed password was " + "passed in as a module parameter! This is a " + "bug, since it should not be possible to be in " + "this part of the module; please tell a " + "maintainer about this event.\n"); + return -EINVAL; + } + len = strlen(buff); + /* ``echo "secret" > seclvl/passwd'' includes a newline */ + if (buff[len - 1] == '\n') { + len--; + } + /* Hash the password, then compare the hashed values */ + if ((rc = plaintext_to_sha1(tmp, buff, len))) { + seclvl_printk(0, KERN_ERR, "Error hashing password: rc = " + "[%d]\n", rc); + return rc; + } + for (i = 0; i < SHA1_DIGEST_SIZE; i++) { + if (hashedPassword[i] != tmp[i]) { + return -EPERM; + } + } + seclvl_printk(0, KERN_INFO, + "Password accepted; seclvl reduced to 0.\n"); + seclvl = 0; + return count; +} + +/* Generate sysfs_attr_passwd */ +static struct seclvl_attribute sysfs_attr_passwd = +__ATTR(passwd, (S_IFREG | S_IRUGO | S_IWUSR), seclvl_read_passwd, + seclvl_write_passwd); + +/** + * Explicitely disallow ptrace'ing the init process. + */ +static int seclvl_ptrace(struct task_struct *parent, struct task_struct *child) +{ + if (seclvl >= 0) { + if (child->pid == 1) { + seclvl_printk(1, KERN_WARNING, "Attempt to ptrace " + "the init process dissallowed in " + "secure level %d\n", seclvl); + return -EPERM; + } + } + return 0; +} + +/** + * Capability checks for seclvl. The majority of the policy + * enforcement for seclvl takes place here. + */ +static int seclvl_capable(struct task_struct *tsk, int cap) +{ + /* init can do anything it wants */ + if (tsk->pid == 1) + return 0; + + switch (seclvl) { + case 2: + /* fall through */ + case 1: + if (cap == CAP_LINUX_IMMUTABLE) { + seclvl_printk(1, KERN_WARNING, "Attempt to modify " + "the IMMUTABLE and/or APPEND extended " + "attribute on a file with the IMMUTABLE " + "and/or APPEND extended attribute set " + "denied in seclvl [%d]\n", seclvl); + return -EPERM; + } else if (cap == CAP_SYS_RAWIO) { // Somewhat broad... + seclvl_printk(1, KERN_WARNING, "Attempt to perform " + "raw I/O while in secure level [%d] " + "denied\n", seclvl); + return -EPERM; + } else if (cap == CAP_NET_ADMIN) { + seclvl_printk(1, KERN_WARNING, "Attempt to perform " + "network administrative task while " + "in secure level [%d] denied\n", seclvl); + return -EPERM; + } else if (cap == CAP_SETUID) { + seclvl_printk(1, KERN_WARNING, "Attempt to setuid " + "while in secure level [%d] denied\n", + seclvl); + return -EPERM; + } else if (cap == CAP_SETGID) { + seclvl_printk(1, KERN_WARNING, "Attempt to setgid " + "while in secure level [%d] denied\n", + seclvl); + } else if (cap == CAP_SYS_MODULE) { + seclvl_printk(1, KERN_WARNING, "Attempt to perform " + "a module operation while in secure " + "level [%d] denied\n", seclvl); + return -EPERM; + } + break; + default: + break; + } + /* from dummy.c */ + if (cap_is_fs_cap(cap) ? tsk->fsuid == 0 : tsk->euid == 0) + return 0; /* capability granted */ + seclvl_printk(1, KERN_WARNING, "Capability denied\n"); + return -EPERM; /* capability denied */ +} + +/** + * Disallow reversing the clock in seclvl > 1 + */ +static int seclvl_settime(struct timespec *tv, struct timezone *tz) +{ + struct timespec now; + if (seclvl > 1) { + now = current_kernel_time(); + if (tv->tv_sec < now.tv_sec || + (tv->tv_sec == now.tv_sec && tv->tv_nsec < now.tv_nsec)) { + seclvl_printk(1, KERN_WARNING, "Attempt to decrement " + "time in secure level %d denied: " + "current->pid = [%d], " + "current->group_leader->pid = [%d]\n", + seclvl, current->pid, + current->group_leader->pid); + return -EPERM; + } /* if attempt to decrement time */ + } /* if seclvl > 1 */ + return 0; +} + +/* claim the blockdev to exclude mounters, release on file close */ +static int seclvl_bd_claim(struct inode *inode) +{ + int holder; + struct block_device *bdev = NULL; + dev_t dev = inode->i_rdev; + bdev = open_by_devnum(dev, FMODE_WRITE); + if (bdev) { + if (bd_claim(bdev, &holder)) { + blkdev_put(bdev); + return -EPERM; + } + /* claimed, mark it to release on close */ + inode->i_security = current; + } + return 0; +} + +/* release the blockdev if you claimed it */ +static void seclvl_bd_release(struct inode *inode) +{ + if (inode && S_ISBLK(inode->i_mode) && inode->i_security == current) { + struct block_device *bdev = inode->i_bdev; + if (bdev) { + bd_release(bdev); + blkdev_put(bdev); + inode->i_security = NULL; + } + } +} + +/** + * Security for writes to block devices is regulated by this seclvl + * function. Deny all writes to block devices in seclvl 2. In + * seclvl 1, we only deny writes to *mounted* block devices. + */ +static int +seclvl_inode_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + if (current->pid != 1 && S_ISBLK(inode->i_mode) && (mask & MAY_WRITE)) { + switch (seclvl) { + case 2: + seclvl_printk(1, KERN_WARNING, "Write to block device " + "denied in secure level [%d]\n", seclvl); + return -EPERM; + case 1: + if (seclvl_bd_claim(inode)) { + seclvl_printk(1, KERN_WARNING, + "Write to mounted block device " + "denied in secure level [%d]\n", + seclvl); + return -EPERM; + } + } + } + return 0; +} + +/** + * The SUID and SGID bits cannot be set in seclvl >= 1 + */ +static int seclvl_inode_setattr(struct dentry *dentry, struct iattr *iattr) +{ + if (seclvl > 0) { + if (iattr->ia_valid & ATTR_MODE) + if (iattr->ia_mode & S_ISUID || + iattr->ia_mode & S_ISGID) { + seclvl_printk(1, KERN_WARNING, "Attempt to " + "modify SUID or SGID bit " + "denied in seclvl [%d]\n", + seclvl); + return -EPERM; + } + } + return 0; +} + +/* release busied block devices */ +static void seclvl_file_free_security(struct file *filp) +{ + struct dentry *dentry = filp->f_dentry; + struct inode *inode = NULL; + + if (dentry) { + inode = dentry->d_inode; + seclvl_bd_release(inode); + } +} + +/** + * Cannot unmount in secure level 2 + */ +static int seclvl_umount(struct vfsmount *mnt, int flags) +{ + if (current->pid == 1) { + return 0; + } + if (seclvl == 2) { + seclvl_printk(1, KERN_WARNING, "Attempt to unmount in secure " + "level %d\n", seclvl); + return -EPERM; + } + return 0; +} + +static struct security_operations seclvl_ops = { + .ptrace = seclvl_ptrace, + .capable = seclvl_capable, + .inode_permission = seclvl_inode_permission, + .inode_setattr = seclvl_inode_setattr, + .file_free_security = seclvl_file_free_security, + .settime = seclvl_settime, + .sb_umount = seclvl_umount, +}; + +/** + * Process the password-related module parameters + */ +static int processPassword(void) +{ + int rc = 0; + hashedPassword[0] = '\0'; + if (*passwd) { + if (*sha1_passwd) { + seclvl_printk(0, KERN_ERR, "Error: Both " + "passwd and sha1_passwd " + "were set, but they are mutually " + "exclusive.\n"); + return -EINVAL; + } + if ((rc = plaintext_to_sha1(hashedPassword, passwd, + strlen(passwd)))) { + seclvl_printk(0, KERN_ERR, "Error: SHA1 support not " + "in kernel\n"); + return rc; + } + /* All static data goes to the BSS, which zero's the + * plaintext password out for us. */ + } else if (*sha1_passwd) { // Base 16 + int i; + i = strlen(sha1_passwd); + if (i != (SHA1_DIGEST_SIZE * 2)) { + seclvl_printk(0, KERN_ERR, "Received [%d] bytes; " + "expected [%d] for the hexadecimal " + "representation of the SHA1 hash of " + "the password.\n", + i, (SHA1_DIGEST_SIZE * 2)); + return -EINVAL; + } + while ((i -= 2) + 2) { + unsigned char tmp; + tmp = sha1_passwd[i + 2]; + sha1_passwd[i + 2] = '\0'; + hashedPassword[i / 2] = (unsigned char) + simple_strtol(&sha1_passwd[i], NULL, 16); + sha1_passwd[i + 2] = tmp; + } + } + return 0; +} + +/** + * Sysfs registrations + */ +static int doSysfsRegistrations(void) +{ + int rc = 0; + if ((rc = subsystem_register(&seclvl_subsys))) { + seclvl_printk(0, KERN_WARNING, + "Error [%d] registering seclvl subsystem\n", rc); + return rc; + } + sysfs_create_file(&seclvl_subsys.kset.kobj, &sysfs_attr_seclvl.attr); + if (*passwd || *sha1_passwd) { + sysfs_create_file(&seclvl_subsys.kset.kobj, + &sysfs_attr_passwd.attr); + } + return 0; +} + +/** + * Initialize the seclvl module. + */ +static int __init seclvl_init(void) +{ + int rc = 0; + if (verbosity < 0 || verbosity > 1) { + printk(KERN_ERR "Error: bad verbosity [%d]; only 0 or 1 " + "are valid values\n", verbosity); + rc = -EINVAL; + goto exit; + } + sysfs_attr_seclvl.attr.owner = THIS_MODULE; + sysfs_attr_passwd.attr.owner = THIS_MODULE; + if (initlvl < -1 || initlvl > 2) { + seclvl_printk(0, KERN_ERR, "Error: bad initial securelevel " + "[%d].\n", initlvl); + rc = -EINVAL; + goto exit; + } + seclvl = initlvl; + if ((rc = processPassword())) { + seclvl_printk(0, KERN_ERR, "Error processing the password " + "module parameter(s): rc = [%d]\n", rc); + goto exit; + } + /* register ourselves with the security framework */ + if (register_security(&seclvl_ops)) { + seclvl_printk(0, KERN_ERR, + "seclvl: Failure registering with the " + "kernel.\n"); + /* try registering with primary module */ + rc = mod_reg_security(MY_NAME, &seclvl_ops); + if (rc) { + seclvl_printk(0, KERN_ERR, "seclvl: Failure " + "registering with primary security " + "module.\n"); + goto exit; + } /* if primary module registered */ + secondary = 1; + } /* if we registered ourselves with the security framework */ + if ((rc = doSysfsRegistrations())) { + seclvl_printk(0, KERN_ERR, "Error registering with sysfs\n"); + goto exit; + } + seclvl_printk(0, KERN_INFO, "seclvl: Successfully initialized.\n"); + exit: + if (rc) { + printk(KERN_ERR "seclvl: Error during initialization: rc = " + "[%d]\n", rc); + } + return rc; +} + +/** + * Remove the seclvl module. + */ +static void __exit seclvl_exit(void) +{ + sysfs_remove_file(&seclvl_subsys.kset.kobj, &sysfs_attr_seclvl.attr); + if (*passwd || *sha1_passwd) { + sysfs_remove_file(&seclvl_subsys.kset.kobj, + &sysfs_attr_passwd.attr); + } + subsystem_unregister(&seclvl_subsys); + if (secondary == 1) { + mod_unreg_security(MY_NAME, &seclvl_ops); + } else if (unregister_security(&seclvl_ops)) { + seclvl_printk(0, KERN_INFO, + "seclvl: Failure unregistering with the " + "kernel\n"); + } +} + +module_init(seclvl_init); +module_exit(seclvl_exit); + +MODULE_AUTHOR("Michael A. Halcrow <mike@halcrow.us>"); +MODULE_DESCRIPTION("LSM implementation of the BSD Secure Levels"); +MODULE_LICENSE("GPL"); diff --git a/security/security.c b/security/security.c new file mode 100644 index 000000000000..ed5fb80769c3 --- /dev/null +++ b/security/security.c @@ -0,0 +1,203 @@ +/* + * Security plug functions + * + * Copyright (C) 2001 WireX Communications, Inc <chris@wirex.com> + * Copyright (C) 2001-2002 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2001 Networks Associates Technology, Inc <ssmalley@nai.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/security.h> + +#define SECURITY_FRAMEWORK_VERSION "1.0.0" + +/* things that live in dummy.c */ +extern struct security_operations dummy_security_ops; +extern void security_fixup_ops(struct security_operations *ops); + +struct security_operations *security_ops; /* Initialized to NULL */ + +static inline int verify(struct security_operations *ops) +{ + /* verify the security_operations structure exists */ + if (!ops) + return -EINVAL; + security_fixup_ops(ops); + return 0; +} + +static void __init do_security_initcalls(void) +{ + initcall_t *call; + call = __security_initcall_start; + while (call < __security_initcall_end) { + (*call) (); + call++; + } +} + +/** + * security_init - initializes the security framework + * + * This should be called early in the kernel initialization sequence. + */ +int __init security_init(void) +{ + printk(KERN_INFO "Security Framework v" SECURITY_FRAMEWORK_VERSION + " initialized\n"); + + if (verify(&dummy_security_ops)) { + printk(KERN_ERR "%s could not verify " + "dummy_security_ops structure.\n", __FUNCTION__); + return -EIO; + } + + security_ops = &dummy_security_ops; + do_security_initcalls(); + + return 0; +} + +/** + * register_security - registers a security framework with the kernel + * @ops: a pointer to the struct security_options that is to be registered + * + * This function is to allow a security module to register itself with the + * kernel security subsystem. Some rudimentary checking is done on the @ops + * value passed to this function. A call to unregister_security() should be + * done to remove this security_options structure from the kernel. + * + * If there is already a security module registered with the kernel, + * an error will be returned. Otherwise 0 is returned on success. + */ +int register_security(struct security_operations *ops) +{ + if (verify(ops)) { + printk(KERN_DEBUG "%s could not verify " + "security_operations structure.\n", __FUNCTION__); + return -EINVAL; + } + + if (security_ops != &dummy_security_ops) + return -EAGAIN; + + security_ops = ops; + + return 0; +} + +/** + * unregister_security - unregisters a security framework with the kernel + * @ops: a pointer to the struct security_options that is to be registered + * + * This function removes a struct security_operations variable that had + * previously been registered with a successful call to register_security(). + * + * If @ops does not match the valued previously passed to register_security() + * an error is returned. Otherwise the default security options is set to the + * the dummy_security_ops structure, and 0 is returned. + */ +int unregister_security(struct security_operations *ops) +{ + if (ops != security_ops) { + printk(KERN_INFO "%s: trying to unregister " + "a security_opts structure that is not " + "registered, failing.\n", __FUNCTION__); + return -EINVAL; + } + + security_ops = &dummy_security_ops; + + return 0; +} + +/** + * mod_reg_security - allows security modules to be "stacked" + * @name: a pointer to a string with the name of the security_options to be registered + * @ops: a pointer to the struct security_options that is to be registered + * + * This function allows security modules to be stacked if the currently loaded + * security module allows this to happen. It passes the @name and @ops to the + * register_security function of the currently loaded security module. + * + * The return value depends on the currently loaded security module, with 0 as + * success. + */ +int mod_reg_security(const char *name, struct security_operations *ops) +{ + if (verify(ops)) { + printk(KERN_INFO "%s could not verify " + "security operations.\n", __FUNCTION__); + return -EINVAL; + } + + if (ops == security_ops) { + printk(KERN_INFO "%s security operations " + "already registered.\n", __FUNCTION__); + return -EINVAL; + } + + return security_ops->register_security(name, ops); +} + +/** + * mod_unreg_security - allows a security module registered with mod_reg_security() to be unloaded + * @name: a pointer to a string with the name of the security_options to be removed + * @ops: a pointer to the struct security_options that is to be removed + * + * This function allows security modules that have been successfully registered + * with a call to mod_reg_security() to be unloaded from the system. + * This calls the currently loaded security module's unregister_security() call + * with the @name and @ops variables. + * + * The return value depends on the currently loaded security module, with 0 as + * success. + */ +int mod_unreg_security(const char *name, struct security_operations *ops) +{ + if (ops == security_ops) { + printk(KERN_INFO "%s invalid attempt to unregister " + " primary security ops.\n", __FUNCTION__); + return -EINVAL; + } + + return security_ops->unregister_security(name, ops); +} + +/** + * capable - calls the currently loaded security module's capable() function with the specified capability + * @cap: the requested capability level. + * + * This function calls the currently loaded security module's capable() + * function with a pointer to the current task and the specified @cap value. + * + * This allows the security module to implement the capable function call + * however it chooses to. + */ +int capable(int cap) +{ + if (security_ops->capable(current, cap)) { + /* capability denied */ + return 0; + } + + /* capability granted */ + current->flags |= PF_SUPERPRIV; + return 1; +} + +EXPORT_SYMBOL_GPL(register_security); +EXPORT_SYMBOL_GPL(unregister_security); +EXPORT_SYMBOL_GPL(mod_reg_security); +EXPORT_SYMBOL_GPL(mod_unreg_security); +EXPORT_SYMBOL(capable); +EXPORT_SYMBOL(security_ops); diff --git a/security/selinux/Kconfig b/security/selinux/Kconfig new file mode 100644 index 000000000000..b59582b92283 --- /dev/null +++ b/security/selinux/Kconfig @@ -0,0 +1,97 @@ +config SECURITY_SELINUX + bool "NSA SELinux Support" + depends on SECURITY && NET && INET + default n + help + This selects NSA Security-Enhanced Linux (SELinux). + You will also need a policy configuration and a labeled filesystem. + You can obtain the policy compiler (checkpolicy), the utility for + labeling filesystems (setfiles), and an example policy configuration + from <http://www.nsa.gov/selinux/>. + If you are unsure how to answer this question, answer N. + +config SECURITY_SELINUX_BOOTPARAM + bool "NSA SELinux boot parameter" + depends on SECURITY_SELINUX + default n + help + This option adds a kernel parameter 'selinux', which allows SELinux + to be disabled at boot. If this option is selected, SELinux + functionality can be disabled with selinux=0 on the kernel + command line. The purpose of this option is to allow a single + kernel image to be distributed with SELinux built in, but not + necessarily enabled. + + If you are unsure how to answer this question, answer N. + +config SECURITY_SELINUX_BOOTPARAM_VALUE + int "NSA SELinux boot parameter default value" + depends on SECURITY_SELINUX_BOOTPARAM + range 0 1 + default 1 + help + This option sets the default value for the kernel parameter + 'selinux', which allows SELinux to be disabled at boot. If this + option is set to 0 (zero), the SELinux kernel parameter will + default to 0, disabling SELinux at bootup. If this option is + set to 1 (one), the SELinux kernel parameter will default to 1, + enabling SELinux at bootup. + + If you are unsure how to answer this question, answer 1. + +config SECURITY_SELINUX_DISABLE + bool "NSA SELinux runtime disable" + depends on SECURITY_SELINUX + default n + help + This option enables writing to a selinuxfs node 'disable', which + allows SELinux to be disabled at runtime prior to the policy load. + SELinux will then remain disabled until the next boot. + This option is similar to the selinux=0 boot parameter, but is to + support runtime disabling of SELinux, e.g. from /sbin/init, for + portability across platforms where boot parameters are difficult + to employ. + + If you are unsure how to answer this question, answer N. + +config SECURITY_SELINUX_DEVELOP + bool "NSA SELinux Development Support" + depends on SECURITY_SELINUX + default y + help + This enables the development support option of NSA SELinux, + which is useful for experimenting with SELinux and developing + policies. If unsure, say Y. With this option enabled, the + kernel will start in permissive mode (log everything, deny nothing) + unless you specify enforcing=1 on the kernel command line. You + can interactively toggle the kernel between enforcing mode and + permissive mode (if permitted by the policy) via /selinux/enforce. + +config SECURITY_SELINUX_AVC_STATS + bool "NSA SELinux AVC Statistics" + depends on SECURITY_SELINUX + default y + help + This option collects access vector cache statistics to + /selinux/avc/cache_stats, which may be monitored via + tools such as avcstat. + +config SECURITY_SELINUX_CHECKREQPROT_VALUE + int "NSA SELinux checkreqprot default value" + depends on SECURITY_SELINUX + range 0 1 + default 1 + help + This option sets the default value for the 'checkreqprot' flag + that determines whether SELinux checks the protection requested + by the application or the protection that will be applied by the + kernel (including any implied execute for read-implies-exec) for + mmap and mprotect calls. If this option is set to 0 (zero), + SELinux will default to checking the protection that will be applied + by the kernel. If this option is set to 1 (one), SELinux will + default to checking the protection requested by the application. + The checkreqprot flag may be changed from the default via the + 'checkreqprot=' boot parameter. It may also be changed at runtime + via /selinux/checkreqprot if authorized by policy. + + If you are unsure how to answer this question, answer 1. diff --git a/security/selinux/Makefile b/security/selinux/Makefile new file mode 100644 index 000000000000..b038cd0fae2e --- /dev/null +++ b/security/selinux/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for building the SELinux module as part of the kernel tree. +# + +obj-$(CONFIG_SECURITY_SELINUX) := selinux.o ss/ + +selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o + +selinux-$(CONFIG_SECURITY_NETWORK) += netif.o + +EXTRA_CFLAGS += -Isecurity/selinux/include + diff --git a/security/selinux/avc.c b/security/selinux/avc.c new file mode 100644 index 000000000000..fe6285e5c68f --- /dev/null +++ b/security/selinux/avc.c @@ -0,0 +1,949 @@ +/* + * Implementation of the kernel access vector cache (AVC). + * + * Authors: Stephen Smalley, <sds@epoch.ncsc.mil> + * James Morris <jmorris@redhat.com> + * + * Update: KaiGai, Kohei <kaigai@ak.jp.nec.com> + * Replaced the avc_lock spinlock by RCU. + * + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.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/types.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/dcache.h> +#include <linux/init.h> +#include <linux/skbuff.h> +#include <linux/percpu.h> +#include <net/sock.h> +#include <linux/un.h> +#include <net/af_unix.h> +#include <linux/ip.h> +#include <linux/audit.h> +#include <linux/ipv6.h> +#include <net/ipv6.h> +#include "avc.h" +#include "avc_ss.h" + +static const struct av_perm_to_string +{ + u16 tclass; + u32 value; + const char *name; +} av_perm_to_string[] = { +#define S_(c, v, s) { c, v, s }, +#include "av_perm_to_string.h" +#undef S_ +}; + +#ifdef CONFIG_AUDIT +static const char *class_to_string[] = { +#define S_(s) s, +#include "class_to_string.h" +#undef S_ +}; +#endif + +#define TB_(s) static const char * s [] = { +#define TE_(s) }; +#define S_(s) s, +#include "common_perm_to_string.h" +#undef TB_ +#undef TE_ +#undef S_ + +static const struct av_inherit +{ + u16 tclass; + const char **common_pts; + u32 common_base; +} av_inherit[] = { +#define S_(c, i, b) { c, common_##i##_perm_to_string, b }, +#include "av_inherit.h" +#undef S_ +}; + +#define AVC_CACHE_SLOTS 512 +#define AVC_DEF_CACHE_THRESHOLD 512 +#define AVC_CACHE_RECLAIM 16 + +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS +#define avc_cache_stats_incr(field) \ +do { \ + per_cpu(avc_cache_stats, get_cpu()).field++; \ + put_cpu(); \ +} while (0) +#else +#define avc_cache_stats_incr(field) do {} while (0) +#endif + +struct avc_entry { + u32 ssid; + u32 tsid; + u16 tclass; + struct av_decision avd; + atomic_t used; /* used recently */ +}; + +struct avc_node { + struct avc_entry ae; + struct list_head list; + struct rcu_head rhead; +}; + +struct avc_cache { + struct list_head slots[AVC_CACHE_SLOTS]; + spinlock_t slots_lock[AVC_CACHE_SLOTS]; /* lock for writes */ + atomic_t lru_hint; /* LRU hint for reclaim scan */ + atomic_t active_nodes; + u32 latest_notif; /* latest revocation notification */ +}; + +struct avc_callback_node { + int (*callback) (u32 event, u32 ssid, u32 tsid, + u16 tclass, u32 perms, + u32 *out_retained); + u32 events; + u32 ssid; + u32 tsid; + u16 tclass; + u32 perms; + struct avc_callback_node *next; +}; + +/* Exported via selinufs */ +unsigned int avc_cache_threshold = AVC_DEF_CACHE_THRESHOLD; + +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS +DEFINE_PER_CPU(struct avc_cache_stats, avc_cache_stats) = { 0 }; +#endif + +static struct avc_cache avc_cache; +static struct avc_callback_node *avc_callbacks; +static kmem_cache_t *avc_node_cachep; + +static inline int avc_hash(u32 ssid, u32 tsid, u16 tclass) +{ + return (ssid ^ (tsid<<2) ^ (tclass<<4)) & (AVC_CACHE_SLOTS - 1); +} + +/** + * avc_dump_av - Display an access vector in human-readable form. + * @tclass: target security class + * @av: access vector + */ +static void avc_dump_av(struct audit_buffer *ab, u16 tclass, u32 av) +{ + const char **common_pts = NULL; + u32 common_base = 0; + int i, i2, perm; + + if (av == 0) { + audit_log_format(ab, " null"); + return; + } + + for (i = 0; i < ARRAY_SIZE(av_inherit); i++) { + if (av_inherit[i].tclass == tclass) { + common_pts = av_inherit[i].common_pts; + common_base = av_inherit[i].common_base; + break; + } + } + + audit_log_format(ab, " {"); + i = 0; + perm = 1; + while (perm < common_base) { + if (perm & av) { + audit_log_format(ab, " %s", common_pts[i]); + av &= ~perm; + } + i++; + perm <<= 1; + } + + while (i < sizeof(av) * 8) { + if (perm & av) { + for (i2 = 0; i2 < ARRAY_SIZE(av_perm_to_string); i2++) { + if ((av_perm_to_string[i2].tclass == tclass) && + (av_perm_to_string[i2].value == perm)) + break; + } + if (i2 < ARRAY_SIZE(av_perm_to_string)) { + audit_log_format(ab, " %s", + av_perm_to_string[i2].name); + av &= ~perm; + } + } + i++; + perm <<= 1; + } + + if (av) + audit_log_format(ab, " 0x%x", av); + + audit_log_format(ab, " }"); +} + +/** + * avc_dump_query - Display a SID pair and a class in human-readable form. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + */ +static void avc_dump_query(struct audit_buffer *ab, u32 ssid, u32 tsid, u16 tclass) +{ + int rc; + char *scontext; + u32 scontext_len; + + rc = security_sid_to_context(ssid, &scontext, &scontext_len); + if (rc) + audit_log_format(ab, "ssid=%d", ssid); + else { + audit_log_format(ab, "scontext=%s", scontext); + kfree(scontext); + } + + rc = security_sid_to_context(tsid, &scontext, &scontext_len); + if (rc) + audit_log_format(ab, " tsid=%d", tsid); + else { + audit_log_format(ab, " tcontext=%s", scontext); + kfree(scontext); + } + audit_log_format(ab, " tclass=%s", class_to_string[tclass]); +} + +/** + * avc_init - Initialize the AVC. + * + * Initialize the access vector cache. + */ +void __init avc_init(void) +{ + int i; + + for (i = 0; i < AVC_CACHE_SLOTS; i++) { + INIT_LIST_HEAD(&avc_cache.slots[i]); + spin_lock_init(&avc_cache.slots_lock[i]); + } + atomic_set(&avc_cache.active_nodes, 0); + atomic_set(&avc_cache.lru_hint, 0); + + avc_node_cachep = kmem_cache_create("avc_node", sizeof(struct avc_node), + 0, SLAB_PANIC, NULL, NULL); + + audit_log(current->audit_context, "AVC INITIALIZED\n"); +} + +int avc_get_hash_stats(char *page) +{ + int i, chain_len, max_chain_len, slots_used; + struct avc_node *node; + + rcu_read_lock(); + + slots_used = 0; + max_chain_len = 0; + for (i = 0; i < AVC_CACHE_SLOTS; i++) { + if (!list_empty(&avc_cache.slots[i])) { + slots_used++; + chain_len = 0; + list_for_each_entry_rcu(node, &avc_cache.slots[i], list) + chain_len++; + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + rcu_read_unlock(); + + return scnprintf(page, PAGE_SIZE, "entries: %d\nbuckets used: %d/%d\n" + "longest chain: %d\n", + atomic_read(&avc_cache.active_nodes), + slots_used, AVC_CACHE_SLOTS, max_chain_len); +} + +static void avc_node_free(struct rcu_head *rhead) +{ + struct avc_node *node = container_of(rhead, struct avc_node, rhead); + kmem_cache_free(avc_node_cachep, node); + avc_cache_stats_incr(frees); +} + +static void avc_node_delete(struct avc_node *node) +{ + list_del_rcu(&node->list); + call_rcu(&node->rhead, avc_node_free); + atomic_dec(&avc_cache.active_nodes); +} + +static void avc_node_kill(struct avc_node *node) +{ + kmem_cache_free(avc_node_cachep, node); + avc_cache_stats_incr(frees); + atomic_dec(&avc_cache.active_nodes); +} + +static void avc_node_replace(struct avc_node *new, struct avc_node *old) +{ + list_replace_rcu(&old->list, &new->list); + call_rcu(&old->rhead, avc_node_free); + atomic_dec(&avc_cache.active_nodes); +} + +static inline int avc_reclaim_node(void) +{ + struct avc_node *node; + int hvalue, try, ecx; + unsigned long flags; + + for (try = 0, ecx = 0; try < AVC_CACHE_SLOTS; try++ ) { + hvalue = atomic_inc_return(&avc_cache.lru_hint) & (AVC_CACHE_SLOTS - 1); + + if (!spin_trylock_irqsave(&avc_cache.slots_lock[hvalue], flags)) + continue; + + list_for_each_entry(node, &avc_cache.slots[hvalue], list) { + if (atomic_dec_and_test(&node->ae.used)) { + /* Recently Unused */ + avc_node_delete(node); + avc_cache_stats_incr(reclaims); + ecx++; + if (ecx >= AVC_CACHE_RECLAIM) { + spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flags); + goto out; + } + } + } + spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flags); + } +out: + return ecx; +} + +static struct avc_node *avc_alloc_node(void) +{ + struct avc_node *node; + + node = kmem_cache_alloc(avc_node_cachep, SLAB_ATOMIC); + if (!node) + goto out; + + memset(node, 0, sizeof(*node)); + INIT_RCU_HEAD(&node->rhead); + INIT_LIST_HEAD(&node->list); + atomic_set(&node->ae.used, 1); + avc_cache_stats_incr(allocations); + + if (atomic_inc_return(&avc_cache.active_nodes) > avc_cache_threshold) + avc_reclaim_node(); + +out: + return node; +} + +static void avc_node_populate(struct avc_node *node, u32 ssid, u32 tsid, u16 tclass, struct avc_entry *ae) +{ + node->ae.ssid = ssid; + node->ae.tsid = tsid; + node->ae.tclass = tclass; + memcpy(&node->ae.avd, &ae->avd, sizeof(node->ae.avd)); +} + +static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid, u16 tclass) +{ + struct avc_node *node, *ret = NULL; + int hvalue; + + hvalue = avc_hash(ssid, tsid, tclass); + list_for_each_entry_rcu(node, &avc_cache.slots[hvalue], list) { + if (ssid == node->ae.ssid && + tclass == node->ae.tclass && + tsid == node->ae.tsid) { + ret = node; + break; + } + } + + if (ret == NULL) { + /* cache miss */ + goto out; + } + + /* cache hit */ + if (atomic_read(&ret->ae.used) != 1) + atomic_set(&ret->ae.used, 1); +out: + return ret; +} + +/** + * avc_lookup - Look up an AVC entry. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions, interpreted based on @tclass + * + * Look up an AVC entry that is valid for the + * @requested permissions between the SID pair + * (@ssid, @tsid), interpreting the permissions + * based on @tclass. If a valid AVC entry exists, + * then this function return the avc_node. + * Otherwise, this function returns NULL. + */ +static struct avc_node *avc_lookup(u32 ssid, u32 tsid, u16 tclass, u32 requested) +{ + struct avc_node *node; + + avc_cache_stats_incr(lookups); + node = avc_search_node(ssid, tsid, tclass); + + if (node && ((node->ae.avd.decided & requested) == requested)) { + avc_cache_stats_incr(hits); + goto out; + } + + node = NULL; + avc_cache_stats_incr(misses); +out: + return node; +} + +static int avc_latest_notif_update(int seqno, int is_insert) +{ + int ret = 0; + static DEFINE_SPINLOCK(notif_lock); + unsigned long flag; + + spin_lock_irqsave(¬if_lock, flag); + if (is_insert) { + if (seqno < avc_cache.latest_notif) { + printk(KERN_WARNING "avc: seqno %d < latest_notif %d\n", + seqno, avc_cache.latest_notif); + ret = -EAGAIN; + } + } else { + if (seqno > avc_cache.latest_notif) + avc_cache.latest_notif = seqno; + } + spin_unlock_irqrestore(¬if_lock, flag); + + return ret; +} + +/** + * avc_insert - Insert an AVC entry. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @ae: AVC entry + * + * Insert an AVC entry for the SID pair + * (@ssid, @tsid) and class @tclass. + * The access vectors and the sequence number are + * normally provided by the security server in + * response to a security_compute_av() call. If the + * sequence number @ae->avd.seqno is not less than the latest + * revocation notification, then the function copies + * the access vectors into a cache entry, returns + * avc_node inserted. Otherwise, this function returns NULL. + */ +static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct avc_entry *ae) +{ + struct avc_node *pos, *node = NULL; + int hvalue; + unsigned long flag; + + if (avc_latest_notif_update(ae->avd.seqno, 1)) + goto out; + + node = avc_alloc_node(); + if (node) { + hvalue = avc_hash(ssid, tsid, tclass); + avc_node_populate(node, ssid, tsid, tclass, ae); + + spin_lock_irqsave(&avc_cache.slots_lock[hvalue], flag); + list_for_each_entry(pos, &avc_cache.slots[hvalue], list) { + if (pos->ae.ssid == ssid && + pos->ae.tsid == tsid && + pos->ae.tclass == tclass) { + avc_node_replace(node, pos); + goto found; + } + } + list_add_rcu(&node->list, &avc_cache.slots[hvalue]); +found: + spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flag); + } +out: + return node; +} + +static inline void avc_print_ipv6_addr(struct audit_buffer *ab, + struct in6_addr *addr, u16 port, + char *name1, char *name2) +{ + if (!ipv6_addr_any(addr)) + audit_log_format(ab, " %s=%04x:%04x:%04x:%04x:%04x:" + "%04x:%04x:%04x", name1, NIP6(*addr)); + if (port) + audit_log_format(ab, " %s=%d", name2, ntohs(port)); +} + +static inline void avc_print_ipv4_addr(struct audit_buffer *ab, u32 addr, + u16 port, char *name1, char *name2) +{ + if (addr) + audit_log_format(ab, " %s=%d.%d.%d.%d", name1, NIPQUAD(addr)); + if (port) + audit_log_format(ab, " %s=%d", name2, ntohs(port)); +} + +/** + * avc_audit - Audit the granting or denial of permissions. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions + * @avd: access vector decisions + * @result: result from avc_has_perm_noaudit + * @a: auxiliary audit data + * + * Audit the granting or denial of permissions in accordance + * with the policy. This function is typically called by + * avc_has_perm() after a permission check, but can also be + * called directly by callers who use avc_has_perm_noaudit() + * in order to separate the permission check from the auditing. + * For example, this separation is useful when the permission check must + * be performed under a lock, to allow the lock to be released + * before calling the auditing code. + */ +void avc_audit(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct av_decision *avd, int result, struct avc_audit_data *a) +{ + struct task_struct *tsk = current; + struct inode *inode = NULL; + u32 denied, audited; + struct audit_buffer *ab; + + denied = requested & ~avd->allowed; + if (denied) { + audited = denied; + if (!(audited & avd->auditdeny)) + return; + } else if (result) { + audited = denied = requested; + } else { + audited = requested; + if (!(audited & avd->auditallow)) + return; + } + + ab = audit_log_start(current->audit_context); + if (!ab) + return; /* audit_panic has been called */ + audit_log_format(ab, "avc: %s ", denied ? "denied" : "granted"); + avc_dump_av(ab, tclass,audited); + audit_log_format(ab, " for "); + if (a && a->tsk) + tsk = a->tsk; + if (tsk && tsk->pid) { + struct mm_struct *mm; + struct vm_area_struct *vma; + audit_log_format(ab, " pid=%d", tsk->pid); + if (tsk == current) + mm = current->mm; + else + mm = get_task_mm(tsk); + if (mm) { + if (down_read_trylock(&mm->mmap_sem)) { + vma = mm->mmap; + while (vma) { + if ((vma->vm_flags & VM_EXECUTABLE) && + vma->vm_file) { + audit_log_d_path(ab, "exe=", + vma->vm_file->f_dentry, + vma->vm_file->f_vfsmnt); + break; + } + vma = vma->vm_next; + } + up_read(&mm->mmap_sem); + } else { + audit_log_format(ab, " comm=%s", tsk->comm); + } + if (tsk != current) + mmput(mm); + } else { + audit_log_format(ab, " comm=%s", tsk->comm); + } + } + if (a) { + switch (a->type) { + case AVC_AUDIT_DATA_IPC: + audit_log_format(ab, " key=%d", a->u.ipc_id); + break; + case AVC_AUDIT_DATA_CAP: + audit_log_format(ab, " capability=%d", a->u.cap); + break; + case AVC_AUDIT_DATA_FS: + if (a->u.fs.dentry) { + struct dentry *dentry = a->u.fs.dentry; + if (a->u.fs.mnt) { + audit_log_d_path(ab, "path=", dentry, + a->u.fs.mnt); + } else { + audit_log_format(ab, " name=%s", + dentry->d_name.name); + } + inode = dentry->d_inode; + } else if (a->u.fs.inode) { + struct dentry *dentry; + inode = a->u.fs.inode; + dentry = d_find_alias(inode); + if (dentry) { + audit_log_format(ab, " name=%s", + dentry->d_name.name); + dput(dentry); + } + } + if (inode) + audit_log_format(ab, " dev=%s ino=%ld", + inode->i_sb->s_id, + inode->i_ino); + break; + case AVC_AUDIT_DATA_NET: + if (a->u.net.sk) { + struct sock *sk = a->u.net.sk; + struct unix_sock *u; + int len = 0; + char *p = NULL; + + switch (sk->sk_family) { + case AF_INET: { + struct inet_sock *inet = inet_sk(sk); + + avc_print_ipv4_addr(ab, inet->rcv_saddr, + inet->sport, + "laddr", "lport"); + avc_print_ipv4_addr(ab, inet->daddr, + inet->dport, + "faddr", "fport"); + break; + } + case AF_INET6: { + struct inet_sock *inet = inet_sk(sk); + struct ipv6_pinfo *inet6 = inet6_sk(sk); + + avc_print_ipv6_addr(ab, &inet6->rcv_saddr, + inet->sport, + "laddr", "lport"); + avc_print_ipv6_addr(ab, &inet6->daddr, + inet->dport, + "faddr", "fport"); + break; + } + case AF_UNIX: + u = unix_sk(sk); + if (u->dentry) { + audit_log_d_path(ab, "path=", + u->dentry, u->mnt); + break; + } + if (!u->addr) + break; + len = u->addr->len-sizeof(short); + p = &u->addr->name->sun_path[0]; + if (*p) + audit_log_format(ab, + "path=%*.*s", len, + len, p); + else + audit_log_format(ab, + "path=@%*.*s", len-1, + len-1, p+1); + break; + } + } + + switch (a->u.net.family) { + case AF_INET: + avc_print_ipv4_addr(ab, a->u.net.v4info.saddr, + a->u.net.sport, + "saddr", "src"); + avc_print_ipv4_addr(ab, a->u.net.v4info.daddr, + a->u.net.dport, + "daddr", "dest"); + break; + case AF_INET6: + avc_print_ipv6_addr(ab, &a->u.net.v6info.saddr, + a->u.net.sport, + "saddr", "src"); + avc_print_ipv6_addr(ab, &a->u.net.v6info.daddr, + a->u.net.dport, + "daddr", "dest"); + break; + } + if (a->u.net.netif) + audit_log_format(ab, " netif=%s", + a->u.net.netif); + break; + } + } + audit_log_format(ab, " "); + avc_dump_query(ab, ssid, tsid, tclass); + audit_log_end(ab); +} + +/** + * avc_add_callback - Register a callback for security events. + * @callback: callback function + * @events: security events + * @ssid: source security identifier or %SECSID_WILD + * @tsid: target security identifier or %SECSID_WILD + * @tclass: target security class + * @perms: permissions + * + * Register a callback function for events in the set @events + * related to the SID pair (@ssid, @tsid) and + * and the permissions @perms, interpreting + * @perms based on @tclass. Returns %0 on success or + * -%ENOMEM if insufficient memory exists to add the callback. + */ +int avc_add_callback(int (*callback)(u32 event, u32 ssid, u32 tsid, + u16 tclass, u32 perms, + u32 *out_retained), + u32 events, u32 ssid, u32 tsid, + u16 tclass, u32 perms) +{ + struct avc_callback_node *c; + int rc = 0; + + c = kmalloc(sizeof(*c), GFP_ATOMIC); + if (!c) { + rc = -ENOMEM; + goto out; + } + + c->callback = callback; + c->events = events; + c->ssid = ssid; + c->tsid = tsid; + c->perms = perms; + c->next = avc_callbacks; + avc_callbacks = c; +out: + return rc; +} + +static inline int avc_sidcmp(u32 x, u32 y) +{ + return (x == y || x == SECSID_WILD || y == SECSID_WILD); +} + +/** + * avc_update_node Update an AVC entry + * @event : Updating event + * @perms : Permission mask bits + * @ssid,@tsid,@tclass : identifier of an AVC entry + * + * if a valid AVC entry doesn't exist,this function returns -ENOENT. + * if kmalloc() called internal returns NULL, this function returns -ENOMEM. + * otherwise, this function update the AVC entry. The original AVC-entry object + * will release later by RCU. + */ +static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass) +{ + int hvalue, rc = 0; + unsigned long flag; + struct avc_node *pos, *node, *orig = NULL; + + node = avc_alloc_node(); + if (!node) { + rc = -ENOMEM; + goto out; + } + + /* Lock the target slot */ + hvalue = avc_hash(ssid, tsid, tclass); + spin_lock_irqsave(&avc_cache.slots_lock[hvalue], flag); + + list_for_each_entry(pos, &avc_cache.slots[hvalue], list){ + if ( ssid==pos->ae.ssid && + tsid==pos->ae.tsid && + tclass==pos->ae.tclass ){ + orig = pos; + break; + } + } + + if (!orig) { + rc = -ENOENT; + avc_node_kill(node); + goto out_unlock; + } + + /* + * Copy and replace original node. + */ + + avc_node_populate(node, ssid, tsid, tclass, &orig->ae); + + switch (event) { + case AVC_CALLBACK_GRANT: + node->ae.avd.allowed |= perms; + break; + case AVC_CALLBACK_TRY_REVOKE: + case AVC_CALLBACK_REVOKE: + node->ae.avd.allowed &= ~perms; + break; + case AVC_CALLBACK_AUDITALLOW_ENABLE: + node->ae.avd.auditallow |= perms; + break; + case AVC_CALLBACK_AUDITALLOW_DISABLE: + node->ae.avd.auditallow &= ~perms; + break; + case AVC_CALLBACK_AUDITDENY_ENABLE: + node->ae.avd.auditdeny |= perms; + break; + case AVC_CALLBACK_AUDITDENY_DISABLE: + node->ae.avd.auditdeny &= ~perms; + break; + } + avc_node_replace(node, orig); +out_unlock: + spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flag); +out: + return rc; +} + +/** + * avc_ss_reset - Flush the cache and revalidate migrated permissions. + * @seqno: policy sequence number + */ +int avc_ss_reset(u32 seqno) +{ + struct avc_callback_node *c; + int i, rc = 0; + unsigned long flag; + struct avc_node *node; + + for (i = 0; i < AVC_CACHE_SLOTS; i++) { + spin_lock_irqsave(&avc_cache.slots_lock[i], flag); + list_for_each_entry(node, &avc_cache.slots[i], list) + avc_node_delete(node); + spin_unlock_irqrestore(&avc_cache.slots_lock[i], flag); + } + + for (c = avc_callbacks; c; c = c->next) { + if (c->events & AVC_CALLBACK_RESET) { + rc = c->callback(AVC_CALLBACK_RESET, + 0, 0, 0, 0, NULL); + if (rc) + goto out; + } + } + + avc_latest_notif_update(seqno, 0); +out: + return rc; +} + +/** + * avc_has_perm_noaudit - Check permissions but perform no auditing. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions, interpreted based on @tclass + * @avd: access vector decisions + * + * Check the AVC to determine whether the @requested permissions are granted + * for the SID pair (@ssid, @tsid), interpreting the permissions + * based on @tclass, and call the security server on a cache miss to obtain + * a new decision and add it to the cache. Return a copy of the decisions + * in @avd. Return %0 if all @requested permissions are granted, + * -%EACCES if any permissions are denied, or another -errno upon + * other errors. This function is typically called by avc_has_perm(), + * but may also be called directly to separate permission checking from + * auditing, e.g. in cases where a lock must be held for the check but + * should be released for the auditing. + */ +int avc_has_perm_noaudit(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct av_decision *avd) +{ + struct avc_node *node; + struct avc_entry entry, *p_ae; + int rc = 0; + u32 denied; + + rcu_read_lock(); + + node = avc_lookup(ssid, tsid, tclass, requested); + if (!node) { + rcu_read_unlock(); + rc = security_compute_av(ssid,tsid,tclass,requested,&entry.avd); + if (rc) + goto out; + rcu_read_lock(); + node = avc_insert(ssid,tsid,tclass,&entry); + } + + p_ae = node ? &node->ae : &entry; + + if (avd) + memcpy(avd, &p_ae->avd, sizeof(*avd)); + + denied = requested & ~(p_ae->avd.allowed); + + if (!requested || denied) { + if (selinux_enforcing) + rc = -EACCES; + else + if (node) + avc_update_node(AVC_CALLBACK_GRANT,requested, + ssid,tsid,tclass); + } + + rcu_read_unlock(); +out: + return rc; +} + +/** + * avc_has_perm - Check permissions and perform any appropriate auditing. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions, interpreted based on @tclass + * @auditdata: auxiliary audit data + * + * Check the AVC to determine whether the @requested permissions are granted + * for the SID pair (@ssid, @tsid), interpreting the permissions + * based on @tclass, and call the security server on a cache miss to obtain + * a new decision and add it to the cache. Audit the granting or denial of + * permissions in accordance with the policy. Return %0 if all @requested + * permissions are granted, -%EACCES if any permissions are denied, or + * another -errno upon other errors. + */ +int avc_has_perm(u32 ssid, u32 tsid, u16 tclass, + u32 requested, struct avc_audit_data *auditdata) +{ + struct av_decision avd; + int rc; + + rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, &avd); + avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata); + return rc; +} diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c new file mode 100644 index 000000000000..8a2cc75b3948 --- /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); +} + +/* Convert a Linux mode and permission mask to an access vector. */ +static inline u32 file_mask_to_av(int mode, int mask) +{ + u32 av = 0; + + if ((mode & S_IFMT) != S_IFDIR) { + if (mask & MAY_EXEC) + av |= FILE__EXECUTE; + if (mask & MAY_READ) + av |= FILE__READ; + + if (mask & MAY_APPEND) + av |= FILE__APPEND; + else if (mask & MAY_WRITE) + av |= FILE__WRITE; + + } else { + if (mask & MAY_EXEC) + av |= DIR__SEARCH; + if (mask & MAY_WRITE) + av |= DIR__WRITE; + if (mask & MAY_READ) + av |= DIR__READ; + } + + return av; +} + +/* Convert a Linux file to an access vector. */ +static inline u32 file_to_av(struct file *file) +{ + u32 av = 0; + + if (file->f_mode & FMODE_READ) + av |= FILE__READ; + if (file->f_mode & FMODE_WRITE) { + if (file->f_flags & O_APPEND) + av |= FILE__APPEND; + else + av |= FILE__WRITE; + } + + return av; +} + +/* Set an inode's SID to a specified value. */ +static int inode_security_set_sid(struct inode *inode, u32 sid) +{ + struct inode_security_struct *isec = inode->i_security; + struct superblock_security_struct *sbsec = inode->i_sb->s_security; + + if (!sbsec->initialized) { + /* Defer initialization to selinux_complete_init. */ + return 0; + } + + down(&isec->sem); + isec->sclass = inode_mode_to_security_class(inode->i_mode); + isec->sid = sid; + isec->initialized = 1; + up(&isec->sem); + return 0; +} + +/* Set the security attributes on a newly created file. */ +static int post_create(struct inode *dir, + struct dentry *dentry) +{ + + struct task_security_struct *tsec; + struct inode *inode; + struct inode_security_struct *dsec; + struct superblock_security_struct *sbsec; + u32 newsid; + char *context; + unsigned int len; + int rc; + + tsec = current->security; + dsec = dir->i_security; + sbsec = dir->i_sb->s_security; + + inode = dentry->d_inode; + if (!inode) { + /* Some file system types (e.g. NFS) may not instantiate + a dentry for all create operations (e.g. symlink), + so we have to check to see if the inode is non-NULL. */ + printk(KERN_WARNING "post_create: no inode, dir (dev=%s, " + "ino=%ld)\n", dir->i_sb->s_id, dir->i_ino); + return 0; + } + + if (tsec->create_sid && sbsec->behavior != SECURITY_FS_USE_MNTPOINT) { + newsid = tsec->create_sid; + } else { + rc = security_transition_sid(tsec->sid, dsec->sid, + inode_mode_to_security_class(inode->i_mode), + &newsid); + if (rc) { + printk(KERN_WARNING "post_create: " + "security_transition_sid failed, rc=%d (dev=%s " + "ino=%ld)\n", + -rc, inode->i_sb->s_id, inode->i_ino); + return rc; + } + } + + rc = inode_security_set_sid(inode, newsid); + if (rc) { + printk(KERN_WARNING "post_create: inode_security_set_sid " + "failed, rc=%d (dev=%s ino=%ld)\n", + -rc, inode->i_sb->s_id, inode->i_ino); + return rc; + } + + if (sbsec->behavior == SECURITY_FS_USE_XATTR && + inode->i_op->setxattr) { + /* Use extended attributes. */ + rc = security_sid_to_context(newsid, &context, &len); + if (rc) { + printk(KERN_WARNING "post_create: sid_to_context " + "failed, rc=%d (dev=%s ino=%ld)\n", + -rc, inode->i_sb->s_id, inode->i_ino); + return rc; + } + down(&inode->i_sem); + rc = inode->i_op->setxattr(dentry, + XATTR_NAME_SELINUX, + context, len, 0); + up(&inode->i_sem); + kfree(context); + if (rc < 0) { + printk(KERN_WARNING "post_create: setxattr failed, " + "rc=%d (dev=%s ino=%ld)\n", + -rc, inode->i_sb->s_id, inode->i_ino); + return rc; + } + } + + return 0; +} + + +/* Hook functions begin here. */ + +static int selinux_ptrace(struct task_struct *parent, struct task_struct *child) +{ + struct task_security_struct *psec = parent->security; + struct task_security_struct *csec = child->security; + int rc; + + rc = secondary_ops->ptrace(parent,child); + if (rc) + return rc; + + rc = task_has_perm(parent, child, PROCESS__PTRACE); + /* Save the SID of the tracing process for later use in apply_creds. */ + if (!rc) + csec->ptrace_sid = psec->sid; + return rc; +} + +static int selinux_capget(struct task_struct *target, kernel_cap_t *effective, + kernel_cap_t *inheritable, kernel_cap_t *permitted) +{ + int error; + + error = task_has_perm(current, target, PROCESS__GETCAP); + if (error) + return error; + + return secondary_ops->capget(target, effective, inheritable, permitted); +} + +static int selinux_capset_check(struct task_struct *target, kernel_cap_t *effective, + kernel_cap_t *inheritable, kernel_cap_t *permitted) +{ + int error; + + error = secondary_ops->capset_check(target, effective, inheritable, permitted); + if (error) + return error; + + return task_has_perm(current, target, PROCESS__SETCAP); +} + +static void selinux_capset_set(struct task_struct *target, kernel_cap_t *effective, + kernel_cap_t *inheritable, kernel_cap_t *permitted) +{ + secondary_ops->capset_set(target, effective, inheritable, permitted); +} + +static int selinux_capable(struct task_struct *tsk, int cap) +{ + int rc; + + rc = secondary_ops->capable(tsk, cap); + if (rc) + return rc; + + return task_has_capability(tsk,cap); +} + +static int selinux_sysctl(ctl_table *table, int op) +{ + int error = 0; + u32 av; + struct task_security_struct *tsec; + u32 tsid; + int rc; + + rc = secondary_ops->sysctl(table, op); + if (rc) + return rc; + + tsec = current->security; + + rc = selinux_proc_get_sid(table->de, (op == 001) ? + SECCLASS_DIR : SECCLASS_FILE, &tsid); + if (rc) { + /* Default to the well-defined sysctl SID. */ + tsid = SECINITSID_SYSCTL; + } + + /* The op values are "defined" in sysctl.c, thereby creating + * a bad coupling between this module and sysctl.c */ + if(op == 001) { + error = avc_has_perm(tsec->sid, tsid, + SECCLASS_DIR, DIR__SEARCH, NULL); + } else { + av = 0; + if (op & 004) + av |= FILE__READ; + if (op & 002) + av |= FILE__WRITE; + if (av) + error = avc_has_perm(tsec->sid, tsid, + SECCLASS_FILE, av, NULL); + } + + return error; +} + +static int selinux_quotactl(int cmds, int type, int id, struct super_block *sb) +{ + int rc = 0; + + if (!sb) + return 0; + + switch (cmds) { + case Q_SYNC: + case Q_QUOTAON: + case Q_QUOTAOFF: + case Q_SETINFO: + case Q_SETQUOTA: + rc = superblock_has_perm(current, + sb, + FILESYSTEM__QUOTAMOD, NULL); + break; + case Q_GETFMT: + case Q_GETINFO: + case Q_GETQUOTA: + rc = superblock_has_perm(current, + sb, + FILESYSTEM__QUOTAGET, NULL); + break; + default: + rc = 0; /* let the kernel handle invalid cmds */ + break; + } + return rc; +} + +static int selinux_quota_on(struct dentry *dentry) +{ + return dentry_has_perm(current, NULL, dentry, FILE__QUOTAON); +} + +static int selinux_syslog(int type) +{ + int rc; + + rc = secondary_ops->syslog(type); + if (rc) + return rc; + + switch (type) { + case 3: /* Read last kernel messages */ + case 10: /* Return size of the log buffer */ + rc = task_has_system(current, SYSTEM__SYSLOG_READ); + break; + case 6: /* Disable logging to console */ + case 7: /* Enable logging to console */ + case 8: /* Set level of messages printed to console */ + rc = task_has_system(current, SYSTEM__SYSLOG_CONSOLE); + break; + case 0: /* Close log */ + case 1: /* Open log */ + case 2: /* Read from log */ + case 4: /* Read/clear last kernel messages */ + case 5: /* Clear ring buffer */ + default: + rc = task_has_system(current, SYSTEM__SYSLOG_MOD); + break; + } + return rc; +} + +/* + * Check that a process has enough memory to allocate a new virtual + * mapping. 0 means there is enough memory for the allocation to + * succeed and -ENOMEM implies there is not. + * + * Note that secondary_ops->capable and task_has_perm_noaudit return 0 + * if the capability is granted, but __vm_enough_memory requires 1 if + * the capability is granted. + * + * Do not audit the selinux permission check, as this is applied to all + * processes that allocate mappings. + */ +static int selinux_vm_enough_memory(long pages) +{ + int rc, cap_sys_admin = 0; + struct task_security_struct *tsec = current->security; + + rc = secondary_ops->capable(current, CAP_SYS_ADMIN); + if (rc == 0) + rc = avc_has_perm_noaudit(tsec->sid, tsec->sid, + SECCLASS_CAPABILITY, + CAP_TO_MASK(CAP_SYS_ADMIN), + NULL); + + if (rc == 0) + cap_sys_admin = 1; + + return __vm_enough_memory(pages, cap_sys_admin); +} + +/* binprm security operations */ + +static int selinux_bprm_alloc_security(struct linux_binprm *bprm) +{ + struct bprm_security_struct *bsec; + + bsec = kmalloc(sizeof(struct bprm_security_struct), GFP_KERNEL); + if (!bsec) + return -ENOMEM; + + memset(bsec, 0, sizeof *bsec); + bsec->magic = SELINUX_MAGIC; + bsec->bprm = bprm; + bsec->sid = SECINITSID_UNLABELED; + bsec->set = 0; + + bprm->security = bsec; + return 0; +} + +static int selinux_bprm_set_security(struct linux_binprm *bprm) +{ + struct task_security_struct *tsec; + struct inode *inode = bprm->file->f_dentry->d_inode; + struct inode_security_struct *isec; + struct bprm_security_struct *bsec; + u32 newsid; + struct avc_audit_data ad; + int rc; + + rc = secondary_ops->bprm_set_security(bprm); + if (rc) + return rc; + + bsec = bprm->security; + + if (bsec->set) + return 0; + + tsec = current->security; + isec = inode->i_security; + + /* Default to the current task SID. */ + bsec->sid = tsec->sid; + + /* Reset create SID on execve. */ + tsec->create_sid = 0; + + if (tsec->exec_sid) { + newsid = tsec->exec_sid; + /* Reset exec SID on execve. */ + tsec->exec_sid = 0; + } else { + /* Check for a default transition on this program. */ + rc = security_transition_sid(tsec->sid, isec->sid, + SECCLASS_PROCESS, &newsid); + if (rc) + return rc; + } + + AVC_AUDIT_DATA_INIT(&ad, FS); + ad.u.fs.mnt = bprm->file->f_vfsmnt; + ad.u.fs.dentry = bprm->file->f_dentry; + + if (bprm->file->f_vfsmnt->mnt_flags & MNT_NOSUID) + newsid = tsec->sid; + + if (tsec->sid == newsid) { + rc = avc_has_perm(tsec->sid, isec->sid, + SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad); + if (rc) + return rc; + } else { + /* Check permissions for the transition. */ + rc = avc_has_perm(tsec->sid, newsid, + SECCLASS_PROCESS, PROCESS__TRANSITION, &ad); + if (rc) + return rc; + + rc = avc_has_perm(newsid, isec->sid, + SECCLASS_FILE, FILE__ENTRYPOINT, &ad); + if (rc) + return rc; + + /* Clear any possibly unsafe personality bits on exec: */ + current->personality &= ~PER_CLEAR_ON_SETID; + + /* Set the security field to the new SID. */ + bsec->sid = newsid; + } + + bsec->set = 1; + return 0; +} + +static int selinux_bprm_check_security (struct linux_binprm *bprm) +{ + return secondary_ops->bprm_check_security(bprm); +} + + +static int selinux_bprm_secureexec (struct linux_binprm *bprm) +{ + struct task_security_struct *tsec = current->security; + int atsecure = 0; + + if (tsec->osid != tsec->sid) { + /* Enable secure mode for SIDs transitions unless + the noatsecure permission is granted between + the two SIDs, i.e. ahp returns 0. */ + atsecure = avc_has_perm(tsec->osid, tsec->sid, + SECCLASS_PROCESS, + PROCESS__NOATSECURE, NULL); + } + + return (atsecure || secondary_ops->bprm_secureexec(bprm)); +} + +static void selinux_bprm_free_security(struct linux_binprm *bprm) +{ + struct bprm_security_struct *bsec = bprm->security; + bprm->security = NULL; + kfree(bsec); +} + +extern struct vfsmount *selinuxfs_mount; +extern struct dentry *selinux_null; + +/* Derived from fs/exec.c:flush_old_files. */ +static inline void flush_unauthorized_files(struct files_struct * files) +{ + struct avc_audit_data ad; + struct file *file, *devnull = NULL; + struct tty_struct *tty = current->signal->tty; + long j = -1; + + if (tty) { + file_list_lock(); + file = list_entry(tty->tty_files.next, typeof(*file), f_list); + if (file) { + /* Revalidate access to controlling tty. + Use inode_has_perm on the tty inode directly rather + than using file_has_perm, as this particular open + file may belong to another process and we are only + interested in the inode-based check here. */ + struct inode *inode = file->f_dentry->d_inode; + if (inode_has_perm(current, inode, + FILE__READ | FILE__WRITE, NULL)) { + /* Reset controlling tty. */ + current->signal->tty = NULL; + current->signal->tty_old_pgrp = 0; + } + } + file_list_unlock(); + } + + /* Revalidate access to inherited open files. */ + + AVC_AUDIT_DATA_INIT(&ad,FS); + + spin_lock(&files->file_lock); + for (;;) { + unsigned long set, i; + int fd; + + j++; + i = j * __NFDBITS; + if (i >= files->max_fds || i >= files->max_fdset) + break; + set = files->open_fds->fds_bits[j]; + if (!set) + continue; + spin_unlock(&files->file_lock); + for ( ; set ; i++,set >>= 1) { + if (set & 1) { + file = fget(i); + if (!file) + continue; + if (file_has_perm(current, + file, + file_to_av(file))) { + sys_close(i); + fd = get_unused_fd(); + if (fd != i) { + if (fd >= 0) + put_unused_fd(fd); + fput(file); + continue; + } + if (devnull) { + atomic_inc(&devnull->f_count); + } else { + devnull = dentry_open(dget(selinux_null), mntget(selinuxfs_mount), O_RDWR); + if (!devnull) { + put_unused_fd(fd); + fput(file); + continue; + } + } + fd_install(fd, devnull); + } + fput(file); + } + } + spin_lock(&files->file_lock); + + } + spin_unlock(&files->file_lock); +} + +static void selinux_bprm_apply_creds(struct linux_binprm *bprm, int unsafe) +{ + struct task_security_struct *tsec; + struct bprm_security_struct *bsec; + u32 sid; + int rc; + + secondary_ops->bprm_apply_creds(bprm, unsafe); + + tsec = current->security; + + bsec = bprm->security; + sid = bsec->sid; + + tsec->osid = tsec->sid; + bsec->unsafe = 0; + if (tsec->sid != sid) { + /* Check for shared state. If not ok, leave SID + unchanged and kill. */ + if (unsafe & LSM_UNSAFE_SHARE) { + rc = avc_has_perm(tsec->sid, sid, SECCLASS_PROCESS, + PROCESS__SHARE, NULL); + if (rc) { + bsec->unsafe = 1; + return; + } + } + + /* Check for ptracing, and update the task SID if ok. + Otherwise, leave SID unchanged and kill. */ + if (unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) { + rc = avc_has_perm(tsec->ptrace_sid, sid, + SECCLASS_PROCESS, PROCESS__PTRACE, + NULL); + if (rc) { + bsec->unsafe = 1; + return; + } + } + tsec->sid = sid; + } +} + +/* + * called after apply_creds without the task lock held + */ +static void selinux_bprm_post_apply_creds(struct linux_binprm *bprm) +{ + struct task_security_struct *tsec; + struct rlimit *rlim, *initrlim; + struct itimerval itimer; + struct bprm_security_struct *bsec; + int rc, i; + + tsec = current->security; + bsec = bprm->security; + + if (bsec->unsafe) { + force_sig_specific(SIGKILL, current); + return; + } + if (tsec->osid == tsec->sid) + return; + + /* Close files for which the new task SID is not authorized. */ + flush_unauthorized_files(current->files); + + /* Check whether the new SID can inherit signal state + from the old SID. If not, clear itimers to avoid + subsequent signal generation and flush and unblock + signals. This must occur _after_ the task SID has + been updated so that any kill done after the flush + will be checked against the new SID. */ + rc = avc_has_perm(tsec->osid, tsec->sid, SECCLASS_PROCESS, + PROCESS__SIGINH, NULL); + if (rc) { + memset(&itimer, 0, sizeof itimer); + for (i = 0; i < 3; i++) + do_setitimer(i, &itimer, NULL); + flush_signals(current); + spin_lock_irq(¤t->sighand->siglock); + flush_signal_handlers(current, 1); + sigemptyset(¤t->blocked); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + } + + /* Check whether the new SID can inherit resource limits + from the old SID. If not, reset all soft limits to + the lower of the current task's hard limit and the init + task's soft limit. Note that the setting of hard limits + (even to lower them) can be controlled by the setrlimit + check. The inclusion of the init task's soft limit into + the computation is to avoid resetting soft limits higher + than the default soft limit for cases where the default + is lower than the hard limit, e.g. RLIMIT_CORE or + RLIMIT_STACK.*/ + rc = avc_has_perm(tsec->osid, tsec->sid, SECCLASS_PROCESS, + PROCESS__RLIMITINH, NULL); + if (rc) { + for (i = 0; i < RLIM_NLIMITS; i++) { + rlim = current->signal->rlim + i; + initrlim = init_task.signal->rlim+i; + rlim->rlim_cur = min(rlim->rlim_max,initrlim->rlim_cur); + } + if (current->signal->rlim[RLIMIT_CPU].rlim_cur != RLIM_INFINITY) { + /* + * This will cause RLIMIT_CPU calculations + * to be refigured. + */ + current->it_prof_expires = jiffies_to_cputime(1); + } + } + + /* Wake up the parent if it is waiting so that it can + recheck wait permission to the new task SID. */ + wake_up_interruptible(¤t->parent->signal->wait_chldexit); +} + +/* superblock security operations */ + +static int selinux_sb_alloc_security(struct super_block *sb) +{ + return superblock_alloc_security(sb); +} + +static void selinux_sb_free_security(struct super_block *sb) +{ + superblock_free_security(sb); +} + +static inline int match_prefix(char *prefix, int plen, char *option, int olen) +{ + if (plen > olen) + return 0; + + return !memcmp(prefix, option, plen); +} + +static inline int selinux_option(char *option, int len) +{ + return (match_prefix("context=", sizeof("context=")-1, option, len) || + match_prefix("fscontext=", sizeof("fscontext=")-1, option, len) || + match_prefix("defcontext=", sizeof("defcontext=")-1, option, len)); +} + +static inline void take_option(char **to, char *from, int *first, int len) +{ + if (!*first) { + **to = ','; + *to += 1; + } + else + *first = 0; + memcpy(*to, from, len); + *to += len; +} + +static int selinux_sb_copy_data(struct file_system_type *type, void *orig, void *copy) +{ + int fnosec, fsec, rc = 0; + char *in_save, *in_curr, *in_end; + char *sec_curr, *nosec_save, *nosec; + + in_curr = orig; + sec_curr = copy; + + /* Binary mount data: just copy */ + if (type->fs_flags & FS_BINARY_MOUNTDATA) { + copy_page(sec_curr, in_curr); + goto out; + } + + nosec = (char *)get_zeroed_page(GFP_KERNEL); + if (!nosec) { + rc = -ENOMEM; + goto out; + } + + nosec_save = nosec; + fnosec = fsec = 1; + in_save = in_end = orig; + + do { + if (*in_end == ',' || *in_end == '\0') { + int len = in_end - in_curr; + + if (selinux_option(in_curr, len)) + take_option(&sec_curr, in_curr, &fsec, len); + else + take_option(&nosec, in_curr, &fnosec, len); + + in_curr = in_end + 1; + } + } while (*in_end++); + + copy_page(in_save, nosec_save); +out: + return rc; +} + +static int selinux_sb_kern_mount(struct super_block *sb, void *data) +{ + struct avc_audit_data ad; + int rc; + + rc = superblock_doinit(sb, data); + if (rc) + return rc; + + AVC_AUDIT_DATA_INIT(&ad,FS); + ad.u.fs.dentry = sb->s_root; + return superblock_has_perm(current, sb, FILESYSTEM__MOUNT, &ad); +} + +static int selinux_sb_statfs(struct super_block *sb) +{ + struct avc_audit_data ad; + + AVC_AUDIT_DATA_INIT(&ad,FS); + ad.u.fs.dentry = sb->s_root; + return superblock_has_perm(current, sb, FILESYSTEM__GETATTR, &ad); +} + +static int selinux_mount(char * dev_name, + struct nameidata *nd, + char * type, + unsigned long flags, + void * data) +{ + int rc; + + rc = secondary_ops->sb_mount(dev_name, nd, type, flags, data); + if (rc) + return rc; + + if (flags & MS_REMOUNT) + return superblock_has_perm(current, nd->mnt->mnt_sb, + FILESYSTEM__REMOUNT, NULL); + else + return dentry_has_perm(current, nd->mnt, nd->dentry, + FILE__MOUNTON); +} + +static int selinux_umount(struct vfsmount *mnt, int flags) +{ + int rc; + + rc = secondary_ops->sb_umount(mnt, flags); + if (rc) + return rc; + + return superblock_has_perm(current,mnt->mnt_sb, + FILESYSTEM__UNMOUNT,NULL); +} + +/* inode security operations */ + +static int selinux_inode_alloc_security(struct inode *inode) +{ + return inode_alloc_security(inode); +} + +static void selinux_inode_free_security(struct inode *inode) +{ + inode_free_security(inode); +} + +static int selinux_inode_create(struct inode *dir, struct dentry *dentry, int mask) +{ + return may_create(dir, dentry, SECCLASS_FILE); +} + +static void selinux_inode_post_create(struct inode *dir, struct dentry *dentry, int mask) +{ + post_create(dir, dentry); +} + +static int selinux_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) +{ + int rc; + + rc = secondary_ops->inode_link(old_dentry,dir,new_dentry); + if (rc) + return rc; + return may_link(dir, old_dentry, MAY_LINK); +} + +static void selinux_inode_post_link(struct dentry *old_dentry, struct inode *inode, struct dentry *new_dentry) +{ + return; +} + +static int selinux_inode_unlink(struct inode *dir, struct dentry *dentry) +{ + int rc; + + rc = secondary_ops->inode_unlink(dir, dentry); + if (rc) + return rc; + return may_link(dir, dentry, MAY_UNLINK); +} + +static int selinux_inode_symlink(struct inode *dir, struct dentry *dentry, const char *name) +{ + return may_create(dir, dentry, SECCLASS_LNK_FILE); +} + +static void selinux_inode_post_symlink(struct inode *dir, struct dentry *dentry, const char *name) +{ + post_create(dir, dentry); +} + +static int selinux_inode_mkdir(struct inode *dir, struct dentry *dentry, int mask) +{ + return may_create(dir, dentry, SECCLASS_DIR); +} + +static void selinux_inode_post_mkdir(struct inode *dir, struct dentry *dentry, int mask) +{ + post_create(dir, dentry); +} + +static int selinux_inode_rmdir(struct inode *dir, struct dentry *dentry) +{ + return may_link(dir, dentry, MAY_RMDIR); +} + +static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) +{ + int rc; + + rc = secondary_ops->inode_mknod(dir, dentry, mode, dev); + if (rc) + return rc; + + return may_create(dir, dentry, inode_mode_to_security_class(mode)); +} + +static void selinux_inode_post_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) +{ + post_create(dir, dentry); +} + +static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dentry, + struct inode *new_inode, struct dentry *new_dentry) +{ + return may_rename(old_inode, old_dentry, new_inode, new_dentry); +} + +static void selinux_inode_post_rename(struct inode *old_inode, struct dentry *old_dentry, + struct inode *new_inode, struct dentry *new_dentry) +{ + return; +} + +static int selinux_inode_readlink(struct dentry *dentry) +{ + return dentry_has_perm(current, NULL, dentry, FILE__READ); +} + +static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *nameidata) +{ + int rc; + + rc = secondary_ops->inode_follow_link(dentry,nameidata); + if (rc) + return rc; + return dentry_has_perm(current, NULL, dentry, FILE__READ); +} + +static int selinux_inode_permission(struct inode *inode, int mask, + struct nameidata *nd) +{ + int rc; + + rc = secondary_ops->inode_permission(inode, mask, nd); + if (rc) + return rc; + + if (!mask) { + /* No permission to check. Existence test. */ + return 0; + } + + return inode_has_perm(current, inode, + file_mask_to_av(inode->i_mode, mask), NULL); +} + +static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) +{ + int rc; + + rc = secondary_ops->inode_setattr(dentry, iattr); + if (rc) + return rc; + + if (iattr->ia_valid & ATTR_FORCE) + return 0; + + if (iattr->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID | + ATTR_ATIME_SET | ATTR_MTIME_SET)) + return dentry_has_perm(current, NULL, dentry, FILE__SETATTR); + + return dentry_has_perm(current, NULL, dentry, FILE__WRITE); +} + +static int selinux_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) +{ + return dentry_has_perm(current, mnt, dentry, FILE__GETATTR); +} + +static int selinux_inode_setxattr(struct dentry *dentry, char *name, void *value, size_t size, int flags) +{ + struct task_security_struct *tsec = current->security; + struct inode *inode = dentry->d_inode; + struct inode_security_struct *isec = inode->i_security; + struct superblock_security_struct *sbsec; + struct avc_audit_data ad; + u32 newsid; + int rc = 0; + + if (strcmp(name, XATTR_NAME_SELINUX)) { + if (!strncmp(name, XATTR_SECURITY_PREFIX, + sizeof XATTR_SECURITY_PREFIX - 1) && + !capable(CAP_SYS_ADMIN)) { + /* A different attribute in the security namespace. + Restrict to administrator. */ + return -EPERM; + } + + /* Not an attribute we recognize, so just check the + ordinary setattr permission. */ + return dentry_has_perm(current, NULL, dentry, FILE__SETATTR); + } + + sbsec = inode->i_sb->s_security; + if (sbsec->behavior == SECURITY_FS_USE_MNTPOINT) + return -EOPNOTSUPP; + + if ((current->fsuid != inode->i_uid) && !capable(CAP_FOWNER)) + return -EPERM; + + AVC_AUDIT_DATA_INIT(&ad,FS); + ad.u.fs.dentry = dentry; + + rc = avc_has_perm(tsec->sid, isec->sid, isec->sclass, + FILE__RELABELFROM, &ad); + if (rc) + return rc; + + rc = security_context_to_sid(value, size, &newsid); + if (rc) + return rc; + + rc = avc_has_perm(tsec->sid, newsid, isec->sclass, + FILE__RELABELTO, &ad); + if (rc) + return rc; + + rc = security_validate_transition(isec->sid, newsid, tsec->sid, + isec->sclass); + if (rc) + return rc; + + return avc_has_perm(newsid, + sbsec->sid, + SECCLASS_FILESYSTEM, + FILESYSTEM__ASSOCIATE, + &ad); +} + +static void selinux_inode_post_setxattr(struct dentry *dentry, char *name, + void *value, size_t size, int flags) +{ + struct inode *inode = dentry->d_inode; + struct inode_security_struct *isec = inode->i_security; + u32 newsid; + int rc; + + if (strcmp(name, XATTR_NAME_SELINUX)) { + /* Not an attribute we recognize, so nothing to do. */ + return; + } + + rc = security_context_to_sid(value, size, &newsid); + if (rc) { + printk(KERN_WARNING "%s: unable to obtain SID for context " + "%s, rc=%d\n", __FUNCTION__, (char*)value, -rc); + return; + } + + isec->sid = newsid; + return; +} + +static int selinux_inode_getxattr (struct dentry *dentry, char *name) +{ + struct inode *inode = dentry->d_inode; + struct superblock_security_struct *sbsec = inode->i_sb->s_security; + + if (sbsec->behavior == SECURITY_FS_USE_MNTPOINT) + return -EOPNOTSUPP; + + return dentry_has_perm(current, NULL, dentry, FILE__GETATTR); +} + +static int selinux_inode_listxattr (struct dentry *dentry) +{ + return dentry_has_perm(current, NULL, dentry, FILE__GETATTR); +} + +static int selinux_inode_removexattr (struct dentry *dentry, char *name) +{ + if (strcmp(name, XATTR_NAME_SELINUX)) { + if (!strncmp(name, XATTR_SECURITY_PREFIX, + sizeof XATTR_SECURITY_PREFIX - 1) && + !capable(CAP_SYS_ADMIN)) { + /* A different attribute in the security namespace. + Restrict to administrator. */ + return -EPERM; + } + + /* Not an attribute we recognize, so just check the + ordinary setattr permission. Might want a separate + permission for removexattr. */ + return dentry_has_perm(current, NULL, dentry, FILE__SETATTR); + } + + /* No one is allowed to remove a SELinux security label. + You can change the label, but all data must be labeled. */ + return -EACCES; +} + +static int selinux_inode_getsecurity(struct inode *inode, const char *name, void *buffer, size_t size) +{ + struct inode_security_struct *isec = inode->i_security; + char *context; + unsigned len; + int rc; + + /* Permission check handled by selinux_inode_getxattr hook.*/ + + if (strcmp(name, XATTR_SELINUX_SUFFIX)) + return -EOPNOTSUPP; + + rc = security_sid_to_context(isec->sid, &context, &len); + if (rc) + return rc; + + if (!buffer || !size) { + kfree(context); + return len; + } + if (size < len) { + kfree(context); + return -ERANGE; + } + memcpy(buffer, context, len); + kfree(context); + return len; +} + +static int selinux_inode_setsecurity(struct inode *inode, const char *name, + const void *value, size_t size, int flags) +{ + struct inode_security_struct *isec = inode->i_security; + u32 newsid; + int rc; + + if (strcmp(name, XATTR_SELINUX_SUFFIX)) + return -EOPNOTSUPP; + + if (!value || !size) + return -EACCES; + + rc = security_context_to_sid((void*)value, size, &newsid); + if (rc) + return rc; + + isec->sid = newsid; + return 0; +} + +static int selinux_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size) +{ + const int len = sizeof(XATTR_NAME_SELINUX); + if (buffer && len <= buffer_size) + memcpy(buffer, XATTR_NAME_SELINUX, len); + return len; +} + +/* file security operations */ + +static int selinux_file_permission(struct file *file, int mask) +{ + struct inode *inode = file->f_dentry->d_inode; + + if (!mask) { + /* No permission to check. Existence test. */ + return 0; + } + + /* file_mask_to_av won't add FILE__WRITE if MAY_APPEND is set */ + if ((file->f_flags & O_APPEND) && (mask & MAY_WRITE)) + mask |= MAY_APPEND; + + return file_has_perm(current, file, + file_mask_to_av(inode->i_mode, mask)); +} + +static int selinux_file_alloc_security(struct file *file) +{ + return file_alloc_security(file); +} + +static void selinux_file_free_security(struct file *file) +{ + file_free_security(file); +} + +static int selinux_file_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int error = 0; + + switch (cmd) { + case FIONREAD: + /* fall through */ + case FIBMAP: + /* fall through */ + case FIGETBSZ: + /* fall through */ + case EXT2_IOC_GETFLAGS: + /* fall through */ + case EXT2_IOC_GETVERSION: + error = file_has_perm(current, file, FILE__GETATTR); + break; + + case EXT2_IOC_SETFLAGS: + /* fall through */ + case EXT2_IOC_SETVERSION: + error = file_has_perm(current, file, FILE__SETATTR); + break; + + /* sys_ioctl() checks */ + case FIONBIO: + /* fall through */ + case FIOASYNC: + error = file_has_perm(current, file, 0); + break; + + case KDSKBENT: + case KDSKBSENT: + error = task_has_capability(current,CAP_SYS_TTY_CONFIG); + break; + + /* default case assumes that the command will go + * to the file's ioctl() function. + */ + default: + error = file_has_perm(current, file, FILE__IOCTL); + + } + return error; +} + +static int file_map_prot_check(struct file *file, unsigned long prot, int shared) +{ +#ifndef CONFIG_PPC32 + if ((prot & PROT_EXEC) && (!file || (!shared && (prot & PROT_WRITE)))) { + /* + * We are making executable an anonymous mapping or a + * private file mapping that will also be writable. + * This has an additional check. + */ + int rc = task_has_perm(current, current, PROCESS__EXECMEM); + if (rc) + return rc; + } +#endif + + if (file) { + /* read access is always possible with a mapping */ + u32 av = FILE__READ; + + /* write access only matters if the mapping is shared */ + if (shared && (prot & PROT_WRITE)) + av |= FILE__WRITE; + + if (prot & PROT_EXEC) + av |= FILE__EXECUTE; + + return file_has_perm(current, file, av); + } + return 0; +} + +static int selinux_file_mmap(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) +{ + int rc; + + rc = secondary_ops->file_mmap(file, reqprot, prot, flags); + if (rc) + return rc; + + if (selinux_checkreqprot) + prot = reqprot; + + return file_map_prot_check(file, prot, + (flags & MAP_TYPE) == MAP_SHARED); +} + +static int selinux_file_mprotect(struct vm_area_struct *vma, + unsigned long reqprot, + unsigned long prot) +{ + int rc; + + rc = secondary_ops->file_mprotect(vma, reqprot, prot); + if (rc) + return rc; + + if (selinux_checkreqprot) + prot = reqprot; + +#ifndef CONFIG_PPC32 + if (vma->vm_file != NULL && vma->anon_vma != NULL && (prot & PROT_EXEC)) { + /* + * We are making executable a file mapping that has + * had some COW done. Since pages might have been written, + * check ability to execute the possibly modified content. + * This typically should only occur for text relocations. + */ + int rc = file_has_perm(current, vma->vm_file, FILE__EXECMOD); + if (rc) + return rc; + } +#endif + + return file_map_prot_check(vma->vm_file, prot, vma->vm_flags&VM_SHARED); +} + +static int selinux_file_lock(struct file *file, unsigned int cmd) +{ + return file_has_perm(current, file, FILE__LOCK); +} + +static int selinux_file_fcntl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int err = 0; + + switch (cmd) { + case F_SETFL: + if (!file->f_dentry || !file->f_dentry->d_inode) { + err = -EINVAL; + break; + } + + if ((file->f_flags & O_APPEND) && !(arg & O_APPEND)) { + err = file_has_perm(current, file,FILE__WRITE); + break; + } + /* fall through */ + case F_SETOWN: + case F_SETSIG: + case F_GETFL: + case F_GETOWN: + case F_GETSIG: + /* Just check FD__USE permission */ + err = file_has_perm(current, file, 0); + break; + case F_GETLK: + case F_SETLK: + case F_SETLKW: +#if BITS_PER_LONG == 32 + case F_GETLK64: + case F_SETLK64: + case F_SETLKW64: +#endif + if (!file->f_dentry || !file->f_dentry->d_inode) { + err = -EINVAL; + break; + } + err = file_has_perm(current, file, FILE__LOCK); + break; + } + + return err; +} + +static int selinux_file_set_fowner(struct file *file) +{ + struct task_security_struct *tsec; + struct file_security_struct *fsec; + + tsec = current->security; + fsec = file->f_security; + fsec->fown_sid = tsec->sid; + + return 0; +} + +static int selinux_file_send_sigiotask(struct task_struct *tsk, + struct fown_struct *fown, int signum) +{ + struct file *file; + u32 perm; + struct task_security_struct *tsec; + struct file_security_struct *fsec; + + /* struct fown_struct is never outside the context of a struct file */ + file = (struct file *)((long)fown - offsetof(struct file,f_owner)); + + tsec = tsk->security; + fsec = file->f_security; + + if (!signum) + perm = signal_to_av(SIGIO); /* as per send_sigio_to_task */ + else + perm = signal_to_av(signum); + + return avc_has_perm(fsec->fown_sid, tsec->sid, + SECCLASS_PROCESS, perm, NULL); +} + +static int selinux_file_receive(struct file *file) +{ + return file_has_perm(current, file, file_to_av(file)); +} + +/* task security operations */ + +static int selinux_task_create(unsigned long clone_flags) +{ + int rc; + + rc = secondary_ops->task_create(clone_flags); + if (rc) + return rc; + + return task_has_perm(current, current, PROCESS__FORK); +} + +static int selinux_task_alloc_security(struct task_struct *tsk) +{ + struct task_security_struct *tsec1, *tsec2; + int rc; + + tsec1 = current->security; + + rc = task_alloc_security(tsk); + if (rc) + return rc; + tsec2 = tsk->security; + + tsec2->osid = tsec1->osid; + tsec2->sid = tsec1->sid; + + /* Retain the exec and create SIDs across fork */ + tsec2->exec_sid = tsec1->exec_sid; + tsec2->create_sid = tsec1->create_sid; + + /* Retain ptracer SID across fork, if any. + This will be reset by the ptrace hook upon any + subsequent ptrace_attach operations. */ + tsec2->ptrace_sid = tsec1->ptrace_sid; + + return 0; +} + +static void selinux_task_free_security(struct task_struct *tsk) +{ + task_free_security(tsk); +} + +static int selinux_task_setuid(uid_t id0, uid_t id1, uid_t id2, int flags) +{ + /* Since setuid only affects the current process, and + since the SELinux controls are not based on the Linux + identity attributes, SELinux does not need to control + this operation. However, SELinux does control the use + of the CAP_SETUID and CAP_SETGID capabilities using the + capable hook. */ + return 0; +} + +static int selinux_task_post_setuid(uid_t id0, uid_t id1, uid_t id2, int flags) +{ + return secondary_ops->task_post_setuid(id0,id1,id2,flags); +} + +static int selinux_task_setgid(gid_t id0, gid_t id1, gid_t id2, int flags) +{ + /* See the comment for setuid above. */ + return 0; +} + +static int selinux_task_setpgid(struct task_struct *p, pid_t pgid) +{ + return task_has_perm(current, p, PROCESS__SETPGID); +} + +static int selinux_task_getpgid(struct task_struct *p) +{ + return task_has_perm(current, p, PROCESS__GETPGID); +} + +static int selinux_task_getsid(struct task_struct *p) +{ + return task_has_perm(current, p, PROCESS__GETSESSION); +} + +static int selinux_task_setgroups(struct group_info *group_info) +{ + /* See the comment for setuid above. */ + return 0; +} + +static int selinux_task_setnice(struct task_struct *p, int nice) +{ + int rc; + + rc = secondary_ops->task_setnice(p, nice); + if (rc) + return rc; + + return task_has_perm(current,p, PROCESS__SETSCHED); +} + +static int selinux_task_setrlimit(unsigned int resource, struct rlimit *new_rlim) +{ + struct rlimit *old_rlim = current->signal->rlim + resource; + int rc; + + rc = secondary_ops->task_setrlimit(resource, new_rlim); + if (rc) + return rc; + + /* Control the ability to change the hard limit (whether + lowering or raising it), so that the hard limit can + later be used as a safe reset point for the soft limit + upon context transitions. See selinux_bprm_apply_creds. */ + if (old_rlim->rlim_max != new_rlim->rlim_max) + return task_has_perm(current, current, PROCESS__SETRLIMIT); + + return 0; +} + +static int selinux_task_setscheduler(struct task_struct *p, int policy, struct sched_param *lp) +{ + return task_has_perm(current, p, PROCESS__SETSCHED); +} + +static int selinux_task_getscheduler(struct task_struct *p) +{ + return task_has_perm(current, p, PROCESS__GETSCHED); +} + +static int selinux_task_kill(struct task_struct *p, struct siginfo *info, int sig) +{ + u32 perm; + int rc; + + rc = secondary_ops->task_kill(p, info, sig); + if (rc) + return rc; + + if (info && ((unsigned long)info == 1 || + (unsigned long)info == 2 || SI_FROMKERNEL(info))) + return 0; + + if (!sig) + perm = PROCESS__SIGNULL; /* null signal; existence test */ + else + perm = signal_to_av(sig); + + return task_has_perm(current, p, perm); +} + +static int selinux_task_prctl(int option, + unsigned long arg2, + unsigned long arg3, + unsigned long arg4, + unsigned long arg5) +{ + /* The current prctl operations do not appear to require + any SELinux controls since they merely observe or modify + the state of the current process. */ + return 0; +} + +static int selinux_task_wait(struct task_struct *p) +{ + u32 perm; + + perm = signal_to_av(p->exit_signal); + + return task_has_perm(p, current, perm); +} + +static void selinux_task_reparent_to_init(struct task_struct *p) +{ + struct task_security_struct *tsec; + + secondary_ops->task_reparent_to_init(p); + + tsec = p->security; + tsec->osid = tsec->sid; + tsec->sid = SECINITSID_KERNEL; + return; +} + +static void selinux_task_to_inode(struct task_struct *p, + struct inode *inode) +{ + struct task_security_struct *tsec = p->security; + struct inode_security_struct *isec = inode->i_security; + + isec->sid = tsec->sid; + isec->initialized = 1; + return; +} + +#ifdef CONFIG_SECURITY_NETWORK + +/* Returns error only if unable to parse addresses */ +static int selinux_parse_skb_ipv4(struct sk_buff *skb, struct avc_audit_data *ad) +{ + int offset, ihlen, ret = -EINVAL; + struct iphdr _iph, *ih; + + offset = skb->nh.raw - skb->data; + ih = skb_header_pointer(skb, offset, sizeof(_iph), &_iph); + if (ih == NULL) + goto out; + + ihlen = ih->ihl * 4; + if (ihlen < sizeof(_iph)) + goto out; + + ad->u.net.v4info.saddr = ih->saddr; + ad->u.net.v4info.daddr = ih->daddr; + ret = 0; + + switch (ih->protocol) { + case IPPROTO_TCP: { + struct tcphdr _tcph, *th; + + if (ntohs(ih->frag_off) & IP_OFFSET) + break; + + offset += ihlen; + th = skb_header_pointer(skb, offset, sizeof(_tcph), &_tcph); + if (th == NULL) + break; + + ad->u.net.sport = th->source; + ad->u.net.dport = th->dest; + break; + } + + case IPPROTO_UDP: { + struct udphdr _udph, *uh; + + if (ntohs(ih->frag_off) & IP_OFFSET) + break; + + offset += ihlen; + uh = skb_header_pointer(skb, offset, sizeof(_udph), &_udph); + if (uh == NULL) + break; + + ad->u.net.sport = uh->source; + ad->u.net.dport = uh->dest; + break; + } + + default: + break; + } +out: + return ret; +} + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + +/* Returns error only if unable to parse addresses */ +static int selinux_parse_skb_ipv6(struct sk_buff *skb, struct avc_audit_data *ad) +{ + u8 nexthdr; + int ret = -EINVAL, offset; + struct ipv6hdr _ipv6h, *ip6; + + offset = skb->nh.raw - skb->data; + ip6 = skb_header_pointer(skb, offset, sizeof(_ipv6h), &_ipv6h); + if (ip6 == NULL) + goto out; + + ipv6_addr_copy(&ad->u.net.v6info.saddr, &ip6->saddr); + ipv6_addr_copy(&ad->u.net.v6info.daddr, &ip6->daddr); + ret = 0; + + nexthdr = ip6->nexthdr; + offset += sizeof(_ipv6h); + offset = ipv6_skip_exthdr(skb, offset, &nexthdr, + skb->tail - skb->head - offset); + if (offset < 0) + goto out; + + switch (nexthdr) { + case IPPROTO_TCP: { + struct tcphdr _tcph, *th; + + th = skb_header_pointer(skb, offset, sizeof(_tcph), &_tcph); + if (th == NULL) + break; + + ad->u.net.sport = th->source; + ad->u.net.dport = th->dest; + break; + } + + case IPPROTO_UDP: { + struct udphdr _udph, *uh; + + uh = skb_header_pointer(skb, offset, sizeof(_udph), &_udph); + if (uh == NULL) + break; + + ad->u.net.sport = uh->source; + ad->u.net.dport = uh->dest; + break; + } + + /* includes fragments */ + default: + break; + } +out: + return ret; +} + +#endif /* IPV6 */ + +static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad, + char **addrp, int *len, int src) +{ + int ret = 0; + + switch (ad->u.net.family) { + case PF_INET: + ret = selinux_parse_skb_ipv4(skb, ad); + if (ret || !addrp) + break; + *len = 4; + *addrp = (char *)(src ? &ad->u.net.v4info.saddr : + &ad->u.net.v4info.daddr); + break; + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + case PF_INET6: + ret = selinux_parse_skb_ipv6(skb, ad); + if (ret || !addrp) + break; + *len = 16; + *addrp = (char *)(src ? &ad->u.net.v6info.saddr : + &ad->u.net.v6info.daddr); + break; +#endif /* IPV6 */ + default: + break; + } + + return ret; +} + +/* socket security operations */ +static int socket_has_perm(struct task_struct *task, struct socket *sock, + u32 perms) +{ + struct inode_security_struct *isec; + struct task_security_struct *tsec; + struct avc_audit_data ad; + int err = 0; + + tsec = task->security; + isec = SOCK_INODE(sock)->i_security; + + if (isec->sid == SECINITSID_KERNEL) + goto out; + + AVC_AUDIT_DATA_INIT(&ad,NET); + ad.u.net.sk = sock->sk; + err = avc_has_perm(tsec->sid, isec->sid, isec->sclass, perms, &ad); + +out: + return err; +} + +static int selinux_socket_create(int family, int type, + int protocol, int kern) +{ + int err = 0; + struct task_security_struct *tsec; + + if (kern) + goto out; + + tsec = current->security; + err = avc_has_perm(tsec->sid, tsec->sid, + socket_type_to_security_class(family, type, + protocol), SOCKET__CREATE, NULL); + +out: + return err; +} + +static void selinux_socket_post_create(struct socket *sock, int family, + int type, int protocol, int kern) +{ + struct inode_security_struct *isec; + struct task_security_struct *tsec; + + isec = SOCK_INODE(sock)->i_security; + + tsec = current->security; + isec->sclass = socket_type_to_security_class(family, type, protocol); + isec->sid = kern ? SECINITSID_KERNEL : tsec->sid; + isec->initialized = 1; + + return; +} + +/* Range of port numbers used to automatically bind. + Need to determine whether we should perform a name_bind + permission check between the socket and the port number. */ +#define ip_local_port_range_0 sysctl_local_port_range[0] +#define ip_local_port_range_1 sysctl_local_port_range[1] + +static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) +{ + u16 family; + int err; + + err = socket_has_perm(current, sock, SOCKET__BIND); + if (err) + goto out; + + /* + * If PF_INET or PF_INET6, check name_bind permission for the port. + */ + family = sock->sk->sk_family; + if (family == PF_INET || family == PF_INET6) { + char *addrp; + struct inode_security_struct *isec; + struct task_security_struct *tsec; + struct avc_audit_data ad; + struct sockaddr_in *addr4 = NULL; + struct sockaddr_in6 *addr6 = NULL; + unsigned short snum; + struct sock *sk = sock->sk; + u32 sid, node_perm, addrlen; + + tsec = current->security; + isec = SOCK_INODE(sock)->i_security; + + if (family == PF_INET) { + addr4 = (struct sockaddr_in *)address; + snum = ntohs(addr4->sin_port); + addrlen = sizeof(addr4->sin_addr.s_addr); + addrp = (char *)&addr4->sin_addr.s_addr; + } else { + addr6 = (struct sockaddr_in6 *)address; + snum = ntohs(addr6->sin6_port); + addrlen = sizeof(addr6->sin6_addr.s6_addr); + addrp = (char *)&addr6->sin6_addr.s6_addr; + } + + if (snum&&(snum < max(PROT_SOCK,ip_local_port_range_0) || + snum > ip_local_port_range_1)) { + err = security_port_sid(sk->sk_family, sk->sk_type, + sk->sk_protocol, snum, &sid); + if (err) + goto out; + AVC_AUDIT_DATA_INIT(&ad,NET); + ad.u.net.sport = htons(snum); + ad.u.net.family = family; + err = avc_has_perm(isec->sid, sid, + isec->sclass, + SOCKET__NAME_BIND, &ad); + if (err) + goto out; + } + + switch(sk->sk_protocol) { + case IPPROTO_TCP: + node_perm = TCP_SOCKET__NODE_BIND; + break; + + case IPPROTO_UDP: + node_perm = UDP_SOCKET__NODE_BIND; + break; + + default: + node_perm = RAWIP_SOCKET__NODE_BIND; + break; + } + + err = security_node_sid(family, addrp, addrlen, &sid); + if (err) + goto out; + + AVC_AUDIT_DATA_INIT(&ad,NET); + ad.u.net.sport = htons(snum); + ad.u.net.family = family; + + if (family == PF_INET) + ad.u.net.v4info.saddr = addr4->sin_addr.s_addr; + else + ipv6_addr_copy(&ad.u.net.v6info.saddr, &addr6->sin6_addr); + + err = avc_has_perm(isec->sid, sid, + isec->sclass, node_perm, &ad); + if (err) + goto out; + } +out: + return err; +} + +static int selinux_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen) +{ + struct inode_security_struct *isec; + int err; + + err = socket_has_perm(current, sock, SOCKET__CONNECT); + if (err) + return err; + + /* + * If a TCP socket, check name_connect permission for the port. + */ + isec = SOCK_INODE(sock)->i_security; + if (isec->sclass == SECCLASS_TCP_SOCKET) { + struct sock *sk = sock->sk; + struct avc_audit_data ad; + struct sockaddr_in *addr4 = NULL; + struct sockaddr_in6 *addr6 = NULL; + unsigned short snum; + u32 sid; + + if (sk->sk_family == PF_INET) { + addr4 = (struct sockaddr_in *)address; + if (addrlen != sizeof(struct sockaddr_in)) + return -EINVAL; + snum = ntohs(addr4->sin_port); + } else { + addr6 = (struct sockaddr_in6 *)address; + if (addrlen != sizeof(struct sockaddr_in6)) + return -EINVAL; + snum = ntohs(addr6->sin6_port); + } + + err = security_port_sid(sk->sk_family, sk->sk_type, + sk->sk_protocol, snum, &sid); + if (err) + goto out; + + AVC_AUDIT_DATA_INIT(&ad,NET); + ad.u.net.dport = htons(snum); + ad.u.net.family = sk->sk_family; + err = avc_has_perm(isec->sid, sid, isec->sclass, + TCP_SOCKET__NAME_CONNECT, &ad); + if (err) + goto out; + } + +out: + return err; +} + +static int selinux_socket_listen(struct socket *sock, int backlog) +{ + return socket_has_perm(current, sock, SOCKET__LISTEN); +} + +static int selinux_socket_accept(struct socket *sock, struct socket *newsock) +{ + int err; + struct inode_security_struct *isec; + struct inode_security_struct *newisec; + + err = socket_has_perm(current, sock, SOCKET__ACCEPT); + if (err) + return err; + + newisec = SOCK_INODE(newsock)->i_security; + + isec = SOCK_INODE(sock)->i_security; + newisec->sclass = isec->sclass; + newisec->sid = isec->sid; + newisec->initialized = 1; + + return 0; +} + +static int selinux_socket_sendmsg(struct socket *sock, struct msghdr *msg, + int size) +{ + return socket_has_perm(current, sock, SOCKET__WRITE); +} + +static int selinux_socket_recvmsg(struct socket *sock, struct msghdr *msg, + int size, int flags) +{ + return socket_has_perm(current, sock, SOCKET__READ); +} + +static int selinux_socket_getsockname(struct socket *sock) +{ + return socket_has_perm(current, sock, SOCKET__GETATTR); +} + +static int selinux_socket_getpeername(struct socket *sock) +{ + return socket_has_perm(current, sock, SOCKET__GETATTR); +} + +static int selinux_socket_setsockopt(struct socket *sock,int level,int optname) +{ + return socket_has_perm(current, sock, SOCKET__SETOPT); +} + +static int selinux_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + return socket_has_perm(current, sock, SOCKET__GETOPT); +} + +static int selinux_socket_shutdown(struct socket *sock, int how) +{ + return socket_has_perm(current, sock, SOCKET__SHUTDOWN); +} + +static int selinux_socket_unix_stream_connect(struct socket *sock, + struct socket *other, + struct sock *newsk) +{ + struct sk_security_struct *ssec; + struct inode_security_struct *isec; + struct inode_security_struct *other_isec; + struct avc_audit_data ad; + int err; + + err = secondary_ops->unix_stream_connect(sock, other, newsk); + if (err) + return err; + + isec = SOCK_INODE(sock)->i_security; + other_isec = SOCK_INODE(other)->i_security; + + AVC_AUDIT_DATA_INIT(&ad,NET); + ad.u.net.sk = other->sk; + + err = avc_has_perm(isec->sid, other_isec->sid, + isec->sclass, + UNIX_STREAM_SOCKET__CONNECTTO, &ad); + if (err) + return err; + + /* connecting socket */ + ssec = sock->sk->sk_security; + ssec->peer_sid = other_isec->sid; + + /* server child socket */ + ssec = newsk->sk_security; + ssec->peer_sid = isec->sid; + + return 0; +} + +static int selinux_socket_unix_may_send(struct socket *sock, + struct socket *other) +{ + struct inode_security_struct *isec; + struct inode_security_struct *other_isec; + struct avc_audit_data ad; + int err; + + isec = SOCK_INODE(sock)->i_security; + other_isec = SOCK_INODE(other)->i_security; + + AVC_AUDIT_DATA_INIT(&ad,NET); + ad.u.net.sk = other->sk; + + err = avc_has_perm(isec->sid, other_isec->sid, + isec->sclass, SOCKET__SENDTO, &ad); + if (err) + return err; + + return 0; +} + +static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + u16 family; + char *addrp; + int len, err = 0; + u32 netif_perm, node_perm, node_sid, if_sid, recv_perm = 0; + u32 sock_sid = 0; + u16 sock_class = 0; + struct socket *sock; + struct net_device *dev; + struct avc_audit_data ad; + + family = sk->sk_family; + if (family != PF_INET && family != PF_INET6) + goto out; + + /* Handle mapped IPv4 packets arriving via IPv6 sockets */ + if (family == PF_INET6 && skb->protocol == ntohs(ETH_P_IP)) + family = PF_INET; + + read_lock_bh(&sk->sk_callback_lock); + sock = sk->sk_socket; + if (sock) { + struct inode *inode; + inode = SOCK_INODE(sock); + if (inode) { + struct inode_security_struct *isec; + isec = inode->i_security; + sock_sid = isec->sid; + sock_class = isec->sclass; + } + } + read_unlock_bh(&sk->sk_callback_lock); + if (!sock_sid) + goto out; + + dev = skb->dev; + if (!dev) + goto out; + + err = sel_netif_sids(dev, &if_sid, NULL); + if (err) + goto out; + + switch (sock_class) { + case SECCLASS_UDP_SOCKET: + netif_perm = NETIF__UDP_RECV; + node_perm = NODE__UDP_RECV; + recv_perm = UDP_SOCKET__RECV_MSG; + break; + + case SECCLASS_TCP_SOCKET: + netif_perm = NETIF__TCP_RECV; + node_perm = NODE__TCP_RECV; + recv_perm = TCP_SOCKET__RECV_MSG; + break; + + default: + netif_perm = NETIF__RAWIP_RECV; + node_perm = NODE__RAWIP_RECV; + break; + } + + AVC_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.netif = dev->name; + ad.u.net.family = family; + + err = selinux_parse_skb(skb, &ad, &addrp, &len, 1); + if (err) + goto out; + + err = avc_has_perm(sock_sid, if_sid, SECCLASS_NETIF, netif_perm, &ad); + if (err) + goto out; + + /* Fixme: this lookup is inefficient */ + err = security_node_sid(family, addrp, len, &node_sid); + if (err) + goto out; + + err = avc_has_perm(sock_sid, node_sid, SECCLASS_NODE, node_perm, &ad); + if (err) + goto out; + + if (recv_perm) { + u32 port_sid; + + /* Fixme: make this more efficient */ + err = security_port_sid(sk->sk_family, sk->sk_type, + sk->sk_protocol, ntohs(ad.u.net.sport), + &port_sid); + if (err) + goto out; + + err = avc_has_perm(sock_sid, port_sid, + sock_class, recv_perm, &ad); + } +out: + return err; +} + +static int selinux_socket_getpeersec(struct socket *sock, char __user *optval, + int __user *optlen, unsigned len) +{ + int err = 0; + char *scontext; + u32 scontext_len; + struct sk_security_struct *ssec; + struct inode_security_struct *isec; + + isec = SOCK_INODE(sock)->i_security; + if (isec->sclass != SECCLASS_UNIX_STREAM_SOCKET) { + err = -ENOPROTOOPT; + goto out; + } + + ssec = sock->sk->sk_security; + + err = security_sid_to_context(ssec->peer_sid, &scontext, &scontext_len); + if (err) + goto out; + + if (scontext_len > len) { + err = -ERANGE; + goto out_len; + } + + if (copy_to_user(optval, scontext, scontext_len)) + err = -EFAULT; + +out_len: + if (put_user(scontext_len, optlen)) + err = -EFAULT; + + kfree(scontext); +out: + return err; +} + +static int selinux_sk_alloc_security(struct sock *sk, int family, int priority) +{ + return sk_alloc_security(sk, family, priority); +} + +static void selinux_sk_free_security(struct sock *sk) +{ + sk_free_security(sk); +} + +static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb) +{ + int err = 0; + u32 perm; + struct nlmsghdr *nlh; + struct socket *sock = sk->sk_socket; + struct inode_security_struct *isec = SOCK_INODE(sock)->i_security; + + if (skb->len < NLMSG_SPACE(0)) { + err = -EINVAL; + goto out; + } + nlh = (struct nlmsghdr *)skb->data; + + err = selinux_nlmsg_lookup(isec->sclass, nlh->nlmsg_type, &perm); + if (err) { + if (err == -EINVAL) { + audit_log(current->audit_context, + "SELinux: unrecognized netlink message" + " type=%hu for sclass=%hu\n", + nlh->nlmsg_type, isec->sclass); + if (!selinux_enforcing) + err = 0; + } + + /* Ignore */ + if (err == -ENOENT) + err = 0; + goto out; + } + + err = socket_has_perm(current, sock, perm); +out: + return err; +} + +#ifdef CONFIG_NETFILTER + +static unsigned int selinux_ip_postroute_last(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *), + u16 family) +{ + char *addrp; + int len, err = NF_ACCEPT; + u32 netif_perm, node_perm, node_sid, if_sid, send_perm = 0; + struct sock *sk; + struct socket *sock; + struct inode *inode; + struct sk_buff *skb = *pskb; + struct inode_security_struct *isec; + struct avc_audit_data ad; + struct net_device *dev = (struct net_device *)out; + + sk = skb->sk; + if (!sk) + goto out; + + sock = sk->sk_socket; + if (!sock) + goto out; + + inode = SOCK_INODE(sock); + if (!inode) + goto out; + + err = sel_netif_sids(dev, &if_sid, NULL); + if (err) + goto out; + + isec = inode->i_security; + + switch (isec->sclass) { + case SECCLASS_UDP_SOCKET: + netif_perm = NETIF__UDP_SEND; + node_perm = NODE__UDP_SEND; + send_perm = UDP_SOCKET__SEND_MSG; + break; + + case SECCLASS_TCP_SOCKET: + netif_perm = NETIF__TCP_SEND; + node_perm = NODE__TCP_SEND; + send_perm = TCP_SOCKET__SEND_MSG; + break; + + default: + netif_perm = NETIF__RAWIP_SEND; + node_perm = NODE__RAWIP_SEND; + break; + } + + + AVC_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.netif = dev->name; + ad.u.net.family = family; + + err = selinux_parse_skb(skb, &ad, &addrp, + &len, 0) ? NF_DROP : NF_ACCEPT; + if (err != NF_ACCEPT) + goto out; + + err = avc_has_perm(isec->sid, if_sid, SECCLASS_NETIF, + netif_perm, &ad) ? NF_DROP : NF_ACCEPT; + if (err != NF_ACCEPT) + goto out; + + /* Fixme: this lookup is inefficient */ + err = security_node_sid(family, addrp, len, + &node_sid) ? NF_DROP : NF_ACCEPT; + if (err != NF_ACCEPT) + goto out; + + err = avc_has_perm(isec->sid, node_sid, SECCLASS_NODE, + node_perm, &ad) ? NF_DROP : NF_ACCEPT; + if (err != NF_ACCEPT) + goto out; + + if (send_perm) { + u32 port_sid; + + /* Fixme: make this more efficient */ + err = security_port_sid(sk->sk_family, + sk->sk_type, + sk->sk_protocol, + ntohs(ad.u.net.dport), + &port_sid) ? NF_DROP : NF_ACCEPT; + if (err != NF_ACCEPT) + goto out; + + err = avc_has_perm(isec->sid, port_sid, isec->sclass, + send_perm, &ad) ? NF_DROP : NF_ACCEPT; + } + +out: + return err; +} + +static unsigned int selinux_ipv4_postroute_last(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_postroute_last(hooknum, pskb, in, out, okfn, PF_INET); +} + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + +static unsigned int selinux_ipv6_postroute_last(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_postroute_last(hooknum, pskb, in, out, okfn, PF_INET6); +} + +#endif /* IPV6 */ + +#endif /* CONFIG_NETFILTER */ + +#else + +static inline int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb) +{ + return 0; +} + +#endif /* CONFIG_SECURITY_NETWORK */ + +static int selinux_netlink_send(struct sock *sk, struct sk_buff *skb) +{ + struct task_security_struct *tsec; + struct av_decision avd; + int err; + + err = secondary_ops->netlink_send(sk, skb); + if (err) + return err; + + tsec = current->security; + + avd.allowed = 0; + avc_has_perm_noaudit(tsec->sid, tsec->sid, + SECCLASS_CAPABILITY, ~0, &avd); + cap_mask(NETLINK_CB(skb).eff_cap, avd.allowed); + + if (policydb_loaded_version >= POLICYDB_VERSION_NLCLASS) + err = selinux_nlmsg_perm(sk, skb); + + return err; +} + +static int selinux_netlink_recv(struct sk_buff *skb) +{ + if (!cap_raised(NETLINK_CB(skb).eff_cap, CAP_NET_ADMIN)) + return -EPERM; + return 0; +} + +static int ipc_alloc_security(struct task_struct *task, + struct kern_ipc_perm *perm, + u16 sclass) +{ + struct task_security_struct *tsec = task->security; + struct ipc_security_struct *isec; + + isec = kmalloc(sizeof(struct ipc_security_struct), GFP_KERNEL); + if (!isec) + return -ENOMEM; + + memset(isec, 0, sizeof(struct ipc_security_struct)); + isec->magic = SELINUX_MAGIC; + isec->sclass = sclass; + isec->ipc_perm = perm; + if (tsec) { + isec->sid = tsec->sid; + } else { + isec->sid = SECINITSID_UNLABELED; + } + perm->security = isec; + + return 0; +} + +static void ipc_free_security(struct kern_ipc_perm *perm) +{ + struct ipc_security_struct *isec = perm->security; + if (!isec || isec->magic != SELINUX_MAGIC) + return; + + perm->security = NULL; + kfree(isec); +} + +static int msg_msg_alloc_security(struct msg_msg *msg) +{ + struct msg_security_struct *msec; + + msec = kmalloc(sizeof(struct msg_security_struct), GFP_KERNEL); + if (!msec) + return -ENOMEM; + + memset(msec, 0, sizeof(struct msg_security_struct)); + msec->magic = SELINUX_MAGIC; + msec->msg = msg; + msec->sid = SECINITSID_UNLABELED; + msg->security = msec; + + return 0; +} + +static void msg_msg_free_security(struct msg_msg *msg) +{ + struct msg_security_struct *msec = msg->security; + if (!msec || msec->magic != SELINUX_MAGIC) + return; + + msg->security = NULL; + kfree(msec); +} + +static int ipc_has_perm(struct kern_ipc_perm *ipc_perms, + u16 sclass, u32 perms) +{ + struct task_security_struct *tsec; + struct ipc_security_struct *isec; + struct avc_audit_data ad; + + tsec = current->security; + isec = ipc_perms->security; + + AVC_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = ipc_perms->key; + + return avc_has_perm(tsec->sid, isec->sid, sclass, perms, &ad); +} + +static int selinux_msg_msg_alloc_security(struct msg_msg *msg) +{ + return msg_msg_alloc_security(msg); +} + +static void selinux_msg_msg_free_security(struct msg_msg *msg) +{ + msg_msg_free_security(msg); +} + +/* message queue security operations */ +static int selinux_msg_queue_alloc_security(struct msg_queue *msq) +{ + struct task_security_struct *tsec; + struct ipc_security_struct *isec; + struct avc_audit_data ad; + int rc; + + rc = ipc_alloc_security(current, &msq->q_perm, SECCLASS_MSGQ); + if (rc) + return rc; + + tsec = current->security; + isec = msq->q_perm.security; + + AVC_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = msq->q_perm.key; + + rc = avc_has_perm(tsec->sid, isec->sid, SECCLASS_MSGQ, + MSGQ__CREATE, &ad); + if (rc) { + ipc_free_security(&msq->q_perm); + return rc; + } + return 0; +} + +static void selinux_msg_queue_free_security(struct msg_queue *msq) +{ + ipc_free_security(&msq->q_perm); +} + +static int selinux_msg_queue_associate(struct msg_queue *msq, int msqflg) +{ + struct task_security_struct *tsec; + struct ipc_security_struct *isec; + struct avc_audit_data ad; + + tsec = current->security; + isec = msq->q_perm.security; + + AVC_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = msq->q_perm.key; + + return avc_has_perm(tsec->sid, isec->sid, SECCLASS_MSGQ, + MSGQ__ASSOCIATE, &ad); +} + +static int selinux_msg_queue_msgctl(struct msg_queue *msq, int cmd) +{ + int err; + int perms; + + switch(cmd) { + case IPC_INFO: + case MSG_INFO: + /* No specific object, just general system-wide information. */ + return task_has_system(current, SYSTEM__IPC_INFO); + case IPC_STAT: + case MSG_STAT: + perms = MSGQ__GETATTR | MSGQ__ASSOCIATE; + break; + case IPC_SET: + perms = MSGQ__SETATTR; + break; + case IPC_RMID: + perms = MSGQ__DESTROY; + break; + default: + return 0; + } + + err = ipc_has_perm(&msq->q_perm, SECCLASS_MSGQ, perms); + return err; +} + +static int selinux_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg, int msqflg) +{ + struct task_security_struct *tsec; + struct ipc_security_struct *isec; + struct msg_security_struct *msec; + struct avc_audit_data ad; + int rc; + + tsec = current->security; + isec = msq->q_perm.security; + msec = msg->security; + + /* + * First time through, need to assign label to the message + */ + if (msec->sid == SECINITSID_UNLABELED) { + /* + * Compute new sid based on current process and + * message queue this message will be stored in + */ + rc = security_transition_sid(tsec->sid, + isec->sid, + SECCLASS_MSG, + &msec->sid); + if (rc) + return rc; + } + + AVC_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = msq->q_perm.key; + + /* Can this process write to the queue? */ + rc = avc_has_perm(tsec->sid, isec->sid, SECCLASS_MSGQ, + MSGQ__WRITE, &ad); + if (!rc) + /* Can this process send the message */ + rc = avc_has_perm(tsec->sid, msec->sid, + SECCLASS_MSG, MSG__SEND, &ad); + if (!rc) + /* Can the message be put in the queue? */ + rc = avc_has_perm(msec->sid, isec->sid, + SECCLASS_MSGQ, MSGQ__ENQUEUE, &ad); + + return rc; +} + +static int selinux_msg_queue_msgrcv(struct msg_queue *msq, struct msg_msg *msg, + struct task_struct *target, + long type, int mode) +{ + struct task_security_struct *tsec; + struct ipc_security_struct *isec; + struct msg_security_struct *msec; + struct avc_audit_data ad; + int rc; + + tsec = target->security; + isec = msq->q_perm.security; + msec = msg->security; + + AVC_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = msq->q_perm.key; + + rc = avc_has_perm(tsec->sid, isec->sid, + SECCLASS_MSGQ, MSGQ__READ, &ad); + if (!rc) + rc = avc_has_perm(tsec->sid, msec->sid, + SECCLASS_MSG, MSG__RECEIVE, &ad); + return rc; +} + +/* Shared Memory security operations */ +static int selinux_shm_alloc_security(struct shmid_kernel *shp) +{ + struct task_security_struct *tsec; + struct ipc_security_struct *isec; + struct avc_audit_data ad; + int rc; + + rc = ipc_alloc_security(current, &shp->shm_perm, SECCLASS_SHM); + if (rc) + return rc; + + tsec = current->security; + isec = shp->shm_perm.security; + + AVC_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = shp->shm_perm.key; + + rc = avc_has_perm(tsec->sid, isec->sid, SECCLASS_SHM, + SHM__CREATE, &ad); + if (rc) { + ipc_free_security(&shp->shm_perm); + return rc; + } + return 0; +} + +static void selinux_shm_free_security(struct shmid_kernel *shp) +{ + ipc_free_security(&shp->shm_perm); +} + +static int selinux_shm_associate(struct shmid_kernel *shp, int shmflg) +{ + struct task_security_struct *tsec; + struct ipc_security_struct *isec; + struct avc_audit_data ad; + + tsec = current->security; + isec = shp->shm_perm.security; + + AVC_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = shp->shm_perm.key; + + return avc_has_perm(tsec->sid, isec->sid, SECCLASS_SHM, + SHM__ASSOCIATE, &ad); +} + +/* Note, at this point, shp is locked down */ +static int selinux_shm_shmctl(struct shmid_kernel *shp, int cmd) +{ + int perms; + int err; + + switch(cmd) { + case IPC_INFO: + case SHM_INFO: + /* No specific object, just general system-wide information. */ + return task_has_system(current, SYSTEM__IPC_INFO); + case IPC_STAT: + case SHM_STAT: + perms = SHM__GETATTR | SHM__ASSOCIATE; + break; + case IPC_SET: + perms = SHM__SETATTR; + break; + case SHM_LOCK: + case SHM_UNLOCK: + perms = SHM__LOCK; + break; + case IPC_RMID: + perms = SHM__DESTROY; + break; + default: + return 0; + } + + err = ipc_has_perm(&shp->shm_perm, SECCLASS_SHM, perms); + return err; +} + +static int selinux_shm_shmat(struct shmid_kernel *shp, + char __user *shmaddr, int shmflg) +{ + u32 perms; + int rc; + + rc = secondary_ops->shm_shmat(shp, shmaddr, shmflg); + if (rc) + return rc; + + if (shmflg & SHM_RDONLY) + perms = SHM__READ; + else + perms = SHM__READ | SHM__WRITE; + + return ipc_has_perm(&shp->shm_perm, SECCLASS_SHM, perms); +} + +/* Semaphore security operations */ +static int selinux_sem_alloc_security(struct sem_array *sma) +{ + struct task_security_struct *tsec; + struct ipc_security_struct *isec; + struct avc_audit_data ad; + int rc; + + rc = ipc_alloc_security(current, &sma->sem_perm, SECCLASS_SEM); + if (rc) + return rc; + + tsec = current->security; + isec = sma->sem_perm.security; + + AVC_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = sma->sem_perm.key; + + rc = avc_has_perm(tsec->sid, isec->sid, SECCLASS_SEM, + SEM__CREATE, &ad); + if (rc) { + ipc_free_security(&sma->sem_perm); + return rc; + } + return 0; +} + +static void selinux_sem_free_security(struct sem_array *sma) +{ + ipc_free_security(&sma->sem_perm); +} + +static int selinux_sem_associate(struct sem_array *sma, int semflg) +{ + struct task_security_struct *tsec; + struct ipc_security_struct *isec; + struct avc_audit_data ad; + + tsec = current->security; + isec = sma->sem_perm.security; + + AVC_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = sma->sem_perm.key; + + return avc_has_perm(tsec->sid, isec->sid, SECCLASS_SEM, + SEM__ASSOCIATE, &ad); +} + +/* Note, at this point, sma is locked down */ +static int selinux_sem_semctl(struct sem_array *sma, int cmd) +{ + int err; + u32 perms; + + switch(cmd) { + case IPC_INFO: + case SEM_INFO: + /* No specific object, just general system-wide information. */ + return task_has_system(current, SYSTEM__IPC_INFO); + case GETPID: + case GETNCNT: + case GETZCNT: + perms = SEM__GETATTR; + break; + case GETVAL: + case GETALL: + perms = SEM__READ; + break; + case SETVAL: + case SETALL: + perms = SEM__WRITE; + break; + case IPC_RMID: + perms = SEM__DESTROY; + break; + case IPC_SET: + perms = SEM__SETATTR; + break; + case IPC_STAT: + case SEM_STAT: + perms = SEM__GETATTR | SEM__ASSOCIATE; + break; + default: + return 0; + } + + err = ipc_has_perm(&sma->sem_perm, SECCLASS_SEM, perms); + return err; +} + +static int selinux_sem_semop(struct sem_array *sma, + struct sembuf *sops, unsigned nsops, int alter) +{ + u32 perms; + + if (alter) + perms = SEM__READ | SEM__WRITE; + else + perms = SEM__READ; + + return ipc_has_perm(&sma->sem_perm, SECCLASS_SEM, perms); +} + +static int selinux_ipc_permission(struct kern_ipc_perm *ipcp, short flag) +{ + struct ipc_security_struct *isec = ipcp->security; + u16 sclass = SECCLASS_IPC; + u32 av = 0; + + if (isec && isec->magic == SELINUX_MAGIC) + sclass = isec->sclass; + + av = 0; + if (flag & S_IRUGO) + av |= IPC__UNIX_READ; + if (flag & S_IWUGO) + av |= IPC__UNIX_WRITE; + + if (av == 0) + return 0; + + return ipc_has_perm(ipcp, sclass, av); +} + +/* module stacking operations */ +static int selinux_register_security (const char *name, struct security_operations *ops) +{ + if (secondary_ops != original_ops) { + printk(KERN_INFO "%s: There is already a secondary security " + "module registered.\n", __FUNCTION__); + return -EINVAL; + } + + secondary_ops = ops; + + printk(KERN_INFO "%s: Registering secondary module %s\n", + __FUNCTION__, + name); + + return 0; +} + +static int selinux_unregister_security (const char *name, struct security_operations *ops) +{ + if (ops != secondary_ops) { + printk (KERN_INFO "%s: trying to unregister a security module " + "that is not registered.\n", __FUNCTION__); + return -EINVAL; + } + + secondary_ops = original_ops; + + return 0; +} + +static void selinux_d_instantiate (struct dentry *dentry, struct inode *inode) +{ + if (inode) + inode_doinit_with_dentry(inode, dentry); +} + +static int selinux_getprocattr(struct task_struct *p, + char *name, void *value, size_t size) +{ + struct task_security_struct *tsec; + u32 sid, len; + char *context; + int error; + + if (current != p) { + error = task_has_perm(current, p, PROCESS__GETATTR); + if (error) + return error; + } + + if (!size) + return -ERANGE; + + tsec = p->security; + + if (!strcmp(name, "current")) + sid = tsec->sid; + else if (!strcmp(name, "prev")) + sid = tsec->osid; + else if (!strcmp(name, "exec")) + sid = tsec->exec_sid; + else if (!strcmp(name, "fscreate")) + sid = tsec->create_sid; + else + return -EINVAL; + + if (!sid) + return 0; + + error = security_sid_to_context(sid, &context, &len); + if (error) + return error; + if (len > size) { + kfree(context); + return -ERANGE; + } + memcpy(value, context, len); + kfree(context); + return len; +} + +static int selinux_setprocattr(struct task_struct *p, + char *name, void *value, size_t size) +{ + struct task_security_struct *tsec; + u32 sid = 0; + int error; + char *str = value; + + if (current != p) { + /* SELinux only allows a process to change its own + security attributes. */ + return -EACCES; + } + + /* + * Basic control over ability to set these attributes at all. + * current == p, but we'll pass them separately in case the + * above restriction is ever removed. + */ + if (!strcmp(name, "exec")) + error = task_has_perm(current, p, PROCESS__SETEXEC); + else if (!strcmp(name, "fscreate")) + error = task_has_perm(current, p, PROCESS__SETFSCREATE); + else if (!strcmp(name, "current")) + error = task_has_perm(current, p, PROCESS__SETCURRENT); + else + error = -EINVAL; + if (error) + return error; + + /* Obtain a SID for the context, if one was specified. */ + if (size && str[1] && str[1] != '\n') { + if (str[size-1] == '\n') { + str[size-1] = 0; + size--; + } + error = security_context_to_sid(value, size, &sid); + if (error) + return error; + } + + /* Permission checking based on the specified context is + performed during the actual operation (execve, + open/mkdir/...), when we know the full context of the + operation. See selinux_bprm_set_security for the execve + checks and may_create for the file creation checks. The + operation will then fail if the context is not permitted. */ + tsec = p->security; + if (!strcmp(name, "exec")) + tsec->exec_sid = sid; + else if (!strcmp(name, "fscreate")) + tsec->create_sid = sid; + else if (!strcmp(name, "current")) { + struct av_decision avd; + + if (sid == 0) + return -EINVAL; + + /* Only allow single threaded processes to change context */ + if (atomic_read(&p->mm->mm_users) != 1) { + struct task_struct *g, *t; + struct mm_struct *mm = p->mm; + read_lock(&tasklist_lock); + do_each_thread(g, t) + if (t->mm == mm && t != p) { + read_unlock(&tasklist_lock); + return -EPERM; + } + while_each_thread(g, t); + read_unlock(&tasklist_lock); + } + + /* Check permissions for the transition. */ + error = avc_has_perm(tsec->sid, sid, SECCLASS_PROCESS, + PROCESS__DYNTRANSITION, NULL); + if (error) + return error; + + /* Check for ptracing, and update the task SID if ok. + Otherwise, leave SID unchanged and fail. */ + task_lock(p); + if (p->ptrace & PT_PTRACED) { + error = avc_has_perm_noaudit(tsec->ptrace_sid, sid, + SECCLASS_PROCESS, + PROCESS__PTRACE, &avd); + if (!error) + tsec->sid = sid; + task_unlock(p); + avc_audit(tsec->ptrace_sid, sid, SECCLASS_PROCESS, + PROCESS__PTRACE, &avd, error, NULL); + if (error) + return error; + } else { + tsec->sid = sid; + task_unlock(p); + } + } + else + return -EINVAL; + + return size; +} + +static struct security_operations selinux_ops = { + .ptrace = selinux_ptrace, + .capget = selinux_capget, + .capset_check = selinux_capset_check, + .capset_set = selinux_capset_set, + .sysctl = selinux_sysctl, + .capable = selinux_capable, + .quotactl = selinux_quotactl, + .quota_on = selinux_quota_on, + .syslog = selinux_syslog, + .vm_enough_memory = selinux_vm_enough_memory, + + .netlink_send = selinux_netlink_send, + .netlink_recv = selinux_netlink_recv, + + .bprm_alloc_security = selinux_bprm_alloc_security, + .bprm_free_security = selinux_bprm_free_security, + .bprm_apply_creds = selinux_bprm_apply_creds, + .bprm_post_apply_creds = selinux_bprm_post_apply_creds, + .bprm_set_security = selinux_bprm_set_security, + .bprm_check_security = selinux_bprm_check_security, + .bprm_secureexec = selinux_bprm_secureexec, + + .sb_alloc_security = selinux_sb_alloc_security, + .sb_free_security = selinux_sb_free_security, + .sb_copy_data = selinux_sb_copy_data, + .sb_kern_mount = selinux_sb_kern_mount, + .sb_statfs = selinux_sb_statfs, + .sb_mount = selinux_mount, + .sb_umount = selinux_umount, + + .inode_alloc_security = selinux_inode_alloc_security, + .inode_free_security = selinux_inode_free_security, + .inode_create = selinux_inode_create, + .inode_post_create = selinux_inode_post_create, + .inode_link = selinux_inode_link, + .inode_post_link = selinux_inode_post_link, + .inode_unlink = selinux_inode_unlink, + .inode_symlink = selinux_inode_symlink, + .inode_post_symlink = selinux_inode_post_symlink, + .inode_mkdir = selinux_inode_mkdir, + .inode_post_mkdir = selinux_inode_post_mkdir, + .inode_rmdir = selinux_inode_rmdir, + .inode_mknod = selinux_inode_mknod, + .inode_post_mknod = selinux_inode_post_mknod, + .inode_rename = selinux_inode_rename, + .inode_post_rename = selinux_inode_post_rename, + .inode_readlink = selinux_inode_readlink, + .inode_follow_link = selinux_inode_follow_link, + .inode_permission = selinux_inode_permission, + .inode_setattr = selinux_inode_setattr, + .inode_getattr = selinux_inode_getattr, + .inode_setxattr = selinux_inode_setxattr, + .inode_post_setxattr = selinux_inode_post_setxattr, + .inode_getxattr = selinux_inode_getxattr, + .inode_listxattr = selinux_inode_listxattr, + .inode_removexattr = selinux_inode_removexattr, + .inode_getsecurity = selinux_inode_getsecurity, + .inode_setsecurity = selinux_inode_setsecurity, + .inode_listsecurity = selinux_inode_listsecurity, + + .file_permission = selinux_file_permission, + .file_alloc_security = selinux_file_alloc_security, + .file_free_security = selinux_file_free_security, + .file_ioctl = selinux_file_ioctl, + .file_mmap = selinux_file_mmap, + .file_mprotect = selinux_file_mprotect, + .file_lock = selinux_file_lock, + .file_fcntl = selinux_file_fcntl, + .file_set_fowner = selinux_file_set_fowner, + .file_send_sigiotask = selinux_file_send_sigiotask, + .file_receive = selinux_file_receive, + + .task_create = selinux_task_create, + .task_alloc_security = selinux_task_alloc_security, + .task_free_security = selinux_task_free_security, + .task_setuid = selinux_task_setuid, + .task_post_setuid = selinux_task_post_setuid, + .task_setgid = selinux_task_setgid, + .task_setpgid = selinux_task_setpgid, + .task_getpgid = selinux_task_getpgid, + .task_getsid = selinux_task_getsid, + .task_setgroups = selinux_task_setgroups, + .task_setnice = selinux_task_setnice, + .task_setrlimit = selinux_task_setrlimit, + .task_setscheduler = selinux_task_setscheduler, + .task_getscheduler = selinux_task_getscheduler, + .task_kill = selinux_task_kill, + .task_wait = selinux_task_wait, + .task_prctl = selinux_task_prctl, + .task_reparent_to_init = selinux_task_reparent_to_init, + .task_to_inode = selinux_task_to_inode, + + .ipc_permission = selinux_ipc_permission, + + .msg_msg_alloc_security = selinux_msg_msg_alloc_security, + .msg_msg_free_security = selinux_msg_msg_free_security, + + .msg_queue_alloc_security = selinux_msg_queue_alloc_security, + .msg_queue_free_security = selinux_msg_queue_free_security, + .msg_queue_associate = selinux_msg_queue_associate, + .msg_queue_msgctl = selinux_msg_queue_msgctl, + .msg_queue_msgsnd = selinux_msg_queue_msgsnd, + .msg_queue_msgrcv = selinux_msg_queue_msgrcv, + + .shm_alloc_security = selinux_shm_alloc_security, + .shm_free_security = selinux_shm_free_security, + .shm_associate = selinux_shm_associate, + .shm_shmctl = selinux_shm_shmctl, + .shm_shmat = selinux_shm_shmat, + + .sem_alloc_security = selinux_sem_alloc_security, + .sem_free_security = selinux_sem_free_security, + .sem_associate = selinux_sem_associate, + .sem_semctl = selinux_sem_semctl, + .sem_semop = selinux_sem_semop, + + .register_security = selinux_register_security, + .unregister_security = selinux_unregister_security, + + .d_instantiate = selinux_d_instantiate, + + .getprocattr = selinux_getprocattr, + .setprocattr = selinux_setprocattr, + +#ifdef CONFIG_SECURITY_NETWORK + .unix_stream_connect = selinux_socket_unix_stream_connect, + .unix_may_send = selinux_socket_unix_may_send, + + .socket_create = selinux_socket_create, + .socket_post_create = selinux_socket_post_create, + .socket_bind = selinux_socket_bind, + .socket_connect = selinux_socket_connect, + .socket_listen = selinux_socket_listen, + .socket_accept = selinux_socket_accept, + .socket_sendmsg = selinux_socket_sendmsg, + .socket_recvmsg = selinux_socket_recvmsg, + .socket_getsockname = selinux_socket_getsockname, + .socket_getpeername = selinux_socket_getpeername, + .socket_getsockopt = selinux_socket_getsockopt, + .socket_setsockopt = selinux_socket_setsockopt, + .socket_shutdown = selinux_socket_shutdown, + .socket_sock_rcv_skb = selinux_socket_sock_rcv_skb, + .socket_getpeersec = selinux_socket_getpeersec, + .sk_alloc_security = selinux_sk_alloc_security, + .sk_free_security = selinux_sk_free_security, +#endif +}; + +static __init int selinux_init(void) +{ + struct task_security_struct *tsec; + + if (!selinux_enabled) { + printk(KERN_INFO "SELinux: Disabled at boot.\n"); + return 0; + } + + printk(KERN_INFO "SELinux: Initializing.\n"); + + /* Set the security state for the initial task. */ + if (task_alloc_security(current)) + panic("SELinux: Failed to initialize initial task.\n"); + tsec = current->security; + tsec->osid = tsec->sid = SECINITSID_KERNEL; + + avc_init(); + + original_ops = secondary_ops = security_ops; + if (!secondary_ops) + panic ("SELinux: No initial security operations\n"); + if (register_security (&selinux_ops)) + panic("SELinux: Unable to register with kernel.\n"); + + if (selinux_enforcing) { + printk(KERN_INFO "SELinux: Starting in enforcing mode\n"); + } else { + printk(KERN_INFO "SELinux: Starting in permissive mode\n"); + } + return 0; +} + +void selinux_complete_init(void) +{ + printk(KERN_INFO "SELinux: Completing initialization.\n"); + + /* Set up any superblocks initialized prior to the policy load. */ + printk(KERN_INFO "SELinux: Setting up existing superblocks.\n"); + spin_lock(&sb_security_lock); +next_sb: + if (!list_empty(&superblock_security_head)) { + struct superblock_security_struct *sbsec = + list_entry(superblock_security_head.next, + struct superblock_security_struct, + list); + struct super_block *sb = sbsec->sb; + spin_lock(&sb_lock); + sb->s_count++; + spin_unlock(&sb_lock); + spin_unlock(&sb_security_lock); + down_read(&sb->s_umount); + if (sb->s_root) + superblock_doinit(sb, NULL); + drop_super(sb); + spin_lock(&sb_security_lock); + list_del_init(&sbsec->list); + goto next_sb; + } + spin_unlock(&sb_security_lock); +} + +/* SELinux requires early initialization in order to label + all processes and objects when they are created. */ +security_initcall(selinux_init); + +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_NETFILTER) + +static struct nf_hook_ops selinux_ipv4_op = { + .hook = selinux_ipv4_postroute_last, + .owner = THIS_MODULE, + .pf = PF_INET, + .hooknum = NF_IP_POST_ROUTING, + .priority = NF_IP_PRI_SELINUX_LAST, +}; + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + +static struct nf_hook_ops selinux_ipv6_op = { + .hook = selinux_ipv6_postroute_last, + .owner = THIS_MODULE, + .pf = PF_INET6, + .hooknum = NF_IP6_POST_ROUTING, + .priority = NF_IP6_PRI_SELINUX_LAST, +}; + +#endif /* IPV6 */ + +static int __init selinux_nf_ip_init(void) +{ + int err = 0; + + if (!selinux_enabled) + goto out; + + printk(KERN_INFO "SELinux: Registering netfilter hooks\n"); + + err = nf_register_hook(&selinux_ipv4_op); + if (err) + panic("SELinux: nf_register_hook for IPv4: error %d\n", err); + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + + err = nf_register_hook(&selinux_ipv6_op); + if (err) + panic("SELinux: nf_register_hook for IPv6: error %d\n", err); + +#endif /* IPV6 */ +out: + return err; +} + +__initcall(selinux_nf_ip_init); + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +static void selinux_nf_ip_exit(void) +{ + printk(KERN_INFO "SELinux: Unregistering netfilter hooks\n"); + + nf_unregister_hook(&selinux_ipv4_op); +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + nf_unregister_hook(&selinux_ipv6_op); +#endif /* IPV6 */ +} +#endif + +#else /* CONFIG_SECURITY_NETWORK && CONFIG_NETFILTER */ + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +#define selinux_nf_ip_exit() +#endif + +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_NETFILTER */ + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +int selinux_disable(void) +{ + extern void exit_sel_fs(void); + static int selinux_disabled = 0; + + if (ss_initialized) { + /* Not permitted after initial policy load. */ + return -EINVAL; + } + + if (selinux_disabled) { + /* Only do this once. */ + return -EINVAL; + } + + printk(KERN_INFO "SELinux: Disabled at runtime.\n"); + + selinux_disabled = 1; + + /* Reset security_ops to the secondary module, dummy or capability. */ + security_ops = secondary_ops; + + /* Unregister netfilter hooks. */ + selinux_nf_ip_exit(); + + /* Unregister selinuxfs. */ + exit_sel_fs(); + + return 0; +} +#endif + + diff --git a/security/selinux/include/av_inherit.h b/security/selinux/include/av_inherit.h new file mode 100644 index 000000000000..9facb27822a1 --- /dev/null +++ b/security/selinux/include/av_inherit.h @@ -0,0 +1,30 @@ +/* This file is automatically generated. Do not edit. */ + S_(SECCLASS_DIR, file, 0x00020000UL) + S_(SECCLASS_FILE, file, 0x00020000UL) + S_(SECCLASS_LNK_FILE, file, 0x00020000UL) + S_(SECCLASS_CHR_FILE, file, 0x00020000UL) + S_(SECCLASS_BLK_FILE, file, 0x00020000UL) + S_(SECCLASS_SOCK_FILE, file, 0x00020000UL) + S_(SECCLASS_FIFO_FILE, file, 0x00020000UL) + S_(SECCLASS_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_TCP_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_UDP_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_RAWIP_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_NETLINK_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_PACKET_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_KEY_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_UNIX_STREAM_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_UNIX_DGRAM_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_IPC, ipc, 0x00000200UL) + S_(SECCLASS_SEM, ipc, 0x00000200UL) + S_(SECCLASS_MSGQ, ipc, 0x00000200UL) + S_(SECCLASS_SHM, ipc, 0x00000200UL) + S_(SECCLASS_NETLINK_ROUTE_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_NETLINK_FIREWALL_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_NETLINK_TCPDIAG_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_NETLINK_NFLOG_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_NETLINK_XFRM_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_NETLINK_SELINUX_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_NETLINK_AUDIT_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_NETLINK_IP6FW_SOCKET, socket, 0x00400000UL) + S_(SECCLASS_NETLINK_DNRT_SOCKET, socket, 0x00400000UL) diff --git a/security/selinux/include/av_perm_to_string.h b/security/selinux/include/av_perm_to_string.h new file mode 100644 index 000000000000..903e8b3cc2e9 --- /dev/null +++ b/security/selinux/include/av_perm_to_string.h @@ -0,0 +1,232 @@ +/* This file is automatically generated. Do not edit. */ + S_(SECCLASS_FILESYSTEM, FILESYSTEM__MOUNT, "mount") + S_(SECCLASS_FILESYSTEM, FILESYSTEM__REMOUNT, "remount") + S_(SECCLASS_FILESYSTEM, FILESYSTEM__UNMOUNT, "unmount") + S_(SECCLASS_FILESYSTEM, FILESYSTEM__GETATTR, "getattr") + S_(SECCLASS_FILESYSTEM, FILESYSTEM__RELABELFROM, "relabelfrom") + S_(SECCLASS_FILESYSTEM, FILESYSTEM__RELABELTO, "relabelto") + S_(SECCLASS_FILESYSTEM, FILESYSTEM__TRANSITION, "transition") + S_(SECCLASS_FILESYSTEM, FILESYSTEM__ASSOCIATE, "associate") + S_(SECCLASS_FILESYSTEM, FILESYSTEM__QUOTAMOD, "quotamod") + S_(SECCLASS_FILESYSTEM, FILESYSTEM__QUOTAGET, "quotaget") + S_(SECCLASS_DIR, DIR__ADD_NAME, "add_name") + S_(SECCLASS_DIR, DIR__REMOVE_NAME, "remove_name") + S_(SECCLASS_DIR, DIR__REPARENT, "reparent") + S_(SECCLASS_DIR, DIR__SEARCH, "search") + S_(SECCLASS_DIR, DIR__RMDIR, "rmdir") + S_(SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, "execute_no_trans") + S_(SECCLASS_FILE, FILE__ENTRYPOINT, "entrypoint") + S_(SECCLASS_FILE, FILE__EXECMOD, "execmod") + S_(SECCLASS_CHR_FILE, CHR_FILE__EXECUTE_NO_TRANS, "execute_no_trans") + S_(SECCLASS_CHR_FILE, CHR_FILE__ENTRYPOINT, "entrypoint") + S_(SECCLASS_CHR_FILE, CHR_FILE__EXECMOD, "execmod") + S_(SECCLASS_FD, FD__USE, "use") + S_(SECCLASS_TCP_SOCKET, TCP_SOCKET__CONNECTTO, "connectto") + S_(SECCLASS_TCP_SOCKET, TCP_SOCKET__NEWCONN, "newconn") + S_(SECCLASS_TCP_SOCKET, TCP_SOCKET__ACCEPTFROM, "acceptfrom") + S_(SECCLASS_TCP_SOCKET, TCP_SOCKET__NODE_BIND, "node_bind") + S_(SECCLASS_TCP_SOCKET, TCP_SOCKET__NAME_CONNECT, "name_connect") + S_(SECCLASS_UDP_SOCKET, UDP_SOCKET__NODE_BIND, "node_bind") + S_(SECCLASS_RAWIP_SOCKET, RAWIP_SOCKET__NODE_BIND, "node_bind") + S_(SECCLASS_NODE, NODE__TCP_RECV, "tcp_recv") + S_(SECCLASS_NODE, NODE__TCP_SEND, "tcp_send") + S_(SECCLASS_NODE, NODE__UDP_RECV, "udp_recv") + S_(SECCLASS_NODE, NODE__UDP_SEND, "udp_send") + S_(SECCLASS_NODE, NODE__RAWIP_RECV, "rawip_recv") + S_(SECCLASS_NODE, NODE__RAWIP_SEND, "rawip_send") + S_(SECCLASS_NODE, NODE__ENFORCE_DEST, "enforce_dest") + S_(SECCLASS_NETIF, NETIF__TCP_RECV, "tcp_recv") + S_(SECCLASS_NETIF, NETIF__TCP_SEND, "tcp_send") + S_(SECCLASS_NETIF, NETIF__UDP_RECV, "udp_recv") + S_(SECCLASS_NETIF, NETIF__UDP_SEND, "udp_send") + S_(SECCLASS_NETIF, NETIF__RAWIP_RECV, "rawip_recv") + S_(SECCLASS_NETIF, NETIF__RAWIP_SEND, "rawip_send") + S_(SECCLASS_UNIX_STREAM_SOCKET, UNIX_STREAM_SOCKET__CONNECTTO, "connectto") + S_(SECCLASS_UNIX_STREAM_SOCKET, UNIX_STREAM_SOCKET__NEWCONN, "newconn") + S_(SECCLASS_UNIX_STREAM_SOCKET, UNIX_STREAM_SOCKET__ACCEPTFROM, "acceptfrom") + S_(SECCLASS_PROCESS, PROCESS__FORK, "fork") + S_(SECCLASS_PROCESS, PROCESS__TRANSITION, "transition") + S_(SECCLASS_PROCESS, PROCESS__SIGCHLD, "sigchld") + S_(SECCLASS_PROCESS, PROCESS__SIGKILL, "sigkill") + S_(SECCLASS_PROCESS, PROCESS__SIGSTOP, "sigstop") + S_(SECCLASS_PROCESS, PROCESS__SIGNULL, "signull") + S_(SECCLASS_PROCESS, PROCESS__SIGNAL, "signal") + S_(SECCLASS_PROCESS, PROCESS__PTRACE, "ptrace") + S_(SECCLASS_PROCESS, PROCESS__GETSCHED, "getsched") + S_(SECCLASS_PROCESS, PROCESS__SETSCHED, "setsched") + S_(SECCLASS_PROCESS, PROCESS__GETSESSION, "getsession") + S_(SECCLASS_PROCESS, PROCESS__GETPGID, "getpgid") + S_(SECCLASS_PROCESS, PROCESS__SETPGID, "setpgid") + S_(SECCLASS_PROCESS, PROCESS__GETCAP, "getcap") + S_(SECCLASS_PROCESS, PROCESS__SETCAP, "setcap") + S_(SECCLASS_PROCESS, PROCESS__SHARE, "share") + S_(SECCLASS_PROCESS, PROCESS__GETATTR, "getattr") + S_(SECCLASS_PROCESS, PROCESS__SETEXEC, "setexec") + S_(SECCLASS_PROCESS, PROCESS__SETFSCREATE, "setfscreate") + S_(SECCLASS_PROCESS, PROCESS__NOATSECURE, "noatsecure") + S_(SECCLASS_PROCESS, PROCESS__SIGINH, "siginh") + S_(SECCLASS_PROCESS, PROCESS__SETRLIMIT, "setrlimit") + S_(SECCLASS_PROCESS, PROCESS__RLIMITINH, "rlimitinh") + S_(SECCLASS_PROCESS, PROCESS__DYNTRANSITION, "dyntransition") + S_(SECCLASS_PROCESS, PROCESS__SETCURRENT, "setcurrent") + S_(SECCLASS_PROCESS, PROCESS__EXECMEM, "execmem") + S_(SECCLASS_MSGQ, MSGQ__ENQUEUE, "enqueue") + S_(SECCLASS_MSG, MSG__SEND, "send") + S_(SECCLASS_MSG, MSG__RECEIVE, "receive") + S_(SECCLASS_SHM, SHM__LOCK, "lock") + S_(SECCLASS_SECURITY, SECURITY__COMPUTE_AV, "compute_av") + S_(SECCLASS_SECURITY, SECURITY__COMPUTE_CREATE, "compute_create") + S_(SECCLASS_SECURITY, SECURITY__COMPUTE_MEMBER, "compute_member") + S_(SECCLASS_SECURITY, SECURITY__CHECK_CONTEXT, "check_context") + S_(SECCLASS_SECURITY, SECURITY__LOAD_POLICY, "load_policy") + S_(SECCLASS_SECURITY, SECURITY__COMPUTE_RELABEL, "compute_relabel") + S_(SECCLASS_SECURITY, SECURITY__COMPUTE_USER, "compute_user") + S_(SECCLASS_SECURITY, SECURITY__SETENFORCE, "setenforce") + S_(SECCLASS_SECURITY, SECURITY__SETBOOL, "setbool") + S_(SECCLASS_SECURITY, SECURITY__SETSECPARAM, "setsecparam") + S_(SECCLASS_SECURITY, SECURITY__SETCHECKREQPROT, "setcheckreqprot") + S_(SECCLASS_SYSTEM, SYSTEM__IPC_INFO, "ipc_info") + S_(SECCLASS_SYSTEM, SYSTEM__SYSLOG_READ, "syslog_read") + S_(SECCLASS_SYSTEM, SYSTEM__SYSLOG_MOD, "syslog_mod") + S_(SECCLASS_SYSTEM, SYSTEM__SYSLOG_CONSOLE, "syslog_console") + S_(SECCLASS_CAPABILITY, CAPABILITY__CHOWN, "chown") + S_(SECCLASS_CAPABILITY, CAPABILITY__DAC_OVERRIDE, "dac_override") + S_(SECCLASS_CAPABILITY, CAPABILITY__DAC_READ_SEARCH, "dac_read_search") + S_(SECCLASS_CAPABILITY, CAPABILITY__FOWNER, "fowner") + S_(SECCLASS_CAPABILITY, CAPABILITY__FSETID, "fsetid") + S_(SECCLASS_CAPABILITY, CAPABILITY__KILL, "kill") + S_(SECCLASS_CAPABILITY, CAPABILITY__SETGID, "setgid") + S_(SECCLASS_CAPABILITY, CAPABILITY__SETUID, "setuid") + S_(SECCLASS_CAPABILITY, CAPABILITY__SETPCAP, "setpcap") + S_(SECCLASS_CAPABILITY, CAPABILITY__LINUX_IMMUTABLE, "linux_immutable") + S_(SECCLASS_CAPABILITY, CAPABILITY__NET_BIND_SERVICE, "net_bind_service") + S_(SECCLASS_CAPABILITY, CAPABILITY__NET_BROADCAST, "net_broadcast") + S_(SECCLASS_CAPABILITY, CAPABILITY__NET_ADMIN, "net_admin") + S_(SECCLASS_CAPABILITY, CAPABILITY__NET_RAW, "net_raw") + S_(SECCLASS_CAPABILITY, CAPABILITY__IPC_LOCK, "ipc_lock") + S_(SECCLASS_CAPABILITY, CAPABILITY__IPC_OWNER, "ipc_owner") + S_(SECCLASS_CAPABILITY, CAPABILITY__SYS_MODULE, "sys_module") + S_(SECCLASS_CAPABILITY, CAPABILITY__SYS_RAWIO, "sys_rawio") + S_(SECCLASS_CAPABILITY, CAPABILITY__SYS_CHROOT, "sys_chroot") + S_(SECCLASS_CAPABILITY, CAPABILITY__SYS_PTRACE, "sys_ptrace") + S_(SECCLASS_CAPABILITY, CAPABILITY__SYS_PACCT, "sys_pacct") + S_(SECCLASS_CAPABILITY, CAPABILITY__SYS_ADMIN, "sys_admin") + S_(SECCLASS_CAPABILITY, CAPABILITY__SYS_BOOT, "sys_boot") + S_(SECCLASS_CAPABILITY, CAPABILITY__SYS_NICE, "sys_nice") + S_(SECCLASS_CAPABILITY, CAPABILITY__SYS_RESOURCE, "sys_resource") + S_(SECCLASS_CAPABILITY, CAPABILITY__SYS_TIME, "sys_time") + S_(SECCLASS_CAPABILITY, CAPABILITY__SYS_TTY_CONFIG, "sys_tty_config") + S_(SECCLASS_CAPABILITY, CAPABILITY__MKNOD, "mknod") + S_(SECCLASS_CAPABILITY, CAPABILITY__LEASE, "lease") + S_(SECCLASS_PASSWD, PASSWD__PASSWD, "passwd") + S_(SECCLASS_PASSWD, PASSWD__CHFN, "chfn") + S_(SECCLASS_PASSWD, PASSWD__CHSH, "chsh") + S_(SECCLASS_PASSWD, PASSWD__ROOTOK, "rootok") + S_(SECCLASS_PASSWD, PASSWD__CRONTAB, "crontab") + S_(SECCLASS_DRAWABLE, DRAWABLE__CREATE, "create") + S_(SECCLASS_DRAWABLE, DRAWABLE__DESTROY, "destroy") + S_(SECCLASS_DRAWABLE, DRAWABLE__DRAW, "draw") + S_(SECCLASS_DRAWABLE, DRAWABLE__COPY, "copy") + S_(SECCLASS_DRAWABLE, DRAWABLE__GETATTR, "getattr") + S_(SECCLASS_GC, GC__CREATE, "create") + S_(SECCLASS_GC, GC__FREE, "free") + S_(SECCLASS_GC, GC__GETATTR, "getattr") + S_(SECCLASS_GC, GC__SETATTR, "setattr") + S_(SECCLASS_WINDOW, WINDOW__ADDCHILD, "addchild") + S_(SECCLASS_WINDOW, WINDOW__CREATE, "create") + S_(SECCLASS_WINDOW, WINDOW__DESTROY, "destroy") + S_(SECCLASS_WINDOW, WINDOW__MAP, "map") + S_(SECCLASS_WINDOW, WINDOW__UNMAP, "unmap") + S_(SECCLASS_WINDOW, WINDOW__CHSTACK, "chstack") + S_(SECCLASS_WINDOW, WINDOW__CHPROPLIST, "chproplist") + S_(SECCLASS_WINDOW, WINDOW__CHPROP, "chprop") + S_(SECCLASS_WINDOW, WINDOW__LISTPROP, "listprop") + S_(SECCLASS_WINDOW, WINDOW__GETATTR, "getattr") + S_(SECCLASS_WINDOW, WINDOW__SETATTR, "setattr") + S_(SECCLASS_WINDOW, WINDOW__SETFOCUS, "setfocus") + S_(SECCLASS_WINDOW, WINDOW__MOVE, "move") + S_(SECCLASS_WINDOW, WINDOW__CHSELECTION, "chselection") + S_(SECCLASS_WINDOW, WINDOW__CHPARENT, "chparent") + S_(SECCLASS_WINDOW, WINDOW__CTRLLIFE, "ctrllife") + S_(SECCLASS_WINDOW, WINDOW__ENUMERATE, "enumerate") + S_(SECCLASS_WINDOW, WINDOW__TRANSPARENT, "transparent") + S_(SECCLASS_WINDOW, WINDOW__MOUSEMOTION, "mousemotion") + S_(SECCLASS_WINDOW, WINDOW__CLIENTCOMEVENT, "clientcomevent") + S_(SECCLASS_WINDOW, WINDOW__INPUTEVENT, "inputevent") + S_(SECCLASS_WINDOW, WINDOW__DRAWEVENT, "drawevent") + S_(SECCLASS_WINDOW, WINDOW__WINDOWCHANGEEVENT, "windowchangeevent") + S_(SECCLASS_WINDOW, WINDOW__WINDOWCHANGEREQUEST, "windowchangerequest") + S_(SECCLASS_WINDOW, WINDOW__SERVERCHANGEEVENT, "serverchangeevent") + S_(SECCLASS_WINDOW, WINDOW__EXTENSIONEVENT, "extensionevent") + S_(SECCLASS_FONT, FONT__LOAD, "load") + S_(SECCLASS_FONT, FONT__FREE, "free") + S_(SECCLASS_FONT, FONT__GETATTR, "getattr") + S_(SECCLASS_FONT, FONT__USE, "use") + S_(SECCLASS_COLORMAP, COLORMAP__CREATE, "create") + S_(SECCLASS_COLORMAP, COLORMAP__FREE, "free") + S_(SECCLASS_COLORMAP, COLORMAP__INSTALL, "install") + S_(SECCLASS_COLORMAP, COLORMAP__UNINSTALL, "uninstall") + S_(SECCLASS_COLORMAP, COLORMAP__LIST, "list") + S_(SECCLASS_COLORMAP, COLORMAP__READ, "read") + S_(SECCLASS_COLORMAP, COLORMAP__STORE, "store") + S_(SECCLASS_COLORMAP, COLORMAP__GETATTR, "getattr") + S_(SECCLASS_COLORMAP, COLORMAP__SETATTR, "setattr") + S_(SECCLASS_PROPERTY, PROPERTY__CREATE, "create") + S_(SECCLASS_PROPERTY, PROPERTY__FREE, "free") + S_(SECCLASS_PROPERTY, PROPERTY__READ, "read") + S_(SECCLASS_PROPERTY, PROPERTY__WRITE, "write") + S_(SECCLASS_CURSOR, CURSOR__CREATE, "create") + S_(SECCLASS_CURSOR, CURSOR__CREATEGLYPH, "createglyph") + S_(SECCLASS_CURSOR, CURSOR__FREE, "free") + S_(SECCLASS_CURSOR, CURSOR__ASSIGN, "assign") + S_(SECCLASS_CURSOR, CURSOR__SETATTR, "setattr") + S_(SECCLASS_XCLIENT, XCLIENT__KILL, "kill") + S_(SECCLASS_XINPUT, XINPUT__LOOKUP, "lookup") + S_(SECCLASS_XINPUT, XINPUT__GETATTR, "getattr") + S_(SECCLASS_XINPUT, XINPUT__SETATTR, "setattr") + S_(SECCLASS_XINPUT, XINPUT__SETFOCUS, "setfocus") + S_(SECCLASS_XINPUT, XINPUT__WARPPOINTER, "warppointer") + S_(SECCLASS_XINPUT, XINPUT__ACTIVEGRAB, "activegrab") + S_(SECCLASS_XINPUT, XINPUT__PASSIVEGRAB, "passivegrab") + S_(SECCLASS_XINPUT, XINPUT__UNGRAB, "ungrab") + S_(SECCLASS_XINPUT, XINPUT__BELL, "bell") + S_(SECCLASS_XINPUT, XINPUT__MOUSEMOTION, "mousemotion") + S_(SECCLASS_XINPUT, XINPUT__RELABELINPUT, "relabelinput") + S_(SECCLASS_XSERVER, XSERVER__SCREENSAVER, "screensaver") + S_(SECCLASS_XSERVER, XSERVER__GETHOSTLIST, "gethostlist") + S_(SECCLASS_XSERVER, XSERVER__SETHOSTLIST, "sethostlist") + S_(SECCLASS_XSERVER, XSERVER__GETFONTPATH, "getfontpath") + S_(SECCLASS_XSERVER, XSERVER__SETFONTPATH, "setfontpath") + S_(SECCLASS_XSERVER, XSERVER__GETATTR, "getattr") + S_(SECCLASS_XSERVER, XSERVER__GRAB, "grab") + S_(SECCLASS_XSERVER, XSERVER__UNGRAB, "ungrab") + S_(SECCLASS_XEXTENSION, XEXTENSION__QUERY, "query") + S_(SECCLASS_XEXTENSION, XEXTENSION__USE, "use") + S_(SECCLASS_PAX, PAX__PAGEEXEC, "pageexec") + S_(SECCLASS_PAX, PAX__EMUTRAMP, "emutramp") + S_(SECCLASS_PAX, PAX__MPROTECT, "mprotect") + S_(SECCLASS_PAX, PAX__RANDMMAP, "randmmap") + S_(SECCLASS_PAX, PAX__RANDEXEC, "randexec") + S_(SECCLASS_PAX, PAX__SEGMEXEC, "segmexec") + S_(SECCLASS_NETLINK_ROUTE_SOCKET, NETLINK_ROUTE_SOCKET__NLMSG_READ, "nlmsg_read") + S_(SECCLASS_NETLINK_ROUTE_SOCKET, NETLINK_ROUTE_SOCKET__NLMSG_WRITE, "nlmsg_write") + S_(SECCLASS_NETLINK_FIREWALL_SOCKET, NETLINK_FIREWALL_SOCKET__NLMSG_READ, "nlmsg_read") + S_(SECCLASS_NETLINK_FIREWALL_SOCKET, NETLINK_FIREWALL_SOCKET__NLMSG_WRITE, "nlmsg_write") + S_(SECCLASS_NETLINK_TCPDIAG_SOCKET, NETLINK_TCPDIAG_SOCKET__NLMSG_READ, "nlmsg_read") + S_(SECCLASS_NETLINK_TCPDIAG_SOCKET, NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE, "nlmsg_write") + S_(SECCLASS_NETLINK_XFRM_SOCKET, NETLINK_XFRM_SOCKET__NLMSG_READ, "nlmsg_read") + S_(SECCLASS_NETLINK_XFRM_SOCKET, NETLINK_XFRM_SOCKET__NLMSG_WRITE, "nlmsg_write") + S_(SECCLASS_NETLINK_AUDIT_SOCKET, NETLINK_AUDIT_SOCKET__NLMSG_READ, "nlmsg_read") + S_(SECCLASS_NETLINK_AUDIT_SOCKET, NETLINK_AUDIT_SOCKET__NLMSG_WRITE, "nlmsg_write") + S_(SECCLASS_NETLINK_IP6FW_SOCKET, NETLINK_IP6FW_SOCKET__NLMSG_READ, "nlmsg_read") + S_(SECCLASS_NETLINK_IP6FW_SOCKET, NETLINK_IP6FW_SOCKET__NLMSG_WRITE, "nlmsg_write") + S_(SECCLASS_DBUS, DBUS__ACQUIRE_SVC, "acquire_svc") + S_(SECCLASS_DBUS, DBUS__SEND_MSG, "send_msg") + S_(SECCLASS_NSCD, NSCD__GETPWD, "getpwd") + S_(SECCLASS_NSCD, NSCD__GETGRP, "getgrp") + S_(SECCLASS_NSCD, NSCD__GETHOST, "gethost") + S_(SECCLASS_NSCD, NSCD__GETSTAT, "getstat") + S_(SECCLASS_NSCD, NSCD__ADMIN, "admin") + S_(SECCLASS_NSCD, NSCD__SHMEMPWD, "shmempwd") + S_(SECCLASS_NSCD, NSCD__SHMEMGRP, "shmemgrp") + S_(SECCLASS_NSCD, NSCD__SHMEMHOST, "shmemhost") diff --git a/security/selinux/include/av_permissions.h b/security/selinux/include/av_permissions.h new file mode 100644 index 000000000000..b0a12ac8f7ee --- /dev/null +++ b/security/selinux/include/av_permissions.h @@ -0,0 +1,902 @@ +/* This file is automatically generated. Do not edit. */ +#define COMMON_FILE__IOCTL 0x00000001UL +#define COMMON_FILE__READ 0x00000002UL +#define COMMON_FILE__WRITE 0x00000004UL +#define COMMON_FILE__CREATE 0x00000008UL +#define COMMON_FILE__GETATTR 0x00000010UL +#define COMMON_FILE__SETATTR 0x00000020UL +#define COMMON_FILE__LOCK 0x00000040UL +#define COMMON_FILE__RELABELFROM 0x00000080UL +#define COMMON_FILE__RELABELTO 0x00000100UL +#define COMMON_FILE__APPEND 0x00000200UL +#define COMMON_FILE__UNLINK 0x00000400UL +#define COMMON_FILE__LINK 0x00000800UL +#define COMMON_FILE__RENAME 0x00001000UL +#define COMMON_FILE__EXECUTE 0x00002000UL +#define COMMON_FILE__SWAPON 0x00004000UL +#define COMMON_FILE__QUOTAON 0x00008000UL +#define COMMON_FILE__MOUNTON 0x00010000UL + +#define COMMON_SOCKET__IOCTL 0x00000001UL +#define COMMON_SOCKET__READ 0x00000002UL +#define COMMON_SOCKET__WRITE 0x00000004UL +#define COMMON_SOCKET__CREATE 0x00000008UL +#define COMMON_SOCKET__GETATTR 0x00000010UL +#define COMMON_SOCKET__SETATTR 0x00000020UL +#define COMMON_SOCKET__LOCK 0x00000040UL +#define COMMON_SOCKET__RELABELFROM 0x00000080UL +#define COMMON_SOCKET__RELABELTO 0x00000100UL +#define COMMON_SOCKET__APPEND 0x00000200UL +#define COMMON_SOCKET__BIND 0x00000400UL +#define COMMON_SOCKET__CONNECT 0x00000800UL +#define COMMON_SOCKET__LISTEN 0x00001000UL +#define COMMON_SOCKET__ACCEPT 0x00002000UL +#define COMMON_SOCKET__GETOPT 0x00004000UL +#define COMMON_SOCKET__SETOPT 0x00008000UL +#define COMMON_SOCKET__SHUTDOWN 0x00010000UL +#define COMMON_SOCKET__RECVFROM 0x00020000UL +#define COMMON_SOCKET__SENDTO 0x00040000UL +#define COMMON_SOCKET__RECV_MSG 0x00080000UL +#define COMMON_SOCKET__SEND_MSG 0x00100000UL +#define COMMON_SOCKET__NAME_BIND 0x00200000UL + +#define COMMON_IPC__CREATE 0x00000001UL +#define COMMON_IPC__DESTROY 0x00000002UL +#define COMMON_IPC__GETATTR 0x00000004UL +#define COMMON_IPC__SETATTR 0x00000008UL +#define COMMON_IPC__READ 0x00000010UL +#define COMMON_IPC__WRITE 0x00000020UL +#define COMMON_IPC__ASSOCIATE 0x00000040UL +#define COMMON_IPC__UNIX_READ 0x00000080UL +#define COMMON_IPC__UNIX_WRITE 0x00000100UL + +#define FILESYSTEM__MOUNT 0x00000001UL +#define FILESYSTEM__REMOUNT 0x00000002UL +#define FILESYSTEM__UNMOUNT 0x00000004UL +#define FILESYSTEM__GETATTR 0x00000008UL +#define FILESYSTEM__RELABELFROM 0x00000010UL +#define FILESYSTEM__RELABELTO 0x00000020UL +#define FILESYSTEM__TRANSITION 0x00000040UL +#define FILESYSTEM__ASSOCIATE 0x00000080UL +#define FILESYSTEM__QUOTAMOD 0x00000100UL +#define FILESYSTEM__QUOTAGET 0x00000200UL + +#define DIR__IOCTL 0x00000001UL +#define DIR__READ 0x00000002UL +#define DIR__WRITE 0x00000004UL +#define DIR__CREATE 0x00000008UL +#define DIR__GETATTR 0x00000010UL +#define DIR__SETATTR 0x00000020UL +#define DIR__LOCK 0x00000040UL +#define DIR__RELABELFROM 0x00000080UL +#define DIR__RELABELTO 0x00000100UL +#define DIR__APPEND 0x00000200UL +#define DIR__UNLINK 0x00000400UL +#define DIR__LINK 0x00000800UL +#define DIR__RENAME 0x00001000UL +#define DIR__EXECUTE 0x00002000UL +#define DIR__SWAPON 0x00004000UL +#define DIR__QUOTAON 0x00008000UL +#define DIR__MOUNTON 0x00010000UL + +#define DIR__ADD_NAME 0x00020000UL +#define DIR__REMOVE_NAME 0x00040000UL +#define DIR__REPARENT 0x00080000UL +#define DIR__SEARCH 0x00100000UL +#define DIR__RMDIR 0x00200000UL + +#define FILE__IOCTL 0x00000001UL +#define FILE__READ 0x00000002UL +#define FILE__WRITE 0x00000004UL +#define FILE__CREATE 0x00000008UL +#define FILE__GETATTR 0x00000010UL +#define FILE__SETATTR 0x00000020UL +#define FILE__LOCK 0x00000040UL +#define FILE__RELABELFROM 0x00000080UL +#define FILE__RELABELTO 0x00000100UL +#define FILE__APPEND 0x00000200UL +#define FILE__UNLINK 0x00000400UL +#define FILE__LINK 0x00000800UL +#define FILE__RENAME 0x00001000UL +#define FILE__EXECUTE 0x00002000UL +#define FILE__SWAPON 0x00004000UL +#define FILE__QUOTAON 0x00008000UL +#define FILE__MOUNTON 0x00010000UL + +#define FILE__EXECUTE_NO_TRANS 0x00020000UL +#define FILE__ENTRYPOINT 0x00040000UL +#define FILE__EXECMOD 0x00080000UL + +#define LNK_FILE__IOCTL 0x00000001UL +#define LNK_FILE__READ 0x00000002UL +#define LNK_FILE__WRITE 0x00000004UL +#define LNK_FILE__CREATE 0x00000008UL +#define LNK_FILE__GETATTR 0x00000010UL +#define LNK_FILE__SETATTR 0x00000020UL +#define LNK_FILE__LOCK 0x00000040UL +#define LNK_FILE__RELABELFROM 0x00000080UL +#define LNK_FILE__RELABELTO 0x00000100UL +#define LNK_FILE__APPEND 0x00000200UL +#define LNK_FILE__UNLINK 0x00000400UL +#define LNK_FILE__LINK 0x00000800UL +#define LNK_FILE__RENAME 0x00001000UL +#define LNK_FILE__EXECUTE 0x00002000UL +#define LNK_FILE__SWAPON 0x00004000UL +#define LNK_FILE__QUOTAON 0x00008000UL +#define LNK_FILE__MOUNTON 0x00010000UL + +#define CHR_FILE__IOCTL 0x00000001UL +#define CHR_FILE__READ 0x00000002UL +#define CHR_FILE__WRITE 0x00000004UL +#define CHR_FILE__CREATE 0x00000008UL +#define CHR_FILE__GETATTR 0x00000010UL +#define CHR_FILE__SETATTR 0x00000020UL +#define CHR_FILE__LOCK 0x00000040UL +#define CHR_FILE__RELABELFROM 0x00000080UL +#define CHR_FILE__RELABELTO 0x00000100UL +#define CHR_FILE__APPEND 0x00000200UL +#define CHR_FILE__UNLINK 0x00000400UL +#define CHR_FILE__LINK 0x00000800UL +#define CHR_FILE__RENAME 0x00001000UL +#define CHR_FILE__EXECUTE 0x00002000UL +#define CHR_FILE__SWAPON 0x00004000UL +#define CHR_FILE__QUOTAON 0x00008000UL +#define CHR_FILE__MOUNTON 0x00010000UL + +#define CHR_FILE__EXECUTE_NO_TRANS 0x00020000UL +#define CHR_FILE__ENTRYPOINT 0x00040000UL +#define CHR_FILE__EXECMOD 0x00080000UL + +#define BLK_FILE__IOCTL 0x00000001UL +#define BLK_FILE__READ 0x00000002UL +#define BLK_FILE__WRITE 0x00000004UL +#define BLK_FILE__CREATE 0x00000008UL +#define BLK_FILE__GETATTR 0x00000010UL +#define BLK_FILE__SETATTR 0x00000020UL +#define BLK_FILE__LOCK 0x00000040UL +#define BLK_FILE__RELABELFROM 0x00000080UL +#define BLK_FILE__RELABELTO 0x00000100UL +#define BLK_FILE__APPEND 0x00000200UL +#define BLK_FILE__UNLINK 0x00000400UL +#define BLK_FILE__LINK 0x00000800UL +#define BLK_FILE__RENAME 0x00001000UL +#define BLK_FILE__EXECUTE 0x00002000UL +#define BLK_FILE__SWAPON 0x00004000UL +#define BLK_FILE__QUOTAON 0x00008000UL +#define BLK_FILE__MOUNTON 0x00010000UL + +#define SOCK_FILE__IOCTL 0x00000001UL +#define SOCK_FILE__READ 0x00000002UL +#define SOCK_FILE__WRITE 0x00000004UL +#define SOCK_FILE__CREATE 0x00000008UL +#define SOCK_FILE__GETATTR 0x00000010UL +#define SOCK_FILE__SETATTR 0x00000020UL +#define SOCK_FILE__LOCK 0x00000040UL +#define SOCK_FILE__RELABELFROM 0x00000080UL +#define SOCK_FILE__RELABELTO 0x00000100UL +#define SOCK_FILE__APPEND 0x00000200UL +#define SOCK_FILE__UNLINK 0x00000400UL +#define SOCK_FILE__LINK 0x00000800UL +#define SOCK_FILE__RENAME 0x00001000UL +#define SOCK_FILE__EXECUTE 0x00002000UL +#define SOCK_FILE__SWAPON 0x00004000UL +#define SOCK_FILE__QUOTAON 0x00008000UL +#define SOCK_FILE__MOUNTON 0x00010000UL + +#define FIFO_FILE__IOCTL 0x00000001UL +#define FIFO_FILE__READ 0x00000002UL +#define FIFO_FILE__WRITE 0x00000004UL +#define FIFO_FILE__CREATE 0x00000008UL +#define FIFO_FILE__GETATTR 0x00000010UL +#define FIFO_FILE__SETATTR 0x00000020UL +#define FIFO_FILE__LOCK 0x00000040UL +#define FIFO_FILE__RELABELFROM 0x00000080UL +#define FIFO_FILE__RELABELTO 0x00000100UL +#define FIFO_FILE__APPEND 0x00000200UL +#define FIFO_FILE__UNLINK 0x00000400UL +#define FIFO_FILE__LINK 0x00000800UL +#define FIFO_FILE__RENAME 0x00001000UL +#define FIFO_FILE__EXECUTE 0x00002000UL +#define FIFO_FILE__SWAPON 0x00004000UL +#define FIFO_FILE__QUOTAON 0x00008000UL +#define FIFO_FILE__MOUNTON 0x00010000UL + +#define FD__USE 0x00000001UL + +#define SOCKET__IOCTL 0x00000001UL +#define SOCKET__READ 0x00000002UL +#define SOCKET__WRITE 0x00000004UL +#define SOCKET__CREATE 0x00000008UL +#define SOCKET__GETATTR 0x00000010UL +#define SOCKET__SETATTR 0x00000020UL +#define SOCKET__LOCK 0x00000040UL +#define SOCKET__RELABELFROM 0x00000080UL +#define SOCKET__RELABELTO 0x00000100UL +#define SOCKET__APPEND 0x00000200UL +#define SOCKET__BIND 0x00000400UL +#define SOCKET__CONNECT 0x00000800UL +#define SOCKET__LISTEN 0x00001000UL +#define SOCKET__ACCEPT 0x00002000UL +#define SOCKET__GETOPT 0x00004000UL +#define SOCKET__SETOPT 0x00008000UL +#define SOCKET__SHUTDOWN 0x00010000UL +#define SOCKET__RECVFROM 0x00020000UL +#define SOCKET__SENDTO 0x00040000UL +#define SOCKET__RECV_MSG 0x00080000UL +#define SOCKET__SEND_MSG 0x00100000UL +#define SOCKET__NAME_BIND 0x00200000UL + +#define TCP_SOCKET__IOCTL 0x00000001UL +#define TCP_SOCKET__READ 0x00000002UL +#define TCP_SOCKET__WRITE 0x00000004UL +#define TCP_SOCKET__CREATE 0x00000008UL +#define TCP_SOCKET__GETATTR 0x00000010UL +#define TCP_SOCKET__SETATTR 0x00000020UL +#define TCP_SOCKET__LOCK 0x00000040UL +#define TCP_SOCKET__RELABELFROM 0x00000080UL +#define TCP_SOCKET__RELABELTO 0x00000100UL +#define TCP_SOCKET__APPEND 0x00000200UL +#define TCP_SOCKET__BIND 0x00000400UL +#define TCP_SOCKET__CONNECT 0x00000800UL +#define TCP_SOCKET__LISTEN 0x00001000UL +#define TCP_SOCKET__ACCEPT 0x00002000UL +#define TCP_SOCKET__GETOPT 0x00004000UL +#define TCP_SOCKET__SETOPT 0x00008000UL +#define TCP_SOCKET__SHUTDOWN 0x00010000UL +#define TCP_SOCKET__RECVFROM 0x00020000UL +#define TCP_SOCKET__SENDTO 0x00040000UL +#define TCP_SOCKET__RECV_MSG 0x00080000UL +#define TCP_SOCKET__SEND_MSG 0x00100000UL +#define TCP_SOCKET__NAME_BIND 0x00200000UL + +#define TCP_SOCKET__CONNECTTO 0x00400000UL +#define TCP_SOCKET__NEWCONN 0x00800000UL +#define TCP_SOCKET__ACCEPTFROM 0x01000000UL +#define TCP_SOCKET__NODE_BIND 0x02000000UL +#define TCP_SOCKET__NAME_CONNECT 0x04000000UL + +#define UDP_SOCKET__IOCTL 0x00000001UL +#define UDP_SOCKET__READ 0x00000002UL +#define UDP_SOCKET__WRITE 0x00000004UL +#define UDP_SOCKET__CREATE 0x00000008UL +#define UDP_SOCKET__GETATTR 0x00000010UL +#define UDP_SOCKET__SETATTR 0x00000020UL +#define UDP_SOCKET__LOCK 0x00000040UL +#define UDP_SOCKET__RELABELFROM 0x00000080UL +#define UDP_SOCKET__RELABELTO 0x00000100UL +#define UDP_SOCKET__APPEND 0x00000200UL +#define UDP_SOCKET__BIND 0x00000400UL +#define UDP_SOCKET__CONNECT 0x00000800UL +#define UDP_SOCKET__LISTEN 0x00001000UL +#define UDP_SOCKET__ACCEPT 0x00002000UL +#define UDP_SOCKET__GETOPT 0x00004000UL +#define UDP_SOCKET__SETOPT 0x00008000UL +#define UDP_SOCKET__SHUTDOWN 0x00010000UL +#define UDP_SOCKET__RECVFROM 0x00020000UL +#define UDP_SOCKET__SENDTO 0x00040000UL +#define UDP_SOCKET__RECV_MSG 0x00080000UL +#define UDP_SOCKET__SEND_MSG 0x00100000UL +#define UDP_SOCKET__NAME_BIND 0x00200000UL + +#define UDP_SOCKET__NODE_BIND 0x00400000UL + +#define RAWIP_SOCKET__IOCTL 0x00000001UL +#define RAWIP_SOCKET__READ 0x00000002UL +#define RAWIP_SOCKET__WRITE 0x00000004UL +#define RAWIP_SOCKET__CREATE 0x00000008UL +#define RAWIP_SOCKET__GETATTR 0x00000010UL +#define RAWIP_SOCKET__SETATTR 0x00000020UL +#define RAWIP_SOCKET__LOCK 0x00000040UL +#define RAWIP_SOCKET__RELABELFROM 0x00000080UL +#define RAWIP_SOCKET__RELABELTO 0x00000100UL +#define RAWIP_SOCKET__APPEND 0x00000200UL +#define RAWIP_SOCKET__BIND 0x00000400UL +#define RAWIP_SOCKET__CONNECT 0x00000800UL +#define RAWIP_SOCKET__LISTEN 0x00001000UL +#define RAWIP_SOCKET__ACCEPT 0x00002000UL +#define RAWIP_SOCKET__GETOPT 0x00004000UL +#define RAWIP_SOCKET__SETOPT 0x00008000UL +#define RAWIP_SOCKET__SHUTDOWN 0x00010000UL +#define RAWIP_SOCKET__RECVFROM 0x00020000UL +#define RAWIP_SOCKET__SENDTO 0x00040000UL +#define RAWIP_SOCKET__RECV_MSG 0x00080000UL +#define RAWIP_SOCKET__SEND_MSG 0x00100000UL +#define RAWIP_SOCKET__NAME_BIND 0x00200000UL + +#define RAWIP_SOCKET__NODE_BIND 0x00400000UL + +#define NODE__TCP_RECV 0x00000001UL +#define NODE__TCP_SEND 0x00000002UL +#define NODE__UDP_RECV 0x00000004UL +#define NODE__UDP_SEND 0x00000008UL +#define NODE__RAWIP_RECV 0x00000010UL +#define NODE__RAWIP_SEND 0x00000020UL +#define NODE__ENFORCE_DEST 0x00000040UL + +#define NETIF__TCP_RECV 0x00000001UL +#define NETIF__TCP_SEND 0x00000002UL +#define NETIF__UDP_RECV 0x00000004UL +#define NETIF__UDP_SEND 0x00000008UL +#define NETIF__RAWIP_RECV 0x00000010UL +#define NETIF__RAWIP_SEND 0x00000020UL + +#define NETLINK_SOCKET__IOCTL 0x00000001UL +#define NETLINK_SOCKET__READ 0x00000002UL +#define NETLINK_SOCKET__WRITE 0x00000004UL +#define NETLINK_SOCKET__CREATE 0x00000008UL +#define NETLINK_SOCKET__GETATTR 0x00000010UL +#define NETLINK_SOCKET__SETATTR 0x00000020UL +#define NETLINK_SOCKET__LOCK 0x00000040UL +#define NETLINK_SOCKET__RELABELFROM 0x00000080UL +#define NETLINK_SOCKET__RELABELTO 0x00000100UL +#define NETLINK_SOCKET__APPEND 0x00000200UL +#define NETLINK_SOCKET__BIND 0x00000400UL +#define NETLINK_SOCKET__CONNECT 0x00000800UL +#define NETLINK_SOCKET__LISTEN 0x00001000UL +#define NETLINK_SOCKET__ACCEPT 0x00002000UL +#define NETLINK_SOCKET__GETOPT 0x00004000UL +#define NETLINK_SOCKET__SETOPT 0x00008000UL +#define NETLINK_SOCKET__SHUTDOWN 0x00010000UL +#define NETLINK_SOCKET__RECVFROM 0x00020000UL +#define NETLINK_SOCKET__SENDTO 0x00040000UL +#define NETLINK_SOCKET__RECV_MSG 0x00080000UL +#define NETLINK_SOCKET__SEND_MSG 0x00100000UL +#define NETLINK_SOCKET__NAME_BIND 0x00200000UL + +#define PACKET_SOCKET__IOCTL 0x00000001UL +#define PACKET_SOCKET__READ 0x00000002UL +#define PACKET_SOCKET__WRITE 0x00000004UL +#define PACKET_SOCKET__CREATE 0x00000008UL +#define PACKET_SOCKET__GETATTR 0x00000010UL +#define PACKET_SOCKET__SETATTR 0x00000020UL +#define PACKET_SOCKET__LOCK 0x00000040UL +#define PACKET_SOCKET__RELABELFROM 0x00000080UL +#define PACKET_SOCKET__RELABELTO 0x00000100UL +#define PACKET_SOCKET__APPEND 0x00000200UL +#define PACKET_SOCKET__BIND 0x00000400UL +#define PACKET_SOCKET__CONNECT 0x00000800UL +#define PACKET_SOCKET__LISTEN 0x00001000UL +#define PACKET_SOCKET__ACCEPT 0x00002000UL +#define PACKET_SOCKET__GETOPT 0x00004000UL +#define PACKET_SOCKET__SETOPT 0x00008000UL +#define PACKET_SOCKET__SHUTDOWN 0x00010000UL +#define PACKET_SOCKET__RECVFROM 0x00020000UL +#define PACKET_SOCKET__SENDTO 0x00040000UL +#define PACKET_SOCKET__RECV_MSG 0x00080000UL +#define PACKET_SOCKET__SEND_MSG 0x00100000UL +#define PACKET_SOCKET__NAME_BIND 0x00200000UL + +#define KEY_SOCKET__IOCTL 0x00000001UL +#define KEY_SOCKET__READ 0x00000002UL +#define KEY_SOCKET__WRITE 0x00000004UL +#define KEY_SOCKET__CREATE 0x00000008UL +#define KEY_SOCKET__GETATTR 0x00000010UL +#define KEY_SOCKET__SETATTR 0x00000020UL +#define KEY_SOCKET__LOCK 0x00000040UL +#define KEY_SOCKET__RELABELFROM 0x00000080UL +#define KEY_SOCKET__RELABELTO 0x00000100UL +#define KEY_SOCKET__APPEND 0x00000200UL +#define KEY_SOCKET__BIND 0x00000400UL +#define KEY_SOCKET__CONNECT 0x00000800UL +#define KEY_SOCKET__LISTEN 0x00001000UL +#define KEY_SOCKET__ACCEPT 0x00002000UL +#define KEY_SOCKET__GETOPT 0x00004000UL +#define KEY_SOCKET__SETOPT 0x00008000UL +#define KEY_SOCKET__SHUTDOWN 0x00010000UL +#define KEY_SOCKET__RECVFROM 0x00020000UL +#define KEY_SOCKET__SENDTO 0x00040000UL +#define KEY_SOCKET__RECV_MSG 0x00080000UL +#define KEY_SOCKET__SEND_MSG 0x00100000UL +#define KEY_SOCKET__NAME_BIND 0x00200000UL + +#define UNIX_STREAM_SOCKET__IOCTL 0x00000001UL +#define UNIX_STREAM_SOCKET__READ 0x00000002UL +#define UNIX_STREAM_SOCKET__WRITE 0x00000004UL +#define UNIX_STREAM_SOCKET__CREATE 0x00000008UL +#define UNIX_STREAM_SOCKET__GETATTR 0x00000010UL +#define UNIX_STREAM_SOCKET__SETATTR 0x00000020UL +#define UNIX_STREAM_SOCKET__LOCK 0x00000040UL +#define UNIX_STREAM_SOCKET__RELABELFROM 0x00000080UL +#define UNIX_STREAM_SOCKET__RELABELTO 0x00000100UL +#define UNIX_STREAM_SOCKET__APPEND 0x00000200UL +#define UNIX_STREAM_SOCKET__BIND 0x00000400UL +#define UNIX_STREAM_SOCKET__CONNECT 0x00000800UL +#define UNIX_STREAM_SOCKET__LISTEN 0x00001000UL +#define UNIX_STREAM_SOCKET__ACCEPT 0x00002000UL +#define UNIX_STREAM_SOCKET__GETOPT 0x00004000UL +#define UNIX_STREAM_SOCKET__SETOPT 0x00008000UL +#define UNIX_STREAM_SOCKET__SHUTDOWN 0x00010000UL +#define UNIX_STREAM_SOCKET__RECVFROM 0x00020000UL +#define UNIX_STREAM_SOCKET__SENDTO 0x00040000UL +#define UNIX_STREAM_SOCKET__RECV_MSG 0x00080000UL +#define UNIX_STREAM_SOCKET__SEND_MSG 0x00100000UL +#define UNIX_STREAM_SOCKET__NAME_BIND 0x00200000UL + +#define UNIX_STREAM_SOCKET__CONNECTTO 0x00400000UL +#define UNIX_STREAM_SOCKET__NEWCONN 0x00800000UL +#define UNIX_STREAM_SOCKET__ACCEPTFROM 0x01000000UL + +#define UNIX_DGRAM_SOCKET__IOCTL 0x00000001UL +#define UNIX_DGRAM_SOCKET__READ 0x00000002UL +#define UNIX_DGRAM_SOCKET__WRITE 0x00000004UL +#define UNIX_DGRAM_SOCKET__CREATE 0x00000008UL +#define UNIX_DGRAM_SOCKET__GETATTR 0x00000010UL +#define UNIX_DGRAM_SOCKET__SETATTR 0x00000020UL +#define UNIX_DGRAM_SOCKET__LOCK 0x00000040UL +#define UNIX_DGRAM_SOCKET__RELABELFROM 0x00000080UL +#define UNIX_DGRAM_SOCKET__RELABELTO 0x00000100UL +#define UNIX_DGRAM_SOCKET__APPEND 0x00000200UL +#define UNIX_DGRAM_SOCKET__BIND 0x00000400UL +#define UNIX_DGRAM_SOCKET__CONNECT 0x00000800UL +#define UNIX_DGRAM_SOCKET__LISTEN 0x00001000UL +#define UNIX_DGRAM_SOCKET__ACCEPT 0x00002000UL +#define UNIX_DGRAM_SOCKET__GETOPT 0x00004000UL +#define UNIX_DGRAM_SOCKET__SETOPT 0x00008000UL +#define UNIX_DGRAM_SOCKET__SHUTDOWN 0x00010000UL +#define UNIX_DGRAM_SOCKET__RECVFROM 0x00020000UL +#define UNIX_DGRAM_SOCKET__SENDTO 0x00040000UL +#define UNIX_DGRAM_SOCKET__RECV_MSG 0x00080000UL +#define UNIX_DGRAM_SOCKET__SEND_MSG 0x00100000UL +#define UNIX_DGRAM_SOCKET__NAME_BIND 0x00200000UL + +#define PROCESS__FORK 0x00000001UL +#define PROCESS__TRANSITION 0x00000002UL +#define PROCESS__SIGCHLD 0x00000004UL +#define PROCESS__SIGKILL 0x00000008UL +#define PROCESS__SIGSTOP 0x00000010UL +#define PROCESS__SIGNULL 0x00000020UL +#define PROCESS__SIGNAL 0x00000040UL +#define PROCESS__PTRACE 0x00000080UL +#define PROCESS__GETSCHED 0x00000100UL +#define PROCESS__SETSCHED 0x00000200UL +#define PROCESS__GETSESSION 0x00000400UL +#define PROCESS__GETPGID 0x00000800UL +#define PROCESS__SETPGID 0x00001000UL +#define PROCESS__GETCAP 0x00002000UL +#define PROCESS__SETCAP 0x00004000UL +#define PROCESS__SHARE 0x00008000UL +#define PROCESS__GETATTR 0x00010000UL +#define PROCESS__SETEXEC 0x00020000UL +#define PROCESS__SETFSCREATE 0x00040000UL +#define PROCESS__NOATSECURE 0x00080000UL +#define PROCESS__SIGINH 0x00100000UL +#define PROCESS__SETRLIMIT 0x00200000UL +#define PROCESS__RLIMITINH 0x00400000UL +#define PROCESS__DYNTRANSITION 0x00800000UL +#define PROCESS__SETCURRENT 0x01000000UL +#define PROCESS__EXECMEM 0x02000000UL + +#define IPC__CREATE 0x00000001UL +#define IPC__DESTROY 0x00000002UL +#define IPC__GETATTR 0x00000004UL +#define IPC__SETATTR 0x00000008UL +#define IPC__READ 0x00000010UL +#define IPC__WRITE 0x00000020UL +#define IPC__ASSOCIATE 0x00000040UL +#define IPC__UNIX_READ 0x00000080UL +#define IPC__UNIX_WRITE 0x00000100UL + +#define SEM__CREATE 0x00000001UL +#define SEM__DESTROY 0x00000002UL +#define SEM__GETATTR 0x00000004UL +#define SEM__SETATTR 0x00000008UL +#define SEM__READ 0x00000010UL +#define SEM__WRITE 0x00000020UL +#define SEM__ASSOCIATE 0x00000040UL +#define SEM__UNIX_READ 0x00000080UL +#define SEM__UNIX_WRITE 0x00000100UL + +#define MSGQ__CREATE 0x00000001UL +#define MSGQ__DESTROY 0x00000002UL +#define MSGQ__GETATTR 0x00000004UL +#define MSGQ__SETATTR 0x00000008UL +#define MSGQ__READ 0x00000010UL +#define MSGQ__WRITE 0x00000020UL +#define MSGQ__ASSOCIATE 0x00000040UL +#define MSGQ__UNIX_READ 0x00000080UL +#define MSGQ__UNIX_WRITE 0x00000100UL + +#define MSGQ__ENQUEUE 0x00000200UL + +#define MSG__SEND 0x00000001UL +#define MSG__RECEIVE 0x00000002UL + +#define SHM__CREATE 0x00000001UL +#define SHM__DESTROY 0x00000002UL +#define SHM__GETATTR 0x00000004UL +#define SHM__SETATTR 0x00000008UL +#define SHM__READ 0x00000010UL +#define SHM__WRITE 0x00000020UL +#define SHM__ASSOCIATE 0x00000040UL +#define SHM__UNIX_READ 0x00000080UL +#define SHM__UNIX_WRITE 0x00000100UL + +#define SHM__LOCK 0x00000200UL + +#define SECURITY__COMPUTE_AV 0x00000001UL +#define SECURITY__COMPUTE_CREATE 0x00000002UL +#define SECURITY__COMPUTE_MEMBER 0x00000004UL +#define SECURITY__CHECK_CONTEXT 0x00000008UL +#define SECURITY__LOAD_POLICY 0x00000010UL +#define SECURITY__COMPUTE_RELABEL 0x00000020UL +#define SECURITY__COMPUTE_USER 0x00000040UL +#define SECURITY__SETENFORCE 0x00000080UL +#define SECURITY__SETBOOL 0x00000100UL +#define SECURITY__SETSECPARAM 0x00000200UL +#define SECURITY__SETCHECKREQPROT 0x00000400UL + +#define SYSTEM__IPC_INFO 0x00000001UL +#define SYSTEM__SYSLOG_READ 0x00000002UL +#define SYSTEM__SYSLOG_MOD 0x00000004UL +#define SYSTEM__SYSLOG_CONSOLE 0x00000008UL + +#define CAPABILITY__CHOWN 0x00000001UL +#define CAPABILITY__DAC_OVERRIDE 0x00000002UL +#define CAPABILITY__DAC_READ_SEARCH 0x00000004UL +#define CAPABILITY__FOWNER 0x00000008UL +#define CAPABILITY__FSETID 0x00000010UL +#define CAPABILITY__KILL 0x00000020UL +#define CAPABILITY__SETGID 0x00000040UL +#define CAPABILITY__SETUID 0x00000080UL +#define CAPABILITY__SETPCAP 0x00000100UL +#define CAPABILITY__LINUX_IMMUTABLE 0x00000200UL +#define CAPABILITY__NET_BIND_SERVICE 0x00000400UL +#define CAPABILITY__NET_BROADCAST 0x00000800UL +#define CAPABILITY__NET_ADMIN 0x00001000UL +#define CAPABILITY__NET_RAW 0x00002000UL +#define CAPABILITY__IPC_LOCK 0x00004000UL +#define CAPABILITY__IPC_OWNER 0x00008000UL +#define CAPABILITY__SYS_MODULE 0x00010000UL +#define CAPABILITY__SYS_RAWIO 0x00020000UL +#define CAPABILITY__SYS_CHROOT 0x00040000UL +#define CAPABILITY__SYS_PTRACE 0x00080000UL +#define CAPABILITY__SYS_PACCT 0x00100000UL +#define CAPABILITY__SYS_ADMIN 0x00200000UL +#define CAPABILITY__SYS_BOOT 0x00400000UL +#define CAPABILITY__SYS_NICE 0x00800000UL +#define CAPABILITY__SYS_RESOURCE 0x01000000UL +#define CAPABILITY__SYS_TIME 0x02000000UL +#define CAPABILITY__SYS_TTY_CONFIG 0x04000000UL +#define CAPABILITY__MKNOD 0x08000000UL +#define CAPABILITY__LEASE 0x10000000UL + +#define PASSWD__PASSWD 0x00000001UL +#define PASSWD__CHFN 0x00000002UL +#define PASSWD__CHSH 0x00000004UL +#define PASSWD__ROOTOK 0x00000008UL +#define PASSWD__CRONTAB 0x00000010UL + +#define DRAWABLE__CREATE 0x00000001UL +#define DRAWABLE__DESTROY 0x00000002UL +#define DRAWABLE__DRAW 0x00000004UL +#define DRAWABLE__COPY 0x00000008UL +#define DRAWABLE__GETATTR 0x00000010UL + +#define GC__CREATE 0x00000001UL +#define GC__FREE 0x00000002UL +#define GC__GETATTR 0x00000004UL +#define GC__SETATTR 0x00000008UL + +#define WINDOW__ADDCHILD 0x00000001UL +#define WINDOW__CREATE 0x00000002UL +#define WINDOW__DESTROY 0x00000004UL +#define WINDOW__MAP 0x00000008UL +#define WINDOW__UNMAP 0x00000010UL +#define WINDOW__CHSTACK 0x00000020UL +#define WINDOW__CHPROPLIST 0x00000040UL +#define WINDOW__CHPROP 0x00000080UL +#define WINDOW__LISTPROP 0x00000100UL +#define WINDOW__GETATTR 0x00000200UL +#define WINDOW__SETATTR 0x00000400UL +#define WINDOW__SETFOCUS 0x00000800UL +#define WINDOW__MOVE 0x00001000UL +#define WINDOW__CHSELECTION 0x00002000UL +#define WINDOW__CHPARENT 0x00004000UL +#define WINDOW__CTRLLIFE 0x00008000UL +#define WINDOW__ENUMERATE 0x00010000UL +#define WINDOW__TRANSPARENT 0x00020000UL +#define WINDOW__MOUSEMOTION 0x00040000UL +#define WINDOW__CLIENTCOMEVENT 0x00080000UL +#define WINDOW__INPUTEVENT 0x00100000UL +#define WINDOW__DRAWEVENT 0x00200000UL +#define WINDOW__WINDOWCHANGEEVENT 0x00400000UL +#define WINDOW__WINDOWCHANGEREQUEST 0x00800000UL +#define WINDOW__SERVERCHANGEEVENT 0x01000000UL +#define WINDOW__EXTENSIONEVENT 0x02000000UL + +#define FONT__LOAD 0x00000001UL +#define FONT__FREE 0x00000002UL +#define FONT__GETATTR 0x00000004UL +#define FONT__USE 0x00000008UL + +#define COLORMAP__CREATE 0x00000001UL +#define COLORMAP__FREE 0x00000002UL +#define COLORMAP__INSTALL 0x00000004UL +#define COLORMAP__UNINSTALL 0x00000008UL +#define COLORMAP__LIST 0x00000010UL +#define COLORMAP__READ 0x00000020UL +#define COLORMAP__STORE 0x00000040UL +#define COLORMAP__GETATTR 0x00000080UL +#define COLORMAP__SETATTR 0x00000100UL + +#define PROPERTY__CREATE 0x00000001UL +#define PROPERTY__FREE 0x00000002UL +#define PROPERTY__READ 0x00000004UL +#define PROPERTY__WRITE 0x00000008UL + +#define CURSOR__CREATE 0x00000001UL +#define CURSOR__CREATEGLYPH 0x00000002UL +#define CURSOR__FREE 0x00000004UL +#define CURSOR__ASSIGN 0x00000008UL +#define CURSOR__SETATTR 0x00000010UL + +#define XCLIENT__KILL 0x00000001UL + +#define XINPUT__LOOKUP 0x00000001UL +#define XINPUT__GETATTR 0x00000002UL +#define XINPUT__SETATTR 0x00000004UL +#define XINPUT__SETFOCUS 0x00000008UL +#define XINPUT__WARPPOINTER 0x00000010UL +#define XINPUT__ACTIVEGRAB 0x00000020UL +#define XINPUT__PASSIVEGRAB 0x00000040UL +#define XINPUT__UNGRAB 0x00000080UL +#define XINPUT__BELL 0x00000100UL +#define XINPUT__MOUSEMOTION 0x00000200UL +#define XINPUT__RELABELINPUT 0x00000400UL + +#define XSERVER__SCREENSAVER 0x00000001UL +#define XSERVER__GETHOSTLIST 0x00000002UL +#define XSERVER__SETHOSTLIST 0x00000004UL +#define XSERVER__GETFONTPATH 0x00000008UL +#define XSERVER__SETFONTPATH 0x00000010UL +#define XSERVER__GETATTR 0x00000020UL +#define XSERVER__GRAB 0x00000040UL +#define XSERVER__UNGRAB 0x00000080UL + +#define XEXTENSION__QUERY 0x00000001UL +#define XEXTENSION__USE 0x00000002UL + +#define PAX__PAGEEXEC 0x00000001UL +#define PAX__EMUTRAMP 0x00000002UL +#define PAX__MPROTECT 0x00000004UL +#define PAX__RANDMMAP 0x00000008UL +#define PAX__RANDEXEC 0x00000010UL +#define PAX__SEGMEXEC 0x00000020UL + +#define NETLINK_ROUTE_SOCKET__IOCTL 0x00000001UL +#define NETLINK_ROUTE_SOCKET__READ 0x00000002UL +#define NETLINK_ROUTE_SOCKET__WRITE 0x00000004UL +#define NETLINK_ROUTE_SOCKET__CREATE 0x00000008UL +#define NETLINK_ROUTE_SOCKET__GETATTR 0x00000010UL +#define NETLINK_ROUTE_SOCKET__SETATTR 0x00000020UL +#define NETLINK_ROUTE_SOCKET__LOCK 0x00000040UL +#define NETLINK_ROUTE_SOCKET__RELABELFROM 0x00000080UL +#define NETLINK_ROUTE_SOCKET__RELABELTO 0x00000100UL +#define NETLINK_ROUTE_SOCKET__APPEND 0x00000200UL +#define NETLINK_ROUTE_SOCKET__BIND 0x00000400UL +#define NETLINK_ROUTE_SOCKET__CONNECT 0x00000800UL +#define NETLINK_ROUTE_SOCKET__LISTEN 0x00001000UL +#define NETLINK_ROUTE_SOCKET__ACCEPT 0x00002000UL +#define NETLINK_ROUTE_SOCKET__GETOPT 0x00004000UL +#define NETLINK_ROUTE_SOCKET__SETOPT 0x00008000UL +#define NETLINK_ROUTE_SOCKET__SHUTDOWN 0x00010000UL +#define NETLINK_ROUTE_SOCKET__RECVFROM 0x00020000UL +#define NETLINK_ROUTE_SOCKET__SENDTO 0x00040000UL +#define NETLINK_ROUTE_SOCKET__RECV_MSG 0x00080000UL +#define NETLINK_ROUTE_SOCKET__SEND_MSG 0x00100000UL +#define NETLINK_ROUTE_SOCKET__NAME_BIND 0x00200000UL + +#define NETLINK_ROUTE_SOCKET__NLMSG_READ 0x00400000UL +#define NETLINK_ROUTE_SOCKET__NLMSG_WRITE 0x00800000UL + +#define NETLINK_FIREWALL_SOCKET__IOCTL 0x00000001UL +#define NETLINK_FIREWALL_SOCKET__READ 0x00000002UL +#define NETLINK_FIREWALL_SOCKET__WRITE 0x00000004UL +#define NETLINK_FIREWALL_SOCKET__CREATE 0x00000008UL +#define NETLINK_FIREWALL_SOCKET__GETATTR 0x00000010UL +#define NETLINK_FIREWALL_SOCKET__SETATTR 0x00000020UL +#define NETLINK_FIREWALL_SOCKET__LOCK 0x00000040UL +#define NETLINK_FIREWALL_SOCKET__RELABELFROM 0x00000080UL +#define NETLINK_FIREWALL_SOCKET__RELABELTO 0x00000100UL +#define NETLINK_FIREWALL_SOCKET__APPEND 0x00000200UL +#define NETLINK_FIREWALL_SOCKET__BIND 0x00000400UL +#define NETLINK_FIREWALL_SOCKET__CONNECT 0x00000800UL +#define NETLINK_FIREWALL_SOCKET__LISTEN 0x00001000UL +#define NETLINK_FIREWALL_SOCKET__ACCEPT 0x00002000UL +#define NETLINK_FIREWALL_SOCKET__GETOPT 0x00004000UL +#define NETLINK_FIREWALL_SOCKET__SETOPT 0x00008000UL +#define NETLINK_FIREWALL_SOCKET__SHUTDOWN 0x00010000UL +#define NETLINK_FIREWALL_SOCKET__RECVFROM 0x00020000UL +#define NETLINK_FIREWALL_SOCKET__SENDTO 0x00040000UL +#define NETLINK_FIREWALL_SOCKET__RECV_MSG 0x00080000UL +#define NETLINK_FIREWALL_SOCKET__SEND_MSG 0x00100000UL +#define NETLINK_FIREWALL_SOCKET__NAME_BIND 0x00200000UL + +#define NETLINK_FIREWALL_SOCKET__NLMSG_READ 0x00400000UL +#define NETLINK_FIREWALL_SOCKET__NLMSG_WRITE 0x00800000UL + +#define NETLINK_TCPDIAG_SOCKET__IOCTL 0x00000001UL +#define NETLINK_TCPDIAG_SOCKET__READ 0x00000002UL +#define NETLINK_TCPDIAG_SOCKET__WRITE 0x00000004UL +#define NETLINK_TCPDIAG_SOCKET__CREATE 0x00000008UL +#define NETLINK_TCPDIAG_SOCKET__GETATTR 0x00000010UL +#define NETLINK_TCPDIAG_SOCKET__SETATTR 0x00000020UL +#define NETLINK_TCPDIAG_SOCKET__LOCK 0x00000040UL +#define NETLINK_TCPDIAG_SOCKET__RELABELFROM 0x00000080UL +#define NETLINK_TCPDIAG_SOCKET__RELABELTO 0x00000100UL +#define NETLINK_TCPDIAG_SOCKET__APPEND 0x00000200UL +#define NETLINK_TCPDIAG_SOCKET__BIND 0x00000400UL +#define NETLINK_TCPDIAG_SOCKET__CONNECT 0x00000800UL +#define NETLINK_TCPDIAG_SOCKET__LISTEN 0x00001000UL +#define NETLINK_TCPDIAG_SOCKET__ACCEPT 0x00002000UL +#define NETLINK_TCPDIAG_SOCKET__GETOPT 0x00004000UL +#define NETLINK_TCPDIAG_SOCKET__SETOPT 0x00008000UL +#define NETLINK_TCPDIAG_SOCKET__SHUTDOWN 0x00010000UL +#define NETLINK_TCPDIAG_SOCKET__RECVFROM 0x00020000UL +#define NETLINK_TCPDIAG_SOCKET__SENDTO 0x00040000UL +#define NETLINK_TCPDIAG_SOCKET__RECV_MSG 0x00080000UL +#define NETLINK_TCPDIAG_SOCKET__SEND_MSG 0x00100000UL +#define NETLINK_TCPDIAG_SOCKET__NAME_BIND 0x00200000UL + +#define NETLINK_TCPDIAG_SOCKET__NLMSG_READ 0x00400000UL +#define NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE 0x00800000UL + +#define NETLINK_NFLOG_SOCKET__IOCTL 0x00000001UL +#define NETLINK_NFLOG_SOCKET__READ 0x00000002UL +#define NETLINK_NFLOG_SOCKET__WRITE 0x00000004UL +#define NETLINK_NFLOG_SOCKET__CREATE 0x00000008UL +#define NETLINK_NFLOG_SOCKET__GETATTR 0x00000010UL +#define NETLINK_NFLOG_SOCKET__SETATTR 0x00000020UL +#define NETLINK_NFLOG_SOCKET__LOCK 0x00000040UL +#define NETLINK_NFLOG_SOCKET__RELABELFROM 0x00000080UL +#define NETLINK_NFLOG_SOCKET__RELABELTO 0x00000100UL +#define NETLINK_NFLOG_SOCKET__APPEND 0x00000200UL +#define NETLINK_NFLOG_SOCKET__BIND 0x00000400UL +#define NETLINK_NFLOG_SOCKET__CONNECT 0x00000800UL +#define NETLINK_NFLOG_SOCKET__LISTEN 0x00001000UL +#define NETLINK_NFLOG_SOCKET__ACCEPT 0x00002000UL +#define NETLINK_NFLOG_SOCKET__GETOPT 0x00004000UL +#define NETLINK_NFLOG_SOCKET__SETOPT 0x00008000UL +#define NETLINK_NFLOG_SOCKET__SHUTDOWN 0x00010000UL +#define NETLINK_NFLOG_SOCKET__RECVFROM 0x00020000UL +#define NETLINK_NFLOG_SOCKET__SENDTO 0x00040000UL +#define NETLINK_NFLOG_SOCKET__RECV_MSG 0x00080000UL +#define NETLINK_NFLOG_SOCKET__SEND_MSG 0x00100000UL +#define NETLINK_NFLOG_SOCKET__NAME_BIND 0x00200000UL + +#define NETLINK_XFRM_SOCKET__IOCTL 0x00000001UL +#define NETLINK_XFRM_SOCKET__READ 0x00000002UL +#define NETLINK_XFRM_SOCKET__WRITE 0x00000004UL +#define NETLINK_XFRM_SOCKET__CREATE 0x00000008UL +#define NETLINK_XFRM_SOCKET__GETATTR 0x00000010UL +#define NETLINK_XFRM_SOCKET__SETATTR 0x00000020UL +#define NETLINK_XFRM_SOCKET__LOCK 0x00000040UL +#define NETLINK_XFRM_SOCKET__RELABELFROM 0x00000080UL +#define NETLINK_XFRM_SOCKET__RELABELTO 0x00000100UL +#define NETLINK_XFRM_SOCKET__APPEND 0x00000200UL +#define NETLINK_XFRM_SOCKET__BIND 0x00000400UL +#define NETLINK_XFRM_SOCKET__CONNECT 0x00000800UL +#define NETLINK_XFRM_SOCKET__LISTEN 0x00001000UL +#define NETLINK_XFRM_SOCKET__ACCEPT 0x00002000UL +#define NETLINK_XFRM_SOCKET__GETOPT 0x00004000UL +#define NETLINK_XFRM_SOCKET__SETOPT 0x00008000UL +#define NETLINK_XFRM_SOCKET__SHUTDOWN 0x00010000UL +#define NETLINK_XFRM_SOCKET__RECVFROM 0x00020000UL +#define NETLINK_XFRM_SOCKET__SENDTO 0x00040000UL +#define NETLINK_XFRM_SOCKET__RECV_MSG 0x00080000UL +#define NETLINK_XFRM_SOCKET__SEND_MSG 0x00100000UL +#define NETLINK_XFRM_SOCKET__NAME_BIND 0x00200000UL + +#define NETLINK_XFRM_SOCKET__NLMSG_READ 0x00400000UL +#define NETLINK_XFRM_SOCKET__NLMSG_WRITE 0x00800000UL + +#define NETLINK_SELINUX_SOCKET__IOCTL 0x00000001UL +#define NETLINK_SELINUX_SOCKET__READ 0x00000002UL +#define NETLINK_SELINUX_SOCKET__WRITE 0x00000004UL +#define NETLINK_SELINUX_SOCKET__CREATE 0x00000008UL +#define NETLINK_SELINUX_SOCKET__GETATTR 0x00000010UL +#define NETLINK_SELINUX_SOCKET__SETATTR 0x00000020UL +#define NETLINK_SELINUX_SOCKET__LOCK 0x00000040UL +#define NETLINK_SELINUX_SOCKET__RELABELFROM 0x00000080UL +#define NETLINK_SELINUX_SOCKET__RELABELTO 0x00000100UL +#define NETLINK_SELINUX_SOCKET__APPEND 0x00000200UL +#define NETLINK_SELINUX_SOCKET__BIND 0x00000400UL +#define NETLINK_SELINUX_SOCKET__CONNECT 0x00000800UL +#define NETLINK_SELINUX_SOCKET__LISTEN 0x00001000UL +#define NETLINK_SELINUX_SOCKET__ACCEPT 0x00002000UL +#define NETLINK_SELINUX_SOCKET__GETOPT 0x00004000UL +#define NETLINK_SELINUX_SOCKET__SETOPT 0x00008000UL +#define NETLINK_SELINUX_SOCKET__SHUTDOWN 0x00010000UL +#define NETLINK_SELINUX_SOCKET__RECVFROM 0x00020000UL +#define NETLINK_SELINUX_SOCKET__SENDTO 0x00040000UL +#define NETLINK_SELINUX_SOCKET__RECV_MSG 0x00080000UL +#define NETLINK_SELINUX_SOCKET__SEND_MSG 0x00100000UL +#define NETLINK_SELINUX_SOCKET__NAME_BIND 0x00200000UL + +#define NETLINK_AUDIT_SOCKET__IOCTL 0x00000001UL +#define NETLINK_AUDIT_SOCKET__READ 0x00000002UL +#define NETLINK_AUDIT_SOCKET__WRITE 0x00000004UL +#define NETLINK_AUDIT_SOCKET__CREATE 0x00000008UL +#define NETLINK_AUDIT_SOCKET__GETATTR 0x00000010UL +#define NETLINK_AUDIT_SOCKET__SETATTR 0x00000020UL +#define NETLINK_AUDIT_SOCKET__LOCK 0x00000040UL +#define NETLINK_AUDIT_SOCKET__RELABELFROM 0x00000080UL +#define NETLINK_AUDIT_SOCKET__RELABELTO 0x00000100UL +#define NETLINK_AUDIT_SOCKET__APPEND 0x00000200UL +#define NETLINK_AUDIT_SOCKET__BIND 0x00000400UL +#define NETLINK_AUDIT_SOCKET__CONNECT 0x00000800UL +#define NETLINK_AUDIT_SOCKET__LISTEN 0x00001000UL +#define NETLINK_AUDIT_SOCKET__ACCEPT 0x00002000UL +#define NETLINK_AUDIT_SOCKET__GETOPT 0x00004000UL +#define NETLINK_AUDIT_SOCKET__SETOPT 0x00008000UL +#define NETLINK_AUDIT_SOCKET__SHUTDOWN 0x00010000UL +#define NETLINK_AUDIT_SOCKET__RECVFROM 0x00020000UL +#define NETLINK_AUDIT_SOCKET__SENDTO 0x00040000UL +#define NETLINK_AUDIT_SOCKET__RECV_MSG 0x00080000UL +#define NETLINK_AUDIT_SOCKET__SEND_MSG 0x00100000UL +#define NETLINK_AUDIT_SOCKET__NAME_BIND 0x00200000UL + +#define NETLINK_AUDIT_SOCKET__NLMSG_READ 0x00400000UL +#define NETLINK_AUDIT_SOCKET__NLMSG_WRITE 0x00800000UL + +#define NETLINK_IP6FW_SOCKET__IOCTL 0x00000001UL +#define NETLINK_IP6FW_SOCKET__READ 0x00000002UL +#define NETLINK_IP6FW_SOCKET__WRITE 0x00000004UL +#define NETLINK_IP6FW_SOCKET__CREATE 0x00000008UL +#define NETLINK_IP6FW_SOCKET__GETATTR 0x00000010UL +#define NETLINK_IP6FW_SOCKET__SETATTR 0x00000020UL +#define NETLINK_IP6FW_SOCKET__LOCK 0x00000040UL +#define NETLINK_IP6FW_SOCKET__RELABELFROM 0x00000080UL +#define NETLINK_IP6FW_SOCKET__RELABELTO 0x00000100UL +#define NETLINK_IP6FW_SOCKET__APPEND 0x00000200UL +#define NETLINK_IP6FW_SOCKET__BIND 0x00000400UL +#define NETLINK_IP6FW_SOCKET__CONNECT 0x00000800UL +#define NETLINK_IP6FW_SOCKET__LISTEN 0x00001000UL +#define NETLINK_IP6FW_SOCKET__ACCEPT 0x00002000UL +#define NETLINK_IP6FW_SOCKET__GETOPT 0x00004000UL +#define NETLINK_IP6FW_SOCKET__SETOPT 0x00008000UL +#define NETLINK_IP6FW_SOCKET__SHUTDOWN 0x00010000UL +#define NETLINK_IP6FW_SOCKET__RECVFROM 0x00020000UL +#define NETLINK_IP6FW_SOCKET__SENDTO 0x00040000UL +#define NETLINK_IP6FW_SOCKET__RECV_MSG 0x00080000UL +#define NETLINK_IP6FW_SOCKET__SEND_MSG 0x00100000UL +#define NETLINK_IP6FW_SOCKET__NAME_BIND 0x00200000UL + +#define NETLINK_IP6FW_SOCKET__NLMSG_READ 0x00400000UL +#define NETLINK_IP6FW_SOCKET__NLMSG_WRITE 0x00800000UL + +#define NETLINK_DNRT_SOCKET__IOCTL 0x00000001UL +#define NETLINK_DNRT_SOCKET__READ 0x00000002UL +#define NETLINK_DNRT_SOCKET__WRITE 0x00000004UL +#define NETLINK_DNRT_SOCKET__CREATE 0x00000008UL +#define NETLINK_DNRT_SOCKET__GETATTR 0x00000010UL +#define NETLINK_DNRT_SOCKET__SETATTR 0x00000020UL +#define NETLINK_DNRT_SOCKET__LOCK 0x00000040UL +#define NETLINK_DNRT_SOCKET__RELABELFROM 0x00000080UL +#define NETLINK_DNRT_SOCKET__RELABELTO 0x00000100UL +#define NETLINK_DNRT_SOCKET__APPEND 0x00000200UL +#define NETLINK_DNRT_SOCKET__BIND 0x00000400UL +#define NETLINK_DNRT_SOCKET__CONNECT 0x00000800UL +#define NETLINK_DNRT_SOCKET__LISTEN 0x00001000UL +#define NETLINK_DNRT_SOCKET__ACCEPT 0x00002000UL +#define NETLINK_DNRT_SOCKET__GETOPT 0x00004000UL +#define NETLINK_DNRT_SOCKET__SETOPT 0x00008000UL +#define NETLINK_DNRT_SOCKET__SHUTDOWN 0x00010000UL +#define NETLINK_DNRT_SOCKET__RECVFROM 0x00020000UL +#define NETLINK_DNRT_SOCKET__SENDTO 0x00040000UL +#define NETLINK_DNRT_SOCKET__RECV_MSG 0x00080000UL +#define NETLINK_DNRT_SOCKET__SEND_MSG 0x00100000UL +#define NETLINK_DNRT_SOCKET__NAME_BIND 0x00200000UL + +#define DBUS__ACQUIRE_SVC 0x00000001UL +#define DBUS__SEND_MSG 0x00000002UL + +#define NSCD__GETPWD 0x00000001UL +#define NSCD__GETGRP 0x00000002UL +#define NSCD__GETHOST 0x00000004UL +#define NSCD__GETSTAT 0x00000008UL +#define NSCD__ADMIN 0x00000010UL +#define NSCD__SHMEMPWD 0x00000020UL +#define NSCD__SHMEMGRP 0x00000040UL +#define NSCD__SHMEMHOST 0x00000080UL + diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h new file mode 100644 index 000000000000..960ef18ddc41 --- /dev/null +++ b/security/selinux/include/avc.h @@ -0,0 +1,137 @@ +/* + * Access vector cache interface for object managers. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SELINUX_AVC_H_ +#define _SELINUX_AVC_H_ + +#include <linux/stddef.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/kdev_t.h> +#include <linux/spinlock.h> +#include <linux/init.h> +#include <linux/in6.h> +#include <asm/system.h> +#include "flask.h" +#include "av_permissions.h" +#include "security.h" + +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +extern int selinux_enforcing; +#else +#define selinux_enforcing 1 +#endif + +/* + * An entry in the AVC. + */ +struct avc_entry; + +struct task_struct; +struct vfsmount; +struct dentry; +struct inode; +struct sock; +struct sk_buff; + +/* Auxiliary data to use in generating the audit record. */ +struct avc_audit_data { + char type; +#define AVC_AUDIT_DATA_FS 1 +#define AVC_AUDIT_DATA_NET 2 +#define AVC_AUDIT_DATA_CAP 3 +#define AVC_AUDIT_DATA_IPC 4 + struct task_struct *tsk; + union { + struct { + struct vfsmount *mnt; + struct dentry *dentry; + struct inode *inode; + } fs; + struct { + char *netif; + struct sock *sk; + u16 family; + u16 dport; + u16 sport; + union { + struct { + u32 daddr; + u32 saddr; + } v4; + struct { + struct in6_addr daddr; + struct in6_addr saddr; + } v6; + } fam; + } net; + int cap; + int ipc_id; + } u; +}; + +#define v4info fam.v4 +#define v6info fam.v6 + +/* Initialize an AVC audit data structure. */ +#define AVC_AUDIT_DATA_INIT(_d,_t) \ + { memset((_d), 0, sizeof(struct avc_audit_data)); (_d)->type = AVC_AUDIT_DATA_##_t; } + +/* + * AVC statistics + */ +struct avc_cache_stats +{ + unsigned int lookups; + unsigned int hits; + unsigned int misses; + unsigned int allocations; + unsigned int reclaims; + unsigned int frees; +}; + +/* + * AVC operations + */ + +void __init avc_init(void); + +void avc_audit(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct av_decision *avd, int result, struct avc_audit_data *auditdata); + +int avc_has_perm_noaudit(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct av_decision *avd); + +int avc_has_perm(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct avc_audit_data *auditdata); + +#define AVC_CALLBACK_GRANT 1 +#define AVC_CALLBACK_TRY_REVOKE 2 +#define AVC_CALLBACK_REVOKE 4 +#define AVC_CALLBACK_RESET 8 +#define AVC_CALLBACK_AUDITALLOW_ENABLE 16 +#define AVC_CALLBACK_AUDITALLOW_DISABLE 32 +#define AVC_CALLBACK_AUDITDENY_ENABLE 64 +#define AVC_CALLBACK_AUDITDENY_DISABLE 128 + +int avc_add_callback(int (*callback)(u32 event, u32 ssid, u32 tsid, + u16 tclass, u32 perms, + u32 *out_retained), + u32 events, u32 ssid, u32 tsid, + u16 tclass, u32 perms); + +/* Exported to selinuxfs */ +int avc_get_hash_stats(char *page); +extern unsigned int avc_cache_threshold; + +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS +DECLARE_PER_CPU(struct avc_cache_stats, avc_cache_stats); +#endif + +#endif /* _SELINUX_AVC_H_ */ + diff --git a/security/selinux/include/avc_ss.h b/security/selinux/include/avc_ss.h new file mode 100644 index 000000000000..450a2831e2e3 --- /dev/null +++ b/security/selinux/include/avc_ss.h @@ -0,0 +1,14 @@ +/* + * Access vector cache interface for the security server. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SELINUX_AVC_SS_H_ +#define _SELINUX_AVC_SS_H_ + +#include "flask.h" + +int avc_ss_reset(u32 seqno); + +#endif /* _SELINUX_AVC_SS_H_ */ + diff --git a/security/selinux/include/class_to_string.h b/security/selinux/include/class_to_string.h new file mode 100644 index 000000000000..519a77d7394a --- /dev/null +++ b/security/selinux/include/class_to_string.h @@ -0,0 +1,58 @@ +/* This file is automatically generated. Do not edit. */ +/* + * Security object class definitions + */ + S_("null") + S_("security") + S_("process") + S_("system") + S_("capability") + S_("filesystem") + S_("file") + S_("dir") + S_("fd") + S_("lnk_file") + S_("chr_file") + S_("blk_file") + S_("sock_file") + S_("fifo_file") + S_("socket") + S_("tcp_socket") + S_("udp_socket") + S_("rawip_socket") + S_("node") + S_("netif") + S_("netlink_socket") + S_("packet_socket") + S_("key_socket") + S_("unix_stream_socket") + S_("unix_dgram_socket") + S_("sem") + S_("msg") + S_("msgq") + S_("shm") + S_("ipc") + S_("passwd") + S_("drawable") + S_("window") + S_("gc") + S_("font") + S_("colormap") + S_("property") + S_("cursor") + S_("xclient") + S_("xinput") + S_("xserver") + S_("xextension") + S_("pax") + S_("netlink_route_socket") + S_("netlink_firewall_socket") + S_("netlink_tcpdiag_socket") + S_("netlink_nflog_socket") + S_("netlink_xfrm_socket") + S_("netlink_selinux_socket") + S_("netlink_audit_socket") + S_("netlink_ip6fw_socket") + S_("netlink_dnrt_socket") + S_("dbus") + S_("nscd") diff --git a/security/selinux/include/common_perm_to_string.h b/security/selinux/include/common_perm_to_string.h new file mode 100644 index 000000000000..ce5b6e2fe9dd --- /dev/null +++ b/security/selinux/include/common_perm_to_string.h @@ -0,0 +1,58 @@ +/* This file is automatically generated. Do not edit. */ +TB_(common_file_perm_to_string) + S_("ioctl") + S_("read") + S_("write") + S_("create") + S_("getattr") + S_("setattr") + S_("lock") + S_("relabelfrom") + S_("relabelto") + S_("append") + S_("unlink") + S_("link") + S_("rename") + S_("execute") + S_("swapon") + S_("quotaon") + S_("mounton") +TE_(common_file_perm_to_string) + +TB_(common_socket_perm_to_string) + S_("ioctl") + S_("read") + S_("write") + S_("create") + S_("getattr") + S_("setattr") + S_("lock") + S_("relabelfrom") + S_("relabelto") + S_("append") + S_("bind") + S_("connect") + S_("listen") + S_("accept") + S_("getopt") + S_("setopt") + S_("shutdown") + S_("recvfrom") + S_("sendto") + S_("recv_msg") + S_("send_msg") + S_("name_bind") +TE_(common_socket_perm_to_string) + +TB_(common_ipc_perm_to_string) + S_("create") + S_("destroy") + S_("getattr") + S_("setattr") + S_("read") + S_("write") + S_("associate") + S_("unix_read") + S_("unix_write") +TE_(common_ipc_perm_to_string) + diff --git a/security/selinux/include/conditional.h b/security/selinux/include/conditional.h new file mode 100644 index 000000000000..67ce7a8d8301 --- /dev/null +++ b/security/selinux/include/conditional.h @@ -0,0 +1,22 @@ +/* + * Interface to booleans in the security server. This is exported + * for the selinuxfs. + * + * Author: Karl MacMillan <kmacmillan@tresys.com> + * + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#ifndef _SELINUX_CONDITIONAL_H_ +#define _SELINUX_CONDITIONAL_H_ + +int security_get_bools(int *len, char ***names, int **values); + +int security_set_bools(int len, int *values); + +int security_get_bool_value(int bool); + +#endif diff --git a/security/selinux/include/flask.h b/security/selinux/include/flask.h new file mode 100644 index 000000000000..4eef1b654e92 --- /dev/null +++ b/security/selinux/include/flask.h @@ -0,0 +1,95 @@ +/* This file is automatically generated. Do not edit. */ +#ifndef _SELINUX_FLASK_H_ +#define _SELINUX_FLASK_H_ + +/* + * Security object class definitions + */ +#define SECCLASS_SECURITY 1 +#define SECCLASS_PROCESS 2 +#define SECCLASS_SYSTEM 3 +#define SECCLASS_CAPABILITY 4 +#define SECCLASS_FILESYSTEM 5 +#define SECCLASS_FILE 6 +#define SECCLASS_DIR 7 +#define SECCLASS_FD 8 +#define SECCLASS_LNK_FILE 9 +#define SECCLASS_CHR_FILE 10 +#define SECCLASS_BLK_FILE 11 +#define SECCLASS_SOCK_FILE 12 +#define SECCLASS_FIFO_FILE 13 +#define SECCLASS_SOCKET 14 +#define SECCLASS_TCP_SOCKET 15 +#define SECCLASS_UDP_SOCKET 16 +#define SECCLASS_RAWIP_SOCKET 17 +#define SECCLASS_NODE 18 +#define SECCLASS_NETIF 19 +#define SECCLASS_NETLINK_SOCKET 20 +#define SECCLASS_PACKET_SOCKET 21 +#define SECCLASS_KEY_SOCKET 22 +#define SECCLASS_UNIX_STREAM_SOCKET 23 +#define SECCLASS_UNIX_DGRAM_SOCKET 24 +#define SECCLASS_SEM 25 +#define SECCLASS_MSG 26 +#define SECCLASS_MSGQ 27 +#define SECCLASS_SHM 28 +#define SECCLASS_IPC 29 +#define SECCLASS_PASSWD 30 +#define SECCLASS_DRAWABLE 31 +#define SECCLASS_WINDOW 32 +#define SECCLASS_GC 33 +#define SECCLASS_FONT 34 +#define SECCLASS_COLORMAP 35 +#define SECCLASS_PROPERTY 36 +#define SECCLASS_CURSOR 37 +#define SECCLASS_XCLIENT 38 +#define SECCLASS_XINPUT 39 +#define SECCLASS_XSERVER 40 +#define SECCLASS_XEXTENSION 41 +#define SECCLASS_PAX 42 +#define SECCLASS_NETLINK_ROUTE_SOCKET 43 +#define SECCLASS_NETLINK_FIREWALL_SOCKET 44 +#define SECCLASS_NETLINK_TCPDIAG_SOCKET 45 +#define SECCLASS_NETLINK_NFLOG_SOCKET 46 +#define SECCLASS_NETLINK_XFRM_SOCKET 47 +#define SECCLASS_NETLINK_SELINUX_SOCKET 48 +#define SECCLASS_NETLINK_AUDIT_SOCKET 49 +#define SECCLASS_NETLINK_IP6FW_SOCKET 50 +#define SECCLASS_NETLINK_DNRT_SOCKET 51 +#define SECCLASS_DBUS 52 +#define SECCLASS_NSCD 53 + +/* + * Security identifier indices for initial entities + */ +#define SECINITSID_KERNEL 1 +#define SECINITSID_SECURITY 2 +#define SECINITSID_UNLABELED 3 +#define SECINITSID_FS 4 +#define SECINITSID_FILE 5 +#define SECINITSID_FILE_LABELS 6 +#define SECINITSID_INIT 7 +#define SECINITSID_ANY_SOCKET 8 +#define SECINITSID_PORT 9 +#define SECINITSID_NETIF 10 +#define SECINITSID_NETMSG 11 +#define SECINITSID_NODE 12 +#define SECINITSID_IGMP_PACKET 13 +#define SECINITSID_ICMP_SOCKET 14 +#define SECINITSID_TCP_SOCKET 15 +#define SECINITSID_SYSCTL_MODPROBE 16 +#define SECINITSID_SYSCTL 17 +#define SECINITSID_SYSCTL_FS 18 +#define SECINITSID_SYSCTL_KERNEL 19 +#define SECINITSID_SYSCTL_NET 20 +#define SECINITSID_SYSCTL_NET_UNIX 21 +#define SECINITSID_SYSCTL_VM 22 +#define SECINITSID_SYSCTL_DEV 23 +#define SECINITSID_KMOD 24 +#define SECINITSID_POLICY 25 +#define SECINITSID_SCMP_PACKET 26 +#define SECINITSID_DEVNULL 27 + +#define SECINITSID_NUM 27 + +#endif diff --git a/security/selinux/include/initial_sid_to_string.h b/security/selinux/include/initial_sid_to_string.h new file mode 100644 index 000000000000..d4fac82793ae --- /dev/null +++ b/security/selinux/include/initial_sid_to_string.h @@ -0,0 +1,33 @@ +/* This file is automatically generated. Do not edit. */ +static char *initial_sid_to_string[] = +{ + "null", + "kernel", + "security", + "unlabeled", + "fs", + "file", + "file_labels", + "init", + "any_socket", + "port", + "netif", + "netmsg", + "node", + "igmp_packet", + "icmp_socket", + "tcp_socket", + "sysctl_modprobe", + "sysctl", + "sysctl_fs", + "sysctl_kernel", + "sysctl_net", + "sysctl_net_unix", + "sysctl_vm", + "sysctl_dev", + "kmod", + "policy", + "scmp_packet", + "devnull", +}; + diff --git a/security/selinux/include/netif.h b/security/selinux/include/netif.h new file mode 100644 index 000000000000..8bd6f9992d2b --- /dev/null +++ b/security/selinux/include/netif.h @@ -0,0 +1,21 @@ +/* + * Network interface table. + * + * Network interfaces (devices) do not have a security field, so we + * maintain a table associating each interface with a SID. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.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. + */ +#ifndef _SELINUX_NETIF_H_ +#define _SELINUX_NETIF_H_ + +int sel_netif_sids(struct net_device *dev, u32 *if_sid, u32 *msg_sid); + +#endif /* _SELINUX_NETIF_H_ */ + diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h new file mode 100644 index 000000000000..887937c8134a --- /dev/null +++ b/security/selinux/include/objsec.h @@ -0,0 +1,112 @@ +/* + * NSA Security-Enhanced Linux (SELinux) security module + * + * This file contains the SELinux security data structures for kernel objects. + * + * Author(s): 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> + * + * 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. + */ +#ifndef _SELINUX_OBJSEC_H_ +#define _SELINUX_OBJSEC_H_ + +#include <linux/list.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/binfmts.h> +#include <linux/in.h> +#include "flask.h" +#include "avc.h" + +struct task_security_struct { + unsigned long magic; /* magic number for this module */ + struct task_struct *task; /* back pointer to task object */ + u32 osid; /* SID prior to last execve */ + u32 sid; /* current SID */ + u32 exec_sid; /* exec SID */ + u32 create_sid; /* fscreate SID */ + u32 ptrace_sid; /* SID of ptrace parent */ +}; + +struct inode_security_struct { + unsigned long magic; /* magic number for this module */ + struct inode *inode; /* back pointer to inode object */ + struct list_head list; /* list of inode_security_struct */ + u32 task_sid; /* SID of creating task */ + u32 sid; /* SID of this object */ + u16 sclass; /* security class of this object */ + unsigned char initialized; /* initialization flag */ + struct semaphore sem; + unsigned char inherit; /* inherit SID from parent entry */ +}; + +struct file_security_struct { + unsigned long magic; /* magic number for this module */ + struct file *file; /* back pointer to file object */ + u32 sid; /* SID of open file description */ + u32 fown_sid; /* SID of file owner (for SIGIO) */ +}; + +struct superblock_security_struct { + unsigned long magic; /* magic number for this module */ + struct super_block *sb; /* back pointer to sb object */ + struct list_head list; /* list of superblock_security_struct */ + u32 sid; /* SID of file system */ + u32 def_sid; /* default SID for labeling */ + unsigned int behavior; /* labeling behavior */ + unsigned char initialized; /* initialization flag */ + unsigned char proc; /* proc fs */ + struct semaphore sem; + struct list_head isec_head; + spinlock_t isec_lock; +}; + +struct msg_security_struct { + unsigned long magic; /* magic number for this module */ + struct msg_msg *msg; /* back pointer */ + u32 sid; /* SID of message */ +}; + +struct ipc_security_struct { + unsigned long magic; /* magic number for this module */ + struct kern_ipc_perm *ipc_perm; /* back pointer */ + u16 sclass; /* security class of this object */ + u32 sid; /* SID of IPC resource */ +}; + +struct bprm_security_struct { + unsigned long magic; /* magic number for this module */ + struct linux_binprm *bprm; /* back pointer to bprm object */ + u32 sid; /* SID for transformed process */ + unsigned char set; + + /* + * unsafe is used to share failure information from bprm_apply_creds() + * to bprm_post_apply_creds(). + */ + char unsafe; +}; + +struct netif_security_struct { + struct net_device *dev; /* back pointer */ + u32 if_sid; /* SID for this interface */ + u32 msg_sid; /* default SID for messages received on this interface */ +}; + +struct sk_security_struct { + unsigned long magic; /* magic number for this module */ + struct sock *sk; /* back pointer to sk object */ + u32 peer_sid; /* SID of peer */ +}; + +extern unsigned int selinux_checkreqprot; + +#endif /* _SELINUX_OBJSEC_H_ */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h new file mode 100644 index 000000000000..fa187c9a351d --- /dev/null +++ b/security/selinux/include/security.h @@ -0,0 +1,97 @@ +/* + * Security server interface. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + * + */ + +#ifndef _SELINUX_SECURITY_H_ +#define _SELINUX_SECURITY_H_ + +#include "flask.h" + +#define SECSID_NULL 0x00000000 /* unspecified SID */ +#define SECSID_WILD 0xffffffff /* wildcard SID */ +#define SECCLASS_NULL 0x0000 /* no class */ + +#define SELINUX_MAGIC 0xf97cff8c + +/* Identify specific policy version changes */ +#define POLICYDB_VERSION_BASE 15 +#define POLICYDB_VERSION_BOOL 16 +#define POLICYDB_VERSION_IPV6 17 +#define POLICYDB_VERSION_NLCLASS 18 +#define POLICYDB_VERSION_VALIDATETRANS 19 +#define POLICYDB_VERSION_MLS 19 + +/* Range of policy versions we understand*/ +#define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE +#define POLICYDB_VERSION_MAX POLICYDB_VERSION_MLS + +#ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM +extern int selinux_enabled; +#else +#define selinux_enabled 1 +#endif + +extern int selinux_mls_enabled; + +int security_load_policy(void * data, size_t len); + +struct av_decision { + u32 allowed; + u32 decided; + u32 auditallow; + u32 auditdeny; + u32 seqno; +}; + +int security_compute_av(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct av_decision *avd); + +int security_transition_sid(u32 ssid, u32 tsid, + u16 tclass, u32 *out_sid); + +int security_member_sid(u32 ssid, u32 tsid, + u16 tclass, u32 *out_sid); + +int security_change_sid(u32 ssid, u32 tsid, + u16 tclass, u32 *out_sid); + +int security_sid_to_context(u32 sid, char **scontext, + u32 *scontext_len); + +int security_context_to_sid(char *scontext, u32 scontext_len, + u32 *out_sid); + +int security_get_user_sids(u32 callsid, char *username, + u32 **sids, u32 *nel); + +int security_port_sid(u16 domain, u16 type, u8 protocol, u16 port, + u32 *out_sid); + +int security_netif_sid(char *name, u32 *if_sid, + u32 *msg_sid); + +int security_node_sid(u16 domain, void *addr, u32 addrlen, + u32 *out_sid); + +int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, + u16 tclass); + +#define SECURITY_FS_USE_XATTR 1 /* use xattr */ +#define SECURITY_FS_USE_TRANS 2 /* use transition SIDs, e.g. devpts/tmpfs */ +#define SECURITY_FS_USE_TASK 3 /* use task SIDs, e.g. pipefs/sockfs */ +#define SECURITY_FS_USE_GENFS 4 /* use the genfs support */ +#define SECURITY_FS_USE_NONE 5 /* no labeling support */ +#define SECURITY_FS_USE_MNTPOINT 6 /* use mountpoint labeling */ + +int security_fs_use(const char *fstype, unsigned int *behavior, + u32 *sid); + +int security_genfs_sid(const char *fstype, char *name, u16 sclass, + u32 *sid); + +#endif /* _SELINUX_SECURITY_H_ */ + diff --git a/security/selinux/netif.c b/security/selinux/netif.c new file mode 100644 index 000000000000..718d7be9f4dd --- /dev/null +++ b/security/selinux/netif.c @@ -0,0 +1,270 @@ +/* + * Network interface table. + * + * Network interfaces (devices) do not have a security field, so we + * maintain a table associating each interface with a SID. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.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/init.h> +#include <linux/types.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/notifier.h> +#include <linux/netdevice.h> +#include <linux/rcupdate.h> + +#include "security.h" +#include "objsec.h" +#include "netif.h" + +#define SEL_NETIF_HASH_SIZE 64 +#define SEL_NETIF_HASH_MAX 1024 + +#undef DEBUG + +#ifdef DEBUG +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +struct sel_netif +{ + struct list_head list; + struct netif_security_struct nsec; + struct rcu_head rcu_head; +}; + +static u32 sel_netif_total; +static LIST_HEAD(sel_netif_list); +static DEFINE_SPINLOCK(sel_netif_lock); +static struct list_head sel_netif_hash[SEL_NETIF_HASH_SIZE]; + +static inline u32 sel_netif_hasfn(struct net_device *dev) +{ + return (dev->ifindex & (SEL_NETIF_HASH_SIZE - 1)); +} + +/* + * All of the devices should normally fit in the hash, so we optimize + * for that case. + */ +static inline struct sel_netif *sel_netif_find(struct net_device *dev) +{ + struct list_head *pos; + int idx = sel_netif_hasfn(dev); + + __list_for_each_rcu(pos, &sel_netif_hash[idx]) { + struct sel_netif *netif = list_entry(pos, + struct sel_netif, list); + if (likely(netif->nsec.dev == dev)) + return netif; + } + return NULL; +} + +static int sel_netif_insert(struct sel_netif *netif) +{ + int idx, ret = 0; + + if (sel_netif_total >= SEL_NETIF_HASH_MAX) { + ret = -ENOSPC; + goto out; + } + + idx = sel_netif_hasfn(netif->nsec.dev); + list_add_rcu(&netif->list, &sel_netif_hash[idx]); + sel_netif_total++; +out: + return ret; +} + +static void sel_netif_free(struct rcu_head *p) +{ + struct sel_netif *netif = container_of(p, struct sel_netif, rcu_head); + + DEBUGP("%s: %s\n", __FUNCTION__, netif->nsec.dev->name); + kfree(netif); +} + +static void sel_netif_destroy(struct sel_netif *netif) +{ + DEBUGP("%s: %s\n", __FUNCTION__, netif->nsec.dev->name); + + list_del_rcu(&netif->list); + sel_netif_total--; + call_rcu(&netif->rcu_head, sel_netif_free); +} + +static struct sel_netif *sel_netif_lookup(struct net_device *dev) +{ + int ret; + struct sel_netif *netif, *new; + struct netif_security_struct *nsec; + + netif = sel_netif_find(dev); + if (likely(netif != NULL)) + goto out; + + new = kmalloc(sizeof(*new), GFP_ATOMIC); + if (!new) { + netif = ERR_PTR(-ENOMEM); + goto out; + } + + memset(new, 0, sizeof(*new)); + nsec = &new->nsec; + + ret = security_netif_sid(dev->name, &nsec->if_sid, &nsec->msg_sid); + if (ret < 0) { + kfree(new); + netif = ERR_PTR(ret); + goto out; + } + + nsec->dev = dev; + + spin_lock_bh(&sel_netif_lock); + + netif = sel_netif_find(dev); + if (netif) { + spin_unlock_bh(&sel_netif_lock); + kfree(new); + goto out; + } + + ret = sel_netif_insert(new); + spin_unlock_bh(&sel_netif_lock); + + if (ret) { + kfree(new); + netif = ERR_PTR(ret); + goto out; + } + + netif = new; + + DEBUGP("new: ifindex=%u name=%s if_sid=%u msg_sid=%u\n", dev->ifindex, dev->name, + nsec->if_sid, nsec->msg_sid); +out: + return netif; +} + +static void sel_netif_assign_sids(u32 if_sid_in, u32 msg_sid_in, u32 *if_sid_out, u32 *msg_sid_out) +{ + if (if_sid_out) + *if_sid_out = if_sid_in; + if (msg_sid_out) + *msg_sid_out = msg_sid_in; +} + +static int sel_netif_sids_slow(struct net_device *dev, u32 *if_sid, u32 *msg_sid) +{ + int ret = 0; + u32 tmp_if_sid, tmp_msg_sid; + + ret = security_netif_sid(dev->name, &tmp_if_sid, &tmp_msg_sid); + if (!ret) + sel_netif_assign_sids(tmp_if_sid, tmp_msg_sid, if_sid, msg_sid); + return ret; +} + +int sel_netif_sids(struct net_device *dev, u32 *if_sid, u32 *msg_sid) +{ + int ret = 0; + struct sel_netif *netif; + + rcu_read_lock(); + netif = sel_netif_lookup(dev); + if (IS_ERR(netif)) { + rcu_read_unlock(); + ret = sel_netif_sids_slow(dev, if_sid, msg_sid); + goto out; + } + sel_netif_assign_sids(netif->nsec.if_sid, netif->nsec.msg_sid, if_sid, msg_sid); + rcu_read_unlock(); +out: + return ret; +} + +static void sel_netif_kill(struct net_device *dev) +{ + struct sel_netif *netif; + + spin_lock_bh(&sel_netif_lock); + netif = sel_netif_find(dev); + if (netif) + sel_netif_destroy(netif); + spin_unlock_bh(&sel_netif_lock); +} + +static void sel_netif_flush(void) +{ + int idx; + + spin_lock_bh(&sel_netif_lock); + for (idx = 0; idx < SEL_NETIF_HASH_SIZE; idx++) { + struct sel_netif *netif; + + list_for_each_entry(netif, &sel_netif_hash[idx], list) + sel_netif_destroy(netif); + } + spin_unlock_bh(&sel_netif_lock); +} + +static int sel_netif_avc_callback(u32 event, u32 ssid, u32 tsid, + u16 class, u32 perms, u32 *retained) +{ + if (event == AVC_CALLBACK_RESET) { + sel_netif_flush(); + synchronize_net(); + } + return 0; +} + +static int sel_netif_netdev_notifier_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct net_device *dev = ptr; + + if (event == NETDEV_DOWN) + sel_netif_kill(dev); + + return NOTIFY_DONE; +} + +static struct notifier_block sel_netif_netdev_notifier = { + .notifier_call = sel_netif_netdev_notifier_handler, +}; + +static __init int sel_netif_init(void) +{ + int i, err = 0; + + if (!selinux_enabled) + goto out; + + for (i = 0; i < SEL_NETIF_HASH_SIZE; i++) + INIT_LIST_HEAD(&sel_netif_hash[i]); + + register_netdevice_notifier(&sel_netif_netdev_notifier); + + err = avc_add_callback(sel_netif_avc_callback, AVC_CALLBACK_RESET, + SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); + if (err) + panic("avc_add_callback() failed, error %d\n", err); + +out: + return err; +} + +__initcall(sel_netif_init); + diff --git a/security/selinux/netlink.c b/security/selinux/netlink.c new file mode 100644 index 000000000000..18d08acafa78 --- /dev/null +++ b/security/selinux/netlink.c @@ -0,0 +1,113 @@ +/* + * Netlink event notifications for SELinux. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.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/init.h> +#include <linux/types.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/skbuff.h> +#include <linux/netlink.h> +#include <linux/selinux_netlink.h> + +static struct sock *selnl; + +static int selnl_msglen(int msgtype) +{ + int ret = 0; + + switch (msgtype) { + case SELNL_MSG_SETENFORCE: + ret = sizeof(struct selnl_msg_setenforce); + break; + + case SELNL_MSG_POLICYLOAD: + ret = sizeof(struct selnl_msg_policyload); + break; + + default: + BUG(); + } + return ret; +} + +static void selnl_add_payload(struct nlmsghdr *nlh, int len, int msgtype, void *data) +{ + switch (msgtype) { + case SELNL_MSG_SETENFORCE: { + struct selnl_msg_setenforce *msg = NLMSG_DATA(nlh); + + memset(msg, 0, len); + msg->val = *((int *)data); + break; + } + + case SELNL_MSG_POLICYLOAD: { + struct selnl_msg_policyload *msg = NLMSG_DATA(nlh); + + memset(msg, 0, len); + msg->seqno = *((u32 *)data); + break; + } + + default: + BUG(); + } +} + +static void selnl_notify(int msgtype, void *data) +{ + int len; + unsigned char *tmp; + struct sk_buff *skb; + struct nlmsghdr *nlh; + + len = selnl_msglen(msgtype); + + skb = alloc_skb(NLMSG_SPACE(len), GFP_USER); + if (!skb) + goto oom; + + tmp = skb->tail; + nlh = NLMSG_PUT(skb, 0, 0, msgtype, len); + selnl_add_payload(nlh, len, msgtype, data); + nlh->nlmsg_len = skb->tail - tmp; + netlink_broadcast(selnl, skb, 0, SELNL_GRP_AVC, GFP_USER); +out: + return; + +nlmsg_failure: + kfree_skb(skb); +oom: + printk(KERN_ERR "SELinux: OOM in %s\n", __FUNCTION__); + goto out; +} + +void selnl_notify_setenforce(int val) +{ + selnl_notify(SELNL_MSG_SETENFORCE, &val); +} + +void selnl_notify_policyload(u32 seqno) +{ + selnl_notify(SELNL_MSG_POLICYLOAD, &seqno); +} + +static int __init selnl_init(void) +{ + selnl = netlink_kernel_create(NETLINK_SELINUX, NULL); + if (selnl == NULL) + panic("SELinux: Cannot create netlink socket."); + netlink_set_nonroot(NETLINK_SELINUX, NL_NONROOT_RECV); + return 0; +} + +__initcall(selnl_init); diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c new file mode 100644 index 000000000000..fa7fa030e6eb --- /dev/null +++ b/security/selinux/nlmsgtab.c @@ -0,0 +1,156 @@ +/* + * Netlink message type permission tables, for user generated messages. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.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/types.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/if.h> +#include <linux/netfilter_ipv4/ip_queue.h> +#include <linux/tcp_diag.h> +#include <linux/xfrm.h> +#include <linux/audit.h> + +#include "flask.h" +#include "av_permissions.h" + +struct nlmsg_perm +{ + u16 nlmsg_type; + u32 perm; +}; + +static struct nlmsg_perm nlmsg_route_perms[] = +{ + { RTM_NEWLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETLINK, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_SETLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_NEWADDR, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELADDR, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETADDR, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWROUTE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELROUTE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETROUTE, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWRULE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELRULE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETRULE, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWQDISC, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELQDISC, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETQDISC, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWACTION, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELACTION, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETACTION, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWPREFIX, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETPREFIX, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_GETMULTICAST, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_GETANYCAST, NETLINK_ROUTE_SOCKET__NLMSG_READ }, +}; + +static struct nlmsg_perm nlmsg_firewall_perms[] = +{ + { IPQM_MODE, NETLINK_FIREWALL_SOCKET__NLMSG_WRITE }, + { IPQM_VERDICT, NETLINK_FIREWALL_SOCKET__NLMSG_WRITE }, +}; + +static struct nlmsg_perm nlmsg_tcpdiag_perms[] = +{ + { TCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, +}; + +static struct nlmsg_perm nlmsg_xfrm_perms[] = +{ + { XFRM_MSG_NEWSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_DELSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_GETSA, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_NEWPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_DELPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_GETPOLICY, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_ALLOCSPI, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_UPDPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_UPDSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, +}; + +static struct nlmsg_perm nlmsg_audit_perms[] = +{ + { AUDIT_GET, NETLINK_AUDIT_SOCKET__NLMSG_READ }, + { AUDIT_SET, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_LIST, NETLINK_AUDIT_SOCKET__NLMSG_READ }, + { AUDIT_ADD, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_DEL, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_LOGIN, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, +}; + + +static int nlmsg_perm(u16 nlmsg_type, u32 *perm, struct nlmsg_perm *tab, size_t tabsize) +{ + int i, err = -EINVAL; + + for (i = 0; i < tabsize/sizeof(struct nlmsg_perm); i++) + if (nlmsg_type == tab[i].nlmsg_type) { + *perm = tab[i].perm; + err = 0; + break; + } + + return err; +} + +int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm) +{ + int err = 0; + + switch (sclass) { + case SECCLASS_NETLINK_ROUTE_SOCKET: + err = nlmsg_perm(nlmsg_type, perm, nlmsg_route_perms, + sizeof(nlmsg_route_perms)); + break; + + case SECCLASS_NETLINK_FIREWALL_SOCKET: + case NETLINK_IP6_FW: + err = nlmsg_perm(nlmsg_type, perm, nlmsg_firewall_perms, + sizeof(nlmsg_firewall_perms)); + break; + + case SECCLASS_NETLINK_TCPDIAG_SOCKET: + err = nlmsg_perm(nlmsg_type, perm, nlmsg_tcpdiag_perms, + sizeof(nlmsg_tcpdiag_perms)); + break; + + case SECCLASS_NETLINK_XFRM_SOCKET: + err = nlmsg_perm(nlmsg_type, perm, nlmsg_xfrm_perms, + sizeof(nlmsg_xfrm_perms)); + break; + + case SECCLASS_NETLINK_AUDIT_SOCKET: + err = nlmsg_perm(nlmsg_type, perm, nlmsg_audit_perms, + sizeof(nlmsg_audit_perms)); + break; + + /* No messaging from userspace, or class unknown/unhandled */ + default: + err = -ENOENT; + break; + } + + return err; +} diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c new file mode 100644 index 000000000000..07221568b505 --- /dev/null +++ b/security/selinux/selinuxfs.c @@ -0,0 +1,1340 @@ +/* Updated: Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com> + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/pagemap.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/security.h> +#include <linux/major.h> +#include <linux/seq_file.h> +#include <linux/percpu.h> +#include <asm/uaccess.h> +#include <asm/semaphore.h> + +/* selinuxfs pseudo filesystem for exporting the security policy API. + Based on the proc code and the fs/nfsd/nfsctl.c code. */ + +#include "flask.h" +#include "avc.h" +#include "avc_ss.h" +#include "security.h" +#include "objsec.h" +#include "conditional.h" + +unsigned int selinux_checkreqprot = CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE; + +static int __init checkreqprot_setup(char *str) +{ + selinux_checkreqprot = simple_strtoul(str,NULL,0) ? 1 : 0; + return 1; +} +__setup("checkreqprot=", checkreqprot_setup); + + +static DECLARE_MUTEX(sel_sem); + +/* global data for booleans */ +static struct dentry *bool_dir = NULL; +static int bool_num = 0; +static int *bool_pending_values = NULL; + +extern void selnl_notify_setenforce(int val); + +/* Check whether a task is allowed to use a security operation. */ +static int task_has_security(struct task_struct *tsk, + u32 perms) +{ + struct task_security_struct *tsec; + + tsec = tsk->security; + if (!tsec) + return -EACCES; + + return avc_has_perm(tsec->sid, SECINITSID_SECURITY, + SECCLASS_SECURITY, perms, NULL); +} + +enum sel_inos { + SEL_ROOT_INO = 2, + SEL_LOAD, /* load policy */ + SEL_ENFORCE, /* get or set enforcing status */ + SEL_CONTEXT, /* validate context */ + SEL_ACCESS, /* compute access decision */ + SEL_CREATE, /* compute create labeling decision */ + SEL_RELABEL, /* compute relabeling decision */ + SEL_USER, /* compute reachable user contexts */ + SEL_POLICYVERS, /* return policy version for this kernel */ + SEL_COMMIT_BOOLS, /* commit new boolean values */ + SEL_MLS, /* return if MLS policy is enabled */ + SEL_DISABLE, /* disable SELinux until next reboot */ + SEL_AVC, /* AVC management directory */ + SEL_MEMBER, /* compute polyinstantiation membership decision */ + SEL_CHECKREQPROT, /* check requested protection, not kernel-applied one */ +}; + +#define TMPBUFLEN 12 +static ssize_t sel_read_enforce(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", selinux_enforcing); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +static ssize_t sel_write_enforce(struct file * file, const char __user * buf, + size_t count, loff_t *ppos) + +{ + char *page; + ssize_t length; + int new_value; + + if (count < 0 || count >= PAGE_SIZE) + return -ENOMEM; + if (*ppos != 0) { + /* No partial writes. */ + return -EINVAL; + } + page = (char*)get_zeroed_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + length = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + if (new_value != selinux_enforcing) { + length = task_has_security(current, SECURITY__SETENFORCE); + if (length) + goto out; + selinux_enforcing = new_value; + if (selinux_enforcing) + avc_ss_reset(0); + selnl_notify_setenforce(selinux_enforcing); + } + length = count; +out: + free_page((unsigned long) page); + return length; +} +#else +#define sel_write_enforce NULL +#endif + +static struct file_operations sel_enforce_ops = { + .read = sel_read_enforce, + .write = sel_write_enforce, +}; + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +static ssize_t sel_write_disable(struct file * file, const char __user * buf, + size_t count, loff_t *ppos) + +{ + char *page; + ssize_t length; + int new_value; + extern int selinux_disable(void); + + if (count < 0 || count >= PAGE_SIZE) + return -ENOMEM; + if (*ppos != 0) { + /* No partial writes. */ + return -EINVAL; + } + page = (char*)get_zeroed_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + length = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + if (new_value) { + length = selinux_disable(); + if (length < 0) + goto out; + } + + length = count; +out: + free_page((unsigned long) page); + return length; +} +#else +#define sel_write_disable NULL +#endif + +static struct file_operations sel_disable_ops = { + .write = sel_write_disable, +}; + +static ssize_t sel_read_policyvers(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", POLICYDB_VERSION_MAX); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static struct file_operations sel_policyvers_ops = { + .read = sel_read_policyvers, +}; + +/* declaration for sel_write_load */ +static int sel_make_bools(void); + +static ssize_t sel_read_mls(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", selinux_mls_enabled); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static struct file_operations sel_mls_ops = { + .read = sel_read_mls, +}; + +static ssize_t sel_write_load(struct file * file, const char __user * buf, + size_t count, loff_t *ppos) + +{ + int ret; + ssize_t length; + void *data = NULL; + + down(&sel_sem); + + length = task_has_security(current, SECURITY__LOAD_POLICY); + if (length) + goto out; + + if (*ppos != 0) { + /* No partial writes. */ + length = -EINVAL; + goto out; + } + + if ((count < 0) || (count > 64 * 1024 * 1024) + || (data = vmalloc(count)) == NULL) { + length = -ENOMEM; + goto out; + } + + length = -EFAULT; + if (copy_from_user(data, buf, count) != 0) + goto out; + + length = security_load_policy(data, count); + if (length) + goto out; + + ret = sel_make_bools(); + if (ret) + length = ret; + else + length = count; +out: + up(&sel_sem); + vfree(data); + return length; +} + +static struct file_operations sel_load_ops = { + .write = sel_write_load, +}; + + +static ssize_t sel_write_context(struct file * file, const char __user * buf, + size_t count, loff_t *ppos) + +{ + char *page; + u32 sid; + ssize_t length; + + length = task_has_security(current, SECURITY__CHECK_CONTEXT); + if (length) + return length; + + if (count < 0 || count >= PAGE_SIZE) + return -ENOMEM; + if (*ppos != 0) { + /* No partial writes. */ + return -EINVAL; + } + page = (char*)get_zeroed_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + length = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + length = security_context_to_sid(page, count, &sid); + if (length < 0) + goto out; + + length = count; +out: + free_page((unsigned long) page); + return length; +} + +static struct file_operations sel_context_ops = { + .write = sel_write_context, +}; + +static ssize_t sel_read_checkreqprot(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", selinux_checkreqprot); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static ssize_t sel_write_checkreqprot(struct file * file, const char __user * buf, + size_t count, loff_t *ppos) +{ + char *page; + ssize_t length; + unsigned int new_value; + + length = task_has_security(current, SECURITY__SETCHECKREQPROT); + if (length) + return length; + + if (count < 0 || count >= PAGE_SIZE) + return -ENOMEM; + if (*ppos != 0) { + /* No partial writes. */ + return -EINVAL; + } + page = (char*)get_zeroed_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + length = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + length = -EINVAL; + if (sscanf(page, "%u", &new_value) != 1) + goto out; + + selinux_checkreqprot = new_value ? 1 : 0; + length = count; +out: + free_page((unsigned long) page); + return length; +} +static struct file_operations sel_checkreqprot_ops = { + .read = sel_read_checkreqprot, + .write = sel_write_checkreqprot, +}; + +/* + * Remaining nodes use transaction based IO methods like nfsd/nfsctl.c + */ +static ssize_t sel_write_access(struct file * file, char *buf, size_t size); +static ssize_t sel_write_create(struct file * file, char *buf, size_t size); +static ssize_t sel_write_relabel(struct file * file, char *buf, size_t size); +static ssize_t sel_write_user(struct file * file, char *buf, size_t size); +static ssize_t sel_write_member(struct file * file, char *buf, size_t size); + +static ssize_t (*write_op[])(struct file *, char *, size_t) = { + [SEL_ACCESS] = sel_write_access, + [SEL_CREATE] = sel_write_create, + [SEL_RELABEL] = sel_write_relabel, + [SEL_USER] = sel_write_user, + [SEL_MEMBER] = sel_write_member, +}; + +static ssize_t selinux_transaction_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) +{ + ino_t ino = file->f_dentry->d_inode->i_ino; + char *data; + ssize_t rv; + + if (ino >= sizeof(write_op)/sizeof(write_op[0]) || !write_op[ino]) + return -EINVAL; + + data = simple_transaction_get(file, buf, size); + if (IS_ERR(data)) + return PTR_ERR(data); + + rv = write_op[ino](file, data, size); + if (rv>0) { + simple_transaction_set(file, rv); + rv = size; + } + return rv; +} + +static struct file_operations transaction_ops = { + .write = selinux_transaction_write, + .read = simple_transaction_read, + .release = simple_transaction_release, +}; + +/* + * payload - write methods + * If the method has a response, the response should be put in buf, + * and the length returned. Otherwise return 0 or and -error. + */ + +static ssize_t sel_write_access(struct file * file, char *buf, size_t size) +{ + char *scon, *tcon; + u32 ssid, tsid; + u16 tclass; + u32 req; + struct av_decision avd; + ssize_t length; + + length = task_has_security(current, SECURITY__COMPUTE_AV); + if (length) + return length; + + length = -ENOMEM; + scon = kmalloc(size+1, GFP_KERNEL); + if (!scon) + return length; + memset(scon, 0, size+1); + + tcon = kmalloc(size+1, GFP_KERNEL); + if (!tcon) + goto out; + memset(tcon, 0, size+1); + + length = -EINVAL; + if (sscanf(buf, "%s %s %hu %x", scon, tcon, &tclass, &req) != 4) + goto out2; + + length = security_context_to_sid(scon, strlen(scon)+1, &ssid); + if (length < 0) + goto out2; + length = security_context_to_sid(tcon, strlen(tcon)+1, &tsid); + if (length < 0) + goto out2; + + length = security_compute_av(ssid, tsid, tclass, req, &avd); + if (length < 0) + goto out2; + + length = scnprintf(buf, SIMPLE_TRANSACTION_LIMIT, + "%x %x %x %x %u", + avd.allowed, avd.decided, + avd.auditallow, avd.auditdeny, + avd.seqno); +out2: + kfree(tcon); +out: + kfree(scon); + return length; +} + +static ssize_t sel_write_create(struct file * file, char *buf, size_t size) +{ + char *scon, *tcon; + u32 ssid, tsid, newsid; + u16 tclass; + ssize_t length; + char *newcon; + u32 len; + + length = task_has_security(current, SECURITY__COMPUTE_CREATE); + if (length) + return length; + + length = -ENOMEM; + scon = kmalloc(size+1, GFP_KERNEL); + if (!scon) + return length; + memset(scon, 0, size+1); + + tcon = kmalloc(size+1, GFP_KERNEL); + if (!tcon) + goto out; + memset(tcon, 0, size+1); + + length = -EINVAL; + if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3) + goto out2; + + length = security_context_to_sid(scon, strlen(scon)+1, &ssid); + if (length < 0) + goto out2; + length = security_context_to_sid(tcon, strlen(tcon)+1, &tsid); + if (length < 0) + goto out2; + + length = security_transition_sid(ssid, tsid, tclass, &newsid); + if (length < 0) + goto out2; + + length = security_sid_to_context(newsid, &newcon, &len); + if (length < 0) + goto out2; + + if (len > SIMPLE_TRANSACTION_LIMIT) { + printk(KERN_ERR "%s: context size (%u) exceeds payload " + "max\n", __FUNCTION__, len); + length = -ERANGE; + goto out3; + } + + memcpy(buf, newcon, len); + length = len; +out3: + kfree(newcon); +out2: + kfree(tcon); +out: + kfree(scon); + return length; +} + +static ssize_t sel_write_relabel(struct file * file, char *buf, size_t size) +{ + char *scon, *tcon; + u32 ssid, tsid, newsid; + u16 tclass; + ssize_t length; + char *newcon; + u32 len; + + length = task_has_security(current, SECURITY__COMPUTE_RELABEL); + if (length) + return length; + + length = -ENOMEM; + scon = kmalloc(size+1, GFP_KERNEL); + if (!scon) + return length; + memset(scon, 0, size+1); + + tcon = kmalloc(size+1, GFP_KERNEL); + if (!tcon) + goto out; + memset(tcon, 0, size+1); + + length = -EINVAL; + if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3) + goto out2; + + length = security_context_to_sid(scon, strlen(scon)+1, &ssid); + if (length < 0) + goto out2; + length = security_context_to_sid(tcon, strlen(tcon)+1, &tsid); + if (length < 0) + goto out2; + + length = security_change_sid(ssid, tsid, tclass, &newsid); + if (length < 0) + goto out2; + + length = security_sid_to_context(newsid, &newcon, &len); + if (length < 0) + goto out2; + + if (len > SIMPLE_TRANSACTION_LIMIT) { + length = -ERANGE; + goto out3; + } + + memcpy(buf, newcon, len); + length = len; +out3: + kfree(newcon); +out2: + kfree(tcon); +out: + kfree(scon); + return length; +} + +static ssize_t sel_write_user(struct file * file, char *buf, size_t size) +{ + char *con, *user, *ptr; + u32 sid, *sids; + ssize_t length; + char *newcon; + int i, rc; + u32 len, nsids; + + length = task_has_security(current, SECURITY__COMPUTE_USER); + if (length) + return length; + + length = -ENOMEM; + con = kmalloc(size+1, GFP_KERNEL); + if (!con) + return length; + memset(con, 0, size+1); + + user = kmalloc(size+1, GFP_KERNEL); + if (!user) + goto out; + memset(user, 0, size+1); + + length = -EINVAL; + if (sscanf(buf, "%s %s", con, user) != 2) + goto out2; + + length = security_context_to_sid(con, strlen(con)+1, &sid); + if (length < 0) + goto out2; + + length = security_get_user_sids(sid, user, &sids, &nsids); + if (length < 0) + goto out2; + + length = sprintf(buf, "%u", nsids) + 1; + ptr = buf + length; + for (i = 0; i < nsids; i++) { + rc = security_sid_to_context(sids[i], &newcon, &len); + if (rc) { + length = rc; + goto out3; + } + if ((length + len) >= SIMPLE_TRANSACTION_LIMIT) { + kfree(newcon); + length = -ERANGE; + goto out3; + } + memcpy(ptr, newcon, len); + kfree(newcon); + ptr += len; + length += len; + } +out3: + kfree(sids); +out2: + kfree(user); +out: + kfree(con); + return length; +} + +static ssize_t sel_write_member(struct file * file, char *buf, size_t size) +{ + char *scon, *tcon; + u32 ssid, tsid, newsid; + u16 tclass; + ssize_t length; + char *newcon; + u32 len; + + length = task_has_security(current, SECURITY__COMPUTE_MEMBER); + if (length) + return length; + + length = -ENOMEM; + scon = kmalloc(size+1, GFP_KERNEL); + if (!scon) + return length; + memset(scon, 0, size+1); + + tcon = kmalloc(size+1, GFP_KERNEL); + if (!tcon) + goto out; + memset(tcon, 0, size+1); + + length = -EINVAL; + if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3) + goto out2; + + length = security_context_to_sid(scon, strlen(scon)+1, &ssid); + if (length < 0) + goto out2; + length = security_context_to_sid(tcon, strlen(tcon)+1, &tsid); + if (length < 0) + goto out2; + + length = security_member_sid(ssid, tsid, tclass, &newsid); + if (length < 0) + goto out2; + + length = security_sid_to_context(newsid, &newcon, &len); + if (length < 0) + goto out2; + + if (len > SIMPLE_TRANSACTION_LIMIT) { + printk(KERN_ERR "%s: context size (%u) exceeds payload " + "max\n", __FUNCTION__, len); + length = -ERANGE; + goto out3; + } + + memcpy(buf, newcon, len); + length = len; +out3: + kfree(newcon); +out2: + kfree(tcon); +out: + kfree(scon); + return length; +} + +static struct inode *sel_make_inode(struct super_block *sb, int mode) +{ + struct inode *ret = new_inode(sb); + + if (ret) { + ret->i_mode = mode; + ret->i_uid = ret->i_gid = 0; + ret->i_blksize = PAGE_CACHE_SIZE; + ret->i_blocks = 0; + ret->i_atime = ret->i_mtime = ret->i_ctime = CURRENT_TIME; + } + return ret; +} + +#define BOOL_INO_OFFSET 30 + +static ssize_t sel_read_bool(struct file *filep, char __user *buf, + size_t count, loff_t *ppos) +{ + char *page = NULL; + ssize_t length; + ssize_t end; + ssize_t ret; + int cur_enforcing; + struct inode *inode; + + down(&sel_sem); + + ret = -EFAULT; + + /* check to see if this file has been deleted */ + if (!filep->f_op) + goto out; + + if (count < 0 || count > PAGE_SIZE) { + ret = -EINVAL; + goto out; + } + if (!(page = (char*)get_zeroed_page(GFP_KERNEL))) { + ret = -ENOMEM; + goto out; + } + + inode = filep->f_dentry->d_inode; + cur_enforcing = security_get_bool_value(inode->i_ino - BOOL_INO_OFFSET); + if (cur_enforcing < 0) { + ret = cur_enforcing; + goto out; + } + + length = scnprintf(page, PAGE_SIZE, "%d %d", cur_enforcing, + bool_pending_values[inode->i_ino - BOOL_INO_OFFSET]); + if (length < 0) { + ret = length; + goto out; + } + + if (*ppos >= length) { + ret = 0; + goto out; + } + if (count + *ppos > length) + count = length - *ppos; + end = count + *ppos; + if (copy_to_user(buf, (char *) page + *ppos, count)) { + ret = -EFAULT; + goto out; + } + *ppos = end; + ret = count; +out: + up(&sel_sem); + if (page) + free_page((unsigned long)page); + return ret; +} + +static ssize_t sel_write_bool(struct file *filep, const char __user *buf, + size_t count, loff_t *ppos) +{ + char *page = NULL; + ssize_t length = -EFAULT; + int new_value; + struct inode *inode; + + down(&sel_sem); + + length = task_has_security(current, SECURITY__SETBOOL); + if (length) + goto out; + + /* check to see if this file has been deleted */ + if (!filep->f_op) + goto out; + + if (count < 0 || count >= PAGE_SIZE) { + length = -ENOMEM; + goto out; + } + if (*ppos != 0) { + /* No partial writes. */ + goto out; + } + page = (char*)get_zeroed_page(GFP_KERNEL); + if (!page) { + length = -ENOMEM; + goto out; + } + + if (copy_from_user(page, buf, count)) + goto out; + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + if (new_value) + new_value = 1; + + inode = filep->f_dentry->d_inode; + bool_pending_values[inode->i_ino - BOOL_INO_OFFSET] = new_value; + length = count; + +out: + up(&sel_sem); + if (page) + free_page((unsigned long) page); + return length; +} + +static struct file_operations sel_bool_ops = { + .read = sel_read_bool, + .write = sel_write_bool, +}; + +static ssize_t sel_commit_bools_write(struct file *filep, + const char __user *buf, + size_t count, loff_t *ppos) +{ + char *page = NULL; + ssize_t length = -EFAULT; + int new_value; + + down(&sel_sem); + + length = task_has_security(current, SECURITY__SETBOOL); + if (length) + goto out; + + /* check to see if this file has been deleted */ + if (!filep->f_op) + goto out; + + if (count < 0 || count >= PAGE_SIZE) { + length = -ENOMEM; + goto out; + } + if (*ppos != 0) { + /* No partial writes. */ + goto out; + } + page = (char*)get_zeroed_page(GFP_KERNEL); + if (!page) { + length = -ENOMEM; + goto out; + } + + if (copy_from_user(page, buf, count)) + goto out; + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + if (new_value) { + security_set_bools(bool_num, bool_pending_values); + } + + length = count; + +out: + up(&sel_sem); + if (page) + free_page((unsigned long) page); + return length; +} + +static struct file_operations sel_commit_bools_ops = { + .write = sel_commit_bools_write, +}; + +/* delete booleans - partial revoke() from + * fs/proc/generic.c proc_kill_inodes */ +static void sel_remove_bools(struct dentry *de) +{ + struct list_head *p, *node; + struct super_block *sb = de->d_sb; + + spin_lock(&dcache_lock); + node = de->d_subdirs.next; + while (node != &de->d_subdirs) { + struct dentry *d = list_entry(node, struct dentry, d_child); + list_del_init(node); + + if (d->d_inode) { + d = dget_locked(d); + spin_unlock(&dcache_lock); + d_delete(d); + simple_unlink(de->d_inode, d); + dput(d); + spin_lock(&dcache_lock); + } + node = de->d_subdirs.next; + } + + spin_unlock(&dcache_lock); + + file_list_lock(); + list_for_each(p, &sb->s_files) { + struct file * filp = list_entry(p, struct file, f_list); + struct dentry * dentry = filp->f_dentry; + + if (dentry->d_parent != de) { + continue; + } + filp->f_op = NULL; + } + file_list_unlock(); +} + +#define BOOL_DIR_NAME "booleans" + +static int sel_make_bools(void) +{ + int i, ret = 0; + ssize_t len; + struct dentry *dentry = NULL; + struct dentry *dir = bool_dir; + struct inode *inode = NULL; + struct inode_security_struct *isec; + char **names = NULL, *page; + int num; + int *values = NULL; + u32 sid; + + /* remove any existing files */ + if (bool_pending_values) + kfree(bool_pending_values); + + sel_remove_bools(dir); + + if (!(page = (char*)get_zeroed_page(GFP_KERNEL))) + return -ENOMEM; + + ret = security_get_bools(&num, &names, &values); + if (ret != 0) + goto out; + + for (i = 0; i < num; i++) { + dentry = d_alloc_name(dir, names[i]); + if (!dentry) { + ret = -ENOMEM; + goto err; + } + inode = sel_make_inode(dir->d_sb, S_IFREG | S_IRUGO | S_IWUSR); + if (!inode) { + ret = -ENOMEM; + goto err; + } + + len = snprintf(page, PAGE_SIZE, "/%s/%s", BOOL_DIR_NAME, names[i]); + if (len < 0) { + ret = -EINVAL; + goto err; + } else if (len >= PAGE_SIZE) { + ret = -ENAMETOOLONG; + goto err; + } + isec = (struct inode_security_struct*)inode->i_security; + if ((ret = security_genfs_sid("selinuxfs", page, SECCLASS_FILE, &sid))) + goto err; + isec->sid = sid; + isec->initialized = 1; + inode->i_fop = &sel_bool_ops; + inode->i_ino = i + BOOL_INO_OFFSET; + d_add(dentry, inode); + } + bool_num = num; + bool_pending_values = values; +out: + free_page((unsigned long)page); + if (names) { + for (i = 0; i < num; i++) { + if (names[i]) + kfree(names[i]); + } + kfree(names); + } + return ret; +err: + d_genocide(dir); + ret = -ENOMEM; + goto out; +} + +#define NULL_FILE_NAME "null" + +struct dentry *selinux_null = NULL; + +static ssize_t sel_read_avc_cache_threshold(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", avc_cache_threshold); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static ssize_t sel_write_avc_cache_threshold(struct file * file, + const char __user * buf, + size_t count, loff_t *ppos) + +{ + char *page; + ssize_t ret; + int new_value; + + if (count < 0 || count >= PAGE_SIZE) { + ret = -ENOMEM; + goto out; + } + + if (*ppos != 0) { + /* No partial writes. */ + ret = -EINVAL; + goto out; + } + + page = (char*)get_zeroed_page(GFP_KERNEL); + if (!page) { + ret = -ENOMEM; + goto out; + } + + if (copy_from_user(page, buf, count)) { + ret = -EFAULT; + goto out_free; + } + + if (sscanf(page, "%u", &new_value) != 1) { + ret = -EINVAL; + goto out; + } + + if (new_value != avc_cache_threshold) { + ret = task_has_security(current, SECURITY__SETSECPARAM); + if (ret) + goto out_free; + avc_cache_threshold = new_value; + } + ret = count; +out_free: + free_page((unsigned long)page); +out: + return ret; +} + +static ssize_t sel_read_avc_hash_stats(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char *page; + ssize_t ret = 0; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) { + ret = -ENOMEM; + goto out; + } + ret = avc_get_hash_stats(page); + if (ret >= 0) + ret = simple_read_from_buffer(buf, count, ppos, page, ret); + free_page((unsigned long)page); +out: + return ret; +} + +static struct file_operations sel_avc_cache_threshold_ops = { + .read = sel_read_avc_cache_threshold, + .write = sel_write_avc_cache_threshold, +}; + +static struct file_operations sel_avc_hash_stats_ops = { + .read = sel_read_avc_hash_stats, +}; + +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS +static struct avc_cache_stats *sel_avc_get_stat_idx(loff_t *idx) +{ + int cpu; + + for (cpu = *idx; cpu < NR_CPUS; ++cpu) { + if (!cpu_possible(cpu)) + continue; + *idx = cpu + 1; + return &per_cpu(avc_cache_stats, cpu); + } + return NULL; +} + +static void *sel_avc_stats_seq_start(struct seq_file *seq, loff_t *pos) +{ + loff_t n = *pos - 1; + + if (*pos == 0) + return SEQ_START_TOKEN; + + return sel_avc_get_stat_idx(&n); +} + +static void *sel_avc_stats_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + return sel_avc_get_stat_idx(pos); +} + +static int sel_avc_stats_seq_show(struct seq_file *seq, void *v) +{ + struct avc_cache_stats *st = v; + + if (v == SEQ_START_TOKEN) + seq_printf(seq, "lookups hits misses allocations reclaims " + "frees\n"); + else + seq_printf(seq, "%u %u %u %u %u %u\n", st->lookups, + st->hits, st->misses, st->allocations, + st->reclaims, st->frees); + return 0; +} + +static void sel_avc_stats_seq_stop(struct seq_file *seq, void *v) +{ } + +static struct seq_operations sel_avc_cache_stats_seq_ops = { + .start = sel_avc_stats_seq_start, + .next = sel_avc_stats_seq_next, + .show = sel_avc_stats_seq_show, + .stop = sel_avc_stats_seq_stop, +}; + +static int sel_open_avc_cache_stats(struct inode *inode, struct file *file) +{ + return seq_open(file, &sel_avc_cache_stats_seq_ops); +} + +static struct file_operations sel_avc_cache_stats_ops = { + .open = sel_open_avc_cache_stats, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif + +static int sel_make_avc_files(struct dentry *dir) +{ + int i, ret = 0; + static struct tree_descr files[] = { + { "cache_threshold", + &sel_avc_cache_threshold_ops, S_IRUGO|S_IWUSR }, + { "hash_stats", &sel_avc_hash_stats_ops, S_IRUGO }, +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS + { "cache_stats", &sel_avc_cache_stats_ops, S_IRUGO }, +#endif + }; + + for (i = 0; i < sizeof (files) / sizeof (files[0]); i++) { + struct inode *inode; + struct dentry *dentry; + + dentry = d_alloc_name(dir, files[i].name); + if (!dentry) { + ret = -ENOMEM; + goto err; + } + + inode = sel_make_inode(dir->d_sb, S_IFREG|files[i].mode); + if (!inode) { + ret = -ENOMEM; + goto err; + } + inode->i_fop = files[i].ops; + d_add(dentry, inode); + } +out: + return ret; +err: + d_genocide(dir); + goto out; +} + +static int sel_make_dir(struct super_block *sb, struct dentry *dentry) +{ + int ret = 0; + struct inode *inode; + + inode = sel_make_inode(sb, S_IFDIR | S_IRUGO | S_IXUGO); + if (!inode) { + ret = -ENOMEM; + goto out; + } + inode->i_op = &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + d_add(dentry, inode); +out: + return ret; +} + +static int sel_fill_super(struct super_block * sb, void * data, int silent) +{ + int ret; + struct dentry *dentry; + struct inode *inode; + struct inode_security_struct *isec; + + static struct tree_descr selinux_files[] = { + [SEL_LOAD] = {"load", &sel_load_ops, S_IRUSR|S_IWUSR}, + [SEL_ENFORCE] = {"enforce", &sel_enforce_ops, S_IRUGO|S_IWUSR}, + [SEL_CONTEXT] = {"context", &sel_context_ops, S_IRUGO|S_IWUGO}, + [SEL_ACCESS] = {"access", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_CREATE] = {"create", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_RELABEL] = {"relabel", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_USER] = {"user", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_POLICYVERS] = {"policyvers", &sel_policyvers_ops, S_IRUGO}, + [SEL_COMMIT_BOOLS] = {"commit_pending_bools", &sel_commit_bools_ops, S_IWUSR}, + [SEL_MLS] = {"mls", &sel_mls_ops, S_IRUGO}, + [SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR}, + [SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR}, + /* last one */ {""} + }; + ret = simple_fill_super(sb, SELINUX_MAGIC, selinux_files); + if (ret) + return ret; + + dentry = d_alloc_name(sb->s_root, BOOL_DIR_NAME); + if (!dentry) + return -ENOMEM; + + inode = sel_make_inode(sb, S_IFDIR | S_IRUGO | S_IXUGO); + if (!inode) + goto out; + inode->i_op = &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + d_add(dentry, inode); + bool_dir = dentry; + ret = sel_make_bools(); + if (ret) + goto out; + + dentry = d_alloc_name(sb->s_root, NULL_FILE_NAME); + if (!dentry) + return -ENOMEM; + + inode = sel_make_inode(sb, S_IFCHR | S_IRUGO | S_IWUGO); + if (!inode) + goto out; + isec = (struct inode_security_struct*)inode->i_security; + isec->sid = SECINITSID_DEVNULL; + isec->sclass = SECCLASS_CHR_FILE; + isec->initialized = 1; + + init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO, MKDEV(MEM_MAJOR, 3)); + d_add(dentry, inode); + selinux_null = dentry; + + dentry = d_alloc_name(sb->s_root, "avc"); + if (!dentry) + return -ENOMEM; + + ret = sel_make_dir(sb, dentry); + if (ret) + goto out; + + ret = sel_make_avc_files(dentry); + if (ret) + goto out; + + return 0; +out: + dput(dentry); + printk(KERN_ERR "%s: failed while creating inodes\n", __FUNCTION__); + return -ENOMEM; +} + +static struct super_block *sel_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return get_sb_single(fs_type, flags, data, sel_fill_super); +} + +static struct file_system_type sel_fs_type = { + .name = "selinuxfs", + .get_sb = sel_get_sb, + .kill_sb = kill_litter_super, +}; + +struct vfsmount *selinuxfs_mount; + +static int __init init_sel_fs(void) +{ + int err; + + if (!selinux_enabled) + return 0; + err = register_filesystem(&sel_fs_type); + if (!err) { + selinuxfs_mount = kern_mount(&sel_fs_type); + if (IS_ERR(selinuxfs_mount)) { + printk(KERN_ERR "selinuxfs: could not mount!\n"); + err = PTR_ERR(selinuxfs_mount); + selinuxfs_mount = NULL; + } + } + return err; +} + +__initcall(init_sel_fs); + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +void exit_sel_fs(void) +{ + unregister_filesystem(&sel_fs_type); +} +#endif diff --git a/security/selinux/ss/Makefile b/security/selinux/ss/Makefile new file mode 100644 index 000000000000..bad78779b9b0 --- /dev/null +++ b/security/selinux/ss/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for building the SELinux security server as part of the kernel tree. +# + +EXTRA_CFLAGS += -Isecurity/selinux/include +obj-y := ss.o + +ss-y := ebitmap.o hashtab.o symtab.o sidtab.o avtab.o policydb.o services.o conditional.o mls.o + diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c new file mode 100644 index 000000000000..f238c034c44e --- /dev/null +++ b/security/selinux/ss/avtab.c @@ -0,0 +1,399 @@ +/* + * Implementation of the access vector table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2003 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/errno.h> + +#include "avtab.h" +#include "policydb.h" + +#define AVTAB_HASH(keyp) \ +((keyp->target_class + \ + (keyp->target_type << 2) + \ + (keyp->source_type << 9)) & \ + AVTAB_HASH_MASK) + +static kmem_cache_t *avtab_node_cachep; + +static struct avtab_node* +avtab_insert_node(struct avtab *h, int hvalue, + struct avtab_node * prev, struct avtab_node * cur, + struct avtab_key *key, struct avtab_datum *datum) +{ + struct avtab_node * newnode; + newnode = kmem_cache_alloc(avtab_node_cachep, SLAB_KERNEL); + if (newnode == NULL) + return NULL; + memset(newnode, 0, sizeof(struct avtab_node)); + newnode->key = *key; + newnode->datum = *datum; + if (prev) { + newnode->next = prev->next; + prev->next = newnode; + } else { + newnode->next = h->htable[hvalue]; + h->htable[hvalue] = newnode; + } + + h->nel++; + return newnode; +} + +static int avtab_insert(struct avtab *h, struct avtab_key *key, struct avtab_datum *datum) +{ + int hvalue; + struct avtab_node *prev, *cur, *newnode; + + if (!h) + return -EINVAL; + + hvalue = AVTAB_HASH(key); + for (prev = NULL, cur = h->htable[hvalue]; + cur; + prev = cur, cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (datum->specified & cur->datum.specified)) + return -EEXIST; + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + + newnode = avtab_insert_node(h, hvalue, prev, cur, key, datum); + if(!newnode) + return -ENOMEM; + + return 0; +} + +/* Unlike avtab_insert(), this function allow multiple insertions of the same + * key/specified mask into the table, as needed by the conditional avtab. + * It also returns a pointer to the node inserted. + */ +struct avtab_node * +avtab_insert_nonunique(struct avtab * h, struct avtab_key * key, struct avtab_datum * datum) +{ + int hvalue; + struct avtab_node *prev, *cur, *newnode; + + if (!h) + return NULL; + hvalue = AVTAB_HASH(key); + for (prev = NULL, cur = h->htable[hvalue]; + cur; + prev = cur, cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (datum->specified & cur->datum.specified)) + break; + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + newnode = avtab_insert_node(h, hvalue, prev, cur, key, datum); + + return newnode; +} + +struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *key, int specified) +{ + int hvalue; + struct avtab_node *cur; + + if (!h) + return NULL; + + hvalue = AVTAB_HASH(key); + for (cur = h->htable[hvalue]; cur; cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->datum.specified)) + return &cur->datum; + + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + + return NULL; +} + +/* This search function returns a node pointer, and can be used in + * conjunction with avtab_search_next_node() + */ +struct avtab_node* +avtab_search_node(struct avtab *h, struct avtab_key *key, int specified) +{ + int hvalue; + struct avtab_node *cur; + + if (!h) + return NULL; + + hvalue = AVTAB_HASH(key); + for (cur = h->htable[hvalue]; cur; cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->datum.specified)) + return cur; + + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + return NULL; +} + +struct avtab_node* +avtab_search_node_next(struct avtab_node *node, int specified) +{ + struct avtab_node *cur; + + if (!node) + return NULL; + + for (cur = node->next; cur; cur = cur->next) { + if (node->key.source_type == cur->key.source_type && + node->key.target_type == cur->key.target_type && + node->key.target_class == cur->key.target_class && + (specified & cur->datum.specified)) + return cur; + + if (node->key.source_type < cur->key.source_type) + break; + if (node->key.source_type == cur->key.source_type && + node->key.target_type < cur->key.target_type) + break; + if (node->key.source_type == cur->key.source_type && + node->key.target_type == cur->key.target_type && + node->key.target_class < cur->key.target_class) + break; + } + return NULL; +} + +void avtab_destroy(struct avtab *h) +{ + int i; + struct avtab_node *cur, *temp; + + if (!h || !h->htable) + return; + + for (i = 0; i < AVTAB_SIZE; i++) { + cur = h->htable[i]; + while (cur != NULL) { + temp = cur; + cur = cur->next; + kmem_cache_free(avtab_node_cachep, temp); + } + h->htable[i] = NULL; + } + vfree(h->htable); + h->htable = NULL; +} + + +int avtab_init(struct avtab *h) +{ + int i; + + h->htable = vmalloc(sizeof(*(h->htable)) * AVTAB_SIZE); + if (!h->htable) + return -ENOMEM; + for (i = 0; i < AVTAB_SIZE; i++) + h->htable[i] = NULL; + h->nel = 0; + return 0; +} + +void avtab_hash_eval(struct avtab *h, char *tag) +{ + int i, chain_len, slots_used, max_chain_len; + struct avtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + for (i = 0; i < AVTAB_SIZE; i++) { + cur = h->htable[i]; + if (cur) { + slots_used++; + chain_len = 0; + while (cur) { + chain_len++; + cur = cur->next; + } + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + printk(KERN_INFO "%s: %d entries and %d/%d buckets used, longest " + "chain length %d\n", tag, h->nel, slots_used, AVTAB_SIZE, + max_chain_len); +} + +int avtab_read_item(void *fp, struct avtab_datum *avdatum, struct avtab_key *avkey) +{ + u32 buf[7]; + u32 items, items2; + int rc; + + memset(avkey, 0, sizeof(struct avtab_key)); + memset(avdatum, 0, sizeof(struct avtab_datum)); + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) { + printk(KERN_ERR "security: avtab: truncated entry\n"); + goto bad; + } + items2 = le32_to_cpu(buf[0]); + if (items2 > ARRAY_SIZE(buf)) { + printk(KERN_ERR "security: avtab: entry overflow\n"); + goto bad; + } + rc = next_entry(buf, fp, sizeof(u32)*items2); + if (rc < 0) { + printk(KERN_ERR "security: avtab: truncated entry\n"); + goto bad; + } + items = 0; + avkey->source_type = le32_to_cpu(buf[items++]); + avkey->target_type = le32_to_cpu(buf[items++]); + avkey->target_class = le32_to_cpu(buf[items++]); + avdatum->specified = le32_to_cpu(buf[items++]); + if (!(avdatum->specified & (AVTAB_AV | AVTAB_TYPE))) { + printk(KERN_ERR "security: avtab: null entry\n"); + goto bad; + } + if ((avdatum->specified & AVTAB_AV) && + (avdatum->specified & AVTAB_TYPE)) { + printk(KERN_ERR "security: avtab: entry has both access vectors and types\n"); + goto bad; + } + if (avdatum->specified & AVTAB_AV) { + if (avdatum->specified & AVTAB_ALLOWED) + avtab_allowed(avdatum) = le32_to_cpu(buf[items++]); + if (avdatum->specified & AVTAB_AUDITDENY) + avtab_auditdeny(avdatum) = le32_to_cpu(buf[items++]); + if (avdatum->specified & AVTAB_AUDITALLOW) + avtab_auditallow(avdatum) = le32_to_cpu(buf[items++]); + } else { + if (avdatum->specified & AVTAB_TRANSITION) + avtab_transition(avdatum) = le32_to_cpu(buf[items++]); + if (avdatum->specified & AVTAB_CHANGE) + avtab_change(avdatum) = le32_to_cpu(buf[items++]); + if (avdatum->specified & AVTAB_MEMBER) + avtab_member(avdatum) = le32_to_cpu(buf[items++]); + } + if (items != items2) { + printk(KERN_ERR "security: avtab: entry only had %d items, expected %d\n", + items2, items); + goto bad; + } + + return 0; +bad: + return -1; +} + +int avtab_read(struct avtab *a, void *fp, u32 config) +{ + int rc; + struct avtab_key avkey; + struct avtab_datum avdatum; + u32 buf[1]; + u32 nel, i; + + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) { + printk(KERN_ERR "security: avtab: truncated table\n"); + goto bad; + } + nel = le32_to_cpu(buf[0]); + if (!nel) { + printk(KERN_ERR "security: avtab: table is empty\n"); + rc = -EINVAL; + goto bad; + } + for (i = 0; i < nel; i++) { + if (avtab_read_item(fp, &avdatum, &avkey)) { + rc = -EINVAL; + goto bad; + } + rc = avtab_insert(a, &avkey, &avdatum); + if (rc) { + if (rc == -ENOMEM) + printk(KERN_ERR "security: avtab: out of memory\n"); + if (rc == -EEXIST) + printk(KERN_ERR "security: avtab: duplicate entry\n"); + goto bad; + } + } + + rc = 0; +out: + return rc; + +bad: + avtab_destroy(a); + goto out; +} + +void avtab_cache_init(void) +{ + avtab_node_cachep = kmem_cache_create("avtab_node", + sizeof(struct avtab_node), + 0, SLAB_PANIC, NULL, NULL); +} + +void avtab_cache_destroy(void) +{ + kmem_cache_destroy (avtab_node_cachep); +} diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h new file mode 100644 index 000000000000..519d4f6dc655 --- /dev/null +++ b/security/selinux/ss/avtab.h @@ -0,0 +1,85 @@ +/* + * An access vector table (avtab) is a hash table + * of access vectors and transition types indexed + * by a type pair and a class. An access vector + * table is used to represent the type enforcement + * tables. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2003 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ +#ifndef _SS_AVTAB_H_ +#define _SS_AVTAB_H_ + +struct avtab_key { + u32 source_type; /* source type */ + u32 target_type; /* target type */ + u32 target_class; /* target object class */ +}; + +struct avtab_datum { +#define AVTAB_ALLOWED 1 +#define AVTAB_AUDITALLOW 2 +#define AVTAB_AUDITDENY 4 +#define AVTAB_AV (AVTAB_ALLOWED | AVTAB_AUDITALLOW | AVTAB_AUDITDENY) +#define AVTAB_TRANSITION 16 +#define AVTAB_MEMBER 32 +#define AVTAB_CHANGE 64 +#define AVTAB_TYPE (AVTAB_TRANSITION | AVTAB_MEMBER | AVTAB_CHANGE) +#define AVTAB_ENABLED 0x80000000 /* reserved for used in cond_avtab */ + u32 specified; /* what fields are specified */ + u32 data[3]; /* access vectors or types */ +#define avtab_allowed(x) (x)->data[0] +#define avtab_auditdeny(x) (x)->data[1] +#define avtab_auditallow(x) (x)->data[2] +#define avtab_transition(x) (x)->data[0] +#define avtab_change(x) (x)->data[1] +#define avtab_member(x) (x)->data[2] +}; + +struct avtab_node { + struct avtab_key key; + struct avtab_datum datum; + struct avtab_node *next; +}; + +struct avtab { + struct avtab_node **htable; + u32 nel; /* number of elements */ +}; + +int avtab_init(struct avtab *); +struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *k, int specified); +void avtab_destroy(struct avtab *h); +void avtab_hash_eval(struct avtab *h, char *tag); + +int avtab_read_item(void *fp, struct avtab_datum *avdatum, struct avtab_key *avkey); +int avtab_read(struct avtab *a, void *fp, u32 config); + +struct avtab_node *avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, + struct avtab_datum *datum); + +struct avtab_node *avtab_search_node(struct avtab *h, struct avtab_key *key, int specified); + +struct avtab_node *avtab_search_node_next(struct avtab_node *node, int specified); + +void avtab_cache_init(void); +void avtab_cache_destroy(void); + +#define AVTAB_HASH_BITS 15 +#define AVTAB_HASH_BUCKETS (1 << AVTAB_HASH_BITS) +#define AVTAB_HASH_MASK (AVTAB_HASH_BUCKETS-1) + +#define AVTAB_SIZE AVTAB_HASH_BUCKETS + +#endif /* _SS_AVTAB_H_ */ + diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c new file mode 100644 index 000000000000..b53441184aca --- /dev/null +++ b/security/selinux/ss/conditional.c @@ -0,0 +1,489 @@ +/* Authors: Karl MacMillan <kmacmillan@tresys.com> + * Frank Mayer <mayerf@tresys.com> + * + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/spinlock.h> +#include <asm/semaphore.h> +#include <linux/slab.h> + +#include "security.h" +#include "conditional.h" + +/* + * cond_evaluate_expr evaluates a conditional expr + * in reverse polish notation. It returns true (1), false (0), + * or undefined (-1). Undefined occurs when the expression + * exceeds the stack depth of COND_EXPR_MAXDEPTH. + */ +static int cond_evaluate_expr(struct policydb *p, struct cond_expr *expr) +{ + + struct cond_expr *cur; + int s[COND_EXPR_MAXDEPTH]; + int sp = -1; + + for (cur = expr; cur != NULL; cur = cur->next) { + switch (cur->expr_type) { + case COND_BOOL: + if (sp == (COND_EXPR_MAXDEPTH - 1)) + return -1; + sp++; + s[sp] = p->bool_val_to_struct[cur->bool - 1]->state; + break; + case COND_NOT: + if (sp < 0) + return -1; + s[sp] = !s[sp]; + break; + case COND_OR: + if (sp < 1) + return -1; + sp--; + s[sp] |= s[sp + 1]; + break; + case COND_AND: + if (sp < 1) + return -1; + sp--; + s[sp] &= s[sp + 1]; + break; + case COND_XOR: + if (sp < 1) + return -1; + sp--; + s[sp] ^= s[sp + 1]; + break; + case COND_EQ: + if (sp < 1) + return -1; + sp--; + s[sp] = (s[sp] == s[sp + 1]); + break; + case COND_NEQ: + if (sp < 1) + return -1; + sp--; + s[sp] = (s[sp] != s[sp + 1]); + break; + default: + return -1; + } + } + return s[0]; +} + +/* + * evaluate_cond_node evaluates the conditional stored in + * a struct cond_node and if the result is different than the + * current state of the node it sets the rules in the true/false + * list appropriately. If the result of the expression is undefined + * all of the rules are disabled for safety. + */ +int evaluate_cond_node(struct policydb *p, struct cond_node *node) +{ + int new_state; + struct cond_av_list* cur; + + new_state = cond_evaluate_expr(p, node->expr); + if (new_state != node->cur_state) { + node->cur_state = new_state; + if (new_state == -1) + printk(KERN_ERR "security: expression result was undefined - disabling all rules.\n"); + /* turn the rules on or off */ + for (cur = node->true_list; cur != NULL; cur = cur->next) { + if (new_state <= 0) { + cur->node->datum.specified &= ~AVTAB_ENABLED; + } else { + cur->node->datum.specified |= AVTAB_ENABLED; + } + } + + for (cur = node->false_list; cur != NULL; cur = cur->next) { + /* -1 or 1 */ + if (new_state) { + cur->node->datum.specified &= ~AVTAB_ENABLED; + } else { + cur->node->datum.specified |= AVTAB_ENABLED; + } + } + } + return 0; +} + +int cond_policydb_init(struct policydb *p) +{ + p->bool_val_to_struct = NULL; + p->cond_list = NULL; + if (avtab_init(&p->te_cond_avtab)) + return -1; + + return 0; +} + +static void cond_av_list_destroy(struct cond_av_list *list) +{ + struct cond_av_list *cur, *next; + for (cur = list; cur != NULL; cur = next) { + next = cur->next; + /* the avtab_ptr_t node is destroy by the avtab */ + kfree(cur); + } +} + +static void cond_node_destroy(struct cond_node *node) +{ + struct cond_expr *cur_expr, *next_expr; + + for (cur_expr = node->expr; cur_expr != NULL; cur_expr = next_expr) { + next_expr = cur_expr->next; + kfree(cur_expr); + } + cond_av_list_destroy(node->true_list); + cond_av_list_destroy(node->false_list); + kfree(node); +} + +static void cond_list_destroy(struct cond_node *list) +{ + struct cond_node *next, *cur; + + if (list == NULL) + return; + + for (cur = list; cur != NULL; cur = next) { + next = cur->next; + cond_node_destroy(cur); + } +} + +void cond_policydb_destroy(struct policydb *p) +{ + if (p->bool_val_to_struct != NULL) + kfree(p->bool_val_to_struct); + avtab_destroy(&p->te_cond_avtab); + cond_list_destroy(p->cond_list); +} + +int cond_init_bool_indexes(struct policydb *p) +{ + if (p->bool_val_to_struct) + kfree(p->bool_val_to_struct); + p->bool_val_to_struct = (struct cond_bool_datum**) + kmalloc(p->p_bools.nprim * sizeof(struct cond_bool_datum*), GFP_KERNEL); + if (!p->bool_val_to_struct) + return -1; + return 0; +} + +int cond_destroy_bool(void *key, void *datum, void *p) +{ + if (key) + kfree(key); + kfree(datum); + return 0; +} + +int cond_index_bool(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct cond_bool_datum *booldatum; + + booldatum = datum; + p = datap; + + if (!booldatum->value || booldatum->value > p->p_bools.nprim) + return -EINVAL; + + p->p_bool_val_to_name[booldatum->value - 1] = key; + p->bool_val_to_struct[booldatum->value -1] = booldatum; + + return 0; +} + +static int bool_isvalid(struct cond_bool_datum *b) +{ + if (!(b->state == 0 || b->state == 1)) + return 0; + return 1; +} + +int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct cond_bool_datum *booldatum; + u32 buf[3], len; + int rc; + + booldatum = kmalloc(sizeof(struct cond_bool_datum), GFP_KERNEL); + if (!booldatum) + return -1; + memset(booldatum, 0, sizeof(struct cond_bool_datum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto err; + + booldatum->value = le32_to_cpu(buf[0]); + booldatum->state = le32_to_cpu(buf[1]); + + if (!bool_isvalid(booldatum)) + goto err; + + len = le32_to_cpu(buf[2]); + + key = kmalloc(len + 1, GFP_KERNEL); + if (!key) + goto err; + rc = next_entry(key, fp, len); + if (rc < 0) + goto err; + key[len] = 0; + if (hashtab_insert(h, key, booldatum)) + goto err; + + return 0; +err: + cond_destroy_bool(key, booldatum, NULL); + return -1; +} + +static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list **ret_list, + struct cond_av_list *other) +{ + struct cond_av_list *list, *last = NULL, *cur; + struct avtab_key key; + struct avtab_datum datum; + struct avtab_node *node_ptr; + int rc; + u32 buf[1], i, len; + u8 found; + + *ret_list = NULL; + + len = 0; + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + return -1; + + len = le32_to_cpu(buf[0]); + if (len == 0) { + return 0; + } + + for (i = 0; i < len; i++) { + if (avtab_read_item(fp, &datum, &key)) + goto err; + + /* + * For type rules we have to make certain there aren't any + * conflicting rules by searching the te_avtab and the + * cond_te_avtab. + */ + if (datum.specified & AVTAB_TYPE) { + if (avtab_search(&p->te_avtab, &key, AVTAB_TYPE)) { + printk("security: type rule already exists outside of a conditional."); + goto err; + } + /* + * If we are reading the false list other will be a pointer to + * the true list. We can have duplicate entries if there is only + * 1 other entry and it is in our true list. + * + * If we are reading the true list (other == NULL) there shouldn't + * be any other entries. + */ + if (other) { + node_ptr = avtab_search_node(&p->te_cond_avtab, &key, AVTAB_TYPE); + if (node_ptr) { + if (avtab_search_node_next(node_ptr, AVTAB_TYPE)) { + printk("security: too many conflicting type rules."); + goto err; + } + found = 0; + for (cur = other; cur != NULL; cur = cur->next) { + if (cur->node == node_ptr) { + found = 1; + break; + } + } + if (!found) { + printk("security: conflicting type rules."); + goto err; + } + } + } else { + if (avtab_search(&p->te_cond_avtab, &key, AVTAB_TYPE)) { + printk("security: conflicting type rules when adding type rule for true."); + goto err; + } + } + } + node_ptr = avtab_insert_nonunique(&p->te_cond_avtab, &key, &datum); + if (!node_ptr) { + printk("security: could not insert rule."); + goto err; + } + + list = kmalloc(sizeof(struct cond_av_list), GFP_KERNEL); + if (!list) + goto err; + memset(list, 0, sizeof(struct cond_av_list)); + + list->node = node_ptr; + if (i == 0) + *ret_list = list; + else + last->next = list; + last = list; + + } + + return 0; +err: + cond_av_list_destroy(*ret_list); + *ret_list = NULL; + return -1; +} + +static int expr_isvalid(struct policydb *p, struct cond_expr *expr) +{ + if (expr->expr_type <= 0 || expr->expr_type > COND_LAST) { + printk("security: conditional expressions uses unknown operator.\n"); + return 0; + } + + if (expr->bool > p->p_bools.nprim) { + printk("security: conditional expressions uses unknown bool.\n"); + return 0; + } + return 1; +} + +static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp) +{ + u32 buf[2], len, i; + int rc; + struct cond_expr *expr = NULL, *last = NULL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + return -1; + + node->cur_state = le32_to_cpu(buf[0]); + + len = 0; + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + return -1; + + /* expr */ + len = le32_to_cpu(buf[0]); + + for (i = 0; i < len; i++ ) { + rc = next_entry(buf, fp, sizeof(u32) * 2); + if (rc < 0) + goto err; + + expr = kmalloc(sizeof(struct cond_expr), GFP_KERNEL); + if (!expr) { + goto err; + } + memset(expr, 0, sizeof(struct cond_expr)); + + expr->expr_type = le32_to_cpu(buf[0]); + expr->bool = le32_to_cpu(buf[1]); + + if (!expr_isvalid(p, expr)) { + kfree(expr); + goto err; + } + + if (i == 0) { + node->expr = expr; + } else { + last->next = expr; + } + last = expr; + } + + if (cond_read_av_list(p, fp, &node->true_list, NULL) != 0) + goto err; + if (cond_read_av_list(p, fp, &node->false_list, node->true_list) != 0) + goto err; + return 0; +err: + cond_node_destroy(node); + return -1; +} + +int cond_read_list(struct policydb *p, void *fp) +{ + struct cond_node *node, *last = NULL; + u32 buf[1], i, len; + int rc; + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + return -1; + + len = le32_to_cpu(buf[0]); + + for (i = 0; i < len; i++) { + node = kmalloc(sizeof(struct cond_node), GFP_KERNEL); + if (!node) + goto err; + memset(node, 0, sizeof(struct cond_node)); + + if (cond_read_node(p, node, fp) != 0) + goto err; + + if (i == 0) { + p->cond_list = node; + } else { + last->next = node; + } + last = node; + } + return 0; +err: + cond_list_destroy(p->cond_list); + return -1; +} + +/* Determine whether additional permissions are granted by the conditional + * av table, and if so, add them to the result + */ +void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decision *avd) +{ + struct avtab_node *node; + + if(!ctab || !key || !avd) + return; + + for(node = avtab_search_node(ctab, key, AVTAB_AV); node != NULL; + node = avtab_search_node_next(node, AVTAB_AV)) { + if ( (__u32) (AVTAB_ALLOWED|AVTAB_ENABLED) == + (node->datum.specified & (AVTAB_ALLOWED|AVTAB_ENABLED))) + avd->allowed |= avtab_allowed(&node->datum); + if ( (__u32) (AVTAB_AUDITDENY|AVTAB_ENABLED) == + (node->datum.specified & (AVTAB_AUDITDENY|AVTAB_ENABLED))) + /* Since a '0' in an auditdeny mask represents a + * permission we do NOT want to audit (dontaudit), we use + * the '&' operand to ensure that all '0's in the mask + * are retained (much unlike the allow and auditallow cases). + */ + avd->auditdeny &= avtab_auditdeny(&node->datum); + if ( (__u32) (AVTAB_AUDITALLOW|AVTAB_ENABLED) == + (node->datum.specified & (AVTAB_AUDITALLOW|AVTAB_ENABLED))) + avd->auditallow |= avtab_auditallow(&node->datum); + } + return; +} diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h new file mode 100644 index 000000000000..f3a1fc6e5d66 --- /dev/null +++ b/security/selinux/ss/conditional.h @@ -0,0 +1,77 @@ +/* Authors: Karl MacMillan <kmacmillan@tresys.com> + * Frank Mayer <mayerf@tresys.com> + * + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#ifndef _CONDITIONAL_H_ +#define _CONDITIONAL_H_ + +#include "avtab.h" +#include "symtab.h" +#include "policydb.h" + +#define COND_EXPR_MAXDEPTH 10 + +/* + * A conditional expression is a list of operators and operands + * in reverse polish notation. + */ +struct cond_expr { +#define COND_BOOL 1 /* plain bool */ +#define COND_NOT 2 /* !bool */ +#define COND_OR 3 /* bool || bool */ +#define COND_AND 4 /* bool && bool */ +#define COND_XOR 5 /* bool ^ bool */ +#define COND_EQ 6 /* bool == bool */ +#define COND_NEQ 7 /* bool != bool */ +#define COND_LAST 8 + __u32 expr_type; + __u32 bool; + struct cond_expr *next; +}; + +/* + * Each cond_node contains a list of rules to be enabled/disabled + * depending on the current value of the conditional expression. This + * struct is for that list. + */ +struct cond_av_list { + struct avtab_node *node; + struct cond_av_list *next; +}; + +/* + * A cond node represents a conditional block in a policy. It + * contains a conditional expression, the current state of the expression, + * two lists of rules to enable/disable depending on the value of the + * expression (the true list corresponds to if and the false list corresponds + * to else).. + */ +struct cond_node { + int cur_state; + struct cond_expr *expr; + struct cond_av_list *true_list; + struct cond_av_list *false_list; + struct cond_node *next; +}; + +int cond_policydb_init(struct policydb* p); +void cond_policydb_destroy(struct policydb* p); + +int cond_init_bool_indexes(struct policydb* p); +int cond_destroy_bool(void *key, void *datum, void *p); + +int cond_index_bool(void *key, void *datum, void *datap); + +int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp); +int cond_read_list(struct policydb *p, void *fp); + +void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decision *avd); + +int evaluate_cond_node(struct policydb *p, struct cond_node *node); + +#endif /* _CONDITIONAL_H_ */ diff --git a/security/selinux/ss/constraint.h b/security/selinux/ss/constraint.h new file mode 100644 index 000000000000..149dda731fd3 --- /dev/null +++ b/security/selinux/ss/constraint.h @@ -0,0 +1,61 @@ +/* + * A constraint is a condition that must be satisfied in + * order for one or more permissions to be granted. + * Constraints are used to impose additional restrictions + * beyond the type-based rules in `te' or the role-based + * transition rules in `rbac'. Constraints are typically + * used to prevent a process from transitioning to a new user + * identity or role unless it is in a privileged type. + * Constraints are likewise typically used to prevent a + * process from labeling an object with a different user + * identity. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_CONSTRAINT_H_ +#define _SS_CONSTRAINT_H_ + +#include "ebitmap.h" + +#define CEXPR_MAXDEPTH 5 + +struct constraint_expr { +#define CEXPR_NOT 1 /* not expr */ +#define CEXPR_AND 2 /* expr and expr */ +#define CEXPR_OR 3 /* expr or expr */ +#define CEXPR_ATTR 4 /* attr op attr */ +#define CEXPR_NAMES 5 /* attr op names */ + u32 expr_type; /* expression type */ + +#define CEXPR_USER 1 /* user */ +#define CEXPR_ROLE 2 /* role */ +#define CEXPR_TYPE 4 /* type */ +#define CEXPR_TARGET 8 /* target if set, source otherwise */ +#define CEXPR_XTARGET 16 /* special 3rd target for validatetrans rule */ +#define CEXPR_L1L2 32 /* low level 1 vs. low level 2 */ +#define CEXPR_L1H2 64 /* low level 1 vs. high level 2 */ +#define CEXPR_H1L2 128 /* high level 1 vs. low level 2 */ +#define CEXPR_H1H2 256 /* high level 1 vs. high level 2 */ +#define CEXPR_L1H1 512 /* low level 1 vs. high level 1 */ +#define CEXPR_L2H2 1024 /* low level 2 vs. high level 2 */ + u32 attr; /* attribute */ + +#define CEXPR_EQ 1 /* == or eq */ +#define CEXPR_NEQ 2 /* != */ +#define CEXPR_DOM 3 /* dom */ +#define CEXPR_DOMBY 4 /* domby */ +#define CEXPR_INCOMP 5 /* incomp */ + u32 op; /* operator */ + + struct ebitmap names; /* names */ + + struct constraint_expr *next; /* next expression */ +}; + +struct constraint_node { + u32 permissions; /* constrained permissions */ + struct constraint_expr *expr; /* constraint on permissions */ + struct constraint_node *next; /* next constraint */ +}; + +#endif /* _SS_CONSTRAINT_H_ */ diff --git a/security/selinux/ss/context.h b/security/selinux/ss/context.h new file mode 100644 index 000000000000..0562bacb7b99 --- /dev/null +++ b/security/selinux/ss/context.h @@ -0,0 +1,107 @@ +/* + * A security context is a set of security attributes + * associated with each subject and object controlled + * by the security policy. Security contexts are + * externally represented as variable-length strings + * that can be interpreted by a user or application + * with an understanding of the security policy. + * Internally, the security server uses a simple + * structure. This structure is private to the + * security server and can be changed without affecting + * clients of the security server. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_CONTEXT_H_ +#define _SS_CONTEXT_H_ + +#include "ebitmap.h" +#include "mls_types.h" +#include "security.h" + +/* + * A security context consists of an authenticated user + * identity, a role, a type and a MLS range. + */ +struct context { + u32 user; + u32 role; + u32 type; + struct mls_range range; +}; + +static inline void mls_context_init(struct context *c) +{ + memset(&c->range, 0, sizeof(c->range)); +} + +static inline int mls_context_cpy(struct context *dst, struct context *src) +{ + int rc; + + if (!selinux_mls_enabled) + return 0; + + dst->range.level[0].sens = src->range.level[0].sens; + rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[0].cat); + if (rc) + goto out; + + dst->range.level[1].sens = src->range.level[1].sens; + rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[1].cat); + if (rc) + ebitmap_destroy(&dst->range.level[0].cat); +out: + return rc; +} + +static inline int mls_context_cmp(struct context *c1, struct context *c2) +{ + if (!selinux_mls_enabled) + return 1; + + return ((c1->range.level[0].sens == c2->range.level[0].sens) && + ebitmap_cmp(&c1->range.level[0].cat,&c2->range.level[0].cat) && + (c1->range.level[1].sens == c2->range.level[1].sens) && + ebitmap_cmp(&c1->range.level[1].cat,&c2->range.level[1].cat)); +} + +static inline void mls_context_destroy(struct context *c) +{ + if (!selinux_mls_enabled) + return; + + ebitmap_destroy(&c->range.level[0].cat); + ebitmap_destroy(&c->range.level[1].cat); + mls_context_init(c); +} + +static inline void context_init(struct context *c) +{ + memset(c, 0, sizeof(*c)); +} + +static inline int context_cpy(struct context *dst, struct context *src) +{ + dst->user = src->user; + dst->role = src->role; + dst->type = src->type; + return mls_context_cpy(dst, src); +} + +static inline void context_destroy(struct context *c) +{ + c->user = c->role = c->type = 0; + mls_context_destroy(c); +} + +static inline int context_cmp(struct context *c1, struct context *c2) +{ + return ((c1->user == c2->user) && + (c1->role == c2->role) && + (c1->type == c2->type) && + mls_context_cmp(c1, c2)); +} + +#endif /* _SS_CONTEXT_H_ */ + diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c new file mode 100644 index 000000000000..d8ce9cc0b9f1 --- /dev/null +++ b/security/selinux/ss/ebitmap.c @@ -0,0 +1,293 @@ +/* + * Implementation of the extensible bitmap type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include "ebitmap.h" +#include "policydb.h" + +int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2) +{ + struct ebitmap_node *n1, *n2; + + if (e1->highbit != e2->highbit) + return 0; + + n1 = e1->node; + n2 = e2->node; + while (n1 && n2 && + (n1->startbit == n2->startbit) && + (n1->map == n2->map)) { + n1 = n1->next; + n2 = n2->next; + } + + if (n1 || n2) + return 0; + + return 1; +} + +int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src) +{ + struct ebitmap_node *n, *new, *prev; + + ebitmap_init(dst); + n = src->node; + prev = NULL; + while (n) { + new = kmalloc(sizeof(*new), GFP_ATOMIC); + if (!new) { + ebitmap_destroy(dst); + return -ENOMEM; + } + memset(new, 0, sizeof(*new)); + new->startbit = n->startbit; + new->map = n->map; + new->next = NULL; + if (prev) + prev->next = new; + else + dst->node = new; + prev = new; + n = n->next; + } + + dst->highbit = src->highbit; + return 0; +} + +int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2) +{ + struct ebitmap_node *n1, *n2; + + if (e1->highbit < e2->highbit) + return 0; + + n1 = e1->node; + n2 = e2->node; + while (n1 && n2 && (n1->startbit <= n2->startbit)) { + if (n1->startbit < n2->startbit) { + n1 = n1->next; + continue; + } + if ((n1->map & n2->map) != n2->map) + return 0; + + n1 = n1->next; + n2 = n2->next; + } + + if (n2) + return 0; + + return 1; +} + +int ebitmap_get_bit(struct ebitmap *e, unsigned long bit) +{ + struct ebitmap_node *n; + + if (e->highbit < bit) + return 0; + + n = e->node; + while (n && (n->startbit <= bit)) { + if ((n->startbit + MAPSIZE) > bit) { + if (n->map & (MAPBIT << (bit - n->startbit))) + return 1; + else + return 0; + } + n = n->next; + } + + return 0; +} + +int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value) +{ + struct ebitmap_node *n, *prev, *new; + + prev = NULL; + n = e->node; + while (n && n->startbit <= bit) { + if ((n->startbit + MAPSIZE) > bit) { + if (value) { + n->map |= (MAPBIT << (bit - n->startbit)); + } else { + n->map &= ~(MAPBIT << (bit - n->startbit)); + if (!n->map) { + /* drop this node from the bitmap */ + + if (!n->next) { + /* + * this was the highest map + * within the bitmap + */ + if (prev) + e->highbit = prev->startbit + MAPSIZE; + else + e->highbit = 0; + } + if (prev) + prev->next = n->next; + else + e->node = n->next; + + kfree(n); + } + } + return 0; + } + prev = n; + n = n->next; + } + + if (!value) + return 0; + + new = kmalloc(sizeof(*new), GFP_ATOMIC); + if (!new) + return -ENOMEM; + memset(new, 0, sizeof(*new)); + + new->startbit = bit & ~(MAPSIZE - 1); + new->map = (MAPBIT << (bit - new->startbit)); + + if (!n) + /* this node will be the highest map within the bitmap */ + e->highbit = new->startbit + MAPSIZE; + + if (prev) { + new->next = prev->next; + prev->next = new; + } else { + new->next = e->node; + e->node = new; + } + + return 0; +} + +void ebitmap_destroy(struct ebitmap *e) +{ + struct ebitmap_node *n, *temp; + + if (!e) + return; + + n = e->node; + while (n) { + temp = n; + n = n->next; + kfree(temp); + } + + e->highbit = 0; + e->node = NULL; + return; +} + +int ebitmap_read(struct ebitmap *e, void *fp) +{ + int rc; + struct ebitmap_node *n, *l; + u32 buf[3], mapsize, count, i; + u64 map; + + ebitmap_init(e); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto out; + + mapsize = le32_to_cpu(buf[0]); + e->highbit = le32_to_cpu(buf[1]); + count = le32_to_cpu(buf[2]); + + if (mapsize != MAPSIZE) { + printk(KERN_ERR "security: ebitmap: map size %u does not " + "match my size %Zd (high bit was %d)\n", mapsize, + MAPSIZE, e->highbit); + goto bad; + } + if (!e->highbit) { + e->node = NULL; + goto ok; + } + if (e->highbit & (MAPSIZE - 1)) { + printk(KERN_ERR "security: ebitmap: high bit (%d) is not a " + "multiple of the map size (%Zd)\n", e->highbit, MAPSIZE); + goto bad; + } + l = NULL; + for (i = 0; i < count; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) { + printk(KERN_ERR "security: ebitmap: truncated map\n"); + goto bad; + } + n = kmalloc(sizeof(*n), GFP_KERNEL); + if (!n) { + printk(KERN_ERR "security: ebitmap: out of memory\n"); + rc = -ENOMEM; + goto bad; + } + memset(n, 0, sizeof(*n)); + + n->startbit = le32_to_cpu(buf[0]); + + if (n->startbit & (MAPSIZE - 1)) { + printk(KERN_ERR "security: ebitmap start bit (%d) is " + "not a multiple of the map size (%Zd)\n", + n->startbit, MAPSIZE); + goto bad_free; + } + if (n->startbit > (e->highbit - MAPSIZE)) { + printk(KERN_ERR "security: ebitmap start bit (%d) is " + "beyond the end of the bitmap (%Zd)\n", + n->startbit, (e->highbit - MAPSIZE)); + goto bad_free; + } + rc = next_entry(&map, fp, sizeof(u64)); + if (rc < 0) { + printk(KERN_ERR "security: ebitmap: truncated map\n"); + goto bad_free; + } + n->map = le64_to_cpu(map); + + if (!n->map) { + printk(KERN_ERR "security: ebitmap: null map in " + "ebitmap (startbit %d)\n", n->startbit); + goto bad_free; + } + if (l) { + if (n->startbit <= l->startbit) { + printk(KERN_ERR "security: ebitmap: start " + "bit %d comes after start bit %d\n", + n->startbit, l->startbit); + goto bad_free; + } + l->next = n; + } else + e->node = n; + + l = n; + } + +ok: + rc = 0; +out: + return rc; +bad_free: + kfree(n); +bad: + if (!rc) + rc = -EINVAL; + ebitmap_destroy(e); + goto out; +} diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h new file mode 100644 index 000000000000..471370233fd9 --- /dev/null +++ b/security/selinux/ss/ebitmap.h @@ -0,0 +1,48 @@ +/* + * An extensible bitmap is a bitmap that supports an + * arbitrary number of bits. Extensible bitmaps are + * used to represent sets of values, such as types, + * roles, categories, and classes. + * + * Each extensible bitmap is implemented as a linked + * list of bitmap nodes, where each bitmap node has + * an explicitly specified starting bit position within + * the total bitmap. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_EBITMAP_H_ +#define _SS_EBITMAP_H_ + +#define MAPTYPE u64 /* portion of bitmap in each node */ +#define MAPSIZE (sizeof(MAPTYPE) * 8) /* number of bits in node bitmap */ +#define MAPBIT 1ULL /* a bit in the node bitmap */ + +struct ebitmap_node { + u32 startbit; /* starting position in the total bitmap */ + MAPTYPE map; /* this node's portion of the bitmap */ + struct ebitmap_node *next; +}; + +struct ebitmap { + struct ebitmap_node *node; /* first node in the bitmap */ + u32 highbit; /* highest position in the total bitmap */ +}; + +#define ebitmap_length(e) ((e)->highbit) +#define ebitmap_startbit(e) ((e)->node ? (e)->node->startbit : 0) + +static inline void ebitmap_init(struct ebitmap *e) +{ + memset(e, 0, sizeof(*e)); +} + +int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2); +int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src); +int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2); +int ebitmap_get_bit(struct ebitmap *e, unsigned long bit); +int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value); +void ebitmap_destroy(struct ebitmap *e); +int ebitmap_read(struct ebitmap *e, void *fp); + +#endif /* _SS_EBITMAP_H_ */ diff --git a/security/selinux/ss/hashtab.c b/security/selinux/ss/hashtab.c new file mode 100644 index 000000000000..26661fcc00ce --- /dev/null +++ b/security/selinux/ss/hashtab.c @@ -0,0 +1,167 @@ +/* + * Implementation of the hash table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include "hashtab.h" + +struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, void *key), + int (*keycmp)(struct hashtab *h, void *key1, void *key2), + u32 size) +{ + struct hashtab *p; + u32 i; + + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) + return p; + + memset(p, 0, sizeof(*p)); + p->size = size; + p->nel = 0; + p->hash_value = hash_value; + p->keycmp = keycmp; + p->htable = kmalloc(sizeof(*(p->htable)) * size, GFP_KERNEL); + if (p->htable == NULL) { + kfree(p); + return NULL; + } + + for (i = 0; i < size; i++) + p->htable[i] = NULL; + + return p; +} + +int hashtab_insert(struct hashtab *h, void *key, void *datum) +{ + u32 hvalue; + struct hashtab_node *prev, *cur, *newnode; + + if (!h || h->nel == HASHTAB_MAX_NODES) + return -EINVAL; + + hvalue = h->hash_value(h, key); + prev = NULL; + cur = h->htable[hvalue]; + while (cur && h->keycmp(h, key, cur->key) > 0) { + prev = cur; + cur = cur->next; + } + + if (cur && (h->keycmp(h, key, cur->key) == 0)) + return -EEXIST; + + newnode = kmalloc(sizeof(*newnode), GFP_KERNEL); + if (newnode == NULL) + return -ENOMEM; + memset(newnode, 0, sizeof(*newnode)); + newnode->key = key; + newnode->datum = datum; + if (prev) { + newnode->next = prev->next; + prev->next = newnode; + } else { + newnode->next = h->htable[hvalue]; + h->htable[hvalue] = newnode; + } + + h->nel++; + return 0; +} + +void *hashtab_search(struct hashtab *h, void *key) +{ + u32 hvalue; + struct hashtab_node *cur; + + if (!h) + return NULL; + + hvalue = h->hash_value(h, key); + cur = h->htable[hvalue]; + while (cur != NULL && h->keycmp(h, key, cur->key) > 0) + cur = cur->next; + + if (cur == NULL || (h->keycmp(h, key, cur->key) != 0)) + return NULL; + + return cur->datum; +} + +void hashtab_destroy(struct hashtab *h) +{ + u32 i; + struct hashtab_node *cur, *temp; + + if (!h) + return; + + for (i = 0; i < h->size; i++) { + cur = h->htable[i]; + while (cur != NULL) { + temp = cur; + cur = cur->next; + kfree(temp); + } + h->htable[i] = NULL; + } + + kfree(h->htable); + h->htable = NULL; + + kfree(h); +} + +int hashtab_map(struct hashtab *h, + int (*apply)(void *k, void *d, void *args), + void *args) +{ + u32 i; + int ret; + struct hashtab_node *cur; + + if (!h) + return 0; + + for (i = 0; i < h->size; i++) { + cur = h->htable[i]; + while (cur != NULL) { + ret = apply(cur->key, cur->datum, args); + if (ret) + return ret; + cur = cur->next; + } + } + return 0; +} + + +void hashtab_stat(struct hashtab *h, struct hashtab_info *info) +{ + u32 i, chain_len, slots_used, max_chain_len; + struct hashtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + for (slots_used = max_chain_len = i = 0; i < h->size; i++) { + cur = h->htable[i]; + if (cur) { + slots_used++; + chain_len = 0; + while (cur) { + chain_len++; + cur = cur->next; + } + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + info->slots_used = slots_used; + info->max_chain_len = max_chain_len; +} diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h new file mode 100644 index 000000000000..4cc85816a718 --- /dev/null +++ b/security/selinux/ss/hashtab.h @@ -0,0 +1,87 @@ +/* + * A hash table (hashtab) maintains associations between + * key values and datum values. The type of the key values + * and the type of the datum values is arbitrary. The + * functions for hash computation and key comparison are + * provided by the creator of the table. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_HASHTAB_H_ +#define _SS_HASHTAB_H_ + +#define HASHTAB_MAX_NODES 0xffffffff + +struct hashtab_node { + void *key; + void *datum; + struct hashtab_node *next; +}; + +struct hashtab { + struct hashtab_node **htable; /* hash table */ + u32 size; /* number of slots in hash table */ + u32 nel; /* number of elements in hash table */ + u32 (*hash_value)(struct hashtab *h, void *key); + /* hash function */ + int (*keycmp)(struct hashtab *h, void *key1, void *key2); + /* key comparison function */ +}; + +struct hashtab_info { + u32 slots_used; + u32 max_chain_len; +}; + +/* + * Creates a new hash table with the specified characteristics. + * + * Returns NULL if insufficent space is available or + * the new hash table otherwise. + */ +struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, void *key), + int (*keycmp)(struct hashtab *h, void *key1, void *key2), + u32 size); + +/* + * Inserts the specified (key, datum) pair into the specified hash table. + * + * Returns -ENOMEM on memory allocation error, + * -EEXIST if there is already an entry with the same key, + * -EINVAL for general errors or + * 0 otherwise. + */ +int hashtab_insert(struct hashtab *h, void *k, void *d); + +/* + * Searches for the entry with the specified key in the hash table. + * + * Returns NULL if no entry has the specified key or + * the datum of the entry otherwise. + */ +void *hashtab_search(struct hashtab *h, void *k); + +/* + * Destroys the specified hash table. + */ +void hashtab_destroy(struct hashtab *h); + +/* + * Applies the specified apply function to (key,datum,args) + * for each entry in the specified hash table. + * + * The order in which the function is applied to the entries + * is dependent upon the internal structure of the hash table. + * + * If apply returns a non-zero status, then hashtab_map will cease + * iterating through the hash table and will propagate the error + * return to its caller. + */ +int hashtab_map(struct hashtab *h, + int (*apply)(void *k, void *d, void *args), + void *args); + +/* Fill info with some hash table statistics */ +void hashtab_stat(struct hashtab *h, struct hashtab_info *info); + +#endif /* _SS_HASHTAB_H */ diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c new file mode 100644 index 000000000000..756036bcc243 --- /dev/null +++ b/security/selinux/ss/mls.c @@ -0,0 +1,527 @@ +/* + * Implementation of the multi-level security (MLS) policy. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include "mls.h" +#include "policydb.h" +#include "services.h" + +/* + * Return the length in bytes for the MLS fields of the + * security context string representation of `context'. + */ +int mls_compute_context_len(struct context * context) +{ + int i, l, len, range; + + if (!selinux_mls_enabled) + return 0; + + len = 1; /* for the beginning ":" */ + for (l = 0; l < 2; l++) { + range = 0; + len += strlen(policydb.p_sens_val_to_name[context->range.level[l].sens - 1]); + + for (i = 1; i <= ebitmap_length(&context->range.level[l].cat); i++) { + if (ebitmap_get_bit(&context->range.level[l].cat, i - 1)) { + if (range) { + range++; + continue; + } + + len += strlen(policydb.p_cat_val_to_name[i - 1]) + 1; + range++; + } else { + if (range > 1) + len += strlen(policydb.p_cat_val_to_name[i - 2]) + 1; + range = 0; + } + } + /* Handle case where last category is the end of range */ + if (range > 1) + len += strlen(policydb.p_cat_val_to_name[i - 2]) + 1; + + if (l == 0) { + if (mls_level_eq(&context->range.level[0], + &context->range.level[1])) + break; + else + len++; + } + } + + return len; +} + +/* + * Write the security context string representation of + * the MLS fields of `context' into the string `*scontext'. + * Update `*scontext' to point to the end of the MLS fields. + */ +void mls_sid_to_context(struct context *context, + char **scontext) +{ + char *scontextp; + int i, l, range, wrote_sep; + + if (!selinux_mls_enabled) + return; + + scontextp = *scontext; + + *scontextp = ':'; + scontextp++; + + for (l = 0; l < 2; l++) { + range = 0; + wrote_sep = 0; + strcpy(scontextp, + policydb.p_sens_val_to_name[context->range.level[l].sens - 1]); + scontextp += strlen(policydb.p_sens_val_to_name[context->range.level[l].sens - 1]); + + /* categories */ + for (i = 1; i <= ebitmap_length(&context->range.level[l].cat); i++) { + if (ebitmap_get_bit(&context->range.level[l].cat, i - 1)) { + if (range) { + range++; + continue; + } + + if (!wrote_sep) { + *scontextp++ = ':'; + wrote_sep = 1; + } else + *scontextp++ = ','; + strcpy(scontextp, policydb.p_cat_val_to_name[i - 1]); + scontextp += strlen(policydb.p_cat_val_to_name[i - 1]); + range++; + } else { + if (range > 1) { + if (range > 2) + *scontextp++ = '.'; + else + *scontextp++ = ','; + + strcpy(scontextp, policydb.p_cat_val_to_name[i - 2]); + scontextp += strlen(policydb.p_cat_val_to_name[i - 2]); + } + range = 0; + } + } + + /* Handle case where last category is the end of range */ + if (range > 1) { + if (range > 2) + *scontextp++ = '.'; + else + *scontextp++ = ','; + + strcpy(scontextp, policydb.p_cat_val_to_name[i - 2]); + scontextp += strlen(policydb.p_cat_val_to_name[i - 2]); + } + + if (l == 0) { + if (mls_level_eq(&context->range.level[0], + &context->range.level[1])) + break; + else { + *scontextp = '-'; + scontextp++; + } + } + } + + *scontext = scontextp; + return; +} + +/* + * Return 1 if the MLS fields in the security context + * structure `c' are valid. Return 0 otherwise. + */ +int mls_context_isvalid(struct policydb *p, struct context *c) +{ + struct level_datum *levdatum; + struct user_datum *usrdatum; + int i, l; + + if (!selinux_mls_enabled) + return 1; + + /* + * MLS range validity checks: high must dominate low, low level must + * be valid (category set <-> sensitivity check), and high level must + * be valid (category set <-> sensitivity check) + */ + if (!mls_level_dom(&c->range.level[1], &c->range.level[0])) + /* High does not dominate low. */ + return 0; + + for (l = 0; l < 2; l++) { + if (!c->range.level[l].sens || c->range.level[l].sens > p->p_levels.nprim) + return 0; + levdatum = hashtab_search(p->p_levels.table, + p->p_sens_val_to_name[c->range.level[l].sens - 1]); + if (!levdatum) + return 0; + + for (i = 1; i <= ebitmap_length(&c->range.level[l].cat); i++) { + if (ebitmap_get_bit(&c->range.level[l].cat, i - 1)) { + if (i > p->p_cats.nprim) + return 0; + if (!ebitmap_get_bit(&levdatum->level->cat, i - 1)) + /* + * Category may not be associated with + * sensitivity in low level. + */ + return 0; + } + } + } + + if (c->role == OBJECT_R_VAL) + return 1; + + /* + * User must be authorized for the MLS range. + */ + if (!c->user || c->user > p->p_users.nprim) + return 0; + usrdatum = p->user_val_to_struct[c->user - 1]; + if (!mls_range_contains(usrdatum->range, c->range)) + return 0; /* user may not be associated with range */ + + return 1; +} + +/* + * Set the MLS fields in the security context structure + * `context' based on the string representation in + * the string `*scontext'. Update `*scontext' to + * point to the end of the string representation of + * the MLS fields. + * + * This function modifies the string in place, inserting + * NULL characters to terminate the MLS fields. + */ +int mls_context_to_sid(char oldc, + char **scontext, + struct context *context) +{ + + char delim; + char *scontextp, *p, *rngptr; + struct level_datum *levdatum; + struct cat_datum *catdatum, *rngdatum; + int l, rc = -EINVAL; + + if (!selinux_mls_enabled) + return 0; + + /* No MLS component to the security context. */ + if (!oldc) + goto out; + + /* Extract low sensitivity. */ + scontextp = p = *scontext; + while (*p && *p != ':' && *p != '-') + p++; + + delim = *p; + if (delim != 0) + *p++ = 0; + + for (l = 0; l < 2; l++) { + levdatum = hashtab_search(policydb.p_levels.table, scontextp); + if (!levdatum) { + rc = -EINVAL; + goto out; + } + + context->range.level[l].sens = levdatum->level->sens; + + if (delim == ':') { + /* Extract category set. */ + while (1) { + scontextp = p; + while (*p && *p != ',' && *p != '-') + p++; + delim = *p; + if (delim != 0) + *p++ = 0; + + /* Separate into range if exists */ + if ((rngptr = strchr(scontextp, '.')) != NULL) { + /* Remove '.' */ + *rngptr++ = 0; + } + + catdatum = hashtab_search(policydb.p_cats.table, + scontextp); + if (!catdatum) { + rc = -EINVAL; + goto out; + } + + rc = ebitmap_set_bit(&context->range.level[l].cat, + catdatum->value - 1, 1); + if (rc) + goto out; + + /* If range, set all categories in range */ + if (rngptr) { + int i; + + rngdatum = hashtab_search(policydb.p_cats.table, rngptr); + if (!rngdatum) { + rc = -EINVAL; + goto out; + } + + if (catdatum->value >= rngdatum->value) { + rc = -EINVAL; + goto out; + } + + for (i = catdatum->value; i < rngdatum->value; i++) { + rc = ebitmap_set_bit(&context->range.level[l].cat, i, 1); + if (rc) + goto out; + } + } + + if (delim != ',') + break; + } + } + if (delim == '-') { + /* Extract high sensitivity. */ + scontextp = p; + while (*p && *p != ':') + p++; + + delim = *p; + if (delim != 0) + *p++ = 0; + } else + break; + } + + if (l == 0) { + context->range.level[1].sens = context->range.level[0].sens; + rc = ebitmap_cpy(&context->range.level[1].cat, + &context->range.level[0].cat); + if (rc) + goto out; + } + *scontext = ++p; + rc = 0; +out: + return rc; +} + +/* + * Copies the MLS range from `src' into `dst'. + */ +static inline int mls_copy_context(struct context *dst, + struct context *src) +{ + int l, rc = 0; + + /* Copy the MLS range from the source context */ + for (l = 0; l < 2; l++) { + dst->range.level[l].sens = src->range.level[l].sens; + rc = ebitmap_cpy(&dst->range.level[l].cat, + &src->range.level[l].cat); + if (rc) + break; + } + + return rc; +} + +/* + * Copies the effective MLS range from `src' into `dst'. + */ +static inline int mls_scopy_context(struct context *dst, + struct context *src) +{ + int l, rc = 0; + + /* Copy the MLS range from the source context */ + for (l = 0; l < 2; l++) { + dst->range.level[l].sens = src->range.level[0].sens; + rc = ebitmap_cpy(&dst->range.level[l].cat, + &src->range.level[0].cat); + if (rc) + break; + } + + return rc; +} + +/* + * Copies the MLS range `range' into `context'. + */ +static inline int mls_range_set(struct context *context, + struct mls_range *range) +{ + int l, rc = 0; + + /* Copy the MLS range into the context */ + for (l = 0; l < 2; l++) { + context->range.level[l].sens = range->level[l].sens; + rc = ebitmap_cpy(&context->range.level[l].cat, + &range->level[l].cat); + if (rc) + break; + } + + return rc; +} + +int mls_setup_user_range(struct context *fromcon, struct user_datum *user, + struct context *usercon) +{ + if (selinux_mls_enabled) { + struct mls_level *fromcon_sen = &(fromcon->range.level[0]); + struct mls_level *fromcon_clr = &(fromcon->range.level[1]); + struct mls_level *user_low = &(user->range.level[0]); + struct mls_level *user_clr = &(user->range.level[1]); + struct mls_level *user_def = &(user->dfltlevel); + struct mls_level *usercon_sen = &(usercon->range.level[0]); + struct mls_level *usercon_clr = &(usercon->range.level[1]); + + /* Honor the user's default level if we can */ + if (mls_level_between(user_def, fromcon_sen, fromcon_clr)) { + *usercon_sen = *user_def; + } else if (mls_level_between(fromcon_sen, user_def, user_clr)) { + *usercon_sen = *fromcon_sen; + } else if (mls_level_between(fromcon_clr, user_low, user_def)) { + *usercon_sen = *user_low; + } else + return -EINVAL; + + /* Lower the clearance of available contexts + if the clearance of "fromcon" is lower than + that of the user's default clearance (but + only if the "fromcon" clearance dominates + the user's computed sensitivity level) */ + if (mls_level_dom(user_clr, fromcon_clr)) { + *usercon_clr = *fromcon_clr; + } else if (mls_level_dom(fromcon_clr, user_clr)) { + *usercon_clr = *user_clr; + } else + return -EINVAL; + } + + return 0; +} + +/* + * Convert the MLS fields in the security context + * structure `c' from the values specified in the + * policy `oldp' to the values specified in the policy `newp'. + */ +int mls_convert_context(struct policydb *oldp, + struct policydb *newp, + struct context *c) +{ + struct level_datum *levdatum; + struct cat_datum *catdatum; + struct ebitmap bitmap; + int l, i; + + if (!selinux_mls_enabled) + return 0; + + for (l = 0; l < 2; l++) { + levdatum = hashtab_search(newp->p_levels.table, + oldp->p_sens_val_to_name[c->range.level[l].sens - 1]); + + if (!levdatum) + return -EINVAL; + c->range.level[l].sens = levdatum->level->sens; + + ebitmap_init(&bitmap); + for (i = 1; i <= ebitmap_length(&c->range.level[l].cat); i++) { + if (ebitmap_get_bit(&c->range.level[l].cat, i - 1)) { + int rc; + + catdatum = hashtab_search(newp->p_cats.table, + oldp->p_cat_val_to_name[i - 1]); + if (!catdatum) + return -EINVAL; + rc = ebitmap_set_bit(&bitmap, catdatum->value - 1, 1); + if (rc) + return rc; + } + } + ebitmap_destroy(&c->range.level[l].cat); + c->range.level[l].cat = bitmap; + } + + return 0; +} + +int mls_compute_sid(struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 specified, + struct context *newcontext) +{ + if (!selinux_mls_enabled) + return 0; + + switch (specified) { + case AVTAB_TRANSITION: + if (tclass == SECCLASS_PROCESS) { + struct range_trans *rangetr; + /* Look for a range transition rule. */ + for (rangetr = policydb.range_tr; rangetr; + rangetr = rangetr->next) { + if (rangetr->dom == scontext->type && + rangetr->type == tcontext->type) { + /* Set the range from the rule */ + return mls_range_set(newcontext, + &rangetr->range); + } + } + } + /* Fallthrough */ + case AVTAB_CHANGE: + if (tclass == SECCLASS_PROCESS) + /* Use the process MLS attributes. */ + return mls_copy_context(newcontext, scontext); + else + /* Use the process effective MLS attributes. */ + return mls_scopy_context(newcontext, scontext); + case AVTAB_MEMBER: + /* Only polyinstantiate the MLS attributes if + the type is being polyinstantiated */ + if (newcontext->type != tcontext->type) { + /* Use the process effective MLS attributes. */ + return mls_scopy_context(newcontext, scontext); + } else { + /* Use the related object MLS attributes. */ + return mls_copy_context(newcontext, tcontext); + } + default: + return -EINVAL; + } + return -EINVAL; +} + diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h new file mode 100644 index 000000000000..0d37beaa85e2 --- /dev/null +++ b/security/selinux/ss/mls.h @@ -0,0 +1,42 @@ +/* + * Multi-level security (MLS) policy operations. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + */ + +#ifndef _SS_MLS_H_ +#define _SS_MLS_H_ + +#include "context.h" +#include "policydb.h" + +int mls_compute_context_len(struct context *context); +void mls_sid_to_context(struct context *context, char **scontext); +int mls_context_isvalid(struct policydb *p, struct context *c); + +int mls_context_to_sid(char oldc, + char **scontext, + struct context *context); + +int mls_convert_context(struct policydb *oldp, + struct policydb *newp, + struct context *context); + +int mls_compute_sid(struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 specified, + struct context *newcontext); + +int mls_setup_user_range(struct context *fromcon, struct user_datum *user, + struct context *usercon); + +#endif /* _SS_MLS_H */ + diff --git a/security/selinux/ss/mls_types.h b/security/selinux/ss/mls_types.h new file mode 100644 index 000000000000..0c692d58d489 --- /dev/null +++ b/security/selinux/ss/mls_types.h @@ -0,0 +1,56 @@ +/* + * Type definitions for the multi-level security (MLS) policy. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + */ + +#ifndef _SS_MLS_TYPES_H_ +#define _SS_MLS_TYPES_H_ + +#include "security.h" + +struct mls_level { + u32 sens; /* sensitivity */ + struct ebitmap cat; /* category set */ +}; + +struct mls_range { + struct mls_level level[2]; /* low == level[0], high == level[1] */ +}; + +static inline int mls_level_eq(struct mls_level *l1, struct mls_level *l2) +{ + if (!selinux_mls_enabled) + return 1; + + return ((l1->sens == l2->sens) && + ebitmap_cmp(&l1->cat, &l2->cat)); +} + +static inline int mls_level_dom(struct mls_level *l1, struct mls_level *l2) +{ + if (!selinux_mls_enabled) + return 1; + + return ((l1->sens >= l2->sens) && + ebitmap_contains(&l1->cat, &l2->cat)); +} + +#define mls_level_incomp(l1, l2) \ +(!mls_level_dom((l1), (l2)) && !mls_level_dom((l2), (l1))) + +#define mls_level_between(l1, l2, l3) \ +(mls_level_dom((l1), (l2)) && mls_level_dom((l3), (l1))) + +#define mls_range_contains(r1, r2) \ +(mls_level_dom(&(r2).level[0], &(r1).level[0]) && \ + mls_level_dom(&(r1).level[1], &(r2).level[1])) + +#endif /* _SS_MLS_TYPES_H_ */ diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c new file mode 100644 index 000000000000..14190efbf333 --- /dev/null +++ b/security/selinux/ss/policydb.c @@ -0,0 +1,1843 @@ +/* + * Implementation of the policy database. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include "security.h" + +#include "policydb.h" +#include "conditional.h" +#include "mls.h" + +#define _DEBUG_HASHES + +#ifdef DEBUG_HASHES +static char *symtab_name[SYM_NUM] = { + "common prefixes", + "classes", + "roles", + "types", + "users", + "bools", + "levels", + "categories", +}; +#endif + +int selinux_mls_enabled = 0; + +static unsigned int symtab_sizes[SYM_NUM] = { + 2, + 32, + 16, + 512, + 128, + 16, + 16, + 16, +}; + +struct policydb_compat_info { + int version; + int sym_num; + int ocon_num; +}; + +/* These need to be updated if SYM_NUM or OCON_NUM changes */ +static struct policydb_compat_info policydb_compat[] = { + { + .version = POLICYDB_VERSION_BASE, + .sym_num = SYM_NUM - 3, + .ocon_num = OCON_NUM - 1, + }, + { + .version = POLICYDB_VERSION_BOOL, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM - 1, + }, + { + .version = POLICYDB_VERSION_IPV6, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_NLCLASS, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_MLS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, +}; + +static struct policydb_compat_info *policydb_lookup_compat(int version) +{ + int i; + struct policydb_compat_info *info = NULL; + + for (i = 0; i < sizeof(policydb_compat)/sizeof(*info); i++) { + if (policydb_compat[i].version == version) { + info = &policydb_compat[i]; + break; + } + } + return info; +} + +/* + * Initialize the role table. + */ +static int roles_init(struct policydb *p) +{ + char *key = NULL; + int rc; + struct role_datum *role; + + role = kmalloc(sizeof(*role), GFP_KERNEL); + if (!role) { + rc = -ENOMEM; + goto out; + } + memset(role, 0, sizeof(*role)); + role->value = ++p->p_roles.nprim; + if (role->value != OBJECT_R_VAL) { + rc = -EINVAL; + goto out_free_role; + } + key = kmalloc(strlen(OBJECT_R)+1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto out_free_role; + } + strcpy(key, OBJECT_R); + rc = hashtab_insert(p->p_roles.table, key, role); + if (rc) + goto out_free_key; +out: + return rc; + +out_free_key: + kfree(key); +out_free_role: + kfree(role); + goto out; +} + +/* + * Initialize a policy database structure. + */ +static int policydb_init(struct policydb *p) +{ + int i, rc; + + memset(p, 0, sizeof(*p)); + + for (i = 0; i < SYM_NUM; i++) { + rc = symtab_init(&p->symtab[i], symtab_sizes[i]); + if (rc) + goto out_free_symtab; + } + + rc = avtab_init(&p->te_avtab); + if (rc) + goto out_free_symtab; + + rc = roles_init(p); + if (rc) + goto out_free_avtab; + + rc = cond_policydb_init(p); + if (rc) + goto out_free_avtab; + +out: + return rc; + +out_free_avtab: + avtab_destroy(&p->te_avtab); + +out_free_symtab: + for (i = 0; i < SYM_NUM; i++) + hashtab_destroy(p->symtab[i].table); + goto out; +} + +/* + * The following *_index functions are used to + * define the val_to_name and val_to_struct arrays + * in a policy database structure. The val_to_name + * arrays are used when converting security context + * structures into string representations. The + * val_to_struct arrays are used when the attributes + * of a class, role, or user are needed. + */ + +static int common_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct common_datum *comdatum; + + comdatum = datum; + p = datap; + if (!comdatum->value || comdatum->value > p->p_commons.nprim) + return -EINVAL; + p->p_common_val_to_name[comdatum->value - 1] = key; + return 0; +} + +static int class_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct class_datum *cladatum; + + cladatum = datum; + p = datap; + if (!cladatum->value || cladatum->value > p->p_classes.nprim) + return -EINVAL; + p->p_class_val_to_name[cladatum->value - 1] = key; + p->class_val_to_struct[cladatum->value - 1] = cladatum; + return 0; +} + +static int role_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct role_datum *role; + + role = datum; + p = datap; + if (!role->value || role->value > p->p_roles.nprim) + return -EINVAL; + p->p_role_val_to_name[role->value - 1] = key; + p->role_val_to_struct[role->value - 1] = role; + return 0; +} + +static int type_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct type_datum *typdatum; + + typdatum = datum; + p = datap; + + if (typdatum->primary) { + if (!typdatum->value || typdatum->value > p->p_types.nprim) + return -EINVAL; + p->p_type_val_to_name[typdatum->value - 1] = key; + } + + return 0; +} + +static int user_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct user_datum *usrdatum; + + usrdatum = datum; + p = datap; + if (!usrdatum->value || usrdatum->value > p->p_users.nprim) + return -EINVAL; + p->p_user_val_to_name[usrdatum->value - 1] = key; + p->user_val_to_struct[usrdatum->value - 1] = usrdatum; + return 0; +} + +static int sens_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct level_datum *levdatum; + + levdatum = datum; + p = datap; + + if (!levdatum->isalias) { + if (!levdatum->level->sens || + levdatum->level->sens > p->p_levels.nprim) + return -EINVAL; + p->p_sens_val_to_name[levdatum->level->sens - 1] = key; + } + + return 0; +} + +static int cat_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct cat_datum *catdatum; + + catdatum = datum; + p = datap; + + if (!catdatum->isalias) { + if (!catdatum->value || catdatum->value > p->p_cats.nprim) + return -EINVAL; + p->p_cat_val_to_name[catdatum->value - 1] = key; + } + + return 0; +} + +static int (*index_f[SYM_NUM]) (void *key, void *datum, void *datap) = +{ + common_index, + class_index, + role_index, + type_index, + user_index, + cond_index_bool, + sens_index, + cat_index, +}; + +/* + * Define the common val_to_name array and the class + * val_to_name and val_to_struct arrays in a policy + * database structure. + * + * Caller must clean up upon failure. + */ +static int policydb_index_classes(struct policydb *p) +{ + int rc; + + p->p_common_val_to_name = + kmalloc(p->p_commons.nprim * sizeof(char *), GFP_KERNEL); + if (!p->p_common_val_to_name) { + rc = -ENOMEM; + goto out; + } + + rc = hashtab_map(p->p_commons.table, common_index, p); + if (rc) + goto out; + + p->class_val_to_struct = + kmalloc(p->p_classes.nprim * sizeof(*(p->class_val_to_struct)), GFP_KERNEL); + if (!p->class_val_to_struct) { + rc = -ENOMEM; + goto out; + } + + p->p_class_val_to_name = + kmalloc(p->p_classes.nprim * sizeof(char *), GFP_KERNEL); + if (!p->p_class_val_to_name) { + rc = -ENOMEM; + goto out; + } + + rc = hashtab_map(p->p_classes.table, class_index, p); +out: + return rc; +} + +#ifdef DEBUG_HASHES +static void symtab_hash_eval(struct symtab *s) +{ + int i; + + for (i = 0; i < SYM_NUM; i++) { + struct hashtab *h = s[i].table; + struct hashtab_info info; + + hashtab_stat(h, &info); + printk(KERN_INFO "%s: %d entries and %d/%d buckets used, " + "longest chain length %d\n", symtab_name[i], h->nel, + info.slots_used, h->size, info.max_chain_len); + } +} +#endif + +/* + * Define the other val_to_name and val_to_struct arrays + * in a policy database structure. + * + * Caller must clean up on failure. + */ +static int policydb_index_others(struct policydb *p) +{ + int i, rc = 0; + + printk(KERN_INFO "security: %d users, %d roles, %d types, %d bools", + p->p_users.nprim, p->p_roles.nprim, p->p_types.nprim, p->p_bools.nprim); + if (selinux_mls_enabled) + printk(", %d sens, %d cats", p->p_levels.nprim, + p->p_cats.nprim); + printk("\n"); + + printk(KERN_INFO "security: %d classes, %d rules\n", + p->p_classes.nprim, p->te_avtab.nel); + +#ifdef DEBUG_HASHES + avtab_hash_eval(&p->te_avtab, "rules"); + symtab_hash_eval(p->symtab); +#endif + + p->role_val_to_struct = + kmalloc(p->p_roles.nprim * sizeof(*(p->role_val_to_struct)), + GFP_KERNEL); + if (!p->role_val_to_struct) { + rc = -ENOMEM; + goto out; + } + + p->user_val_to_struct = + kmalloc(p->p_users.nprim * sizeof(*(p->user_val_to_struct)), + GFP_KERNEL); + if (!p->user_val_to_struct) { + rc = -ENOMEM; + goto out; + } + + if (cond_init_bool_indexes(p)) { + rc = -ENOMEM; + goto out; + } + + for (i = SYM_ROLES; i < SYM_NUM; i++) { + p->sym_val_to_name[i] = + kmalloc(p->symtab[i].nprim * sizeof(char *), GFP_KERNEL); + if (!p->sym_val_to_name[i]) { + rc = -ENOMEM; + goto out; + } + rc = hashtab_map(p->symtab[i].table, index_f[i], p); + if (rc) + goto out; + } + +out: + return rc; +} + +/* + * The following *_destroy functions are used to + * free any memory allocated for each kind of + * symbol data in the policy database. + */ + +static int perm_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int common_destroy(void *key, void *datum, void *p) +{ + struct common_datum *comdatum; + + kfree(key); + comdatum = datum; + hashtab_map(comdatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(comdatum->permissions.table); + kfree(datum); + return 0; +} + +static int class_destroy(void *key, void *datum, void *p) +{ + struct class_datum *cladatum; + struct constraint_node *constraint, *ctemp; + struct constraint_expr *e, *etmp; + + kfree(key); + cladatum = datum; + hashtab_map(cladatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(cladatum->permissions.table); + constraint = cladatum->constraints; + while (constraint) { + e = constraint->expr; + while (e) { + ebitmap_destroy(&e->names); + etmp = e; + e = e->next; + kfree(etmp); + } + ctemp = constraint; + constraint = constraint->next; + kfree(ctemp); + } + + constraint = cladatum->validatetrans; + while (constraint) { + e = constraint->expr; + while (e) { + ebitmap_destroy(&e->names); + etmp = e; + e = e->next; + kfree(etmp); + } + ctemp = constraint; + constraint = constraint->next; + kfree(ctemp); + } + + kfree(cladatum->comkey); + kfree(datum); + return 0; +} + +static int role_destroy(void *key, void *datum, void *p) +{ + struct role_datum *role; + + kfree(key); + role = datum; + ebitmap_destroy(&role->dominates); + ebitmap_destroy(&role->types); + kfree(datum); + return 0; +} + +static int type_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int user_destroy(void *key, void *datum, void *p) +{ + struct user_datum *usrdatum; + + kfree(key); + usrdatum = datum; + ebitmap_destroy(&usrdatum->roles); + ebitmap_destroy(&usrdatum->range.level[0].cat); + ebitmap_destroy(&usrdatum->range.level[1].cat); + ebitmap_destroy(&usrdatum->dfltlevel.cat); + kfree(datum); + return 0; +} + +static int sens_destroy(void *key, void *datum, void *p) +{ + struct level_datum *levdatum; + + kfree(key); + levdatum = datum; + ebitmap_destroy(&levdatum->level->cat); + kfree(levdatum->level); + kfree(datum); + return 0; +} + +static int cat_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int (*destroy_f[SYM_NUM]) (void *key, void *datum, void *datap) = +{ + common_destroy, + class_destroy, + role_destroy, + type_destroy, + user_destroy, + cond_destroy_bool, + sens_destroy, + cat_destroy, +}; + +static void ocontext_destroy(struct ocontext *c, int i) +{ + context_destroy(&c->context[0]); + context_destroy(&c->context[1]); + if (i == OCON_ISID || i == OCON_FS || + i == OCON_NETIF || i == OCON_FSUSE) + kfree(c->u.name); + kfree(c); +} + +/* + * Free any memory allocated by a policy database structure. + */ +void policydb_destroy(struct policydb *p) +{ + struct ocontext *c, *ctmp; + struct genfs *g, *gtmp; + int i; + + for (i = 0; i < SYM_NUM; i++) { + hashtab_map(p->symtab[i].table, destroy_f[i], NULL); + hashtab_destroy(p->symtab[i].table); + } + + for (i = 0; i < SYM_NUM; i++) { + if (p->sym_val_to_name[i]) + kfree(p->sym_val_to_name[i]); + } + + if (p->class_val_to_struct) + kfree(p->class_val_to_struct); + if (p->role_val_to_struct) + kfree(p->role_val_to_struct); + if (p->user_val_to_struct) + kfree(p->user_val_to_struct); + + avtab_destroy(&p->te_avtab); + + for (i = 0; i < OCON_NUM; i++) { + c = p->ocontexts[i]; + while (c) { + ctmp = c; + c = c->next; + ocontext_destroy(ctmp,i); + } + } + + g = p->genfs; + while (g) { + kfree(g->fstype); + c = g->head; + while (c) { + ctmp = c; + c = c->next; + ocontext_destroy(ctmp,OCON_FSUSE); + } + gtmp = g; + g = g->next; + kfree(gtmp); + } + + cond_policydb_destroy(p); + + return; +} + +/* + * Load the initial SIDs specified in a policy database + * structure into a SID table. + */ +int policydb_load_isids(struct policydb *p, struct sidtab *s) +{ + struct ocontext *head, *c; + int rc; + + rc = sidtab_init(s); + if (rc) { + printk(KERN_ERR "security: out of memory on SID table init\n"); + goto out; + } + + head = p->ocontexts[OCON_ISID]; + for (c = head; c; c = c->next) { + if (!c->context[0].user) { + printk(KERN_ERR "security: SID %s was never " + "defined.\n", c->u.name); + rc = -EINVAL; + goto out; + } + if (sidtab_insert(s, c->sid[0], &c->context[0])) { + printk(KERN_ERR "security: unable to load initial " + "SID %s.\n", c->u.name); + rc = -EINVAL; + goto out; + } + } +out: + return rc; +} + +/* + * Return 1 if the fields in the security context + * structure `c' are valid. Return 0 otherwise. + */ +int policydb_context_isvalid(struct policydb *p, struct context *c) +{ + struct role_datum *role; + struct user_datum *usrdatum; + + if (!c->role || c->role > p->p_roles.nprim) + return 0; + + if (!c->user || c->user > p->p_users.nprim) + return 0; + + if (!c->type || c->type > p->p_types.nprim) + return 0; + + if (c->role != OBJECT_R_VAL) { + /* + * Role must be authorized for the type. + */ + role = p->role_val_to_struct[c->role - 1]; + if (!ebitmap_get_bit(&role->types, + c->type - 1)) + /* role may not be associated with type */ + return 0; + + /* + * User must be authorized for the role. + */ + usrdatum = p->user_val_to_struct[c->user - 1]; + if (!usrdatum) + return 0; + + if (!ebitmap_get_bit(&usrdatum->roles, + c->role - 1)) + /* user may not be associated with role */ + return 0; + } + + if (!mls_context_isvalid(p, c)) + return 0; + + return 1; +} + +/* + * Read a MLS range structure from a policydb binary + * representation file. + */ +static int mls_read_range_helper(struct mls_range *r, void *fp) +{ + u32 buf[2], items; + int rc; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto out; + + items = le32_to_cpu(buf[0]); + if (items > ARRAY_SIZE(buf)) { + printk(KERN_ERR "security: mls: range overflow\n"); + rc = -EINVAL; + goto out; + } + rc = next_entry(buf, fp, sizeof(u32) * items); + if (rc < 0) { + printk(KERN_ERR "security: mls: truncated range\n"); + goto out; + } + r->level[0].sens = le32_to_cpu(buf[0]); + if (items > 1) + r->level[1].sens = le32_to_cpu(buf[1]); + else + r->level[1].sens = r->level[0].sens; + + rc = ebitmap_read(&r->level[0].cat, fp); + if (rc) { + printk(KERN_ERR "security: mls: error reading low " + "categories\n"); + goto out; + } + if (items > 1) { + rc = ebitmap_read(&r->level[1].cat, fp); + if (rc) { + printk(KERN_ERR "security: mls: error reading high " + "categories\n"); + goto bad_high; + } + } else { + rc = ebitmap_cpy(&r->level[1].cat, &r->level[0].cat); + if (rc) { + printk(KERN_ERR "security: mls: out of memory\n"); + goto bad_high; + } + } + + rc = 0; +out: + return rc; +bad_high: + ebitmap_destroy(&r->level[0].cat); + goto out; +} + +/* + * Read and validate a security context structure + * from a policydb binary representation file. + */ +static int context_read_and_validate(struct context *c, + struct policydb *p, + void *fp) +{ + u32 buf[3]; + int rc; + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) { + printk(KERN_ERR "security: context truncated\n"); + goto out; + } + c->user = le32_to_cpu(buf[0]); + c->role = le32_to_cpu(buf[1]); + c->type = le32_to_cpu(buf[2]); + if (p->policyvers >= POLICYDB_VERSION_MLS) { + if (mls_read_range_helper(&c->range, fp)) { + printk(KERN_ERR "security: error reading MLS range of " + "context\n"); + rc = -EINVAL; + goto out; + } + } + + if (!policydb_context_isvalid(p, c)) { + printk(KERN_ERR "security: invalid security context\n"); + context_destroy(c); + rc = -EINVAL; + } +out: + return rc; +} + +/* + * The following *_read functions are used to + * read the symbol data from a policy database + * binary representation file. + */ + +static int perm_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct perm_datum *perdatum; + int rc; + u32 buf[2], len; + + perdatum = kmalloc(sizeof(*perdatum), GFP_KERNEL); + if (!perdatum) { + rc = -ENOMEM; + goto out; + } + memset(perdatum, 0, sizeof(*perdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + perdatum->value = le32_to_cpu(buf[1]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + rc = hashtab_insert(h, key, perdatum); + if (rc) + goto bad; +out: + return rc; +bad: + perm_destroy(key, perdatum, NULL); + goto out; +} + +static int common_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct common_datum *comdatum; + u32 buf[4], len, nel; + int i, rc; + + comdatum = kmalloc(sizeof(*comdatum), GFP_KERNEL); + if (!comdatum) { + rc = -ENOMEM; + goto out; + } + memset(comdatum, 0, sizeof(*comdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + comdatum->value = le32_to_cpu(buf[1]); + + rc = symtab_init(&comdatum->permissions, PERM_SYMTAB_SIZE); + if (rc) + goto bad; + comdatum->permissions.nprim = le32_to_cpu(buf[2]); + nel = le32_to_cpu(buf[3]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + for (i = 0; i < nel; i++) { + rc = perm_read(p, comdatum->permissions.table, fp); + if (rc) + goto bad; + } + + rc = hashtab_insert(h, key, comdatum); + if (rc) + goto bad; +out: + return rc; +bad: + common_destroy(key, comdatum, NULL); + goto out; +} + +static int read_cons_helper(struct constraint_node **nodep, int ncons, + int allowxtarget, void *fp) +{ + struct constraint_node *c, *lc; + struct constraint_expr *e, *le; + u32 buf[3], nexpr; + int rc, i, j, depth; + + lc = NULL; + for (i = 0; i < ncons; i++) { + c = kmalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + memset(c, 0, sizeof(*c)); + + if (lc) { + lc->next = c; + } else { + *nodep = c; + } + + rc = next_entry(buf, fp, (sizeof(u32) * 2)); + if (rc < 0) + return rc; + c->permissions = le32_to_cpu(buf[0]); + nexpr = le32_to_cpu(buf[1]); + le = NULL; + depth = -1; + for (j = 0; j < nexpr; j++) { + e = kmalloc(sizeof(*e), GFP_KERNEL); + if (!e) + return -ENOMEM; + memset(e, 0, sizeof(*e)); + + if (le) { + le->next = e; + } else { + c->expr = e; + } + + rc = next_entry(buf, fp, (sizeof(u32) * 3)); + if (rc < 0) + return rc; + e->expr_type = le32_to_cpu(buf[0]); + e->attr = le32_to_cpu(buf[1]); + e->op = le32_to_cpu(buf[2]); + + switch (e->expr_type) { + case CEXPR_NOT: + if (depth < 0) + return -EINVAL; + break; + case CEXPR_AND: + case CEXPR_OR: + if (depth < 1) + return -EINVAL; + depth--; + break; + case CEXPR_ATTR: + if (depth == (CEXPR_MAXDEPTH - 1)) + return -EINVAL; + depth++; + break; + case CEXPR_NAMES: + if (!allowxtarget && (e->attr & CEXPR_XTARGET)) + return -EINVAL; + if (depth == (CEXPR_MAXDEPTH - 1)) + return -EINVAL; + depth++; + if (ebitmap_read(&e->names, fp)) + return -EINVAL; + break; + default: + return -EINVAL; + } + le = e; + } + if (depth != 0) + return -EINVAL; + lc = c; + } + + return 0; +} + +static int class_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct class_datum *cladatum; + u32 buf[6], len, len2, ncons, nel; + int i, rc; + + cladatum = kmalloc(sizeof(*cladatum), GFP_KERNEL); + if (!cladatum) { + rc = -ENOMEM; + goto out; + } + memset(cladatum, 0, sizeof(*cladatum)); + + rc = next_entry(buf, fp, sizeof(u32)*6); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + len2 = le32_to_cpu(buf[1]); + cladatum->value = le32_to_cpu(buf[2]); + + rc = symtab_init(&cladatum->permissions, PERM_SYMTAB_SIZE); + if (rc) + goto bad; + cladatum->permissions.nprim = le32_to_cpu(buf[3]); + nel = le32_to_cpu(buf[4]); + + ncons = le32_to_cpu(buf[5]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + if (len2) { + cladatum->comkey = kmalloc(len2 + 1,GFP_KERNEL); + if (!cladatum->comkey) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(cladatum->comkey, fp, len2); + if (rc < 0) + goto bad; + cladatum->comkey[len2] = 0; + + cladatum->comdatum = hashtab_search(p->p_commons.table, + cladatum->comkey); + if (!cladatum->comdatum) { + printk(KERN_ERR "security: unknown common %s\n", + cladatum->comkey); + rc = -EINVAL; + goto bad; + } + } + for (i = 0; i < nel; i++) { + rc = perm_read(p, cladatum->permissions.table, fp); + if (rc) + goto bad; + } + + rc = read_cons_helper(&cladatum->constraints, ncons, 0, fp); + if (rc) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_VALIDATETRANS) { + /* grab the validatetrans rules */ + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + ncons = le32_to_cpu(buf[0]); + rc = read_cons_helper(&cladatum->validatetrans, ncons, 1, fp); + if (rc) + goto bad; + } + + rc = hashtab_insert(h, key, cladatum); + if (rc) + goto bad; + + rc = 0; +out: + return rc; +bad: + class_destroy(key, cladatum, NULL); + goto out; +} + +static int role_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct role_datum *role; + int rc; + u32 buf[2], len; + + role = kmalloc(sizeof(*role), GFP_KERNEL); + if (!role) { + rc = -ENOMEM; + goto out; + } + memset(role, 0, sizeof(*role)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + role->value = le32_to_cpu(buf[1]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + rc = ebitmap_read(&role->dominates, fp); + if (rc) + goto bad; + + rc = ebitmap_read(&role->types, fp); + if (rc) + goto bad; + + if (strcmp(key, OBJECT_R) == 0) { + if (role->value != OBJECT_R_VAL) { + printk(KERN_ERR "Role %s has wrong value %d\n", + OBJECT_R, role->value); + rc = -EINVAL; + goto bad; + } + rc = 0; + goto bad; + } + + rc = hashtab_insert(h, key, role); + if (rc) + goto bad; +out: + return rc; +bad: + role_destroy(key, role, NULL); + goto out; +} + +static int type_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct type_datum *typdatum; + int rc; + u32 buf[3], len; + + typdatum = kmalloc(sizeof(*typdatum),GFP_KERNEL); + if (!typdatum) { + rc = -ENOMEM; + return rc; + } + memset(typdatum, 0, sizeof(*typdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + typdatum->value = le32_to_cpu(buf[1]); + typdatum->primary = le32_to_cpu(buf[2]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + rc = hashtab_insert(h, key, typdatum); + if (rc) + goto bad; +out: + return rc; +bad: + type_destroy(key, typdatum, NULL); + goto out; +} + + +/* + * Read a MLS level structure from a policydb binary + * representation file. + */ +static int mls_read_level(struct mls_level *lp, void *fp) +{ + u32 buf[1]; + int rc; + + memset(lp, 0, sizeof(*lp)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) { + printk(KERN_ERR "security: mls: truncated level\n"); + goto bad; + } + lp->sens = le32_to_cpu(buf[0]); + + if (ebitmap_read(&lp->cat, fp)) { + printk(KERN_ERR "security: mls: error reading level " + "categories\n"); + goto bad; + } + return 0; + +bad: + return -EINVAL; +} + +static int user_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct user_datum *usrdatum; + int rc; + u32 buf[2], len; + + usrdatum = kmalloc(sizeof(*usrdatum), GFP_KERNEL); + if (!usrdatum) { + rc = -ENOMEM; + goto out; + } + memset(usrdatum, 0, sizeof(*usrdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + usrdatum->value = le32_to_cpu(buf[1]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + rc = ebitmap_read(&usrdatum->roles, fp); + if (rc) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_MLS) { + rc = mls_read_range_helper(&usrdatum->range, fp); + if (rc) + goto bad; + rc = mls_read_level(&usrdatum->dfltlevel, fp); + if (rc) + goto bad; + } + + rc = hashtab_insert(h, key, usrdatum); + if (rc) + goto bad; +out: + return rc; +bad: + user_destroy(key, usrdatum, NULL); + goto out; +} + +static int sens_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct level_datum *levdatum; + int rc; + u32 buf[2], len; + + levdatum = kmalloc(sizeof(*levdatum), GFP_ATOMIC); + if (!levdatum) { + rc = -ENOMEM; + goto out; + } + memset(levdatum, 0, sizeof(*levdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + levdatum->isalias = le32_to_cpu(buf[1]); + + key = kmalloc(len + 1,GFP_ATOMIC); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + levdatum->level = kmalloc(sizeof(struct mls_level), GFP_ATOMIC); + if (!levdatum->level) { + rc = -ENOMEM; + goto bad; + } + if (mls_read_level(levdatum->level, fp)) { + rc = -EINVAL; + goto bad; + } + + rc = hashtab_insert(h, key, levdatum); + if (rc) + goto bad; +out: + return rc; +bad: + sens_destroy(key, levdatum, NULL); + goto out; +} + +static int cat_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct cat_datum *catdatum; + int rc; + u32 buf[3], len; + + catdatum = kmalloc(sizeof(*catdatum), GFP_ATOMIC); + if (!catdatum) { + rc = -ENOMEM; + goto out; + } + memset(catdatum, 0, sizeof(*catdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + catdatum->value = le32_to_cpu(buf[1]); + catdatum->isalias = le32_to_cpu(buf[2]); + + key = kmalloc(len + 1,GFP_ATOMIC); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + rc = hashtab_insert(h, key, catdatum); + if (rc) + goto bad; +out: + return rc; + +bad: + cat_destroy(key, catdatum, NULL); + goto out; +} + +static int (*read_f[SYM_NUM]) (struct policydb *p, struct hashtab *h, void *fp) = +{ + common_read, + class_read, + role_read, + type_read, + user_read, + cond_read_bool, + sens_read, + cat_read, +}; + +extern int ss_initialized; + +/* + * Read the configuration data from a policy database binary + * representation file into a policy database structure. + */ +int policydb_read(struct policydb *p, void *fp) +{ + struct role_allow *ra, *lra; + struct role_trans *tr, *ltr; + struct ocontext *l, *c, *newc; + struct genfs *genfs_p, *genfs, *newgenfs; + int i, j, rc; + u32 buf[8], len, len2, config, nprim, nel, nel2; + char *policydb_str; + struct policydb_compat_info *info; + struct range_trans *rt, *lrt; + + config = 0; + + rc = policydb_init(p); + if (rc) + goto out; + + /* Read the magic number and string length. */ + rc = next_entry(buf, fp, sizeof(u32)* 2); + if (rc < 0) + goto bad; + + for (i = 0; i < 2; i++) + buf[i] = le32_to_cpu(buf[i]); + + if (buf[0] != POLICYDB_MAGIC) { + printk(KERN_ERR "security: policydb magic number 0x%x does " + "not match expected magic number 0x%x\n", + buf[0], POLICYDB_MAGIC); + goto bad; + } + + len = buf[1]; + if (len != strlen(POLICYDB_STRING)) { + printk(KERN_ERR "security: policydb string length %d does not " + "match expected length %Zu\n", + len, strlen(POLICYDB_STRING)); + goto bad; + } + policydb_str = kmalloc(len + 1,GFP_KERNEL); + if (!policydb_str) { + printk(KERN_ERR "security: unable to allocate memory for policydb " + "string of length %d\n", len); + rc = -ENOMEM; + goto bad; + } + rc = next_entry(policydb_str, fp, len); + if (rc < 0) { + printk(KERN_ERR "security: truncated policydb string identifier\n"); + kfree(policydb_str); + goto bad; + } + policydb_str[len] = 0; + if (strcmp(policydb_str, POLICYDB_STRING)) { + printk(KERN_ERR "security: policydb string %s does not match " + "my string %s\n", policydb_str, POLICYDB_STRING); + kfree(policydb_str); + goto bad; + } + /* Done with policydb_str. */ + kfree(policydb_str); + policydb_str = NULL; + + /* Read the version, config, and table sizes. */ + rc = next_entry(buf, fp, sizeof(u32)*4); + if (rc < 0) + goto bad; + for (i = 0; i < 4; i++) + buf[i] = le32_to_cpu(buf[i]); + + p->policyvers = buf[0]; + if (p->policyvers < POLICYDB_VERSION_MIN || + p->policyvers > POLICYDB_VERSION_MAX) { + printk(KERN_ERR "security: policydb version %d does not match " + "my version range %d-%d\n", + buf[0], POLICYDB_VERSION_MIN, POLICYDB_VERSION_MAX); + goto bad; + } + + if ((buf[1] & POLICYDB_CONFIG_MLS)) { + if (ss_initialized && !selinux_mls_enabled) { + printk(KERN_ERR "Cannot switch between non-MLS and MLS " + "policies\n"); + goto bad; + } + selinux_mls_enabled = 1; + config |= POLICYDB_CONFIG_MLS; + + if (p->policyvers < POLICYDB_VERSION_MLS) { + printk(KERN_ERR "security policydb version %d (MLS) " + "not backwards compatible\n", p->policyvers); + goto bad; + } + } else { + if (ss_initialized && selinux_mls_enabled) { + printk(KERN_ERR "Cannot switch between MLS and non-MLS " + "policies\n"); + goto bad; + } + } + + info = policydb_lookup_compat(p->policyvers); + if (!info) { + printk(KERN_ERR "security: unable to find policy compat info " + "for version %d\n", p->policyvers); + goto bad; + } + + if (buf[2] != info->sym_num || buf[3] != info->ocon_num) { + printk(KERN_ERR "security: policydb table sizes (%d,%d) do " + "not match mine (%d,%d)\n", buf[2], buf[3], + info->sym_num, info->ocon_num); + goto bad; + } + + for (i = 0; i < info->sym_num; i++) { + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc < 0) + goto bad; + nprim = le32_to_cpu(buf[0]); + nel = le32_to_cpu(buf[1]); + for (j = 0; j < nel; j++) { + rc = read_f[i](p, p->symtab[i].table, fp); + if (rc) + goto bad; + } + + p->symtab[i].nprim = nprim; + } + + rc = avtab_read(&p->te_avtab, fp, config); + if (rc) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_BOOL) { + rc = cond_read_list(p, fp); + if (rc) + goto bad; + } + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel = le32_to_cpu(buf[0]); + ltr = NULL; + for (i = 0; i < nel; i++) { + tr = kmalloc(sizeof(*tr), GFP_KERNEL); + if (!tr) { + rc = -ENOMEM; + goto bad; + } + memset(tr, 0, sizeof(*tr)); + if (ltr) { + ltr->next = tr; + } else { + p->role_tr = tr; + } + rc = next_entry(buf, fp, sizeof(u32)*3); + if (rc < 0) + goto bad; + tr->role = le32_to_cpu(buf[0]); + tr->type = le32_to_cpu(buf[1]); + tr->new_role = le32_to_cpu(buf[2]); + ltr = tr; + } + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel = le32_to_cpu(buf[0]); + lra = NULL; + for (i = 0; i < nel; i++) { + ra = kmalloc(sizeof(*ra), GFP_KERNEL); + if (!ra) { + rc = -ENOMEM; + goto bad; + } + memset(ra, 0, sizeof(*ra)); + if (lra) { + lra->next = ra; + } else { + p->role_allow = ra; + } + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc < 0) + goto bad; + ra->role = le32_to_cpu(buf[0]); + ra->new_role = le32_to_cpu(buf[1]); + lra = ra; + } + + rc = policydb_index_classes(p); + if (rc) + goto bad; + + rc = policydb_index_others(p); + if (rc) + goto bad; + + for (i = 0; i < info->ocon_num; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel = le32_to_cpu(buf[0]); + l = NULL; + for (j = 0; j < nel; j++) { + c = kmalloc(sizeof(*c), GFP_KERNEL); + if (!c) { + rc = -ENOMEM; + goto bad; + } + memset(c, 0, sizeof(*c)); + if (l) { + l->next = c; + } else { + p->ocontexts[i] = c; + } + l = c; + rc = -EINVAL; + switch (i) { + case OCON_ISID: + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + c->sid[0] = le32_to_cpu(buf[0]); + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto bad; + break; + case OCON_FS: + case OCON_NETIF: + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + len = le32_to_cpu(buf[0]); + c->u.name = kmalloc(len + 1,GFP_KERNEL); + if (!c->u.name) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(c->u.name, fp, len); + if (rc < 0) + goto bad; + c->u.name[len] = 0; + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto bad; + rc = context_read_and_validate(&c->context[1], p, fp); + if (rc) + goto bad; + break; + case OCON_PORT: + rc = next_entry(buf, fp, sizeof(u32)*3); + if (rc < 0) + goto bad; + c->u.port.protocol = le32_to_cpu(buf[0]); + c->u.port.low_port = le32_to_cpu(buf[1]); + c->u.port.high_port = le32_to_cpu(buf[2]); + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto bad; + break; + case OCON_NODE: + rc = next_entry(buf, fp, sizeof(u32)* 2); + if (rc < 0) + goto bad; + c->u.node.addr = le32_to_cpu(buf[0]); + c->u.node.mask = le32_to_cpu(buf[1]); + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto bad; + break; + case OCON_FSUSE: + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc < 0) + goto bad; + c->v.behavior = le32_to_cpu(buf[0]); + if (c->v.behavior > SECURITY_FS_USE_NONE) + goto bad; + len = le32_to_cpu(buf[1]); + c->u.name = kmalloc(len + 1,GFP_KERNEL); + if (!c->u.name) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(c->u.name, fp, len); + if (rc < 0) + goto bad; + c->u.name[len] = 0; + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto bad; + break; + case OCON_NODE6: { + int k; + + rc = next_entry(buf, fp, sizeof(u32) * 8); + if (rc < 0) + goto bad; + for (k = 0; k < 4; k++) + c->u.node6.addr[k] = le32_to_cpu(buf[k]); + for (k = 0; k < 4; k++) + c->u.node6.mask[k] = le32_to_cpu(buf[k+4]); + if (context_read_and_validate(&c->context[0], p, fp)) + goto bad; + break; + } + } + } + } + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel = le32_to_cpu(buf[0]); + genfs_p = NULL; + rc = -EINVAL; + for (i = 0; i < nel; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + len = le32_to_cpu(buf[0]); + newgenfs = kmalloc(sizeof(*newgenfs), GFP_KERNEL); + if (!newgenfs) { + rc = -ENOMEM; + goto bad; + } + memset(newgenfs, 0, sizeof(*newgenfs)); + + newgenfs->fstype = kmalloc(len + 1,GFP_KERNEL); + if (!newgenfs->fstype) { + rc = -ENOMEM; + kfree(newgenfs); + goto bad; + } + rc = next_entry(newgenfs->fstype, fp, len); + if (rc < 0) { + kfree(newgenfs->fstype); + kfree(newgenfs); + goto bad; + } + newgenfs->fstype[len] = 0; + for (genfs_p = NULL, genfs = p->genfs; genfs; + genfs_p = genfs, genfs = genfs->next) { + if (strcmp(newgenfs->fstype, genfs->fstype) == 0) { + printk(KERN_ERR "security: dup genfs " + "fstype %s\n", newgenfs->fstype); + kfree(newgenfs->fstype); + kfree(newgenfs); + goto bad; + } + if (strcmp(newgenfs->fstype, genfs->fstype) < 0) + break; + } + newgenfs->next = genfs; + if (genfs_p) + genfs_p->next = newgenfs; + else + p->genfs = newgenfs; + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel2 = le32_to_cpu(buf[0]); + for (j = 0; j < nel2; j++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + len = le32_to_cpu(buf[0]); + + newc = kmalloc(sizeof(*newc), GFP_KERNEL); + if (!newc) { + rc = -ENOMEM; + goto bad; + } + memset(newc, 0, sizeof(*newc)); + + newc->u.name = kmalloc(len + 1,GFP_KERNEL); + if (!newc->u.name) { + rc = -ENOMEM; + goto bad_newc; + } + rc = next_entry(newc->u.name, fp, len); + if (rc < 0) + goto bad_newc; + newc->u.name[len] = 0; + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad_newc; + newc->v.sclass = le32_to_cpu(buf[0]); + if (context_read_and_validate(&newc->context[0], p, fp)) + goto bad_newc; + for (l = NULL, c = newgenfs->head; c; + l = c, c = c->next) { + if (!strcmp(newc->u.name, c->u.name) && + (!c->v.sclass || !newc->v.sclass || + newc->v.sclass == c->v.sclass)) { + printk(KERN_ERR "security: dup genfs " + "entry (%s,%s)\n", + newgenfs->fstype, c->u.name); + goto bad_newc; + } + len = strlen(newc->u.name); + len2 = strlen(c->u.name); + if (len > len2) + break; + } + + newc->next = c; + if (l) + l->next = newc; + else + newgenfs->head = newc; + } + } + + if (p->policyvers >= POLICYDB_VERSION_MLS) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel = le32_to_cpu(buf[0]); + lrt = NULL; + for (i = 0; i < nel; i++) { + rt = kmalloc(sizeof(*rt), GFP_KERNEL); + if (!rt) { + rc = -ENOMEM; + goto bad; + } + memset(rt, 0, sizeof(*rt)); + if (lrt) + lrt->next = rt; + else + p->range_tr = rt; + rc = next_entry(buf, fp, (sizeof(u32) * 2)); + if (rc < 0) + goto bad; + rt->dom = le32_to_cpu(buf[0]); + rt->type = le32_to_cpu(buf[1]); + rc = mls_read_range_helper(&rt->range, fp); + if (rc) + goto bad; + lrt = rt; + } + } + + rc = 0; +out: + return rc; +bad_newc: + ocontext_destroy(newc,OCON_FSUSE); +bad: + if (!rc) + rc = -EINVAL; + policydb_destroy(p); + goto out; +} diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h new file mode 100644 index 000000000000..2470e2a1a1c3 --- /dev/null +++ b/security/selinux/ss/policydb.h @@ -0,0 +1,275 @@ +/* + * A policy database (policydb) specifies the + * configuration data for the security policy. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#ifndef _SS_POLICYDB_H_ +#define _SS_POLICYDB_H_ + +#include "symtab.h" +#include "avtab.h" +#include "sidtab.h" +#include "context.h" +#include "constraint.h" + +/* + * A datum type is defined for each kind of symbol + * in the configuration data: individual permissions, + * common prefixes for access vectors, classes, + * users, roles, types, sensitivities, categories, etc. + */ + +/* Permission attributes */ +struct perm_datum { + u32 value; /* permission bit + 1 */ +}; + +/* Attributes of a common prefix for access vectors */ +struct common_datum { + u32 value; /* internal common value */ + struct symtab permissions; /* common permissions */ +}; + +/* Class attributes */ +struct class_datum { + u32 value; /* class value */ + char *comkey; /* common name */ + struct common_datum *comdatum; /* common datum */ + struct symtab permissions; /* class-specific permission symbol table */ + struct constraint_node *constraints; /* constraints on class permissions */ + struct constraint_node *validatetrans; /* special transition rules */ +}; + +/* Role attributes */ +struct role_datum { + u32 value; /* internal role value */ + struct ebitmap dominates; /* set of roles dominated by this role */ + struct ebitmap types; /* set of authorized types for role */ +}; + +struct role_trans { + u32 role; /* current role */ + u32 type; /* program executable type */ + u32 new_role; /* new role */ + struct role_trans *next; +}; + +struct role_allow { + u32 role; /* current role */ + u32 new_role; /* new role */ + struct role_allow *next; +}; + +/* Type attributes */ +struct type_datum { + u32 value; /* internal type value */ + unsigned char primary; /* primary name? */ +}; + +/* User attributes */ +struct user_datum { + u32 value; /* internal user value */ + struct ebitmap roles; /* set of authorized roles for user */ + struct mls_range range; /* MLS range (min - max) for user */ + struct mls_level dfltlevel; /* default login MLS level for user */ +}; + + +/* Sensitivity attributes */ +struct level_datum { + struct mls_level *level; /* sensitivity and associated categories */ + unsigned char isalias; /* is this sensitivity an alias for another? */ +}; + +/* Category attributes */ +struct cat_datum { + u32 value; /* internal category bit + 1 */ + unsigned char isalias; /* is this category an alias for another? */ +}; + +struct range_trans { + u32 dom; /* current process domain */ + u32 type; /* program executable type */ + struct mls_range range; /* new range */ + struct range_trans *next; +}; + +/* Boolean data type */ +struct cond_bool_datum { + __u32 value; /* internal type value */ + int state; +}; + +struct cond_node; + +/* + * The configuration data includes security contexts for + * initial SIDs, unlabeled file systems, TCP and UDP port numbers, + * network interfaces, and nodes. This structure stores the + * relevant data for one such entry. Entries of the same kind + * (e.g. all initial SIDs) are linked together into a list. + */ +struct ocontext { + union { + char *name; /* name of initial SID, fs, netif, fstype, path */ + struct { + u8 protocol; + u16 low_port; + u16 high_port; + } port; /* TCP or UDP port information */ + struct { + u32 addr; + u32 mask; + } node; /* node information */ + struct { + u32 addr[4]; + u32 mask[4]; + } node6; /* IPv6 node information */ + } u; + union { + u32 sclass; /* security class for genfs */ + u32 behavior; /* labeling behavior for fs_use */ + } v; + struct context context[2]; /* security context(s) */ + u32 sid[2]; /* SID(s) */ + struct ocontext *next; +}; + +struct genfs { + char *fstype; + struct ocontext *head; + struct genfs *next; +}; + +/* symbol table array indices */ +#define SYM_COMMONS 0 +#define SYM_CLASSES 1 +#define SYM_ROLES 2 +#define SYM_TYPES 3 +#define SYM_USERS 4 +#define SYM_BOOLS 5 +#define SYM_LEVELS 6 +#define SYM_CATS 7 +#define SYM_NUM 8 + +/* object context array indices */ +#define OCON_ISID 0 /* initial SIDs */ +#define OCON_FS 1 /* unlabeled file systems */ +#define OCON_PORT 2 /* TCP and UDP port numbers */ +#define OCON_NETIF 3 /* network interfaces */ +#define OCON_NODE 4 /* nodes */ +#define OCON_FSUSE 5 /* fs_use */ +#define OCON_NODE6 6 /* IPv6 nodes */ +#define OCON_NUM 7 + +/* The policy database */ +struct policydb { + /* symbol tables */ + struct symtab symtab[SYM_NUM]; +#define p_commons symtab[SYM_COMMONS] +#define p_classes symtab[SYM_CLASSES] +#define p_roles symtab[SYM_ROLES] +#define p_types symtab[SYM_TYPES] +#define p_users symtab[SYM_USERS] +#define p_bools symtab[SYM_BOOLS] +#define p_levels symtab[SYM_LEVELS] +#define p_cats symtab[SYM_CATS] + + /* symbol names indexed by (value - 1) */ + char **sym_val_to_name[SYM_NUM]; +#define p_common_val_to_name sym_val_to_name[SYM_COMMONS] +#define p_class_val_to_name sym_val_to_name[SYM_CLASSES] +#define p_role_val_to_name sym_val_to_name[SYM_ROLES] +#define p_type_val_to_name sym_val_to_name[SYM_TYPES] +#define p_user_val_to_name sym_val_to_name[SYM_USERS] +#define p_bool_val_to_name sym_val_to_name[SYM_BOOLS] +#define p_sens_val_to_name sym_val_to_name[SYM_LEVELS] +#define p_cat_val_to_name sym_val_to_name[SYM_CATS] + + /* class, role, and user attributes indexed by (value - 1) */ + struct class_datum **class_val_to_struct; + struct role_datum **role_val_to_struct; + struct user_datum **user_val_to_struct; + + /* type enforcement access vectors and transitions */ + struct avtab te_avtab; + + /* role transitions */ + struct role_trans *role_tr; + + /* bools indexed by (value - 1) */ + struct cond_bool_datum **bool_val_to_struct; + /* type enforcement conditional access vectors and transitions */ + struct avtab te_cond_avtab; + /* linked list indexing te_cond_avtab by conditional */ + struct cond_node* cond_list; + + /* role allows */ + struct role_allow *role_allow; + + /* security contexts of initial SIDs, unlabeled file systems, + TCP or UDP port numbers, network interfaces and nodes */ + struct ocontext *ocontexts[OCON_NUM]; + + /* security contexts for files in filesystems that cannot support + a persistent label mapping or use another + fixed labeling behavior. */ + struct genfs *genfs; + + /* range transitions */ + struct range_trans *range_tr; + + unsigned int policyvers; +}; + +extern void policydb_destroy(struct policydb *p); +extern int policydb_load_isids(struct policydb *p, struct sidtab *s); +extern int policydb_context_isvalid(struct policydb *p, struct context *c); +extern int policydb_read(struct policydb *p, void *fp); + +#define PERM_SYMTAB_SIZE 32 + +#define POLICYDB_CONFIG_MLS 1 + +#define OBJECT_R "object_r" +#define OBJECT_R_VAL 1 + +#define POLICYDB_MAGIC SELINUX_MAGIC +#define POLICYDB_STRING "SE Linux" + +struct policy_file { + char *data; + size_t len; +}; + +static inline int next_entry(void *buf, struct policy_file *fp, size_t bytes) +{ + if (bytes > fp->len) + return -EINVAL; + + memcpy(buf, fp->data, bytes); + fp->data += bytes; + fp->len -= bytes; + return 0; +} + +#endif /* _SS_POLICYDB_H_ */ + diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c new file mode 100644 index 000000000000..5a820cf88c9c --- /dev/null +++ b/security/selinux/ss/services.c @@ -0,0 +1,1777 @@ +/* + * Implementation of the security services. + * + * Authors : Stephen Smalley, <sds@epoch.ncsc.mil> + * James Morris <jmorris@redhat.com> + * + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/sched.h> +#include <linux/audit.h> +#include <asm/semaphore.h> +#include "flask.h" +#include "avc.h" +#include "avc_ss.h" +#include "security.h" +#include "context.h" +#include "policydb.h" +#include "sidtab.h" +#include "services.h" +#include "conditional.h" +#include "mls.h" + +extern void selnl_notify_policyload(u32 seqno); +unsigned int policydb_loaded_version; + +static DEFINE_RWLOCK(policy_rwlock); +#define POLICY_RDLOCK read_lock(&policy_rwlock) +#define POLICY_WRLOCK write_lock_irq(&policy_rwlock) +#define POLICY_RDUNLOCK read_unlock(&policy_rwlock) +#define POLICY_WRUNLOCK write_unlock_irq(&policy_rwlock) + +static DECLARE_MUTEX(load_sem); +#define LOAD_LOCK down(&load_sem) +#define LOAD_UNLOCK up(&load_sem) + +static struct sidtab sidtab; +struct policydb policydb; +int ss_initialized = 0; + +/* + * The largest sequence number that has been used when + * providing an access decision to the access vector cache. + * The sequence number only changes when a policy change + * occurs. + */ +static u32 latest_granting = 0; + +/* Forward declaration. */ +static int context_struct_to_string(struct context *context, char **scontext, + u32 *scontext_len); + +/* + * Return the boolean value of a constraint expression + * when it is applied to the specified source and target + * security contexts. + * + * xcontext is a special beast... It is used by the validatetrans rules + * only. For these rules, scontext is the context before the transition, + * tcontext is the context after the transition, and xcontext is the context + * of the process performing the transition. All other callers of + * constraint_expr_eval should pass in NULL for xcontext. + */ +static int constraint_expr_eval(struct context *scontext, + struct context *tcontext, + struct context *xcontext, + struct constraint_expr *cexpr) +{ + u32 val1, val2; + struct context *c; + struct role_datum *r1, *r2; + struct mls_level *l1, *l2; + struct constraint_expr *e; + int s[CEXPR_MAXDEPTH]; + int sp = -1; + + for (e = cexpr; e; e = e->next) { + switch (e->expr_type) { + case CEXPR_NOT: + BUG_ON(sp < 0); + s[sp] = !s[sp]; + break; + case CEXPR_AND: + BUG_ON(sp < 1); + sp--; + s[sp] &= s[sp+1]; + break; + case CEXPR_OR: + BUG_ON(sp < 1); + sp--; + s[sp] |= s[sp+1]; + break; + case CEXPR_ATTR: + if (sp == (CEXPR_MAXDEPTH-1)) + return 0; + switch (e->attr) { + case CEXPR_USER: + val1 = scontext->user; + val2 = tcontext->user; + break; + case CEXPR_TYPE: + val1 = scontext->type; + val2 = tcontext->type; + break; + case CEXPR_ROLE: + val1 = scontext->role; + val2 = tcontext->role; + r1 = policydb.role_val_to_struct[val1 - 1]; + r2 = policydb.role_val_to_struct[val2 - 1]; + switch (e->op) { + case CEXPR_DOM: + s[++sp] = ebitmap_get_bit(&r1->dominates, + val2 - 1); + continue; + case CEXPR_DOMBY: + s[++sp] = ebitmap_get_bit(&r2->dominates, + val1 - 1); + continue; + case CEXPR_INCOMP: + s[++sp] = ( !ebitmap_get_bit(&r1->dominates, + val2 - 1) && + !ebitmap_get_bit(&r2->dominates, + val1 - 1) ); + continue; + default: + break; + } + break; + case CEXPR_L1L2: + l1 = &(scontext->range.level[0]); + l2 = &(tcontext->range.level[0]); + goto mls_ops; + case CEXPR_L1H2: + l1 = &(scontext->range.level[0]); + l2 = &(tcontext->range.level[1]); + goto mls_ops; + case CEXPR_H1L2: + l1 = &(scontext->range.level[1]); + l2 = &(tcontext->range.level[0]); + goto mls_ops; + case CEXPR_H1H2: + l1 = &(scontext->range.level[1]); + l2 = &(tcontext->range.level[1]); + goto mls_ops; + case CEXPR_L1H1: + l1 = &(scontext->range.level[0]); + l2 = &(scontext->range.level[1]); + goto mls_ops; + case CEXPR_L2H2: + l1 = &(tcontext->range.level[0]); + l2 = &(tcontext->range.level[1]); + goto mls_ops; +mls_ops: + switch (e->op) { + case CEXPR_EQ: + s[++sp] = mls_level_eq(l1, l2); + continue; + case CEXPR_NEQ: + s[++sp] = !mls_level_eq(l1, l2); + continue; + case CEXPR_DOM: + s[++sp] = mls_level_dom(l1, l2); + continue; + case CEXPR_DOMBY: + s[++sp] = mls_level_dom(l2, l1); + continue; + case CEXPR_INCOMP: + s[++sp] = mls_level_incomp(l2, l1); + continue; + default: + BUG(); + return 0; + } + break; + default: + BUG(); + return 0; + } + + switch (e->op) { + case CEXPR_EQ: + s[++sp] = (val1 == val2); + break; + case CEXPR_NEQ: + s[++sp] = (val1 != val2); + break; + default: + BUG(); + return 0; + } + break; + case CEXPR_NAMES: + if (sp == (CEXPR_MAXDEPTH-1)) + return 0; + c = scontext; + if (e->attr & CEXPR_TARGET) + c = tcontext; + else if (e->attr & CEXPR_XTARGET) { + c = xcontext; + if (!c) { + BUG(); + return 0; + } + } + if (e->attr & CEXPR_USER) + val1 = c->user; + else if (e->attr & CEXPR_ROLE) + val1 = c->role; + else if (e->attr & CEXPR_TYPE) + val1 = c->type; + else { + BUG(); + return 0; + } + + switch (e->op) { + case CEXPR_EQ: + s[++sp] = ebitmap_get_bit(&e->names, val1 - 1); + break; + case CEXPR_NEQ: + s[++sp] = !ebitmap_get_bit(&e->names, val1 - 1); + break; + default: + BUG(); + return 0; + } + break; + default: + BUG(); + return 0; + } + } + + BUG_ON(sp != 0); + return s[0]; +} + +/* + * Compute access vectors based on a context structure pair for + * the permissions in a particular class. + */ +static int context_struct_compute_av(struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 requested, + struct av_decision *avd) +{ + struct constraint_node *constraint; + struct role_allow *ra; + struct avtab_key avkey; + struct avtab_datum *avdatum; + struct class_datum *tclass_datum; + + /* + * Remap extended Netlink classes for old policy versions. + * Do this here rather than socket_type_to_security_class() + * in case a newer policy version is loaded, allowing sockets + * to remain in the correct class. + */ + if (policydb_loaded_version < POLICYDB_VERSION_NLCLASS) + if (tclass >= SECCLASS_NETLINK_ROUTE_SOCKET && + tclass <= SECCLASS_NETLINK_DNRT_SOCKET) + tclass = SECCLASS_NETLINK_SOCKET; + + if (!tclass || tclass > policydb.p_classes.nprim) { + printk(KERN_ERR "security_compute_av: unrecognized class %d\n", + tclass); + return -EINVAL; + } + tclass_datum = policydb.class_val_to_struct[tclass - 1]; + + /* + * Initialize the access vectors to the default values. + */ + avd->allowed = 0; + avd->decided = 0xffffffff; + avd->auditallow = 0; + avd->auditdeny = 0xffffffff; + avd->seqno = latest_granting; + + /* + * If a specific type enforcement rule was defined for + * this permission check, then use it. + */ + avkey.source_type = scontext->type; + avkey.target_type = tcontext->type; + avkey.target_class = tclass; + avdatum = avtab_search(&policydb.te_avtab, &avkey, AVTAB_AV); + if (avdatum) { + if (avdatum->specified & AVTAB_ALLOWED) + avd->allowed = avtab_allowed(avdatum); + if (avdatum->specified & AVTAB_AUDITDENY) + avd->auditdeny = avtab_auditdeny(avdatum); + if (avdatum->specified & AVTAB_AUDITALLOW) + avd->auditallow = avtab_auditallow(avdatum); + } + + /* Check conditional av table for additional permissions */ + cond_compute_av(&policydb.te_cond_avtab, &avkey, avd); + + /* + * Remove any permissions prohibited by a constraint (this includes + * the MLS policy). + */ + constraint = tclass_datum->constraints; + while (constraint) { + if ((constraint->permissions & (avd->allowed)) && + !constraint_expr_eval(scontext, tcontext, NULL, + constraint->expr)) { + avd->allowed = (avd->allowed) & ~(constraint->permissions); + } + constraint = constraint->next; + } + + /* + * If checking process transition permission and the + * role is changing, then check the (current_role, new_role) + * pair. + */ + if (tclass == SECCLASS_PROCESS && + (avd->allowed & (PROCESS__TRANSITION | PROCESS__DYNTRANSITION)) && + scontext->role != tcontext->role) { + for (ra = policydb.role_allow; ra; ra = ra->next) { + if (scontext->role == ra->role && + tcontext->role == ra->new_role) + break; + } + if (!ra) + avd->allowed = (avd->allowed) & ~(PROCESS__TRANSITION | + PROCESS__DYNTRANSITION); + } + + return 0; +} + +static int security_validtrans_handle_fail(struct context *ocontext, + struct context *ncontext, + struct context *tcontext, + u16 tclass) +{ + char *o = NULL, *n = NULL, *t = NULL; + u32 olen, nlen, tlen; + + if (context_struct_to_string(ocontext, &o, &olen) < 0) + goto out; + if (context_struct_to_string(ncontext, &n, &nlen) < 0) + goto out; + if (context_struct_to_string(tcontext, &t, &tlen) < 0) + goto out; + audit_log(current->audit_context, + "security_validate_transition: denied for" + " oldcontext=%s newcontext=%s taskcontext=%s tclass=%s", + o, n, t, policydb.p_class_val_to_name[tclass-1]); +out: + kfree(o); + kfree(n); + kfree(t); + + if (!selinux_enforcing) + return 0; + return -EPERM; +} + +int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, + u16 tclass) +{ + struct context *ocontext; + struct context *ncontext; + struct context *tcontext; + struct class_datum *tclass_datum; + struct constraint_node *constraint; + int rc = 0; + + if (!ss_initialized) + return 0; + + POLICY_RDLOCK; + + /* + * Remap extended Netlink classes for old policy versions. + * Do this here rather than socket_type_to_security_class() + * in case a newer policy version is loaded, allowing sockets + * to remain in the correct class. + */ + if (policydb_loaded_version < POLICYDB_VERSION_NLCLASS) + if (tclass >= SECCLASS_NETLINK_ROUTE_SOCKET && + tclass <= SECCLASS_NETLINK_DNRT_SOCKET) + tclass = SECCLASS_NETLINK_SOCKET; + + if (!tclass || tclass > policydb.p_classes.nprim) { + printk(KERN_ERR "security_validate_transition: " + "unrecognized class %d\n", tclass); + rc = -EINVAL; + goto out; + } + tclass_datum = policydb.class_val_to_struct[tclass - 1]; + + ocontext = sidtab_search(&sidtab, oldsid); + if (!ocontext) { + printk(KERN_ERR "security_validate_transition: " + " unrecognized SID %d\n", oldsid); + rc = -EINVAL; + goto out; + } + + ncontext = sidtab_search(&sidtab, newsid); + if (!ncontext) { + printk(KERN_ERR "security_validate_transition: " + " unrecognized SID %d\n", newsid); + rc = -EINVAL; + goto out; + } + + tcontext = sidtab_search(&sidtab, tasksid); + if (!tcontext) { + printk(KERN_ERR "security_validate_transition: " + " unrecognized SID %d\n", tasksid); + rc = -EINVAL; + goto out; + } + + constraint = tclass_datum->validatetrans; + while (constraint) { + if (!constraint_expr_eval(ocontext, ncontext, tcontext, + constraint->expr)) { + rc = security_validtrans_handle_fail(ocontext, ncontext, + tcontext, tclass); + goto out; + } + constraint = constraint->next; + } + +out: + POLICY_RDUNLOCK; + return rc; +} + +/** + * security_compute_av - Compute access vector decisions. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions + * @avd: access vector decisions + * + * Compute a set of access vector decisions based on the + * SID pair (@ssid, @tsid) for the permissions in @tclass. + * Return -%EINVAL if any of the parameters are invalid or %0 + * if the access vector decisions were computed successfully. + */ +int security_compute_av(u32 ssid, + u32 tsid, + u16 tclass, + u32 requested, + struct av_decision *avd) +{ + struct context *scontext = NULL, *tcontext = NULL; + int rc = 0; + + if (!ss_initialized) { + avd->allowed = requested; + avd->decided = requested; + avd->auditallow = 0; + avd->auditdeny = 0xffffffff; + avd->seqno = latest_granting; + return 0; + } + + POLICY_RDLOCK; + + scontext = sidtab_search(&sidtab, ssid); + if (!scontext) { + printk(KERN_ERR "security_compute_av: unrecognized SID %d\n", + ssid); + rc = -EINVAL; + goto out; + } + tcontext = sidtab_search(&sidtab, tsid); + if (!tcontext) { + printk(KERN_ERR "security_compute_av: unrecognized SID %d\n", + tsid); + rc = -EINVAL; + goto out; + } + + rc = context_struct_compute_av(scontext, tcontext, tclass, + requested, avd); +out: + POLICY_RDUNLOCK; + return rc; +} + +/* + * Write the security context string representation of + * the context structure `context' into a dynamically + * allocated string of the correct size. Set `*scontext' + * to point to this string and set `*scontext_len' to + * the length of the string. + */ +static int context_struct_to_string(struct context *context, char **scontext, u32 *scontext_len) +{ + char *scontextp; + + *scontext = NULL; + *scontext_len = 0; + + /* Compute the size of the context. */ + *scontext_len += strlen(policydb.p_user_val_to_name[context->user - 1]) + 1; + *scontext_len += strlen(policydb.p_role_val_to_name[context->role - 1]) + 1; + *scontext_len += strlen(policydb.p_type_val_to_name[context->type - 1]) + 1; + *scontext_len += mls_compute_context_len(context); + + /* Allocate space for the context; caller must free this space. */ + scontextp = kmalloc(*scontext_len, GFP_ATOMIC); + if (!scontextp) { + return -ENOMEM; + } + *scontext = scontextp; + + /* + * Copy the user name, role name and type name into the context. + */ + sprintf(scontextp, "%s:%s:%s", + policydb.p_user_val_to_name[context->user - 1], + policydb.p_role_val_to_name[context->role - 1], + policydb.p_type_val_to_name[context->type - 1]); + scontextp += strlen(policydb.p_user_val_to_name[context->user - 1]) + + 1 + strlen(policydb.p_role_val_to_name[context->role - 1]) + + 1 + strlen(policydb.p_type_val_to_name[context->type - 1]); + + mls_sid_to_context(context, &scontextp); + + *scontextp = 0; + + return 0; +} + +#include "initial_sid_to_string.h" + +/** + * security_sid_to_context - Obtain a context for a given SID. + * @sid: security identifier, SID + * @scontext: security context + * @scontext_len: length in bytes + * + * Write the string representation of the context associated with @sid + * into a dynamically allocated string of the correct size. Set @scontext + * to point to this string and set @scontext_len to the length of the string. + */ +int security_sid_to_context(u32 sid, char **scontext, u32 *scontext_len) +{ + struct context *context; + int rc = 0; + + if (!ss_initialized) { + if (sid <= SECINITSID_NUM) { + char *scontextp; + + *scontext_len = strlen(initial_sid_to_string[sid]) + 1; + scontextp = kmalloc(*scontext_len,GFP_ATOMIC); + strcpy(scontextp, initial_sid_to_string[sid]); + *scontext = scontextp; + goto out; + } + printk(KERN_ERR "security_sid_to_context: called before initial " + "load_policy on unknown SID %d\n", sid); + rc = -EINVAL; + goto out; + } + POLICY_RDLOCK; + context = sidtab_search(&sidtab, sid); + if (!context) { + printk(KERN_ERR "security_sid_to_context: unrecognized SID " + "%d\n", sid); + rc = -EINVAL; + goto out_unlock; + } + rc = context_struct_to_string(context, scontext, scontext_len); +out_unlock: + POLICY_RDUNLOCK; +out: + return rc; + +} + +/** + * security_context_to_sid - Obtain a SID for a given security context. + * @scontext: security context + * @scontext_len: length in bytes + * @sid: security identifier, SID + * + * Obtains a SID associated with the security context that + * has the string representation specified by @scontext. + * Returns -%EINVAL if the context is invalid, -%ENOMEM if insufficient + * memory is available, or 0 on success. + */ +int security_context_to_sid(char *scontext, u32 scontext_len, u32 *sid) +{ + char *scontext2; + struct context context; + struct role_datum *role; + struct type_datum *typdatum; + struct user_datum *usrdatum; + char *scontextp, *p, oldc; + int rc = 0; + + if (!ss_initialized) { + int i; + + for (i = 1; i < SECINITSID_NUM; i++) { + if (!strcmp(initial_sid_to_string[i], scontext)) { + *sid = i; + goto out; + } + } + *sid = SECINITSID_KERNEL; + goto out; + } + *sid = SECSID_NULL; + + /* Copy the string so that we can modify the copy as we parse it. + The string should already by null terminated, but we append a + null suffix to the copy to avoid problems with the existing + attr package, which doesn't view the null terminator as part + of the attribute value. */ + scontext2 = kmalloc(scontext_len+1,GFP_KERNEL); + if (!scontext2) { + rc = -ENOMEM; + goto out; + } + memcpy(scontext2, scontext, scontext_len); + scontext2[scontext_len] = 0; + + context_init(&context); + *sid = SECSID_NULL; + + POLICY_RDLOCK; + + /* Parse the security context. */ + + rc = -EINVAL; + scontextp = (char *) scontext2; + + /* Extract the user. */ + p = scontextp; + while (*p && *p != ':') + p++; + + if (*p == 0) + goto out_unlock; + + *p++ = 0; + + usrdatum = hashtab_search(policydb.p_users.table, scontextp); + if (!usrdatum) + goto out_unlock; + + context.user = usrdatum->value; + + /* Extract role. */ + scontextp = p; + while (*p && *p != ':') + p++; + + if (*p == 0) + goto out_unlock; + + *p++ = 0; + + role = hashtab_search(policydb.p_roles.table, scontextp); + if (!role) + goto out_unlock; + context.role = role->value; + + /* Extract type. */ + scontextp = p; + while (*p && *p != ':') + p++; + oldc = *p; + *p++ = 0; + + typdatum = hashtab_search(policydb.p_types.table, scontextp); + if (!typdatum) + goto out_unlock; + + context.type = typdatum->value; + + rc = mls_context_to_sid(oldc, &p, &context); + if (rc) + goto out_unlock; + + if ((p - scontext2) < scontext_len) { + rc = -EINVAL; + goto out_unlock; + } + + /* Check the validity of the new context. */ + if (!policydb_context_isvalid(&policydb, &context)) { + rc = -EINVAL; + goto out_unlock; + } + /* Obtain the new sid. */ + rc = sidtab_context_to_sid(&sidtab, &context, sid); +out_unlock: + POLICY_RDUNLOCK; + context_destroy(&context); + kfree(scontext2); +out: + return rc; +} + +static int compute_sid_handle_invalid_context( + struct context *scontext, + struct context *tcontext, + u16 tclass, + struct context *newcontext) +{ + char *s = NULL, *t = NULL, *n = NULL; + u32 slen, tlen, nlen; + + if (context_struct_to_string(scontext, &s, &slen) < 0) + goto out; + if (context_struct_to_string(tcontext, &t, &tlen) < 0) + goto out; + if (context_struct_to_string(newcontext, &n, &nlen) < 0) + goto out; + audit_log(current->audit_context, + "security_compute_sid: invalid context %s" + " for scontext=%s" + " tcontext=%s" + " tclass=%s", + n, s, t, policydb.p_class_val_to_name[tclass-1]); +out: + kfree(s); + kfree(t); + kfree(n); + if (!selinux_enforcing) + return 0; + return -EACCES; +} + +static int security_compute_sid(u32 ssid, + u32 tsid, + u16 tclass, + u32 specified, + u32 *out_sid) +{ + struct context *scontext = NULL, *tcontext = NULL, newcontext; + struct role_trans *roletr = NULL; + struct avtab_key avkey; + struct avtab_datum *avdatum; + struct avtab_node *node; + unsigned int type_change = 0; + int rc = 0; + + if (!ss_initialized) { + switch (tclass) { + case SECCLASS_PROCESS: + *out_sid = ssid; + break; + default: + *out_sid = tsid; + break; + } + goto out; + } + + POLICY_RDLOCK; + + scontext = sidtab_search(&sidtab, ssid); + if (!scontext) { + printk(KERN_ERR "security_compute_sid: unrecognized SID %d\n", + ssid); + rc = -EINVAL; + goto out_unlock; + } + tcontext = sidtab_search(&sidtab, tsid); + if (!tcontext) { + printk(KERN_ERR "security_compute_sid: unrecognized SID %d\n", + tsid); + rc = -EINVAL; + goto out_unlock; + } + + context_init(&newcontext); + + /* Set the user identity. */ + switch (specified) { + case AVTAB_TRANSITION: + case AVTAB_CHANGE: + /* Use the process user identity. */ + newcontext.user = scontext->user; + break; + case AVTAB_MEMBER: + /* Use the related object owner. */ + newcontext.user = tcontext->user; + break; + } + + /* Set the role and type to default values. */ + switch (tclass) { + case SECCLASS_PROCESS: + /* Use the current role and type of process. */ + newcontext.role = scontext->role; + newcontext.type = scontext->type; + break; + default: + /* Use the well-defined object role. */ + newcontext.role = OBJECT_R_VAL; + /* Use the type of the related object. */ + newcontext.type = tcontext->type; + } + + /* Look for a type transition/member/change rule. */ + avkey.source_type = scontext->type; + avkey.target_type = tcontext->type; + avkey.target_class = tclass; + avdatum = avtab_search(&policydb.te_avtab, &avkey, AVTAB_TYPE); + + /* If no permanent rule, also check for enabled conditional rules */ + if(!avdatum) { + node = avtab_search_node(&policydb.te_cond_avtab, &avkey, specified); + for (; node != NULL; node = avtab_search_node_next(node, specified)) { + if (node->datum.specified & AVTAB_ENABLED) { + avdatum = &node->datum; + break; + } + } + } + + type_change = (avdatum && (avdatum->specified & specified)); + if (type_change) { + /* Use the type from the type transition/member/change rule. */ + switch (specified) { + case AVTAB_TRANSITION: + newcontext.type = avtab_transition(avdatum); + break; + case AVTAB_MEMBER: + newcontext.type = avtab_member(avdatum); + break; + case AVTAB_CHANGE: + newcontext.type = avtab_change(avdatum); + break; + } + } + + /* Check for class-specific changes. */ + switch (tclass) { + case SECCLASS_PROCESS: + if (specified & AVTAB_TRANSITION) { + /* Look for a role transition rule. */ + for (roletr = policydb.role_tr; roletr; + roletr = roletr->next) { + if (roletr->role == scontext->role && + roletr->type == tcontext->type) { + /* Use the role transition rule. */ + newcontext.role = roletr->new_role; + break; + } + } + } + break; + default: + break; + } + + /* Set the MLS attributes. + This is done last because it may allocate memory. */ + rc = mls_compute_sid(scontext, tcontext, tclass, specified, &newcontext); + if (rc) + goto out_unlock; + + /* Check the validity of the context. */ + if (!policydb_context_isvalid(&policydb, &newcontext)) { + rc = compute_sid_handle_invalid_context(scontext, + tcontext, + tclass, + &newcontext); + if (rc) + goto out_unlock; + } + /* Obtain the sid for the context. */ + rc = sidtab_context_to_sid(&sidtab, &newcontext, out_sid); +out_unlock: + POLICY_RDUNLOCK; + context_destroy(&newcontext); +out: + return rc; +} + +/** + * security_transition_sid - Compute the SID for a new subject/object. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @out_sid: security identifier for new subject/object + * + * Compute a SID to use for labeling a new subject or object in the + * class @tclass based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the new SID was + * computed successfully. + */ +int security_transition_sid(u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(ssid, tsid, tclass, AVTAB_TRANSITION, out_sid); +} + +/** + * security_member_sid - Compute the SID for member selection. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @out_sid: security identifier for selected member + * + * Compute a SID to use when selecting a member of a polyinstantiated + * object of class @tclass based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the SID was + * computed successfully. + */ +int security_member_sid(u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(ssid, tsid, tclass, AVTAB_MEMBER, out_sid); +} + +/** + * security_change_sid - Compute the SID for object relabeling. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @out_sid: security identifier for selected member + * + * Compute a SID to use for relabeling an object of class @tclass + * based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the SID was + * computed successfully. + */ +int security_change_sid(u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(ssid, tsid, tclass, AVTAB_CHANGE, out_sid); +} + +/* + * Verify that each permission that is defined under the + * existing policy is still defined with the same value + * in the new policy. + */ +static int validate_perm(void *key, void *datum, void *p) +{ + struct hashtab *h; + struct perm_datum *perdatum, *perdatum2; + int rc = 0; + + + h = p; + perdatum = datum; + + perdatum2 = hashtab_search(h, key); + if (!perdatum2) { + printk(KERN_ERR "security: permission %s disappeared", + (char *)key); + rc = -ENOENT; + goto out; + } + if (perdatum->value != perdatum2->value) { + printk(KERN_ERR "security: the value of permission %s changed", + (char *)key); + rc = -EINVAL; + } +out: + return rc; +} + +/* + * Verify that each class that is defined under the + * existing policy is still defined with the same + * attributes in the new policy. + */ +static int validate_class(void *key, void *datum, void *p) +{ + struct policydb *newp; + struct class_datum *cladatum, *cladatum2; + int rc; + + newp = p; + cladatum = datum; + + cladatum2 = hashtab_search(newp->p_classes.table, key); + if (!cladatum2) { + printk(KERN_ERR "security: class %s disappeared\n", + (char *)key); + rc = -ENOENT; + goto out; + } + if (cladatum->value != cladatum2->value) { + printk(KERN_ERR "security: the value of class %s changed\n", + (char *)key); + rc = -EINVAL; + goto out; + } + if ((cladatum->comdatum && !cladatum2->comdatum) || + (!cladatum->comdatum && cladatum2->comdatum)) { + printk(KERN_ERR "security: the inherits clause for the access " + "vector definition for class %s changed\n", (char *)key); + rc = -EINVAL; + goto out; + } + if (cladatum->comdatum) { + rc = hashtab_map(cladatum->comdatum->permissions.table, validate_perm, + cladatum2->comdatum->permissions.table); + if (rc) { + printk(" in the access vector definition for class " + "%s\n", (char *)key); + goto out; + } + } + rc = hashtab_map(cladatum->permissions.table, validate_perm, + cladatum2->permissions.table); + if (rc) + printk(" in access vector definition for class %s\n", + (char *)key); +out: + return rc; +} + +/* Clone the SID into the new SID table. */ +static int clone_sid(u32 sid, + struct context *context, + void *arg) +{ + struct sidtab *s = arg; + + return sidtab_insert(s, sid, context); +} + +static inline int convert_context_handle_invalid_context(struct context *context) +{ + int rc = 0; + + if (selinux_enforcing) { + rc = -EINVAL; + } else { + char *s; + u32 len; + + context_struct_to_string(context, &s, &len); + printk(KERN_ERR "security: context %s is invalid\n", s); + kfree(s); + } + return rc; +} + +struct convert_context_args { + struct policydb *oldp; + struct policydb *newp; +}; + +/* + * Convert the values in the security context + * structure `c' from the values specified + * in the policy `p->oldp' to the values specified + * in the policy `p->newp'. Verify that the + * context is valid under the new policy. + */ +static int convert_context(u32 key, + struct context *c, + void *p) +{ + struct convert_context_args *args; + struct context oldc; + struct role_datum *role; + struct type_datum *typdatum; + struct user_datum *usrdatum; + char *s; + u32 len; + int rc; + + args = p; + + rc = context_cpy(&oldc, c); + if (rc) + goto out; + + rc = -EINVAL; + + /* Convert the user. */ + usrdatum = hashtab_search(args->newp->p_users.table, + args->oldp->p_user_val_to_name[c->user - 1]); + if (!usrdatum) { + goto bad; + } + c->user = usrdatum->value; + + /* Convert the role. */ + role = hashtab_search(args->newp->p_roles.table, + args->oldp->p_role_val_to_name[c->role - 1]); + if (!role) { + goto bad; + } + c->role = role->value; + + /* Convert the type. */ + typdatum = hashtab_search(args->newp->p_types.table, + args->oldp->p_type_val_to_name[c->type - 1]); + if (!typdatum) { + goto bad; + } + c->type = typdatum->value; + + rc = mls_convert_context(args->oldp, args->newp, c); + if (rc) + goto bad; + + /* Check the validity of the new context. */ + if (!policydb_context_isvalid(args->newp, c)) { + rc = convert_context_handle_invalid_context(&oldc); + if (rc) + goto bad; + } + + context_destroy(&oldc); +out: + return rc; +bad: + context_struct_to_string(&oldc, &s, &len); + context_destroy(&oldc); + printk(KERN_ERR "security: invalidating context %s\n", s); + kfree(s); + goto out; +} + +extern void selinux_complete_init(void); + +/** + * security_load_policy - Load a security policy configuration. + * @data: binary policy data + * @len: length of data in bytes + * + * Load a new set of security policy configuration data, + * validate it and convert the SID table as necessary. + * This function will flush the access vector cache after + * loading the new policy. + */ +int security_load_policy(void *data, size_t len) +{ + struct policydb oldpolicydb, newpolicydb; + struct sidtab oldsidtab, newsidtab; + struct convert_context_args args; + u32 seqno; + int rc = 0; + struct policy_file file = { data, len }, *fp = &file; + + LOAD_LOCK; + + if (!ss_initialized) { + avtab_cache_init(); + if (policydb_read(&policydb, fp)) { + LOAD_UNLOCK; + avtab_cache_destroy(); + return -EINVAL; + } + if (policydb_load_isids(&policydb, &sidtab)) { + LOAD_UNLOCK; + policydb_destroy(&policydb); + avtab_cache_destroy(); + return -EINVAL; + } + policydb_loaded_version = policydb.policyvers; + ss_initialized = 1; + + LOAD_UNLOCK; + selinux_complete_init(); + return 0; + } + +#if 0 + sidtab_hash_eval(&sidtab, "sids"); +#endif + + if (policydb_read(&newpolicydb, fp)) { + LOAD_UNLOCK; + return -EINVAL; + } + + sidtab_init(&newsidtab); + + /* Verify that the existing classes did not change. */ + if (hashtab_map(policydb.p_classes.table, validate_class, &newpolicydb)) { + printk(KERN_ERR "security: the definition of an existing " + "class changed\n"); + rc = -EINVAL; + goto err; + } + + /* Clone the SID table. */ + sidtab_shutdown(&sidtab); + if (sidtab_map(&sidtab, clone_sid, &newsidtab)) { + rc = -ENOMEM; + goto err; + } + + /* Convert the internal representations of contexts + in the new SID table and remove invalid SIDs. */ + args.oldp = &policydb; + args.newp = &newpolicydb; + sidtab_map_remove_on_error(&newsidtab, convert_context, &args); + + /* Save the old policydb and SID table to free later. */ + memcpy(&oldpolicydb, &policydb, sizeof policydb); + sidtab_set(&oldsidtab, &sidtab); + + /* Install the new policydb and SID table. */ + POLICY_WRLOCK; + memcpy(&policydb, &newpolicydb, sizeof policydb); + sidtab_set(&sidtab, &newsidtab); + seqno = ++latest_granting; + policydb_loaded_version = policydb.policyvers; + POLICY_WRUNLOCK; + LOAD_UNLOCK; + + /* Free the old policydb and SID table. */ + policydb_destroy(&oldpolicydb); + sidtab_destroy(&oldsidtab); + + avc_ss_reset(seqno); + selnl_notify_policyload(seqno); + + return 0; + +err: + LOAD_UNLOCK; + sidtab_destroy(&newsidtab); + policydb_destroy(&newpolicydb); + return rc; + +} + +/** + * security_port_sid - Obtain the SID for a port. + * @domain: communication domain aka address family + * @type: socket type + * @protocol: protocol number + * @port: port number + * @out_sid: security identifier + */ +int security_port_sid(u16 domain, + u16 type, + u8 protocol, + u16 port, + u32 *out_sid) +{ + struct ocontext *c; + int rc = 0; + + POLICY_RDLOCK; + + c = policydb.ocontexts[OCON_PORT]; + while (c) { + if (c->u.port.protocol == protocol && + c->u.port.low_port <= port && + c->u.port.high_port >= port) + break; + c = c->next; + } + + if (c) { + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *out_sid = c->sid[0]; + } else { + *out_sid = SECINITSID_PORT; + } + +out: + POLICY_RDUNLOCK; + return rc; +} + +/** + * security_netif_sid - Obtain the SID for a network interface. + * @name: interface name + * @if_sid: interface SID + * @msg_sid: default SID for received packets + */ +int security_netif_sid(char *name, + u32 *if_sid, + u32 *msg_sid) +{ + int rc = 0; + struct ocontext *c; + + POLICY_RDLOCK; + + c = policydb.ocontexts[OCON_NETIF]; + while (c) { + if (strcmp(name, c->u.name) == 0) + break; + c = c->next; + } + + if (c) { + if (!c->sid[0] || !c->sid[1]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + rc = sidtab_context_to_sid(&sidtab, + &c->context[1], + &c->sid[1]); + if (rc) + goto out; + } + *if_sid = c->sid[0]; + *msg_sid = c->sid[1]; + } else { + *if_sid = SECINITSID_NETIF; + *msg_sid = SECINITSID_NETMSG; + } + +out: + POLICY_RDUNLOCK; + return rc; +} + +static int match_ipv6_addrmask(u32 *input, u32 *addr, u32 *mask) +{ + int i, fail = 0; + + for(i = 0; i < 4; i++) + if(addr[i] != (input[i] & mask[i])) { + fail = 1; + break; + } + + return !fail; +} + +/** + * security_node_sid - Obtain the SID for a node (host). + * @domain: communication domain aka address family + * @addrp: address + * @addrlen: address length in bytes + * @out_sid: security identifier + */ +int security_node_sid(u16 domain, + void *addrp, + u32 addrlen, + u32 *out_sid) +{ + int rc = 0; + struct ocontext *c; + + POLICY_RDLOCK; + + switch (domain) { + case AF_INET: { + u32 addr; + + if (addrlen != sizeof(u32)) { + rc = -EINVAL; + goto out; + } + + addr = *((u32 *)addrp); + + c = policydb.ocontexts[OCON_NODE]; + while (c) { + if (c->u.node.addr == (addr & c->u.node.mask)) + break; + c = c->next; + } + break; + } + + case AF_INET6: + if (addrlen != sizeof(u64) * 2) { + rc = -EINVAL; + goto out; + } + c = policydb.ocontexts[OCON_NODE6]; + while (c) { + if (match_ipv6_addrmask(addrp, c->u.node6.addr, + c->u.node6.mask)) + break; + c = c->next; + } + break; + + default: + *out_sid = SECINITSID_NODE; + goto out; + } + + if (c) { + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *out_sid = c->sid[0]; + } else { + *out_sid = SECINITSID_NODE; + } + +out: + POLICY_RDUNLOCK; + return rc; +} + +#define SIDS_NEL 25 + +/** + * security_get_user_sids - Obtain reachable SIDs for a user. + * @fromsid: starting SID + * @username: username + * @sids: array of reachable SIDs for user + * @nel: number of elements in @sids + * + * Generate the set of SIDs for legal security contexts + * for a given user that can be reached by @fromsid. + * Set *@sids to point to a dynamically allocated + * array containing the set of SIDs. Set *@nel to the + * number of elements in the array. + */ + +int security_get_user_sids(u32 fromsid, + char *username, + u32 **sids, + u32 *nel) +{ + struct context *fromcon, usercon; + u32 *mysids, *mysids2, sid; + u32 mynel = 0, maxnel = SIDS_NEL; + struct user_datum *user; + struct role_datum *role; + struct av_decision avd; + int rc = 0, i, j; + + if (!ss_initialized) { + *sids = NULL; + *nel = 0; + goto out; + } + + POLICY_RDLOCK; + + fromcon = sidtab_search(&sidtab, fromsid); + if (!fromcon) { + rc = -EINVAL; + goto out_unlock; + } + + user = hashtab_search(policydb.p_users.table, username); + if (!user) { + rc = -EINVAL; + goto out_unlock; + } + usercon.user = user->value; + + mysids = kmalloc(maxnel*sizeof(*mysids), GFP_ATOMIC); + if (!mysids) { + rc = -ENOMEM; + goto out_unlock; + } + memset(mysids, 0, maxnel*sizeof(*mysids)); + + for (i = ebitmap_startbit(&user->roles); i < ebitmap_length(&user->roles); i++) { + if (!ebitmap_get_bit(&user->roles, i)) + continue; + role = policydb.role_val_to_struct[i]; + usercon.role = i+1; + for (j = ebitmap_startbit(&role->types); j < ebitmap_length(&role->types); j++) { + if (!ebitmap_get_bit(&role->types, j)) + continue; + usercon.type = j+1; + + if (mls_setup_user_range(fromcon, user, &usercon)) + continue; + + rc = context_struct_compute_av(fromcon, &usercon, + SECCLASS_PROCESS, + PROCESS__TRANSITION, + &avd); + if (rc || !(avd.allowed & PROCESS__TRANSITION)) + continue; + rc = sidtab_context_to_sid(&sidtab, &usercon, &sid); + if (rc) { + kfree(mysids); + goto out_unlock; + } + if (mynel < maxnel) { + mysids[mynel++] = sid; + } else { + maxnel += SIDS_NEL; + mysids2 = kmalloc(maxnel*sizeof(*mysids2), GFP_ATOMIC); + if (!mysids2) { + rc = -ENOMEM; + kfree(mysids); + goto out_unlock; + } + memset(mysids2, 0, maxnel*sizeof(*mysids2)); + memcpy(mysids2, mysids, mynel * sizeof(*mysids2)); + kfree(mysids); + mysids = mysids2; + mysids[mynel++] = sid; + } + } + } + + *sids = mysids; + *nel = mynel; + +out_unlock: + POLICY_RDUNLOCK; +out: + return rc; +} + +/** + * security_genfs_sid - Obtain a SID for a file in a filesystem + * @fstype: filesystem type + * @path: path from root of mount + * @sclass: file security class + * @sid: SID for path + * + * Obtain a SID to use for a file in a filesystem that + * cannot support xattr or use a fixed labeling behavior like + * transition SIDs or task SIDs. + */ +int security_genfs_sid(const char *fstype, + char *path, + u16 sclass, + u32 *sid) +{ + int len; + struct genfs *genfs; + struct ocontext *c; + int rc = 0, cmp = 0; + + POLICY_RDLOCK; + + for (genfs = policydb.genfs; genfs; genfs = genfs->next) { + cmp = strcmp(fstype, genfs->fstype); + if (cmp <= 0) + break; + } + + if (!genfs || cmp) { + *sid = SECINITSID_UNLABELED; + rc = -ENOENT; + goto out; + } + + for (c = genfs->head; c; c = c->next) { + len = strlen(c->u.name); + if ((!c->v.sclass || sclass == c->v.sclass) && + (strncmp(c->u.name, path, len) == 0)) + break; + } + + if (!c) { + *sid = SECINITSID_UNLABELED; + rc = -ENOENT; + goto out; + } + + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + + *sid = c->sid[0]; +out: + POLICY_RDUNLOCK; + return rc; +} + +/** + * security_fs_use - Determine how to handle labeling for a filesystem. + * @fstype: filesystem type + * @behavior: labeling behavior + * @sid: SID for filesystem (superblock) + */ +int security_fs_use( + const char *fstype, + unsigned int *behavior, + u32 *sid) +{ + int rc = 0; + struct ocontext *c; + + POLICY_RDLOCK; + + c = policydb.ocontexts[OCON_FSUSE]; + while (c) { + if (strcmp(fstype, c->u.name) == 0) + break; + c = c->next; + } + + if (c) { + *behavior = c->v.behavior; + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *sid = c->sid[0]; + } else { + rc = security_genfs_sid(fstype, "/", SECCLASS_DIR, sid); + if (rc) { + *behavior = SECURITY_FS_USE_NONE; + rc = 0; + } else { + *behavior = SECURITY_FS_USE_GENFS; + } + } + +out: + POLICY_RDUNLOCK; + return rc; +} + +int security_get_bools(int *len, char ***names, int **values) +{ + int i, rc = -ENOMEM; + + POLICY_RDLOCK; + *names = NULL; + *values = NULL; + + *len = policydb.p_bools.nprim; + if (!*len) { + rc = 0; + goto out; + } + + *names = (char**)kmalloc(sizeof(char*) * *len, GFP_ATOMIC); + if (!*names) + goto err; + memset(*names, 0, sizeof(char*) * *len); + + *values = (int*)kmalloc(sizeof(int) * *len, GFP_ATOMIC); + if (!*values) + goto err; + + for (i = 0; i < *len; i++) { + size_t name_len; + (*values)[i] = policydb.bool_val_to_struct[i]->state; + name_len = strlen(policydb.p_bool_val_to_name[i]) + 1; + (*names)[i] = (char*)kmalloc(sizeof(char) * name_len, GFP_ATOMIC); + if (!(*names)[i]) + goto err; + strncpy((*names)[i], policydb.p_bool_val_to_name[i], name_len); + (*names)[i][name_len - 1] = 0; + } + rc = 0; +out: + POLICY_RDUNLOCK; + return rc; +err: + if (*names) { + for (i = 0; i < *len; i++) + if ((*names)[i]) + kfree((*names)[i]); + } + if (*values) + kfree(*values); + goto out; +} + + +int security_set_bools(int len, int *values) +{ + int i, rc = 0; + int lenp, seqno = 0; + struct cond_node *cur; + + POLICY_WRLOCK; + + lenp = policydb.p_bools.nprim; + if (len != lenp) { + rc = -EFAULT; + goto out; + } + + printk(KERN_INFO "security: committed booleans { "); + for (i = 0; i < len; i++) { + if (values[i]) { + policydb.bool_val_to_struct[i]->state = 1; + } else { + policydb.bool_val_to_struct[i]->state = 0; + } + if (i != 0) + printk(", "); + printk("%s:%d", policydb.p_bool_val_to_name[i], + policydb.bool_val_to_struct[i]->state); + } + printk(" }\n"); + + for (cur = policydb.cond_list; cur != NULL; cur = cur->next) { + rc = evaluate_cond_node(&policydb, cur); + if (rc) + goto out; + } + + seqno = ++latest_granting; + +out: + POLICY_WRUNLOCK; + if (!rc) { + avc_ss_reset(seqno); + selnl_notify_policyload(seqno); + } + return rc; +} + +int security_get_bool_value(int bool) +{ + int rc = 0; + int len; + + POLICY_RDLOCK; + + len = policydb.p_bools.nprim; + if (bool >= len) { + rc = -EFAULT; + goto out; + } + + rc = policydb.bool_val_to_struct[bool]->state; +out: + POLICY_RDUNLOCK; + return rc; +} diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h new file mode 100644 index 000000000000..e8d907e903cd --- /dev/null +++ b/security/selinux/ss/services.h @@ -0,0 +1,15 @@ +/* + * Implementation of the security services. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_SERVICES_H_ +#define _SS_SERVICES_H_ + +#include "policydb.h" +#include "sidtab.h" + +extern struct policydb policydb; + +#endif /* _SS_SERVICES_H_ */ + diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c new file mode 100644 index 000000000000..871c33bd0741 --- /dev/null +++ b/security/selinux/ss/sidtab.c @@ -0,0 +1,305 @@ +/* + * Implementation of the SID table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include "flask.h" +#include "security.h" +#include "sidtab.h" + +#define SIDTAB_HASH(sid) \ +(sid & SIDTAB_HASH_MASK) + +#define INIT_SIDTAB_LOCK(s) spin_lock_init(&s->lock) +#define SIDTAB_LOCK(s, x) spin_lock_irqsave(&s->lock, x) +#define SIDTAB_UNLOCK(s, x) spin_unlock_irqrestore(&s->lock, x) + +int sidtab_init(struct sidtab *s) +{ + int i; + + s->htable = kmalloc(sizeof(*(s->htable)) * SIDTAB_SIZE, GFP_ATOMIC); + if (!s->htable) + return -ENOMEM; + for (i = 0; i < SIDTAB_SIZE; i++) + s->htable[i] = NULL; + s->nel = 0; + s->next_sid = 1; + s->shutdown = 0; + INIT_SIDTAB_LOCK(s); + return 0; +} + +int sidtab_insert(struct sidtab *s, u32 sid, struct context *context) +{ + int hvalue, rc = 0; + struct sidtab_node *prev, *cur, *newnode; + + if (!s) { + rc = -ENOMEM; + goto out; + } + + hvalue = SIDTAB_HASH(sid); + prev = NULL; + cur = s->htable[hvalue]; + while (cur != NULL && sid > cur->sid) { + prev = cur; + cur = cur->next; + } + + if (cur && sid == cur->sid) { + rc = -EEXIST; + goto out; + } + + newnode = kmalloc(sizeof(*newnode), GFP_ATOMIC); + if (newnode == NULL) { + rc = -ENOMEM; + goto out; + } + newnode->sid = sid; + if (context_cpy(&newnode->context, context)) { + kfree(newnode); + rc = -ENOMEM; + goto out; + } + + if (prev) { + newnode->next = prev->next; + wmb(); + prev->next = newnode; + } else { + newnode->next = s->htable[hvalue]; + wmb(); + s->htable[hvalue] = newnode; + } + + s->nel++; + if (sid >= s->next_sid) + s->next_sid = sid + 1; +out: + return rc; +} + +struct context *sidtab_search(struct sidtab *s, u32 sid) +{ + int hvalue; + struct sidtab_node *cur; + + if (!s) + return NULL; + + hvalue = SIDTAB_HASH(sid); + cur = s->htable[hvalue]; + while (cur != NULL && sid > cur->sid) + cur = cur->next; + + if (cur == NULL || sid != cur->sid) { + /* Remap invalid SIDs to the unlabeled SID. */ + sid = SECINITSID_UNLABELED; + hvalue = SIDTAB_HASH(sid); + cur = s->htable[hvalue]; + while (cur != NULL && sid > cur->sid) + cur = cur->next; + if (!cur || sid != cur->sid) + return NULL; + } + + return &cur->context; +} + +int sidtab_map(struct sidtab *s, + int (*apply) (u32 sid, + struct context *context, + void *args), + void *args) +{ + int i, rc = 0; + struct sidtab_node *cur; + + if (!s) + goto out; + + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = s->htable[i]; + while (cur != NULL) { + rc = apply(cur->sid, &cur->context, args); + if (rc) + goto out; + cur = cur->next; + } + } +out: + return rc; +} + +void sidtab_map_remove_on_error(struct sidtab *s, + int (*apply) (u32 sid, + struct context *context, + void *args), + void *args) +{ + int i, ret; + struct sidtab_node *last, *cur, *temp; + + if (!s) + return; + + for (i = 0; i < SIDTAB_SIZE; i++) { + last = NULL; + cur = s->htable[i]; + while (cur != NULL) { + ret = apply(cur->sid, &cur->context, args); + if (ret) { + if (last) { + last->next = cur->next; + } else { + s->htable[i] = cur->next; + } + + temp = cur; + cur = cur->next; + context_destroy(&temp->context); + kfree(temp); + s->nel--; + } else { + last = cur; + cur = cur->next; + } + } + } + + return; +} + +static inline u32 sidtab_search_context(struct sidtab *s, + struct context *context) +{ + int i; + struct sidtab_node *cur; + + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = s->htable[i]; + while (cur != NULL) { + if (context_cmp(&cur->context, context)) + return cur->sid; + cur = cur->next; + } + } + return 0; +} + +int sidtab_context_to_sid(struct sidtab *s, + struct context *context, + u32 *out_sid) +{ + u32 sid; + int ret = 0; + unsigned long flags; + + *out_sid = SECSID_NULL; + + sid = sidtab_search_context(s, context); + if (!sid) { + SIDTAB_LOCK(s, flags); + /* Rescan now that we hold the lock. */ + sid = sidtab_search_context(s, context); + if (sid) + goto unlock_out; + /* No SID exists for the context. Allocate a new one. */ + if (s->next_sid == UINT_MAX || s->shutdown) { + ret = -ENOMEM; + goto unlock_out; + } + sid = s->next_sid++; + ret = sidtab_insert(s, sid, context); + if (ret) + s->next_sid--; +unlock_out: + SIDTAB_UNLOCK(s, flags); + } + + if (ret) + return ret; + + *out_sid = sid; + return 0; +} + +void sidtab_hash_eval(struct sidtab *h, char *tag) +{ + int i, chain_len, slots_used, max_chain_len; + struct sidtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = h->htable[i]; + if (cur) { + slots_used++; + chain_len = 0; + while (cur) { + chain_len++; + cur = cur->next; + } + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + printk(KERN_INFO "%s: %d entries and %d/%d buckets used, longest " + "chain length %d\n", tag, h->nel, slots_used, SIDTAB_SIZE, + max_chain_len); +} + +void sidtab_destroy(struct sidtab *s) +{ + int i; + struct sidtab_node *cur, *temp; + + if (!s) + return; + + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = s->htable[i]; + while (cur != NULL) { + temp = cur; + cur = cur->next; + context_destroy(&temp->context); + kfree(temp); + } + s->htable[i] = NULL; + } + kfree(s->htable); + s->htable = NULL; + s->nel = 0; + s->next_sid = 1; +} + +void sidtab_set(struct sidtab *dst, struct sidtab *src) +{ + unsigned long flags; + + SIDTAB_LOCK(src, flags); + dst->htable = src->htable; + dst->nel = src->nel; + dst->next_sid = src->next_sid; + dst->shutdown = 0; + SIDTAB_UNLOCK(src, flags); +} + +void sidtab_shutdown(struct sidtab *s) +{ + unsigned long flags; + + SIDTAB_LOCK(s, flags); + s->shutdown = 1; + SIDTAB_UNLOCK(s, flags); +} diff --git a/security/selinux/ss/sidtab.h b/security/selinux/ss/sidtab.h new file mode 100644 index 000000000000..2fe9dfa3eb3a --- /dev/null +++ b/security/selinux/ss/sidtab.h @@ -0,0 +1,59 @@ +/* + * A security identifier table (sidtab) is a hash table + * of security context structures indexed by SID value. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_SIDTAB_H_ +#define _SS_SIDTAB_H_ + +#include "context.h" + +struct sidtab_node { + u32 sid; /* security identifier */ + struct context context; /* security context structure */ + struct sidtab_node *next; +}; + +#define SIDTAB_HASH_BITS 7 +#define SIDTAB_HASH_BUCKETS (1 << SIDTAB_HASH_BITS) +#define SIDTAB_HASH_MASK (SIDTAB_HASH_BUCKETS-1) + +#define SIDTAB_SIZE SIDTAB_HASH_BUCKETS + +struct sidtab { + struct sidtab_node **htable; + unsigned int nel; /* number of elements */ + unsigned int next_sid; /* next SID to allocate */ + unsigned char shutdown; + spinlock_t lock; +}; + +int sidtab_init(struct sidtab *s); +int sidtab_insert(struct sidtab *s, u32 sid, struct context *context); +struct context *sidtab_search(struct sidtab *s, u32 sid); + +int sidtab_map(struct sidtab *s, + int (*apply) (u32 sid, + struct context *context, + void *args), + void *args); + +void sidtab_map_remove_on_error(struct sidtab *s, + int (*apply) (u32 sid, + struct context *context, + void *args), + void *args); + +int sidtab_context_to_sid(struct sidtab *s, + struct context *context, + u32 *sid); + +void sidtab_hash_eval(struct sidtab *h, char *tag); +void sidtab_destroy(struct sidtab *s); +void sidtab_set(struct sidtab *dst, struct sidtab *src); +void sidtab_shutdown(struct sidtab *s); + +#endif /* _SS_SIDTAB_H_ */ + + diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c new file mode 100644 index 000000000000..24a10d36d3b6 --- /dev/null +++ b/security/selinux/ss/symtab.c @@ -0,0 +1,44 @@ +/* + * Implementation of the symbol table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include "symtab.h" + +static unsigned int symhash(struct hashtab *h, void *key) +{ + char *p, *keyp; + unsigned int size; + unsigned int val; + + val = 0; + keyp = key; + size = strlen(keyp); + for (p = keyp; (p - keyp) < size; p++) + val = (val << 4 | (val >> (8*sizeof(unsigned int)-4))) ^ (*p); + return val & (h->size - 1); +} + +static int symcmp(struct hashtab *h, void *key1, void *key2) +{ + char *keyp1, *keyp2; + + keyp1 = key1; + keyp2 = key2; + return strcmp(keyp1, keyp2); +} + + +int symtab_init(struct symtab *s, unsigned int size) +{ + s->table = hashtab_create(symhash, symcmp, size); + if (!s->table) + return -1; + s->nprim = 0; + return 0; +} + diff --git a/security/selinux/ss/symtab.h b/security/selinux/ss/symtab.h new file mode 100644 index 000000000000..ca422b42fbc0 --- /dev/null +++ b/security/selinux/ss/symtab.h @@ -0,0 +1,23 @@ +/* + * A symbol table (symtab) maintains associations between symbol + * strings and datum values. The type of the datum values + * is arbitrary. The symbol table type is implemented + * using the hash table type (hashtab). + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_SYMTAB_H_ +#define _SS_SYMTAB_H_ + +#include "hashtab.h" + +struct symtab { + struct hashtab *table; /* hash table (keyed on a string) */ + u32 nprim; /* number of primary names in table */ +}; + +int symtab_init(struct symtab *s, unsigned int size); + +#endif /* _SS_SYMTAB_H_ */ + + |