diff options
Diffstat (limited to 'security')
34 files changed, 1246 insertions, 379 deletions
diff --git a/security/Makefile b/security/Makefile index 9e8b0252501..7ef1107a728 100644 --- a/security/Makefile +++ b/security/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o obj-$(CONFIG_SECURITY_SMACK) += commoncap.o smack/built-in.o obj-$(CONFIG_SECURITY_CAPABILITIES) += commoncap.o capability.o obj-$(CONFIG_SECURITY_ROOTPLUG) += commoncap.o root_plug.o +obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o diff --git a/security/capability.c b/security/capability.c index 2c6e06d18fa..38ac54e3aed 100644 --- a/security/capability.c +++ b/security/capability.c @@ -44,6 +44,7 @@ static struct security_operations capability_ops = { .task_setioprio = cap_task_setioprio, .task_setnice = cap_task_setnice, .task_post_setuid = cap_task_post_setuid, + .task_prctl = cap_task_prctl, .task_reparent_to_init = cap_task_reparent_to_init, .syslog = cap_syslog, diff --git a/security/commoncap.c b/security/commoncap.c index 852905789ca..5edabc7542a 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -24,11 +24,8 @@ #include <linux/hugetlb.h> #include <linux/mount.h> #include <linux/sched.h> - -/* Global security state */ - -unsigned securebits = SECUREBITS_DEFAULT; /* systemwide security settings */ -EXPORT_SYMBOL(securebits); +#include <linux/prctl.h> +#include <linux/securebits.h> int cap_netlink_send(struct sock *sk, struct sk_buff *skb) { @@ -368,7 +365,7 @@ void cap_bprm_apply_creds (struct linux_binprm *bprm, int unsafe) /* AUD: Audit candidate if current->cap_effective is set */ - current->keep_capabilities = 0; + current->securebits &= ~issecure_mask(SECURE_KEEP_CAPS); } int cap_bprm_secureexec (struct linux_binprm *bprm) @@ -386,8 +383,8 @@ int cap_bprm_secureexec (struct linux_binprm *bprm) current->egid != current->gid); } -int cap_inode_setxattr(struct dentry *dentry, char *name, void *value, - size_t size, int flags) +int cap_inode_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) { if (!strcmp(name, XATTR_NAME_CAPS)) { if (!capable(CAP_SETFCAP)) @@ -400,7 +397,7 @@ int cap_inode_setxattr(struct dentry *dentry, char *name, void *value, return 0; } -int cap_inode_removexattr(struct dentry *dentry, char *name) +int cap_inode_removexattr(struct dentry *dentry, const char *name) { if (!strcmp(name, XATTR_NAME_CAPS)) { if (!capable(CAP_SETFCAP)) @@ -448,7 +445,7 @@ static inline void cap_emulate_setxuid (int old_ruid, int old_euid, { if ((old_ruid == 0 || old_euid == 0 || old_suid == 0) && (current->uid != 0 && current->euid != 0 && current->suid != 0) && - !current->keep_capabilities) { + !issecure(SECURE_KEEP_CAPS)) { cap_clear (current->cap_permitted); cap_clear (current->cap_effective); } @@ -547,7 +544,7 @@ int cap_task_setnice (struct task_struct *p, int nice) * this task could get inconsistent info. There can be no * racing writer bc a task can only change its own caps. */ -long cap_prctl_drop(unsigned long cap) +static long cap_prctl_drop(unsigned long cap) { if (!capable(CAP_SETPCAP)) return -EPERM; @@ -556,6 +553,7 @@ long cap_prctl_drop(unsigned long cap) cap_lower(current->cap_bset, cap); return 0; } + #else int cap_task_setscheduler (struct task_struct *p, int policy, struct sched_param *lp) @@ -572,12 +570,99 @@ int cap_task_setnice (struct task_struct *p, int nice) } #endif +int cap_task_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5, long *rc_p) +{ + long error = 0; + + switch (option) { + case PR_CAPBSET_READ: + if (!cap_valid(arg2)) + error = -EINVAL; + else + error = !!cap_raised(current->cap_bset, arg2); + break; +#ifdef CONFIG_SECURITY_FILE_CAPABILITIES + case PR_CAPBSET_DROP: + error = cap_prctl_drop(arg2); + break; + + /* + * The next four prctl's remain to assist with transitioning a + * system from legacy UID=0 based privilege (when filesystem + * capabilities are not in use) to a system using filesystem + * capabilities only - as the POSIX.1e draft intended. + * + * Note: + * + * PR_SET_SECUREBITS = + * issecure_mask(SECURE_KEEP_CAPS_LOCKED) + * | issecure_mask(SECURE_NOROOT) + * | issecure_mask(SECURE_NOROOT_LOCKED) + * | issecure_mask(SECURE_NO_SETUID_FIXUP) + * | issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED) + * + * will ensure that the current process and all of its + * children will be locked into a pure + * capability-based-privilege environment. + */ + case PR_SET_SECUREBITS: + if ((((current->securebits & SECURE_ALL_LOCKS) >> 1) + & (current->securebits ^ arg2)) /*[1]*/ + || ((current->securebits & SECURE_ALL_LOCKS + & ~arg2)) /*[2]*/ + || (arg2 & ~(SECURE_ALL_LOCKS | SECURE_ALL_BITS)) /*[3]*/ + || (cap_capable(current, CAP_SETPCAP) != 0)) { /*[4]*/ + /* + * [1] no changing of bits that are locked + * [2] no unlocking of locks + * [3] no setting of unsupported bits + * [4] doing anything requires privilege (go read about + * the "sendmail capabilities bug") + */ + error = -EPERM; /* cannot change a locked bit */ + } else { + current->securebits = arg2; + } + break; + case PR_GET_SECUREBITS: + error = current->securebits; + break; + +#endif /* def CONFIG_SECURITY_FILE_CAPABILITIES */ + + case PR_GET_KEEPCAPS: + if (issecure(SECURE_KEEP_CAPS)) + error = 1; + break; + case PR_SET_KEEPCAPS: + if (arg2 > 1) /* Note, we rely on arg2 being unsigned here */ + error = -EINVAL; + else if (issecure(SECURE_KEEP_CAPS_LOCKED)) + error = -EPERM; + else if (arg2) + current->securebits |= issecure_mask(SECURE_KEEP_CAPS); + else + current->securebits &= + ~issecure_mask(SECURE_KEEP_CAPS); + break; + + default: + /* No functionality available - continue with default */ + return 0; + } + + /* Functionality provided */ + *rc_p = error; + return 1; +} + void cap_task_reparent_to_init (struct task_struct *p) { cap_set_init_eff(p->cap_effective); cap_clear(p->cap_inheritable); cap_set_full(p->cap_permitted); - p->keep_capabilities = 0; + p->securebits = SECUREBITS_DEFAULT; return; } diff --git a/security/device_cgroup.c b/security/device_cgroup.c new file mode 100644 index 00000000000..4ea583689ee --- /dev/null +++ b/security/device_cgroup.c @@ -0,0 +1,575 @@ +/* + * dev_cgroup.c - device cgroup subsystem + * + * Copyright 2007 IBM Corp + */ + +#include <linux/device_cgroup.h> +#include <linux/cgroup.h> +#include <linux/ctype.h> +#include <linux/list.h> +#include <linux/uaccess.h> +#include <linux/seq_file.h> + +#define ACC_MKNOD 1 +#define ACC_READ 2 +#define ACC_WRITE 4 +#define ACC_MASK (ACC_MKNOD | ACC_READ | ACC_WRITE) + +#define DEV_BLOCK 1 +#define DEV_CHAR 2 +#define DEV_ALL 4 /* this represents all devices */ + +/* + * whitelist locking rules: + * cgroup_lock() cannot be taken under dev_cgroup->lock. + * dev_cgroup->lock can be taken with or without cgroup_lock(). + * + * modifications always require cgroup_lock + * modifications to a list which is visible require the + * dev_cgroup->lock *and* cgroup_lock() + * walking the list requires dev_cgroup->lock or cgroup_lock(). + * + * reasoning: dev_whitelist_copy() needs to kmalloc, so needs + * a mutex, which the cgroup_lock() is. Since modifying + * a visible list requires both locks, either lock can be + * taken for walking the list. + */ + +struct dev_whitelist_item { + u32 major, minor; + short type; + short access; + struct list_head list; +}; + +struct dev_cgroup { + struct cgroup_subsys_state css; + struct list_head whitelist; + spinlock_t lock; +}; + +static inline struct dev_cgroup *cgroup_to_devcgroup(struct cgroup *cgroup) +{ + return container_of(cgroup_subsys_state(cgroup, devices_subsys_id), + struct dev_cgroup, css); +} + +struct cgroup_subsys devices_subsys; + +static int devcgroup_can_attach(struct cgroup_subsys *ss, + struct cgroup *new_cgroup, struct task_struct *task) +{ + if (current != task && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + return 0; +} + +/* + * called under cgroup_lock() + */ +static int dev_whitelist_copy(struct list_head *dest, struct list_head *orig) +{ + struct dev_whitelist_item *wh, *tmp, *new; + + list_for_each_entry(wh, orig, list) { + new = kmalloc(sizeof(*wh), GFP_KERNEL); + if (!new) + goto free_and_exit; + new->major = wh->major; + new->minor = wh->minor; + new->type = wh->type; + new->access = wh->access; + list_add_tail(&new->list, dest); + } + + return 0; + +free_and_exit: + list_for_each_entry_safe(wh, tmp, dest, list) { + list_del(&wh->list); + kfree(wh); + } + return -ENOMEM; +} + +/* Stupid prototype - don't bother combining existing entries */ +/* + * called under cgroup_lock() + * since the list is visible to other tasks, we need the spinlock also + */ +static int dev_whitelist_add(struct dev_cgroup *dev_cgroup, + struct dev_whitelist_item *wh) +{ + struct dev_whitelist_item *whcopy; + + whcopy = kmalloc(sizeof(*whcopy), GFP_KERNEL); + if (!whcopy) + return -ENOMEM; + + memcpy(whcopy, wh, sizeof(*whcopy)); + spin_lock(&dev_cgroup->lock); + list_add_tail(&whcopy->list, &dev_cgroup->whitelist); + spin_unlock(&dev_cgroup->lock); + return 0; +} + +/* + * called under cgroup_lock() + * since the list is visible to other tasks, we need the spinlock also + */ +static void dev_whitelist_rm(struct dev_cgroup *dev_cgroup, + struct dev_whitelist_item *wh) +{ + struct dev_whitelist_item *walk, *tmp; + + spin_lock(&dev_cgroup->lock); + list_for_each_entry_safe(walk, tmp, &dev_cgroup->whitelist, list) { + if (walk->type == DEV_ALL) + goto remove; + if (walk->type != wh->type) + continue; + if (walk->major != ~0 && walk->major != wh->major) + continue; + if (walk->minor != ~0 && walk->minor != wh->minor) + continue; + +remove: + walk->access &= ~wh->access; + if (!walk->access) { + list_del(&walk->list); + kfree(walk); + } + } + spin_unlock(&dev_cgroup->lock); +} + +/* + * called from kernel/cgroup.c with cgroup_lock() held. + */ +static struct cgroup_subsys_state *devcgroup_create(struct cgroup_subsys *ss, + struct cgroup *cgroup) +{ + struct dev_cgroup *dev_cgroup, *parent_dev_cgroup; + struct cgroup *parent_cgroup; + int ret; + + dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL); + if (!dev_cgroup) + return ERR_PTR(-ENOMEM); + INIT_LIST_HEAD(&dev_cgroup->whitelist); + parent_cgroup = cgroup->parent; + + if (parent_cgroup == NULL) { + struct dev_whitelist_item *wh; + wh = kmalloc(sizeof(*wh), GFP_KERNEL); + if (!wh) { + kfree(dev_cgroup); + return ERR_PTR(-ENOMEM); + } + wh->minor = wh->major = ~0; + wh->type = DEV_ALL; + wh->access = ACC_MKNOD | ACC_READ | ACC_WRITE; + list_add(&wh->list, &dev_cgroup->whitelist); + } else { + parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup); + ret = dev_whitelist_copy(&dev_cgroup->whitelist, + &parent_dev_cgroup->whitelist); + if (ret) { + kfree(dev_cgroup); + return ERR_PTR(ret); + } + } + + spin_lock_init(&dev_cgroup->lock); + return &dev_cgroup->css; +} + +static void devcgroup_destroy(struct cgroup_subsys *ss, + struct cgroup *cgroup) +{ + struct dev_cgroup *dev_cgroup; + struct dev_whitelist_item *wh, *tmp; + + dev_cgroup = cgroup_to_devcgroup(cgroup); + list_for_each_entry_safe(wh, tmp, &dev_cgroup->whitelist, list) { + list_del(&wh->list); + kfree(wh); + } + kfree(dev_cgroup); +} + +#define DEVCG_ALLOW 1 +#define DEVCG_DENY 2 +#define DEVCG_LIST 3 + +#define MAJMINLEN 10 +#define ACCLEN 4 + +static void set_access(char *acc, short access) +{ + int idx = 0; + memset(acc, 0, ACCLEN); + if (access & ACC_READ) + acc[idx++] = 'r'; + if (access & ACC_WRITE) + acc[idx++] = 'w'; + if (access & ACC_MKNOD) + acc[idx++] = 'm'; +} + +static char type_to_char(short type) +{ + if (type == DEV_ALL) + return 'a'; + if (type == DEV_CHAR) + return 'c'; + if (type == DEV_BLOCK) + return 'b'; + return 'X'; +} + +static void set_majmin(char *str, unsigned m) +{ + memset(str, 0, MAJMINLEN); + if (m == ~0) + sprintf(str, "*"); + else + snprintf(str, MAJMINLEN, "%d", m); +} + +static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, + struct seq_file *m) +{ + struct dev_cgroup *devcgroup = cgroup_to_devcgroup(cgroup); + struct dev_whitelist_item *wh; + char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; + + spin_lock(&devcgroup->lock); + list_for_each_entry(wh, &devcgroup->whitelist, list) { + set_access(acc, wh->access); + set_majmin(maj, wh->major); + set_majmin(min, wh->minor); + seq_printf(m, "%c %s:%s %s\n", type_to_char(wh->type), + maj, min, acc); + } + spin_unlock(&devcgroup->lock); + + return 0; +} + +/* + * may_access_whitelist: + * does the access granted to dev_cgroup c contain the access + * requested in whitelist item refwh. + * return 1 if yes, 0 if no. + * call with c->lock held + */ +static int may_access_whitelist(struct dev_cgroup *c, + struct dev_whitelist_item *refwh) +{ + struct dev_whitelist_item *whitem; + + list_for_each_entry(whitem, &c->whitelist, list) { + if (whitem->type & DEV_ALL) + return 1; + if ((refwh->type & DEV_BLOCK) && !(whitem->type & DEV_BLOCK)) + continue; + if ((refwh->type & DEV_CHAR) && !(whitem->type & DEV_CHAR)) + continue; + if (whitem->major != ~0 && whitem->major != refwh->major) + continue; + if (whitem->minor != ~0 && whitem->minor != refwh->minor) + continue; + if (refwh->access & (~(whitem->access | ACC_MASK))) + continue; + return 1; + } + return 0; +} + +/* + * parent_has_perm: + * when adding a new allow rule to a device whitelist, the rule + * must be allowed in the parent device + */ +static int parent_has_perm(struct cgroup *childcg, + struct dev_whitelist_item *wh) +{ + struct cgroup *pcg = childcg->parent; + struct dev_cgroup *parent; + int ret; + + if (!pcg) + return 1; + parent = cgroup_to_devcgroup(pcg); + spin_lock(&parent->lock); + ret = may_access_whitelist(parent, wh); + spin_unlock(&parent->lock); + return ret; +} + +/* + * Modify the whitelist using allow/deny rules. + * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD + * so we can give a container CAP_MKNOD to let it create devices but not + * modify the whitelist. + * It seems likely we'll want to add a CAP_CONTAINER capability to allow + * us to also grant CAP_SYS_ADMIN to containers without giving away the + * device whitelist controls, but for now we'll stick with CAP_SYS_ADMIN + * + * Taking rules away is always allowed (given CAP_SYS_ADMIN). Granting + * new access is only allowed if you're in the top-level cgroup, or your + * parent cgroup has the access you're asking for. + */ +static ssize_t devcgroup_access_write(struct cgroup *cgroup, struct cftype *cft, + struct file *file, const char __user *userbuf, + size_t nbytes, loff_t *ppos) +{ + struct cgroup *cur_cgroup; + struct dev_cgroup *devcgroup, *cur_devcgroup; + int filetype = cft->private; + char *buffer, *b; + int retval = 0, count; + struct dev_whitelist_item wh; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + devcgroup = cgroup_to_devcgroup(cgroup); + cur_cgroup = task_cgroup(current, devices_subsys.subsys_id); + cur_devcgroup = cgroup_to_devcgroup(cur_cgroup); + + buffer = kmalloc(nbytes+1, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + if (copy_from_user(buffer, userbuf, nbytes)) { + retval = -EFAULT; + goto out1; + } + buffer[nbytes] = 0; /* nul-terminate */ + + cgroup_lock(); + if (cgroup_is_removed(cgroup)) { + retval = -ENODEV; + goto out2; + } + + memset(&wh, 0, sizeof(wh)); + b = buffer; + + switch (*b) { + case 'a': + wh.type = DEV_ALL; + wh.access = ACC_MASK; + goto handle; + case 'b': + wh.type = DEV_BLOCK; + break; + case 'c': + wh.type = DEV_CHAR; + break; + default: + retval = -EINVAL; + goto out2; + } + b++; + if (!isspace(*b)) { + retval = -EINVAL; + goto out2; + } + b++; + if (*b == '*') { + wh.major = ~0; + b++; + } else if (isdigit(*b)) { + wh.major = 0; + while (isdigit(*b)) { + wh.major = wh.major*10+(*b-'0'); + b++; + } + } else { + retval = -EINVAL; + goto out2; + } + if (*b != ':') { + retval = -EINVAL; + goto out2; + } + b++; + + /* read minor */ + if (*b == '*') { + wh.minor = ~0; + b++; + } else if (isdigit(*b)) { + wh.minor = 0; + while (isdigit(*b)) { + wh.minor = wh.minor*10+(*b-'0'); + b++; + } + } else { + retval = -EINVAL; + goto out2; + } + if (!isspace(*b)) { + retval = -EINVAL; + goto out2; + } + for (b++, count = 0; count < 3; count++, b++) { + switch (*b) { + case 'r': + wh.access |= ACC_READ; + break; + case 'w': + wh.access |= ACC_WRITE; + break; + case 'm': + wh.access |= ACC_MKNOD; + break; + case '\n': + case '\0': + count = 3; + break; + default: + retval = -EINVAL; + goto out2; + } + } + +handle: + retval = 0; + switch (filetype) { + case DEVCG_ALLOW: + if (!parent_has_perm(cgroup, &wh)) + retval = -EPERM; + else + retval = dev_whitelist_add(devcgroup, &wh); + break; + case DEVCG_DENY: + dev_whitelist_rm(devcgroup, &wh); + break; + default: + retval = -EINVAL; + goto out2; + } + + if (retval == 0) + retval = nbytes; + +out2: + cgroup_unlock(); +out1: + kfree(buffer); + return retval; +} + +static struct cftype dev_cgroup_files[] = { + { + .name = "allow", + .write = devcgroup_access_write, + .private = DEVCG_ALLOW, + }, + { + .name = "deny", + .write = devcgroup_access_write, + .private = DEVCG_DENY, + }, + { + .name = "list", + .read_seq_string = devcgroup_seq_read, + .private = DEVCG_LIST, + }, +}; + +static int devcgroup_populate(struct cgroup_subsys *ss, + struct cgroup *cgroup) +{ + return cgroup_add_files(cgroup, ss, dev_cgroup_files, + ARRAY_SIZE(dev_cgroup_files)); +} + +struct cgroup_subsys devices_subsys = { + .name = "devices", + .can_attach = devcgroup_can_attach, + .create = devcgroup_create, + .destroy = devcgroup_destroy, + .populate = devcgroup_populate, + .subsys_id = devices_subsys_id, +}; + +int devcgroup_inode_permission(struct inode *inode, int mask) +{ + struct cgroup *cgroup; + struct dev_cgroup *dev_cgroup; + struct dev_whitelist_item *wh; + + dev_t device = inode->i_rdev; + if (!device) + return 0; + if (!S_ISBLK(inode->i_mode) && !S_ISCHR(inode->i_mode)) + return 0; + cgroup = task_cgroup(current, devices_subsys.subsys_id); + dev_cgroup = cgroup_to_devcgroup(cgroup); + if (!dev_cgroup) + return 0; + + spin_lock(&dev_cgroup->lock); + list_for_each_entry(wh, &dev_cgroup->whitelist, list) { + if (wh->type & DEV_ALL) + goto acc_check; + if ((wh->type & DEV_BLOCK) && !S_ISBLK(inode->i_mode)) + continue; + if ((wh->type & DEV_CHAR) && !S_ISCHR(inode->i_mode)) + continue; + if (wh->major != ~0 && wh->major != imajor(inode)) + continue; + if (wh->minor != ~0 && wh->minor != iminor(inode)) + continue; +acc_check: + if ((mask & MAY_WRITE) && !(wh->access & ACC_WRITE)) + continue; + if ((mask & MAY_READ) && !(wh->access & ACC_READ)) + continue; + spin_unlock(&dev_cgroup->lock); + return 0; + } + spin_unlock(&dev_cgroup->lock); + + return -EPERM; +} + +int devcgroup_inode_mknod(int mode, dev_t dev) +{ + struct cgroup *cgroup; + struct dev_cgroup *dev_cgroup; + struct dev_whitelist_item *wh; + + cgroup = task_cgroup(current, devices_subsys.subsys_id); + dev_cgroup = cgroup_to_devcgroup(cgroup); + if (!dev_cgroup) + return 0; + + spin_lock(&dev_cgroup->lock); + list_for_each_entry(wh, &dev_cgroup->whitelist, list) { + if (wh->type & DEV_ALL) + goto acc_check; + if ((wh->type & DEV_BLOCK) && !S_ISBLK(mode)) + continue; + if ((wh->type & DEV_CHAR) && !S_ISCHR(mode)) + continue; + if (wh->major != ~0 && wh->major != MAJOR(dev)) + continue; + if (wh->minor != ~0 && wh->minor != MINOR(dev)) + continue; +acc_check: + if (!(wh->access & ACC_MKNOD)) + continue; + spin_unlock(&dev_cgroup->lock); + return 0; + } + spin_unlock(&dev_cgroup->lock); + return -EPERM; +} diff --git a/security/dummy.c b/security/dummy.c index b0232bbf427..48cf30226e1 100644 --- a/security/dummy.c +++ b/security/dummy.c @@ -365,8 +365,8 @@ 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) +static int dummy_inode_setxattr (struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) { if (!strncmp(name, XATTR_SECURITY_PREFIX, sizeof(XATTR_SECURITY_PREFIX) - 1) && @@ -375,12 +375,13 @@ static int dummy_inode_setxattr (struct dentry *dentry, char *name, void *value, return 0; } -static void dummy_inode_post_setxattr (struct dentry *dentry, char *name, void *value, - size_t size, int flags) +static void dummy_inode_post_setxattr (struct dentry *dentry, const char *name, + const void *value, size_t size, + int flags) { } -static int dummy_inode_getxattr (struct dentry *dentry, char *name) +static int dummy_inode_getxattr (struct dentry *dentry, const char *name) { return 0; } @@ -390,7 +391,7 @@ static int dummy_inode_listxattr (struct dentry *dentry) return 0; } -static int dummy_inode_removexattr (struct dentry *dentry, char *name) +static int dummy_inode_removexattr (struct dentry *dentry, const char *name) { if (!strncmp(name, XATTR_SECURITY_PREFIX, sizeof(XATTR_SECURITY_PREFIX) - 1) && @@ -604,7 +605,7 @@ static int dummy_task_kill (struct task_struct *p, struct siginfo *info, } static int dummy_task_prctl (int option, unsigned long arg2, unsigned long arg3, - unsigned long arg4, unsigned long arg5) + unsigned long arg4, unsigned long arg5, long *rc_p) { return 0; } @@ -993,6 +994,13 @@ static inline int dummy_key_permission(key_ref_t key_ref, { return 0; } + +static int dummy_key_getsecurity(struct key *key, char **_buffer) +{ + *_buffer = NULL; + return 0; +} + #endif /* CONFIG_KEYS */ #ifdef CONFIG_AUDIT @@ -1209,6 +1217,7 @@ void security_fixup_ops (struct security_operations *ops) set_to_dummy_if_null(ops, key_alloc); set_to_dummy_if_null(ops, key_free); set_to_dummy_if_null(ops, key_permission); + set_to_dummy_if_null(ops, key_getsecurity); #endif /* CONFIG_KEYS */ #ifdef CONFIG_AUDIT set_to_dummy_if_null(ops, audit_rule_init); diff --git a/security/keys/Makefile b/security/keys/Makefile index 5145adfb6a0..747a464943a 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile @@ -14,3 +14,4 @@ obj-y := \ obj-$(CONFIG_KEYS_COMPAT) += compat.o obj-$(CONFIG_PROC_FS) += proc.o +obj-$(CONFIG_SYSCTL) += sysctl.o diff --git a/security/keys/compat.c b/security/keys/compat.c index e10ec995f27..c766c68a63b 100644 --- a/security/keys/compat.c +++ b/security/keys/compat.c @@ -79,6 +79,9 @@ asmlinkage long compat_sys_keyctl(u32 option, case KEYCTL_ASSUME_AUTHORITY: return keyctl_assume_authority(arg2); + case KEYCTL_GET_SECURITY: + return keyctl_get_security(arg2, compat_ptr(arg3), arg4); + default: return -EOPNOTSUPP; } diff --git a/security/keys/internal.h b/security/keys/internal.h index 7d894ef7037..8c05587f501 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -57,10 +57,6 @@ struct key_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; @@ -68,6 +64,16 @@ 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); +/* + * key quota limits + * - root has its own separate limits to everyone else + */ +extern unsigned key_quota_root_maxkeys; +extern unsigned key_quota_root_maxbytes; +extern unsigned key_quota_maxkeys; +extern unsigned key_quota_maxbytes; + +#define KEYQUOTA_LINK_BYTES 4 /* a link in a keyring is worth 4 bytes */ extern struct rb_root key_serial_tree; @@ -77,8 +83,6 @@ extern struct mutex key_construction_mutex; 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 key_ref_t __keyring_search_one(key_ref_t keyring_ref, @@ -102,14 +106,15 @@ extern key_ref_t search_process_keyrings(struct key_type *type, key_match_func_t match, struct task_struct *tsk); -extern struct key *find_keyring_by_name(const char *name, key_serial_t bound); +extern struct key *find_keyring_by_name(const char *name, bool skip_perm_check); extern int install_thread_keyring(struct task_struct *tsk); extern int install_process_keyring(struct task_struct *tsk); extern struct key *request_key_and_link(struct key_type *type, const char *description, - const char *callout_info, + const void *callout_info, + size_t callout_len, void *aux, struct key *dest_keyring, unsigned long flags); @@ -120,13 +125,15 @@ extern struct key *request_key_and_link(struct key_type *type, struct request_key_auth { struct key *target_key; struct task_struct *context; - char *callout_info; + void *callout_info; + size_t callout_len; pid_t pid; }; extern struct key_type key_type_request_key_auth; extern struct key *request_key_auth_new(struct key *target, - const char *callout_info); + const void *callout_info, + size_t callout_len); extern struct key *key_get_instantiation_authkey(key_serial_t target_id); @@ -152,7 +159,8 @@ extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t); extern long keyctl_set_reqkey_keyring(int); extern long keyctl_set_timeout(key_serial_t, unsigned); extern long keyctl_assume_authority(key_serial_t); - +extern long keyctl_get_security(key_serial_t keyid, char __user *buffer, + size_t buflen); /* * debugging key validation diff --git a/security/keys/key.c b/security/keys/key.c index 654d23baf35..14948cf83ef 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -1,6 +1,6 @@ /* Basic authentication token and access key management * - * Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved. + * Copyright (C) 2004-2008 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or @@ -27,6 +27,11 @@ DEFINE_SPINLOCK(key_serial_lock); struct rb_root key_user_tree; /* tree of quota records indexed by UID */ DEFINE_SPINLOCK(key_user_lock); +unsigned int key_quota_root_maxkeys = 200; /* root's key count quota */ +unsigned int key_quota_root_maxbytes = 20000; /* root's key space quota */ +unsigned int key_quota_maxkeys = 200; /* general key count quota */ +unsigned int key_quota_maxbytes = 20000; /* general key space quota */ + static LIST_HEAD(key_types_list); static DECLARE_RWSEM(key_types_sem); @@ -139,36 +144,6 @@ void key_user_put(struct key_user *user) /*****************************************************************************/ /* - * 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 * - these are assigned randomly to avoid security issues through covert * channel problems @@ -266,11 +241,16 @@ struct key *key_alloc(struct key_type *type, const char *desc, /* check that the user's quota permits allocation of another key and * its description */ if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + unsigned maxkeys = (uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + spin_lock(&user->lock); if (!(flags & KEY_ALLOC_QUOTA_OVERRUN)) { - if (user->qnkeys + 1 >= KEYQUOTA_MAX_KEYS || - user->qnbytes + quotalen >= KEYQUOTA_MAX_BYTES - ) + if (user->qnkeys + 1 >= maxkeys || + user->qnbytes + quotalen >= maxbytes || + user->qnbytes + quotalen < user->qnbytes) goto no_quota; } @@ -375,11 +355,14 @@ int key_payload_reserve(struct key *key, size_t datalen) /* contemplate the quota adjustment */ if (delta != 0 && test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + unsigned maxbytes = (key->user->uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + spin_lock(&key->user->lock); if (delta > 0 && - key->user->qnbytes + delta > KEYQUOTA_MAX_BYTES - ) { + (key->user->qnbytes + delta >= maxbytes || + key->user->qnbytes + delta < key->user->qnbytes)) { ret = -EDQUOT; } else { @@ -757,11 +740,11 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, const char *description, const void *payload, size_t plen, + key_perm_t perm, unsigned long flags) { struct key_type *ktype; struct key *keyring, *key = NULL; - key_perm_t perm; key_ref_t key_ref; int ret; @@ -806,15 +789,17 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, goto found_matching_key; } - /* decide on the permissions we want */ - perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR; - perm |= KEY_USR_VIEW | KEY_USR_SEARCH | KEY_USR_LINK | KEY_USR_SETATTR; + /* if the client doesn't provide, decide on the permissions we want */ + if (perm == KEY_PERM_UNDEF) { + perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR; + perm |= KEY_USR_VIEW | KEY_USR_SEARCH | KEY_USR_LINK | KEY_USR_SETATTR; - if (ktype->read) - perm |= KEY_POS_READ | KEY_USR_READ; + if (ktype->read) + perm |= KEY_POS_READ | KEY_USR_READ; - if (ktype == &key_type_keyring || ktype->update) - perm |= KEY_USR_WRITE; + if (ktype == &key_type_keyring || ktype->update) + perm |= KEY_USR_WRITE; + } /* allocate a new key */ key = key_alloc(ktype, description, current->fsuid, current->fsgid, @@ -1018,17 +1003,4 @@ void __init key_init(void) 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 index d9ca15c109c..acc9c89e40a 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -19,6 +19,8 @@ #include <linux/capability.h> #include <linux/string.h> #include <linux/err.h> +#include <linux/vmalloc.h> +#include <linux/security.h> #include <asm/uaccess.h> #include "internal.h" @@ -62,9 +64,10 @@ asmlinkage long sys_add_key(const char __user *_type, char type[32], *description; void *payload; long ret; + bool vm; ret = -EINVAL; - if (plen > 32767) + if (plen > 1024 * 1024 - 1) goto error; /* draw all the data into kernel space */ @@ -81,11 +84,18 @@ asmlinkage long sys_add_key(const char __user *_type, /* pull the payload in if one was supplied */ payload = NULL; + vm = false; if (_payload) { ret = -ENOMEM; payload = kmalloc(plen, GFP_KERNEL); - if (!payload) - goto error2; + if (!payload) { + if (plen <= PAGE_SIZE) + goto error2; + vm = true; + payload = vmalloc(plen); + if (!payload) + goto error2; + } ret = -EFAULT; if (copy_from_user(payload, _payload, plen) != 0) @@ -102,7 +112,8 @@ asmlinkage long sys_add_key(const char __user *_type, /* create or update the requested key and add it to the target * keyring */ key_ref = key_create_or_update(keyring_ref, type, description, - payload, plen, KEY_ALLOC_IN_QUOTA); + payload, plen, KEY_PERM_UNDEF, + KEY_ALLOC_IN_QUOTA); if (!IS_ERR(key_ref)) { ret = key_ref_to_ptr(key_ref)->serial; key_ref_put(key_ref); @@ -113,7 +124,10 @@ asmlinkage long sys_add_key(const char __user *_type, key_ref_put(keyring_ref); error3: - kfree(payload); + if (!vm) + kfree(payload); + else + vfree(payload); error2: kfree(description); error: @@ -140,6 +154,7 @@ asmlinkage long sys_request_key(const char __user *_type, struct key_type *ktype; struct key *key; key_ref_t dest_ref; + size_t callout_len; char type[32], *description, *callout_info; long ret; @@ -157,12 +172,14 @@ asmlinkage long sys_request_key(const char __user *_type, /* pull the callout info into kernel space */ callout_info = NULL; + callout_len = 0; if (_callout_info) { callout_info = strndup_user(_callout_info, PAGE_SIZE); if (IS_ERR(callout_info)) { ret = PTR_ERR(callout_info); goto error2; } + callout_len = strlen(callout_info); } /* get the destination keyring if specified */ @@ -183,8 +200,8 @@ asmlinkage long sys_request_key(const char __user *_type, } /* do the search */ - key = request_key_and_link(ktype, description, callout_info, NULL, - key_ref_to_ptr(dest_ref), + key = request_key_and_link(ktype, description, callout_info, + callout_len, NULL, key_ref_to_ptr(dest_ref), KEY_ALLOC_IN_QUOTA); if (IS_ERR(key)) { ret = PTR_ERR(key); @@ -714,10 +731,16 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) /* transfer the quota burden to the new user */ if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + unsigned maxkeys = (uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + spin_lock(&newowner->lock); - if (newowner->qnkeys + 1 >= KEYQUOTA_MAX_KEYS || - newowner->qnbytes + key->quotalen >= - KEYQUOTA_MAX_BYTES) + if (newowner->qnkeys + 1 >= maxkeys || + newowner->qnbytes + key->quotalen >= maxbytes || + newowner->qnbytes + key->quotalen < + newowner->qnbytes) goto quota_overrun; newowner->qnkeys++; @@ -821,9 +844,10 @@ long keyctl_instantiate_key(key_serial_t id, key_ref_t keyring_ref; void *payload; long ret; + bool vm = false; ret = -EINVAL; - if (plen > 32767) + if (plen > 1024 * 1024 - 1) goto error; /* the appropriate instantiation authorisation key must have been @@ -843,8 +867,14 @@ long keyctl_instantiate_key(key_serial_t id, if (_payload) { ret = -ENOMEM; payload = kmalloc(plen, GFP_KERNEL); - if (!payload) - goto error; + if (!payload) { + if (plen <= PAGE_SIZE) + goto error; + vm = true; + payload = vmalloc(plen); + if (!payload) + goto error; + } ret = -EFAULT; if (copy_from_user(payload, _payload, plen) != 0) @@ -877,7 +907,10 @@ long keyctl_instantiate_key(key_serial_t id, } error2: - kfree(payload); + if (!vm) + kfree(payload); + else + vfree(payload); error: return ret; @@ -1055,6 +1088,66 @@ error: } /* end keyctl_assume_authority() */ +/* + * get the security label of a key + * - the key must grant us 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 information available, + * irrespective of how much we may have copied (including the terminal NUL) + * - implements keyctl(KEYCTL_GET_SECURITY) + */ +long keyctl_get_security(key_serial_t keyid, + char __user *buffer, + size_t buflen) +{ + struct key *key, *instkey; + key_ref_t key_ref; + char *context; + long ret; + + key_ref = lookup_user_key(NULL, keyid, 0, 1, KEY_VIEW); + if (IS_ERR(key_ref)) { + if (PTR_ERR(key_ref) != -EACCES) + return PTR_ERR(key_ref); + + /* viewing a key under construction is also permitted if we + * have the authorisation token handy */ + instkey = key_get_instantiation_authkey(keyid); + if (IS_ERR(instkey)) + return PTR_ERR(key_ref); + key_put(instkey); + + key_ref = lookup_user_key(NULL, keyid, 0, 1, 0); + if (IS_ERR(key_ref)) + return PTR_ERR(key_ref); + } + + key = key_ref_to_ptr(key_ref); + ret = security_key_getsecurity(key, &context); + if (ret == 0) { + /* if no information was returned, give userspace an empty + * string */ + ret = 1; + if (buffer && buflen > 0 && + copy_to_user(buffer, "", 1) != 0) + ret = -EFAULT; + } else if (ret > 0) { + /* return as much data as there's room for */ + if (buffer && buflen > 0) { + if (buflen > ret) + buflen = ret; + + if (copy_to_user(buffer, context, buflen) != 0) + ret = -EFAULT; + } + + kfree(context); + } + + key_ref_put(key_ref); + return ret; +} + /*****************************************************************************/ /* * the key control system call @@ -1135,6 +1228,11 @@ asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3, case KEYCTL_ASSUME_AUTHORITY: return keyctl_assume_authority((key_serial_t) arg2); + case KEYCTL_GET_SECURITY: + return keyctl_get_security((key_serial_t) arg2, + (char *) arg3, + (size_t) arg4); + default: return -EOPNOTSUPP; } diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 88292e3dee9..a9ab8affc09 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -1,6 +1,6 @@ -/* keyring.c: keyring handling +/* Keyring handling * - * Copyright (C) 2004-5 Red Hat, Inc. All Rights Reserved. + * Copyright (C) 2004-2005, 2008 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or @@ -79,7 +79,7 @@ static 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) +static void keyring_publish_name(struct key *keyring) { int bucket; @@ -292,7 +292,7 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref, struct keyring_list *keylist; struct timespec now; - unsigned long possessed; + unsigned long possessed, kflags; struct key *keyring, *key; key_ref_t key_ref; long err; @@ -319,6 +319,32 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref, err = -EAGAIN; sp = 0; + /* firstly we should check to see if this top-level keyring is what we + * are looking for */ + key_ref = ERR_PTR(-EAGAIN); + kflags = keyring->flags; + if (keyring->type == type && match(keyring, description)) { + key = keyring; + + /* check it isn't negative and hasn't expired or been + * revoked */ + if (kflags & (1 << KEY_FLAG_REVOKED)) + goto error_2; + if (key->expiry && now.tv_sec >= key->expiry) + goto error_2; + key_ref = ERR_PTR(-ENOKEY); + if (kflags & (1 << KEY_FLAG_NEGATIVE)) + goto error_2; + goto found; + } + + /* otherwise, the top keyring must not be revoked, expired, or + * negatively instantiated if we are to search it */ + key_ref = ERR_PTR(-EAGAIN); + if (kflags & ((1 << KEY_FLAG_REVOKED) | (1 << KEY_FLAG_NEGATIVE)) || + (keyring->expiry && now.tv_sec >= keyring->expiry)) + goto error_2; + /* start processing a new keyring */ descend: if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) @@ -331,13 +357,14 @@ descend: /* iterate through the keys in this keyring first */ for (kix = 0; kix < keylist->nkeys; kix++) { key = keylist->keys[kix]; + kflags = key->flags; /* ignore keys not of this type */ if (key->type != type) continue; /* skip revoked keys and expired keys */ - if (test_bit(KEY_FLAG_REVOKED, &key->flags)) + if (kflags & (1 << KEY_FLAG_REVOKED)) continue; if (key->expiry && now.tv_sec >= key->expiry) @@ -352,8 +379,8 @@ descend: context, KEY_SEARCH) < 0) continue; - /* we set a different error code if we find a negative key */ - if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) { + /* we set a different error code if we pass a negative key */ + if (kflags & (1 << KEY_FLAG_NEGATIVE)) { err = -ENOKEY; continue; } @@ -489,10 +516,9 @@ key_ref_t __keyring_search_one(key_ref_t keyring_ref, /* * 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 + * - normally only finds keyrings with search permission for the current process */ -struct key *find_keyring_by_name(const char *name, key_serial_t bound) +struct key *find_keyring_by_name(const char *name, bool skip_perm_check) { struct key *keyring; int bucket; @@ -518,15 +544,11 @@ struct key *find_keyring_by_name(const char *name, key_serial_t bound) if (strcmp(keyring->description, name) != 0) continue; - if (key_permission(make_key_ref(keyring, 0), + if (!skip_perm_check && + key_permission(make_key_ref(keyring, 0), KEY_SEARCH) < 0) 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); diff --git a/security/keys/proc.c b/security/keys/proc.c index 694126003ed..f619170da76 100644 --- a/security/keys/proc.c +++ b/security/keys/proc.c @@ -70,19 +70,15 @@ 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); + p = proc_create("keys", 0, NULL, &proc_keys_fops); if (!p) panic("Cannot create /proc/keys\n"); - - p->proc_fops = &proc_keys_fops; #endif - p = create_proc_entry("key-users", 0, NULL); + p = proc_create("key-users", 0, NULL, &proc_key_users_fops); if (!p) panic("Cannot create /proc/key-users\n"); - p->proc_fops = &proc_key_users_fops; - return 0; } /* end key_proc_init() */ @@ -246,6 +242,10 @@ 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); + unsigned maxkeys = (user->uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (user->uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; seq_printf(m, "%5u: %5d %d/%d %d/%d %d/%d\n", user->uid, @@ -253,10 +253,9 @@ static int proc_key_users_show(struct seq_file *m, void *v) atomic_read(&user->nkeys), atomic_read(&user->nikeys), user->qnkeys, - KEYQUOTA_MAX_KEYS, + maxkeys, user->qnbytes, - KEYQUOTA_MAX_BYTES - ); + maxbytes); return 0; diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index c886a2bb792..5be6d018759 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -1,6 +1,6 @@ -/* process_keys.c: management of a process's keyrings +/* Management of a process's keyrings * - * Copyright (C) 2004-5 Red Hat, Inc. All Rights Reserved. + * Copyright (C) 2004-2005, 2008 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or @@ -23,6 +23,9 @@ /* session keyring create vs join semaphore */ static DEFINE_MUTEX(key_session_mutex); +/* user keyring creation semaphore */ +static DEFINE_MUTEX(key_user_keyring_mutex); + /* the root user's tracking struct */ struct key_user root_key_user = { .usage = ATOMIC_INIT(3), @@ -33,78 +36,84 @@ struct key_user root_key_user = { .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, - .sem = __RWSEM_INITIALIZER(root_user_keyring.sem), - .perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL, - .flags = 1 << 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, - .sem = __RWSEM_INITIALIZER(root_session_keyring.sem), - .perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL, - .flags = 1 << KEY_FLAG_INSTANTIATED, - .description = "_uid_ses.0", -#ifdef KEY_DEBUGGING - .magic = KEY_DEBUG_MAGIC, -#endif -}; - /*****************************************************************************/ /* - * allocate the keyrings to be associated with a UID + * install user and user session keyrings for a particular UID */ -int alloc_uid_keyring(struct user_struct *user, - struct task_struct *ctx) +static int install_user_keyrings(struct task_struct *tsk) { + struct user_struct *user = tsk->user; struct key *uid_keyring, *session_keyring; char buf[20]; int ret; - /* concoct a default session keyring */ - sprintf(buf, "_uid_ses.%u", user->uid); + kenter("%p{%u}", user, user->uid); - session_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, ctx, - KEY_ALLOC_IN_QUOTA, NULL); - if (IS_ERR(session_keyring)) { - ret = PTR_ERR(session_keyring); - goto error; + if (user->uid_keyring) { + kleave(" = 0 [exist]"); + return 0; } - /* and a UID specific keyring, pointed to by the default session - * keyring */ - sprintf(buf, "_uid.%u", user->uid); + mutex_lock(&key_user_keyring_mutex); + ret = 0; - uid_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, ctx, - KEY_ALLOC_IN_QUOTA, session_keyring); - if (IS_ERR(uid_keyring)) { - key_put(session_keyring); - ret = PTR_ERR(uid_keyring); - goto error; + if (!user->uid_keyring) { + /* get the UID-specific keyring + * - there may be one in existence already as it may have been + * pinned by a session, but the user_struct pointing to it + * may have been destroyed by setuid */ + sprintf(buf, "_uid.%u", user->uid); + + uid_keyring = find_keyring_by_name(buf, true); + if (IS_ERR(uid_keyring)) { + uid_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, + tsk, KEY_ALLOC_IN_QUOTA, + NULL); + if (IS_ERR(uid_keyring)) { + ret = PTR_ERR(uid_keyring); + goto error; + } + } + + /* get a default session keyring (which might also exist + * already) */ + sprintf(buf, "_uid_ses.%u", user->uid); + + session_keyring = find_keyring_by_name(buf, true); + if (IS_ERR(session_keyring)) { + session_keyring = + keyring_alloc(buf, user->uid, (gid_t) -1, + tsk, KEY_ALLOC_IN_QUOTA, NULL); + if (IS_ERR(session_keyring)) { + ret = PTR_ERR(session_keyring); + goto error_release; + } + + /* we install a link from the user session keyring to + * the user keyring */ + ret = key_link(session_keyring, uid_keyring); + if (ret < 0) + goto error_release_both; + } + + /* install the keyrings */ + user->uid_keyring = uid_keyring; + user->session_keyring = session_keyring; } - /* install the keyrings */ - user->uid_keyring = uid_keyring; - user->session_keyring = session_keyring; - ret = 0; + mutex_unlock(&key_user_keyring_mutex); + kleave(" = 0"); + return 0; +error_release_both: + key_put(session_keyring); +error_release: + key_put(uid_keyring); error: + mutex_unlock(&key_user_keyring_mutex); + kleave(" = %d", ret); return ret; - -} /* end alloc_uid_keyring() */ +} /*****************************************************************************/ /* @@ -481,7 +490,7 @@ key_ref_t search_process_keyrings(struct key_type *type, } } /* or search the user-session keyring */ - else { + else if (context->user->session_keyring) { key_ref = keyring_search_aux( make_key_ref(context->user->session_keyring, 1), context, type, description, match); @@ -614,6 +623,9 @@ key_ref_t lookup_user_key(struct task_struct *context, key_serial_t id, if (!context->signal->session_keyring) { /* always install a session keyring upon access if one * doesn't exist yet */ + ret = install_user_keyrings(context); + if (ret < 0) + goto error; ret = install_session_keyring( context, context->user->session_keyring); if (ret < 0) @@ -628,12 +640,24 @@ key_ref_t lookup_user_key(struct task_struct *context, key_serial_t id, break; case KEY_SPEC_USER_KEYRING: + if (!context->user->uid_keyring) { + ret = install_user_keyrings(context); + if (ret < 0) + goto error; + } + key = context->user->uid_keyring; atomic_inc(&key->usage); key_ref = make_key_ref(key, 1); break; case KEY_SPEC_USER_SESSION_KEYRING: + if (!context->user->session_keyring) { + ret = install_user_keyrings(context); + if (ret < 0) + goto error; + } + key = context->user->session_keyring; atomic_inc(&key->usage); key_ref = make_key_ref(key, 1); @@ -744,7 +768,7 @@ long join_session_keyring(const char *name) mutex_lock(&key_session_mutex); /* look for an existing keyring of this name */ - keyring = find_keyring_by_name(name, 0); + keyring = find_keyring_by_name(name, false); if (PTR_ERR(keyring) == -ENOKEY) { /* not found - try and create a new one */ keyring = keyring_alloc(name, tsk->uid, tsk->gid, tsk, diff --git a/security/keys/request_key.c b/security/keys/request_key.c index 5ecc5057fb5..ba32ca6469b 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -16,6 +16,7 @@ #include <linux/kmod.h> #include <linux/err.h> #include <linux/keyctl.h> +#include <linux/slab.h> #include "internal.h" /* @@ -161,21 +162,22 @@ error_alloc: * call out to userspace for key construction * - we ignore program failure and go on key status instead */ -static int construct_key(struct key *key, const char *callout_info, void *aux) +static int construct_key(struct key *key, const void *callout_info, + size_t callout_len, void *aux) { struct key_construction *cons; request_key_actor_t actor; struct key *authkey; int ret; - kenter("%d,%s,%p", key->serial, callout_info, aux); + kenter("%d,%p,%zu,%p", key->serial, callout_info, callout_len, aux); cons = kmalloc(sizeof(*cons), GFP_KERNEL); if (!cons) return -ENOMEM; /* allocate an authorisation key */ - authkey = request_key_auth_new(key, callout_info); + authkey = request_key_auth_new(key, callout_info, callout_len); if (IS_ERR(authkey)) { kfree(cons); ret = PTR_ERR(authkey); @@ -331,6 +333,7 @@ alloc_failed: static struct key *construct_key_and_link(struct key_type *type, const char *description, const char *callout_info, + size_t callout_len, void *aux, struct key *dest_keyring, unsigned long flags) @@ -348,7 +351,7 @@ static struct key *construct_key_and_link(struct key_type *type, key_user_put(user); if (ret == 0) { - ret = construct_key(key, callout_info, aux); + ret = construct_key(key, callout_info, callout_len, aux); if (ret < 0) goto construction_failed; } @@ -370,7 +373,8 @@ construction_failed: */ struct key *request_key_and_link(struct key_type *type, const char *description, - const char *callout_info, + const void *callout_info, + size_t callout_len, void *aux, struct key *dest_keyring, unsigned long flags) @@ -378,8 +382,8 @@ struct key *request_key_and_link(struct key_type *type, struct key *key; key_ref_t key_ref; - kenter("%s,%s,%s,%p,%p,%lx", - type->name, description, callout_info, aux, + kenter("%s,%s,%p,%zu,%p,%p,%lx", + type->name, description, callout_info, callout_len, aux, dest_keyring, flags); /* search all the process keyrings for a key */ @@ -398,7 +402,8 @@ struct key *request_key_and_link(struct key_type *type, goto error; key = construct_key_and_link(type, description, callout_info, - aux, dest_keyring, flags); + callout_len, aux, dest_keyring, + flags); } error: @@ -434,10 +439,13 @@ struct key *request_key(struct key_type *type, const char *callout_info) { struct key *key; + size_t callout_len = 0; int ret; - key = request_key_and_link(type, description, callout_info, NULL, - NULL, KEY_ALLOC_IN_QUOTA); + if (callout_info) + callout_len = strlen(callout_info); + key = request_key_and_link(type, description, callout_info, callout_len, + NULL, NULL, KEY_ALLOC_IN_QUOTA); if (!IS_ERR(key)) { ret = wait_for_key_construction(key, false); if (ret < 0) { @@ -458,14 +466,15 @@ EXPORT_SYMBOL(request_key); */ struct key *request_key_with_auxdata(struct key_type *type, const char *description, - const char *callout_info, + const void *callout_info, + size_t callout_len, void *aux) { struct key *key; int ret; - key = request_key_and_link(type, description, callout_info, aux, - NULL, KEY_ALLOC_IN_QUOTA); + key = request_key_and_link(type, description, callout_info, callout_len, + aux, NULL, KEY_ALLOC_IN_QUOTA); if (!IS_ERR(key)) { ret = wait_for_key_construction(key, false); if (ret < 0) { @@ -485,10 +494,12 @@ EXPORT_SYMBOL(request_key_with_auxdata); */ struct key *request_key_async(struct key_type *type, const char *description, - const char *callout_info) + const void *callout_info, + size_t callout_len) { - return request_key_and_link(type, description, callout_info, NULL, - NULL, KEY_ALLOC_IN_QUOTA); + return request_key_and_link(type, description, callout_info, + callout_len, NULL, NULL, + KEY_ALLOC_IN_QUOTA); } EXPORT_SYMBOL(request_key_async); @@ -500,10 +511,11 @@ EXPORT_SYMBOL(request_key_async); */ struct key *request_key_async_with_auxdata(struct key_type *type, const char *description, - const char *callout_info, + const void *callout_info, + size_t callout_len, void *aux) { - return request_key_and_link(type, description, callout_info, aux, - NULL, KEY_ALLOC_IN_QUOTA); + return request_key_and_link(type, description, callout_info, + callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA); } EXPORT_SYMBOL(request_key_async_with_auxdata); diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c index e42b5252486..bd237b0a633 100644 --- a/security/keys/request_key_auth.c +++ b/security/keys/request_key_auth.c @@ -15,6 +15,7 @@ #include <linux/sched.h> #include <linux/err.h> #include <linux/seq_file.h> +#include <linux/slab.h> #include <asm/uaccess.h> #include "internal.h" @@ -61,7 +62,7 @@ static void request_key_auth_describe(const struct key *key, seq_puts(m, "key:"); seq_puts(m, key->description); - seq_printf(m, " pid:%d ci:%zu", rka->pid, strlen(rka->callout_info)); + seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len); } /* end request_key_auth_describe() */ @@ -77,7 +78,7 @@ static long request_key_auth_read(const struct key *key, size_t datalen; long ret; - datalen = strlen(rka->callout_info); + datalen = rka->callout_len; ret = datalen; /* we can return the data as is */ @@ -137,7 +138,8 @@ static void request_key_auth_destroy(struct key *key) * create an authorisation token for /sbin/request-key or whoever to gain * access to the caller's security data */ -struct key *request_key_auth_new(struct key *target, const char *callout_info) +struct key *request_key_auth_new(struct key *target, const void *callout_info, + size_t callout_len) { struct request_key_auth *rka, *irka; struct key *authkey = NULL; @@ -152,7 +154,7 @@ struct key *request_key_auth_new(struct key *target, const char *callout_info) kleave(" = -ENOMEM"); return ERR_PTR(-ENOMEM); } - rka->callout_info = kmalloc(strlen(callout_info) + 1, GFP_KERNEL); + rka->callout_info = kmalloc(callout_len, GFP_KERNEL); if (!rka->callout_info) { kleave(" = -ENOMEM"); kfree(rka); @@ -186,7 +188,8 @@ struct key *request_key_auth_new(struct key *target, const char *callout_info) } rka->target_key = key_get(target); - strcpy(rka->callout_info, callout_info); + memcpy(rka->callout_info, callout_info, callout_len); + rka->callout_len = callout_len; /* allocate the auth key */ sprintf(desc, "%x", target->serial); diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c new file mode 100644 index 00000000000..b611d493c2d --- /dev/null +++ b/security/keys/sysctl.c @@ -0,0 +1,50 @@ +/* Key management controls + * + * Copyright (C) 2008 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 Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/key.h> +#include <linux/sysctl.h> +#include "internal.h" + +ctl_table key_sysctls[] = { + { + .ctl_name = CTL_UNNUMBERED, + .procname = "maxkeys", + .data = &key_quota_maxkeys, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { + .ctl_name = CTL_UNNUMBERED, + .procname = "maxbytes", + .data = &key_quota_maxbytes, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { + .ctl_name = CTL_UNNUMBERED, + .procname = "root_maxkeys", + .data = &key_quota_root_maxkeys, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { + .ctl_name = CTL_UNNUMBERED, + .procname = "root_maxbytes", + .data = &key_quota_root_maxbytes, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { .ctl_name = 0 } +}; diff --git a/security/root_plug.c b/security/root_plug.c index 6112d1404c8..a41cf42a4fa 100644 --- a/security/root_plug.c +++ b/security/root_plug.c @@ -86,6 +86,7 @@ static struct security_operations rootplug_security_ops = { .task_post_setuid = cap_task_post_setuid, .task_reparent_to_init = cap_task_reparent_to_init, + .task_prctl = cap_task_prctl, .bprm_check_security = rootplug_bprm_check_security, }; diff --git a/security/security.c b/security/security.c index 8a285c7b996..8e64a29dc55 100644 --- a/security/security.c +++ b/security/security.c @@ -491,23 +491,23 @@ void security_inode_delete(struct inode *inode) security_ops->inode_delete(inode); } -int security_inode_setxattr(struct dentry *dentry, char *name, - void *value, size_t size, int flags) +int security_inode_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) { if (unlikely(IS_PRIVATE(dentry->d_inode))) return 0; return security_ops->inode_setxattr(dentry, name, value, size, flags); } -void security_inode_post_setxattr(struct dentry *dentry, char *name, - void *value, size_t size, int flags) +void security_inode_post_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) { if (unlikely(IS_PRIVATE(dentry->d_inode))) return; security_ops->inode_post_setxattr(dentry, name, value, size, flags); } -int security_inode_getxattr(struct dentry *dentry, char *name) +int security_inode_getxattr(struct dentry *dentry, const char *name) { if (unlikely(IS_PRIVATE(dentry->d_inode))) return 0; @@ -521,7 +521,7 @@ int security_inode_listxattr(struct dentry *dentry) return security_ops->inode_listxattr(dentry); } -int security_inode_removexattr(struct dentry *dentry, char *name) +int security_inode_removexattr(struct dentry *dentry, const char *name) { if (unlikely(IS_PRIVATE(dentry->d_inode))) return 0; @@ -733,9 +733,9 @@ int security_task_wait(struct task_struct *p) } int security_task_prctl(int option, unsigned long arg2, unsigned long arg3, - unsigned long arg4, unsigned long arg5) + unsigned long arg4, unsigned long arg5, long *rc_p) { - return security_ops->task_prctl(option, arg2, arg3, arg4, arg5); + return security_ops->task_prctl(option, arg2, arg3, arg4, arg5, rc_p); } void security_task_reparent_to_init(struct task_struct *p) @@ -1156,6 +1156,11 @@ int security_key_permission(key_ref_t key_ref, return security_ops->key_permission(key_ref, context, perm); } +int security_key_getsecurity(struct key *key, char **_buffer) +{ + return security_ops->key_getsecurity(key, _buffer); +} + #endif /* CONFIG_KEYS */ #ifdef CONFIG_AUDIT diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 308e2cf17d7..4e4de98941a 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2619,7 +2619,7 @@ static int selinux_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) return dentry_has_perm(current, mnt, dentry, FILE__GETATTR); } -static int selinux_inode_setotherxattr(struct dentry *dentry, char *name) +static int selinux_inode_setotherxattr(struct dentry *dentry, const char *name) { if (!strncmp(name, XATTR_SECURITY_PREFIX, sizeof XATTR_SECURITY_PREFIX - 1)) { @@ -2638,7 +2638,8 @@ static int selinux_inode_setotherxattr(struct dentry *dentry, char *name) return dentry_has_perm(current, NULL, dentry, FILE__SETATTR); } -static int selinux_inode_setxattr(struct dentry *dentry, char *name, void *value, size_t size, int flags) +static int selinux_inode_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) { struct task_security_struct *tsec = current->security; struct inode *inode = dentry->d_inode; @@ -2687,8 +2688,9 @@ static int selinux_inode_setxattr(struct dentry *dentry, char *name, void *value &ad); } -static void selinux_inode_post_setxattr(struct dentry *dentry, char *name, - void *value, size_t size, int flags) +static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, + int flags) { struct inode *inode = dentry->d_inode; struct inode_security_struct *isec = inode->i_security; @@ -2711,7 +2713,7 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, char *name, return; } -static int selinux_inode_getxattr(struct dentry *dentry, char *name) +static int selinux_inode_getxattr(struct dentry *dentry, const char *name) { return dentry_has_perm(current, NULL, dentry, FILE__GETATTR); } @@ -2721,7 +2723,7 @@ 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) +static int selinux_inode_removexattr(struct dentry *dentry, const char *name) { if (strcmp(name, XATTR_NAME_SELINUX)) return selinux_inode_setotherxattr(dentry, name); @@ -3303,12 +3305,13 @@ static int selinux_task_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, - unsigned long arg5) + unsigned long arg5, + long *rc_p) { /* 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; + return secondary_ops->task_prctl(option, arg2, arg3, arg4, arg5, rc_p); } static int selinux_task_wait(struct task_struct *p) @@ -5297,6 +5300,20 @@ static int selinux_key_permission(key_ref_t key_ref, SECCLASS_KEY, perm, NULL); } +static int selinux_key_getsecurity(struct key *key, char **_buffer) +{ + struct key_security_struct *ksec = key->security; + char *context = NULL; + unsigned len; + int rc; + + rc = security_sid_to_context(ksec->sid, &context, &len); + if (!rc) + rc = len; + *_buffer = context; + return rc; +} + #endif static struct security_operations selinux_ops = { @@ -5485,6 +5502,7 @@ static struct security_operations selinux_ops = { .key_alloc = selinux_key_alloc, .key_free = selinux_key_free, .key_permission = selinux_key_permission, + .key_getsecurity = selinux_key_getsecurity, #endif #ifdef CONFIG_AUDIT @@ -5533,14 +5551,6 @@ static __init int selinux_init(void) else printk(KERN_DEBUG "SELinux: Starting in permissive mode\n"); -#ifdef CONFIG_KEYS - /* Add security information to initial keyrings */ - selinux_key_alloc(&root_user_keyring, current, - KEY_ALLOC_NOT_IN_QUOTA); - selinux_key_alloc(&root_session_keyring, current, - KEY_ALLOC_NOT_IN_QUOTA); -#endif - return 0; } diff --git a/security/selinux/include/avc_ss.h b/security/selinux/include/avc_ss.h index ff869e8b6f4..c0d314d9f8e 100644 --- a/security/selinux/include/avc_ss.h +++ b/security/selinux/include/avc_ss.h @@ -10,22 +10,19 @@ int avc_ss_reset(u32 seqno); -struct av_perm_to_string -{ +struct av_perm_to_string { u16 tclass; u32 value; const char *name; }; -struct av_inherit -{ +struct av_inherit { u16 tclass; const char **common_pts; u32 common_base; }; -struct selinux_class_perm -{ +struct selinux_class_perm { const struct av_perm_to_string *av_perm_to_string; u32 av_pts_len; const char **class_to_string; diff --git a/security/selinux/include/netlabel.h b/security/selinux/include/netlabel.h index 9a9e7cd9a37..487a7d81fe2 100644 --- a/security/selinux/include/netlabel.h +++ b/security/selinux/include/netlabel.h @@ -64,7 +64,7 @@ static inline void selinux_netlbl_cache_invalidate(void) } static inline void selinux_netlbl_sk_security_reset( - struct sk_security_struct *ssec, + struct sk_security_struct *ssec, int family) { return; diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 300b61bad7b..032c2357dad 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -4,16 +4,16 @@ * 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> + * 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. + * as published by the Free Software Foundation. */ #ifndef _SELINUX_OBJSEC_H_ #define _SELINUX_OBJSEC_H_ @@ -28,58 +28,58 @@ #include "avc.h" struct task_security_struct { - u32 osid; /* SID prior to last execve */ - u32 sid; /* current SID */ - u32 exec_sid; /* exec SID */ - u32 create_sid; /* fscreate SID */ - u32 keycreate_sid; /* keycreate SID */ - u32 sockcreate_sid; /* fscreate SID */ + u32 osid; /* SID prior to last execve */ + u32 sid; /* current SID */ + u32 exec_sid; /* exec SID */ + u32 create_sid; /* fscreate SID */ + u32 keycreate_sid; /* keycreate SID */ + u32 sockcreate_sid; /* fscreate SID */ }; struct inode_security_struct { - 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 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 mutex lock; - unsigned char inherit; /* inherit SID from parent entry */ + unsigned char inherit; /* inherit SID from parent entry */ }; struct file_security_struct { - u32 sid; /* SID of open file description */ - u32 fown_sid; /* SID of file owner (for SIGIO) */ - u32 isid; /* SID of inode at the time of file open */ - u32 pseqno; /* Policy seqno at the time of file open */ + u32 sid; /* SID of open file description */ + u32 fown_sid; /* SID of file owner (for SIGIO) */ + u32 isid; /* SID of inode at the time of file open */ + u32 pseqno; /* Policy seqno at the time of file open */ }; struct superblock_security_struct { - struct super_block *sb; /* back pointer to sb object */ - struct list_head list; /* list of superblock_security_struct */ + struct super_block *sb; /* back pointer to sb object */ + struct list_head list; /* list of superblock_security_struct */ u32 sid; /* SID of file system superblock */ u32 def_sid; /* default SID for labeling */ u32 mntpoint_sid; /* SECURITY_FS_USE_MNTPOINT context for files */ - unsigned int behavior; /* labeling behavior */ - unsigned char initialized; /* initialization flag */ + unsigned int behavior; /* labeling behavior */ + unsigned char initialized; /* initialization flag */ unsigned char flags; /* which mount options were specified */ - unsigned char proc; /* proc fs */ + unsigned char proc; /* proc fs */ struct mutex lock; struct list_head isec_head; spinlock_t isec_lock; }; struct msg_security_struct { - u32 sid; /* SID of message */ + u32 sid; /* SID of message */ }; struct ipc_security_struct { u16 sclass; /* security class of this object */ - u32 sid; /* SID of IPC resource */ + u32 sid; /* SID of IPC resource */ }; struct bprm_security_struct { - u32 sid; /* SID for transformed process */ + u32 sid; /* SID for transformed process */ unsigned char set; /* @@ -123,7 +123,7 @@ struct sk_security_struct { }; struct key_security_struct { - u32 sid; /* SID of key */ + u32 sid; /* SID of key */ }; extern unsigned int selinux_checkreqprot; diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 1904c462a60..cdb14add27d 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -62,7 +62,7 @@ enum { extern int selinux_policycap_netpeer; extern int selinux_policycap_openperm; -int security_load_policy(void * data, size_t len); +int security_load_policy(void *data, size_t len); int security_policycap_supported(unsigned int req_cap); @@ -93,7 +93,7 @@ int security_change_sid(u32 ssid, u32 tsid, int security_sid_to_context(u32 sid, char **scontext, u32 *scontext_len); -int security_context_to_sid(char *scontext, u32 scontext_len, +int security_context_to_sid(const char *scontext, u32 scontext_len, u32 *out_sid); int security_context_to_sid_default(char *scontext, u32 scontext_len, @@ -110,7 +110,7 @@ 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); + u16 tclass); int security_sid_mls_copy(u32 sid, u32 mls_sid, u32 *new_sid); diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c index 2edc4c5e0c6..b6ccd09379f 100644 --- a/security/selinux/netnode.c +++ b/security/selinux/netnode.c @@ -40,11 +40,17 @@ #include <net/ipv6.h> #include <asm/bug.h> +#include "netnode.h" #include "objsec.h" #define SEL_NETNODE_HASH_SIZE 256 #define SEL_NETNODE_HASH_BKT_LIMIT 16 +struct sel_netnode_bkt { + unsigned int size; + struct list_head list; +}; + struct sel_netnode { struct netnode_security_struct nsec; @@ -60,7 +66,7 @@ struct sel_netnode { static LIST_HEAD(sel_netnode_list); static DEFINE_SPINLOCK(sel_netnode_lock); -static struct list_head sel_netnode_hash[SEL_NETNODE_HASH_SIZE]; +static struct sel_netnode_bkt sel_netnode_hash[SEL_NETNODE_HASH_SIZE]; /** * sel_netnode_free - Frees a node entry @@ -87,7 +93,7 @@ static void sel_netnode_free(struct rcu_head *p) * the bucket number for the given IP address. * */ -static u32 sel_netnode_hashfn_ipv4(__be32 addr) +static unsigned int sel_netnode_hashfn_ipv4(__be32 addr) { /* at some point we should determine if the mismatch in byte order * affects the hash function dramatically */ @@ -103,7 +109,7 @@ static u32 sel_netnode_hashfn_ipv4(__be32 addr) * the bucket number for the given IP address. * */ -static u32 sel_netnode_hashfn_ipv6(const struct in6_addr *addr) +static unsigned int sel_netnode_hashfn_ipv6(const struct in6_addr *addr) { /* just hash the least significant 32 bits to keep things fast (they * are the most likely to be different anyway), we can revisit this @@ -123,7 +129,7 @@ static u32 sel_netnode_hashfn_ipv6(const struct in6_addr *addr) */ static struct sel_netnode *sel_netnode_find(const void *addr, u16 family) { - u32 idx; + unsigned int idx; struct sel_netnode *node; switch (family) { @@ -137,7 +143,7 @@ static struct sel_netnode *sel_netnode_find(const void *addr, u16 family) BUG(); } - list_for_each_entry_rcu(node, &sel_netnode_hash[idx], list) + list_for_each_entry_rcu(node, &sel_netnode_hash[idx].list, list) if (node->nsec.family == family) switch (family) { case PF_INET: @@ -159,15 +165,12 @@ static struct sel_netnode *sel_netnode_find(const void *addr, u16 family) * @node: the new node record * * Description: - * Add a new node record to the network address hash table. Returns zero on - * success, negative values on failure. + * Add a new node record to the network address hash table. * */ -static int sel_netnode_insert(struct sel_netnode *node) +static void sel_netnode_insert(struct sel_netnode *node) { - u32 idx; - u32 count = 0; - struct sel_netnode *iter; + unsigned int idx; switch (node->nsec.family) { case PF_INET: @@ -179,32 +182,21 @@ static int sel_netnode_insert(struct sel_netnode *node) default: BUG(); } - list_add_rcu(&node->list, &sel_netnode_hash[idx]); + + INIT_RCU_HEAD(&node->rcu); /* we need to impose a limit on the growth of the hash table so check * this bucket to make sure it is within the specified bounds */ - list_for_each_entry(iter, &sel_netnode_hash[idx], list) - if (++count > SEL_NETNODE_HASH_BKT_LIMIT) { - list_del_rcu(&iter->list); - call_rcu(&iter->rcu, sel_netnode_free); - break; - } - - return 0; -} - -/** - * sel_netnode_destroy - Remove a node record from the table - * @node: the existing node record - * - * Description: - * Remove an existing node record from the network address table. - * - */ -static void sel_netnode_destroy(struct sel_netnode *node) -{ - list_del_rcu(&node->list); - call_rcu(&node->rcu, sel_netnode_free); + list_add_rcu(&node->list, &sel_netnode_hash[idx].list); + if (sel_netnode_hash[idx].size == SEL_NETNODE_HASH_BKT_LIMIT) { + struct sel_netnode *tail; + tail = list_entry( + rcu_dereference(sel_netnode_hash[idx].list.prev), + struct sel_netnode, list); + list_del_rcu(&tail->list); + call_rcu(&tail->rcu, sel_netnode_free); + } else + sel_netnode_hash[idx].size++; } /** @@ -222,7 +214,7 @@ static void sel_netnode_destroy(struct sel_netnode *node) */ static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid) { - int ret; + int ret = -ENOMEM; struct sel_netnode *node; struct sel_netnode *new = NULL; @@ -230,25 +222,21 @@ static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid) node = sel_netnode_find(addr, family); if (node != NULL) { *sid = node->nsec.sid; - ret = 0; - goto out; + spin_unlock_bh(&sel_netnode_lock); + return 0; } new = kzalloc(sizeof(*new), GFP_ATOMIC); - if (new == NULL) { - ret = -ENOMEM; + if (new == NULL) goto out; - } switch (family) { case PF_INET: ret = security_node_sid(PF_INET, - addr, sizeof(struct in_addr), - &new->nsec.sid); + addr, sizeof(struct in_addr), sid); new->nsec.addr.ipv4 = *(__be32 *)addr; break; case PF_INET6: ret = security_node_sid(PF_INET6, - addr, sizeof(struct in6_addr), - &new->nsec.sid); + addr, sizeof(struct in6_addr), sid); ipv6_addr_copy(&new->nsec.addr.ipv6, addr); break; default: @@ -256,11 +244,10 @@ static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid) } if (ret != 0) goto out; + new->nsec.family = family; - ret = sel_netnode_insert(new); - if (ret != 0) - goto out; - *sid = new->nsec.sid; + new->nsec.sid = *sid; + sel_netnode_insert(new); out: spin_unlock_bh(&sel_netnode_lock); @@ -312,13 +299,18 @@ int sel_netnode_sid(void *addr, u16 family, u32 *sid) */ static void sel_netnode_flush(void) { - u32 idx; - struct sel_netnode *node; + unsigned int idx; + struct sel_netnode *node, *node_tmp; spin_lock_bh(&sel_netnode_lock); - for (idx = 0; idx < SEL_NETNODE_HASH_SIZE; idx++) - list_for_each_entry(node, &sel_netnode_hash[idx], list) - sel_netnode_destroy(node); + for (idx = 0; idx < SEL_NETNODE_HASH_SIZE; idx++) { + list_for_each_entry_safe(node, node_tmp, + &sel_netnode_hash[idx].list, list) { + list_del_rcu(&node->list); + call_rcu(&node->rcu, sel_netnode_free); + } + sel_netnode_hash[idx].size = 0; + } spin_unlock_bh(&sel_netnode_lock); } @@ -340,8 +332,10 @@ static __init int sel_netnode_init(void) if (!selinux_enabled) return 0; - for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++) - INIT_LIST_HEAD(&sel_netnode_hash[iter]); + for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++) { + INIT_LIST_HEAD(&sel_netnode_hash[iter].list); + sel_netnode_hash[iter].size = 0; + } ret = avc_add_callback(sel_netnode_avc_callback, AVC_CALLBACK_RESET, SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); diff --git a/security/selinux/netport.c b/security/selinux/netport.c index 68ede3c498a..90b4cff7c35 100644 --- a/security/selinux/netport.c +++ b/security/selinux/netport.c @@ -114,8 +114,7 @@ static struct sel_netport *sel_netport_find(u8 protocol, u16 pnum) idx = sel_netport_hashfn(pnum); list_for_each_entry_rcu(port, &sel_netport_hash[idx].list, list) - if (port->psec.port == pnum && - port->psec.protocol == protocol) + if (port->psec.port == pnum && port->psec.protocol == protocol) return port; return NULL; @@ -126,11 +125,10 @@ static struct sel_netport *sel_netport_find(u8 protocol, u16 pnum) * @port: the new port record * * Description: - * Add a new port record to the network address hash table. Returns zero on - * success, negative values on failure. + * Add a new port record to the network address hash table. * */ -static int sel_netport_insert(struct sel_netport *port) +static void sel_netport_insert(struct sel_netport *port) { unsigned int idx; @@ -140,13 +138,13 @@ static int sel_netport_insert(struct sel_netport *port) list_add_rcu(&port->list, &sel_netport_hash[idx].list); if (sel_netport_hash[idx].size == SEL_NETPORT_HASH_BKT_LIMIT) { struct sel_netport *tail; - tail = list_entry(port->list.prev, struct sel_netport, list); - list_del_rcu(port->list.prev); + tail = list_entry( + rcu_dereference(sel_netport_hash[idx].list.prev), + struct sel_netport, list); + list_del_rcu(&tail->list); call_rcu(&tail->rcu, sel_netport_free); } else sel_netport_hash[idx].size++; - - return 0; } /** @@ -163,7 +161,7 @@ static int sel_netport_insert(struct sel_netport *port) */ static int sel_netport_sid_slow(u8 protocol, u16 pnum, u32 *sid) { - int ret; + int ret = -ENOMEM; struct sel_netport *port; struct sel_netport *new = NULL; @@ -171,23 +169,20 @@ static int sel_netport_sid_slow(u8 protocol, u16 pnum, u32 *sid) port = sel_netport_find(protocol, pnum); if (port != NULL) { *sid = port->psec.sid; - ret = 0; - goto out; + spin_unlock_bh(&sel_netport_lock); + return 0; } new = kzalloc(sizeof(*new), GFP_ATOMIC); - if (new == NULL) { - ret = -ENOMEM; + if (new == NULL) goto out; - } - ret = security_port_sid(protocol, pnum, &new->psec.sid); + ret = security_port_sid(protocol, pnum, sid); if (ret != 0) goto out; + new->psec.port = pnum; new->psec.protocol = protocol; - ret = sel_netport_insert(new); - if (ret != 0) - goto out; - *sid = new->psec.sid; + new->psec.sid = *sid; + sel_netport_insert(new); out: spin_unlock_bh(&sel_netport_lock); @@ -239,11 +234,12 @@ int sel_netport_sid(u8 protocol, u16 pnum, u32 *sid) static void sel_netport_flush(void) { unsigned int idx; - struct sel_netport *port; + struct sel_netport *port, *port_tmp; spin_lock_bh(&sel_netport_lock); for (idx = 0; idx < SEL_NETPORT_HASH_SIZE; idx++) { - list_for_each_entry(port, &sel_netport_hash[idx].list, list) { + list_for_each_entry_safe(port, port_tmp, + &sel_netport_hash[idx].list, list) { list_del_rcu(&port->list); call_rcu(&port->rcu, sel_netport_free); } diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h index f3a1fc6e5d6..65b9f8366e9 100644 --- a/security/selinux/ss/conditional.h +++ b/security/selinux/ss/conditional.h @@ -59,10 +59,10 @@ struct cond_node { struct cond_node *next; }; -int cond_policydb_init(struct policydb* p); -void cond_policydb_destroy(struct policydb* p); +int cond_policydb_init(struct policydb *p); +void cond_policydb_destroy(struct policydb *p); -int cond_init_bool_indexes(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); diff --git a/security/selinux/ss/context.h b/security/selinux/ss/context.h index 2eee0dab524..b9a6f7fc62f 100644 --- a/security/selinux/ss/context.h +++ b/security/selinux/ss/context.h @@ -84,9 +84,9 @@ static inline int mls_context_cmp(struct context *c1, struct context *c2) 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) && + 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)); + ebitmap_cmp(&c1->range.level[1].cat, &c2->range.level[1].cat)); } static inline void mls_context_destroy(struct context *c) diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h index 7e2ff3e3c6d..953872cd84a 100644 --- a/security/selinux/ss/hashtab.h +++ b/security/selinux/ss/hashtab.h @@ -40,8 +40,8 @@ struct hashtab_info { * the new hash table otherwise. */ struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, const void *key), - int (*keycmp)(struct hashtab *h, const void *key1, const void *key2), - u32 size); + int (*keycmp)(struct hashtab *h, const void *key1, const void *key2), + u32 size); /* * Inserts the specified (key, datum) pair into the specified hash table. @@ -49,7 +49,7 @@ struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, const void * * Returns -ENOMEM on memory allocation error, * -EEXIST if there is already an entry with the same key, * -EINVAL for general errors or - * 0 otherwise. + 0 otherwise. */ int hashtab_insert(struct hashtab *h, void *k, void *d); diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h index ab53663d9f5..0fdf6257ef6 100644 --- a/security/selinux/ss/mls.h +++ b/security/selinux/ss/mls.h @@ -13,7 +13,7 @@ /* * Updated: Hewlett-Packard <paul.moore@hp.com> * - * Added support to import/export the MLS label from NetLabel + * Added support to import/export the MLS label from NetLabel * * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 */ @@ -31,7 +31,7 @@ int mls_range_isvalid(struct policydb *p, struct mls_range *r); int mls_level_isvalid(struct policydb *p, struct mls_level *l); int mls_context_to_sid(char oldc, - char **scontext, + char **scontext, struct context *context, struct sidtab *s, u32 def_sid); @@ -49,7 +49,7 @@ int mls_compute_sid(struct context *scontext, struct context *newcontext); int mls_setup_user_range(struct context *fromcon, struct user_datum *user, - struct context *usercon); + struct context *usercon); #ifdef CONFIG_NETLABEL void mls_export_netlbl_lvl(struct context *context, diff --git a/security/selinux/ss/mls_types.h b/security/selinux/ss/mls_types.h index 0c692d58d48..b6e943a2106 100644 --- a/security/selinux/ss/mls_types.h +++ b/security/selinux/ss/mls_types.h @@ -31,7 +31,7 @@ static inline int mls_level_eq(struct mls_level *l1, struct mls_level *l2) return 1; return ((l1->sens == l2->sens) && - ebitmap_cmp(&l1->cat, &l2->cat)); + ebitmap_cmp(&l1->cat, &l2->cat)); } static inline int mls_level_dom(struct mls_level *l1, struct mls_level *l2) @@ -40,7 +40,7 @@ static inline int mls_level_dom(struct mls_level *l1, struct mls_level *l2) return 1; return ((l1->sens >= l2->sens) && - ebitmap_contains(&l1->cat, &l2->cat)); + ebitmap_contains(&l1->cat, &l2->cat)); } #define mls_level_incomp(l1, l2) \ diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h index ba593a3da87..4253370fda6 100644 --- a/security/selinux/ss/policydb.h +++ b/security/selinux/ss/policydb.h @@ -12,12 +12,12 @@ * * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> * - * Added conditional policy language extensions + * 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 + * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2. */ @@ -221,7 +221,7 @@ struct policydb { /* 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; + struct cond_node *cond_list; /* role allows */ struct role_allow *role_allow; @@ -230,10 +230,10 @@ struct policydb { TCP or UDP port numbers, network interfaces and nodes */ struct ocontext *ocontexts[OCON_NUM]; - /* security contexts for files in filesystems that cannot support + /* security contexts for files in filesystems that cannot support a persistent label mapping or use another fixed labeling behavior. */ - struct genfs *genfs; + struct genfs *genfs; /* range transitions */ struct range_trans *range_tr; diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 2daaddbb301..25cac5a2aa8 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -708,7 +708,7 @@ out: } -static int security_context_to_sid_core(char *scontext, u32 scontext_len, +static int security_context_to_sid_core(const char *scontext, u32 scontext_len, u32 *sid, u32 def_sid, gfp_t gfp_flags) { char *scontext2; @@ -835,7 +835,7 @@ out: * 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) +int security_context_to_sid(const char *scontext, u32 scontext_len, u32 *sid) { return security_context_to_sid_core(scontext, scontext_len, sid, SECSID_NULL, GFP_KERNEL); diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 4215971434e..5d2ec5650e6 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -574,8 +574,8 @@ static int smack_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_setxattr(struct dentry *dentry, char *name, - void *value, size_t size, int flags) +static int smack_inode_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) { int rc = 0; @@ -604,8 +604,8 @@ static int smack_inode_setxattr(struct dentry *dentry, char *name, * Set the pointer in the inode blob to the entry found * in the master label list. */ -static void smack_inode_post_setxattr(struct dentry *dentry, char *name, - void *value, size_t size, int flags) +static void smack_inode_post_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) { struct inode_smack *isp; char *nsp; @@ -641,7 +641,7 @@ static void smack_inode_post_setxattr(struct dentry *dentry, char *name, * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_getxattr(struct dentry *dentry, char *name) +static int smack_inode_getxattr(struct dentry *dentry, const char *name) { return smk_curacc(smk_of_inode(dentry->d_inode), MAY_READ); } @@ -655,7 +655,7 @@ static int smack_inode_getxattr(struct dentry *dentry, char *name) * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_removexattr(struct dentry *dentry, char *name) +static int smack_inode_removexattr(struct dentry *dentry, const char *name) { int rc = 0; @@ -1242,7 +1242,7 @@ static void smack_set_catset(char *catset, struct netlbl_lsm_secattr *sap) int rc; int byte; - if (catset == 0) + if (!catset) return; sap->flags |= NETLBL_SECATTR_MLS_CAT; @@ -2495,6 +2495,7 @@ struct security_operations smack_ops = { .task_wait = smack_task_wait, .task_reparent_to_init = cap_task_reparent_to_init, .task_to_inode = smack_task_to_inode, + .task_prctl = cap_task_prctl, .ipc_permission = smack_ipc_permission, diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 5d1bee0fa51..271a835fbbe 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -317,7 +317,7 @@ static const struct file_operations smk_load_ops = { /** * smk_cipso_doi - initialize the CIPSO domain */ -void smk_cipso_doi(void) +static void smk_cipso_doi(void) { int rc; struct cipso_v4_doi *doip; @@ -351,7 +351,7 @@ void smk_cipso_doi(void) /** * smk_unlbl_ambient - initialize the unlabeled domain */ -void smk_unlbl_ambient(char *oldambient) +static void smk_unlbl_ambient(char *oldambient) { int rc; struct netlbl_audit audit_info; |