diff options
Diffstat (limited to 'security')
-rw-r--r-- | security/capability.c | 3 | ||||
-rw-r--r-- | security/device_cgroup.c | 267 | ||||
-rw-r--r-- | security/integrity/ima/ima_main.c | 14 | ||||
-rw-r--r-- | security/keys/request_key.c | 13 | ||||
-rw-r--r-- | security/security.c | 4 | ||||
-rw-r--r-- | security/selinux/hooks.c | 39 | ||||
-rw-r--r-- | security/smack/smack.h | 5 | ||||
-rw-r--r-- | security/smack/smack_access.c | 2 | ||||
-rw-r--r-- | security/smack/smack_lsm.c | 4 | ||||
-rw-r--r-- | security/smack/smackfs.c | 254 | ||||
-rw-r--r-- | security/tomoyo/tomoyo.c | 5 |
11 files changed, 443 insertions, 167 deletions
diff --git a/security/capability.c b/security/capability.c index 6783c3e6c88..1728d4e375d 100644 --- a/security/capability.c +++ b/security/capability.c @@ -98,9 +98,10 @@ static int cap_sb_set_mnt_opts(struct super_block *sb, return 0; } -static void cap_sb_clone_mnt_opts(const struct super_block *oldsb, +static int cap_sb_clone_mnt_opts(const struct super_block *oldsb, struct super_block *newsb) { + return 0; } static int cap_sb_parse_opts_str(char *options, struct security_mnt_opts *opts) diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 1c69e38e3a2..dd0dc574d78 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -25,6 +25,12 @@ static DEFINE_MUTEX(devcgroup_mutex); +enum devcg_behavior { + DEVCG_DEFAULT_NONE, + DEVCG_DEFAULT_ALLOW, + DEVCG_DEFAULT_DENY, +}; + /* * exception list locking rules: * hold devcgroup_mutex for update/read. @@ -42,10 +48,9 @@ struct dev_exception_item { struct dev_cgroup { struct cgroup_subsys_state css; struct list_head exceptions; - enum { - DEVCG_DEFAULT_ALLOW, - DEVCG_DEFAULT_DENY, - } behavior; + enum devcg_behavior behavior; + /* temporary list for pending propagation operations */ + struct list_head propagate_pending; }; static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s) @@ -182,35 +187,62 @@ static void dev_exception_clean(struct dev_cgroup *dev_cgroup) __dev_exception_clean(dev_cgroup); } +static inline bool is_devcg_online(const struct dev_cgroup *devcg) +{ + return (devcg->behavior != DEVCG_DEFAULT_NONE); +} + +/** + * devcgroup_online - initializes devcgroup's behavior and exceptions based on + * parent's + * @cgroup: cgroup getting online + * returns 0 in case of success, error code otherwise + */ +static int devcgroup_online(struct cgroup *cgroup) +{ + struct dev_cgroup *dev_cgroup, *parent_dev_cgroup = NULL; + int ret = 0; + + mutex_lock(&devcgroup_mutex); + dev_cgroup = cgroup_to_devcgroup(cgroup); + if (cgroup->parent) + parent_dev_cgroup = cgroup_to_devcgroup(cgroup->parent); + + if (parent_dev_cgroup == NULL) + dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW; + else { + ret = dev_exceptions_copy(&dev_cgroup->exceptions, + &parent_dev_cgroup->exceptions); + if (!ret) + dev_cgroup->behavior = parent_dev_cgroup->behavior; + } + mutex_unlock(&devcgroup_mutex); + + return ret; +} + +static void devcgroup_offline(struct cgroup *cgroup) +{ + struct dev_cgroup *dev_cgroup = cgroup_to_devcgroup(cgroup); + + mutex_lock(&devcgroup_mutex); + dev_cgroup->behavior = DEVCG_DEFAULT_NONE; + mutex_unlock(&devcgroup_mutex); +} + /* * called from kernel/cgroup.c with cgroup_lock() held. */ static struct cgroup_subsys_state *devcgroup_css_alloc(struct cgroup *cgroup) { - struct dev_cgroup *dev_cgroup, *parent_dev_cgroup; - struct cgroup *parent_cgroup; - int ret; + struct dev_cgroup *dev_cgroup; dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL); if (!dev_cgroup) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&dev_cgroup->exceptions); - parent_cgroup = cgroup->parent; - - if (parent_cgroup == NULL) - dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW; - else { - parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup); - mutex_lock(&devcgroup_mutex); - ret = dev_exceptions_copy(&dev_cgroup->exceptions, - &parent_dev_cgroup->exceptions); - dev_cgroup->behavior = parent_dev_cgroup->behavior; - mutex_unlock(&devcgroup_mutex); - if (ret) { - kfree(dev_cgroup); - return ERR_PTR(ret); - } - } + INIT_LIST_HEAD(&dev_cgroup->propagate_pending); + dev_cgroup->behavior = DEVCG_DEFAULT_NONE; return &dev_cgroup->css; } @@ -304,9 +336,11 @@ static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, * verify if a certain access is allowed. * @dev_cgroup: dev cgroup to be tested against * @refex: new exception + * @behavior: behavior of the exception */ -static int may_access(struct dev_cgroup *dev_cgroup, - struct dev_exception_item *refex) +static bool may_access(struct dev_cgroup *dev_cgroup, + struct dev_exception_item *refex, + enum devcg_behavior behavior) { struct dev_exception_item *ex; bool match = false; @@ -330,18 +364,29 @@ static int may_access(struct dev_cgroup *dev_cgroup, break; } - /* - * In two cases we'll consider this new exception valid: - * - the dev cgroup has its default policy to allow + exception list: - * the new exception should *not* match any of the exceptions - * (behavior == DEVCG_DEFAULT_ALLOW, !match) - * - the dev cgroup has its default policy to deny + exception list: - * the new exception *should* match the exceptions - * (behavior == DEVCG_DEFAULT_DENY, match) - */ - if ((dev_cgroup->behavior == DEVCG_DEFAULT_DENY) == match) - return 1; - return 0; + if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { + if (behavior == DEVCG_DEFAULT_ALLOW) { + /* the exception will deny access to certain devices */ + return true; + } else { + /* the exception will allow access to certain devices */ + if (match) + /* + * a new exception allowing access shouldn't + * match an parent's exception + */ + return false; + return true; + } + } else { + /* only behavior == DEVCG_DEFAULT_DENY allowed here */ + if (match) + /* parent has an exception that matches the proposed */ + return true; + else + return false; + } + return false; } /* @@ -358,7 +403,7 @@ static int parent_has_perm(struct dev_cgroup *childcg, if (!pcg) return 1; parent = cgroup_to_devcgroup(pcg); - return may_access(parent, ex); + return may_access(parent, ex, childcg->behavior); } /** @@ -374,6 +419,111 @@ static inline int may_allow_all(struct dev_cgroup *parent) return parent->behavior == DEVCG_DEFAULT_ALLOW; } +/** + * revalidate_active_exceptions - walks through the active exception list and + * revalidates the exceptions based on parent's + * behavior and exceptions. The exceptions that + * are no longer valid will be removed. + * Called with devcgroup_mutex held. + * @devcg: cgroup which exceptions will be checked + * + * This is one of the three key functions for hierarchy implementation. + * This function is responsible for re-evaluating all the cgroup's active + * exceptions due to a parent's exception change. + * Refer to Documentation/cgroups/devices.txt for more details. + */ +static void revalidate_active_exceptions(struct dev_cgroup *devcg) +{ + struct dev_exception_item *ex; + struct list_head *this, *tmp; + + list_for_each_safe(this, tmp, &devcg->exceptions) { + ex = container_of(this, struct dev_exception_item, list); + if (!parent_has_perm(devcg, ex)) + dev_exception_rm(devcg, ex); + } +} + +/** + * get_online_devcg - walks the cgroup tree and fills a list with the online + * groups + * @root: cgroup used as starting point + * @online: list that will be filled with online groups + * + * Must be called with devcgroup_mutex held. Grabs RCU lock. + * Because devcgroup_mutex is held, no devcg will become online or offline + * during the tree walk (see devcgroup_online, devcgroup_offline) + * A separated list is needed because propagate_behavior() and + * propagate_exception() need to allocate memory and can block. + */ +static void get_online_devcg(struct cgroup *root, struct list_head *online) +{ + struct cgroup *pos; + struct dev_cgroup *devcg; + + lockdep_assert_held(&devcgroup_mutex); + + rcu_read_lock(); + cgroup_for_each_descendant_pre(pos, root) { + devcg = cgroup_to_devcgroup(pos); + if (is_devcg_online(devcg)) + list_add_tail(&devcg->propagate_pending, online); + } + rcu_read_unlock(); +} + +/** + * propagate_exception - propagates a new exception to the children + * @devcg_root: device cgroup that added a new exception + * @ex: new exception to be propagated + * + * returns: 0 in case of success, != 0 in case of error + */ +static int propagate_exception(struct dev_cgroup *devcg_root, + struct dev_exception_item *ex) +{ + struct cgroup *root = devcg_root->css.cgroup; + struct dev_cgroup *devcg, *parent, *tmp; + int rc = 0; + LIST_HEAD(pending); + + get_online_devcg(root, &pending); + + list_for_each_entry_safe(devcg, tmp, &pending, propagate_pending) { + parent = cgroup_to_devcgroup(devcg->css.cgroup->parent); + + /* + * in case both root's behavior and devcg is allow, a new + * restriction means adding to the exception list + */ + if (devcg_root->behavior == DEVCG_DEFAULT_ALLOW && + devcg->behavior == DEVCG_DEFAULT_ALLOW) { + rc = dev_exception_add(devcg, ex); + if (rc) + break; + } else { + /* + * in the other possible cases: + * root's behavior: allow, devcg's: deny + * root's behavior: deny, devcg's: deny + * the exception will be removed + */ + dev_exception_rm(devcg, ex); + } + revalidate_active_exceptions(devcg); + + list_del_init(&devcg->propagate_pending); + } + return rc; +} + +static inline bool has_children(struct dev_cgroup *devcgroup) +{ + struct cgroup *cgrp = devcgroup->css.cgroup; + + return !list_empty(&cgrp->children); +} + /* * Modify the exception list using allow/deny rules. * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD @@ -392,7 +542,7 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, { const char *b; char temp[12]; /* 11 + 1 characters needed for a u32 */ - int count, rc; + int count, rc = 0; struct dev_exception_item ex; struct cgroup *p = devcgroup->css.cgroup; struct dev_cgroup *parent = NULL; @@ -410,6 +560,9 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, case 'a': switch (filetype) { case DEVCG_ALLOW: + if (has_children(devcgroup)) + return -EINVAL; + if (!may_allow_all(parent)) return -EPERM; dev_exception_clean(devcgroup); @@ -423,6 +576,9 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, return rc; break; case DEVCG_DENY: + if (has_children(devcgroup)) + return -EINVAL; + dev_exception_clean(devcgroup); devcgroup->behavior = DEVCG_DEFAULT_DENY; break; @@ -517,22 +673,28 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, dev_exception_rm(devcgroup, &ex); return 0; } - return dev_exception_add(devcgroup, &ex); + rc = dev_exception_add(devcgroup, &ex); + break; case DEVCG_DENY: /* * If the default policy is to deny by default, try to remove * an matching exception instead. And be silent about it: we * don't want to break compatibility */ - if (devcgroup->behavior == DEVCG_DEFAULT_DENY) { + if (devcgroup->behavior == DEVCG_DEFAULT_DENY) dev_exception_rm(devcgroup, &ex); - return 0; - } - return dev_exception_add(devcgroup, &ex); + else + rc = dev_exception_add(devcgroup, &ex); + + if (rc) + break; + /* we only propagate new restrictions */ + rc = propagate_exception(devcgroup, &ex); + break; default: - return -EINVAL; + rc = -EINVAL; } - return 0; + return rc; } static int devcgroup_access_write(struct cgroup *cgrp, struct cftype *cft, @@ -571,17 +733,10 @@ struct cgroup_subsys devices_subsys = { .can_attach = devcgroup_can_attach, .css_alloc = devcgroup_css_alloc, .css_free = devcgroup_css_free, + .css_online = devcgroup_online, + .css_offline = devcgroup_offline, .subsys_id = devices_subsys_id, .base_cftypes = dev_cgroup_files, - - /* - * While devices cgroup has the rudimentary hierarchy support which - * checks the parent's restriction, it doesn't properly propagates - * config changes in ancestors to their descendents. A child - * should only be allowed to add more restrictions to the parent's - * configuration. Fix it and remove the following. - */ - .broken_hierarchy = true, }; /** @@ -609,7 +764,7 @@ static int __devcgroup_check_permission(short type, u32 major, u32 minor, rcu_read_lock(); dev_cgroup = task_devcgroup(current); - rc = may_access(dev_cgroup, &ex); + rc = may_access(dev_cgroup, &ex, dev_cgroup->behavior); rcu_read_unlock(); if (!rc) diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 3b3b7e6bf8d..6c491a63128 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -189,11 +189,9 @@ static int process_measurement(struct file *file, const char *filename, if (rc != 0) goto out_digsig; - if (function != BPRM_CHECK) - pathname = ima_d_path(&file->f_path, &pathbuf); - + pathname = !filename ? ima_d_path(&file->f_path, &pathbuf) : filename; if (!pathname) - pathname = filename; + pathname = (const char *)file->f_dentry->d_name.name; if (action & IMA_MEASURE) ima_store_measurement(iint, file, pathname); @@ -226,8 +224,7 @@ out: int ima_file_mmap(struct file *file, unsigned long prot) { if (file && (prot & PROT_EXEC)) - return process_measurement(file, file->f_dentry->d_name.name, - MAY_EXEC, MMAP_CHECK); + return process_measurement(file, NULL, MAY_EXEC, MMAP_CHECK); return 0; } @@ -265,7 +262,7 @@ int ima_bprm_check(struct linux_binprm *bprm) int ima_file_check(struct file *file, int mask) { ima_rdwr_violation_check(file); - return process_measurement(file, file->f_dentry->d_name.name, + return process_measurement(file, NULL, mask & (MAY_READ | MAY_WRITE | MAY_EXEC), FILE_CHECK); } @@ -290,8 +287,7 @@ int ima_module_check(struct file *file) #endif return 0; /* We rely on module signature checking */ } - return process_measurement(file, file->f_dentry->d_name.name, - MAY_EXEC, MODULE_CHECK); + return process_measurement(file, NULL, MAY_EXEC, MODULE_CHECK); } static int __init init_ima(void) diff --git a/security/keys/request_key.c b/security/keys/request_key.c index 4bd6bdb7419..c411f9bb156 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -93,9 +93,16 @@ static void umh_keys_cleanup(struct subprocess_info *info) static int call_usermodehelper_keys(char *path, char **argv, char **envp, struct key *session_keyring, int wait) { - return call_usermodehelper_fns(path, argv, envp, wait, - umh_keys_init, umh_keys_cleanup, - key_get(session_keyring)); + struct subprocess_info *info; + + info = call_usermodehelper_setup(path, argv, envp, GFP_KERNEL, + umh_keys_init, umh_keys_cleanup, + session_keyring); + if (!info) + return -ENOMEM; + + key_get(session_keyring); + return call_usermodehelper_exec(info, wait); } /* diff --git a/security/security.c b/security/security.c index 03f248b84e9..a3dce87d1ae 100644 --- a/security/security.c +++ b/security/security.c @@ -299,10 +299,10 @@ int security_sb_set_mnt_opts(struct super_block *sb, } EXPORT_SYMBOL(security_sb_set_mnt_opts); -void security_sb_clone_mnt_opts(const struct super_block *oldsb, +int security_sb_clone_mnt_opts(const struct super_block *oldsb, struct super_block *newsb) { - security_ops->sb_clone_mnt_opts(oldsb, newsb); + return security_ops->sb_clone_mnt_opts(oldsb, newsb); } EXPORT_SYMBOL(security_sb_clone_mnt_opts); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index bf889ee5150..5c6f2cd2d09 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -751,7 +751,37 @@ out_double_mount: goto out; } -static void selinux_sb_clone_mnt_opts(const struct super_block *oldsb, +static int selinux_cmp_sb_context(const struct super_block *oldsb, + const struct super_block *newsb) +{ + struct superblock_security_struct *old = oldsb->s_security; + struct superblock_security_struct *new = newsb->s_security; + char oldflags = old->flags & SE_MNTMASK; + char newflags = new->flags & SE_MNTMASK; + + if (oldflags != newflags) + goto mismatch; + if ((oldflags & FSCONTEXT_MNT) && old->sid != new->sid) + goto mismatch; + if ((oldflags & CONTEXT_MNT) && old->mntpoint_sid != new->mntpoint_sid) + goto mismatch; + if ((oldflags & DEFCONTEXT_MNT) && old->def_sid != new->def_sid) + goto mismatch; + if (oldflags & ROOTCONTEXT_MNT) { + struct inode_security_struct *oldroot = oldsb->s_root->d_inode->i_security; + struct inode_security_struct *newroot = newsb->s_root->d_inode->i_security; + if (oldroot->sid != newroot->sid) + goto mismatch; + } + return 0; +mismatch: + printk(KERN_WARNING "SELinux: mount invalid. Same superblock, " + "different security settings for (dev %s, " + "type %s)\n", newsb->s_id, newsb->s_type->name); + return -EBUSY; +} + +static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, struct super_block *newsb) { const struct superblock_security_struct *oldsbsec = oldsb->s_security; @@ -766,14 +796,14 @@ static void selinux_sb_clone_mnt_opts(const struct super_block *oldsb, * mount options. thus we can safely deal with this superblock later */ if (!ss_initialized) - return; + return 0; /* how can we clone if the old one wasn't set up?? */ BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED)); - /* if fs is reusing a sb, just let its options stand... */ + /* if fs is reusing a sb, make sure that the contexts match */ if (newsbsec->flags & SE_SBINITIALIZED) - return; + return selinux_cmp_sb_context(oldsb, newsb); mutex_lock(&newsbsec->lock); @@ -806,6 +836,7 @@ static void selinux_sb_clone_mnt_opts(const struct super_block *oldsb, sb_finish_set_opts(newsb); mutex_unlock(&newsbsec->lock); + return 0; } static int selinux_parse_opts_str(char *options, diff --git a/security/smack/smack.h b/security/smack/smack.h index 99b36124f71..8ad30955e15 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -149,11 +149,6 @@ struct smack_known { #define SMACK_CIPSO_SOCKET 1 /* - * smackfs magic number - */ -#define SMACK_MAGIC 0x43415d53 /* "SMAC" */ - -/* * CIPSO defaults. */ #define SMACK_CIPSO_DOI_DEFAULT 3 /* Historical */ diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index db14689a21e..2e397a88d41 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -252,6 +252,8 @@ static inline void smack_str_from_perm(char *string, int access) string[i++] = 'x'; if (access & MAY_APPEND) string[i++] = 'a'; + if (access & MAY_TRANSMUTE) + string[i++] = 't'; string[i] = '\0'; } /** diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index fa64740abb5..d52c780bdb7 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -654,7 +654,7 @@ static int smack_inode_unlink(struct inode *dir, struct dentry *dentry) /* * You also need write access to the containing directory */ - smk_ad_setfield_u_fs_path_dentry(&ad, NULL); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_INODE); smk_ad_setfield_u_fs_inode(&ad, dir); rc = smk_curacc(smk_of_inode(dir), MAY_WRITE, &ad); } @@ -685,7 +685,7 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry) /* * You also need write access to the containing directory */ - smk_ad_setfield_u_fs_path_dentry(&ad, NULL); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_INODE); smk_ad_setfield_u_fs_inode(&ad, dir); rc = smk_curacc(smk_of_inode(dir), MAY_WRITE, &ad); } diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 76a5dca4640..53a08b85bda 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -26,6 +26,7 @@ #include <linux/seq_file.h> #include <linux/ctype.h> #include <linux/audit.h> +#include <linux/magic.h> #include "smack.h" /* @@ -50,12 +51,12 @@ enum smk_inos { SMK_ACCESS2 = 16, /* make an access check with long labels */ SMK_CIPSO2 = 17, /* load long label -> CIPSO mapping */ SMK_REVOKE_SUBJ = 18, /* set rules with subject label to '-' */ + SMK_CHANGE_RULE = 19, /* change or add rules (long labels) */ }; /* * List locks */ -static DEFINE_MUTEX(smack_list_lock); static DEFINE_MUTEX(smack_cipso_lock); static DEFINE_MUTEX(smack_ambient_lock); static DEFINE_MUTEX(smk_netlbladdr_lock); @@ -110,6 +111,13 @@ struct smack_master_list { LIST_HEAD(smack_rule_list); +struct smack_parsed_rule { + char *smk_subject; + char *smk_object; + int smk_access1; + int smk_access2; +}; + static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; const char *smack_cipso_option = SMACK_CIPSO_OPTION; @@ -167,25 +175,28 @@ static void smk_netlabel_audit_set(struct netlbl_audit *nap) #define SMK_NETLBLADDRMIN 9 /** - * smk_set_access - add a rule to the rule list - * @srp: the new rule to add + * smk_set_access - add a rule to the rule list or replace an old rule + * @srp: the rule to add or replace * @rule_list: the list of rules * @rule_lock: the rule list lock + * @global: if non-zero, indicates a global rule * * Looks through the current subject/object/access list for * the subject/object pair and replaces the access that was * there. If the pair isn't found add it with the specified * access. * - * Returns 1 if a rule was found to exist already, 0 if it is new * Returns 0 if nothing goes wrong or -ENOMEM if it fails * during the allocation of the new pair to add. */ -static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list, - struct mutex *rule_lock) +static int smk_set_access(struct smack_parsed_rule *srp, + struct list_head *rule_list, + struct mutex *rule_lock, int global) { struct smack_rule *sp; + struct smack_master_list *smlp; int found = 0; + int rc = 0; mutex_lock(rule_lock); @@ -197,23 +208,89 @@ static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list, if (sp->smk_object == srp->smk_object && sp->smk_subject == srp->smk_subject) { found = 1; - sp->smk_access = srp->smk_access; + sp->smk_access |= srp->smk_access1; + sp->smk_access &= ~srp->smk_access2; break; } } - if (found == 0) - list_add_rcu(&srp->list, rule_list); + if (found == 0) { + sp = kzalloc(sizeof(*sp), GFP_KERNEL); + if (sp == NULL) { + rc = -ENOMEM; + goto out; + } + + sp->smk_subject = srp->smk_subject; + sp->smk_object = srp->smk_object; + sp->smk_access = srp->smk_access1 & ~srp->smk_access2; + + list_add_rcu(&sp->list, rule_list); + /* + * If this is a global as opposed to self and a new rule + * it needs to get added for reporting. + */ + if (global) { + smlp = kzalloc(sizeof(*smlp), GFP_KERNEL); + if (smlp != NULL) { + smlp->smk_rule = sp; + list_add_rcu(&smlp->list, &smack_rule_list); + } else + rc = -ENOMEM; + } + } + +out: mutex_unlock(rule_lock); + return rc; +} + +/** + * smk_perm_from_str - parse smack accesses from a text string + * @string: a text string that contains a Smack accesses code + * + * Returns an integer with respective bits set for specified accesses. + */ +static int smk_perm_from_str(const char *string) +{ + int perm = 0; + const char *cp; - return found; + for (cp = string; ; cp++) + switch (*cp) { + case '-': + break; + case 'r': + case 'R': + perm |= MAY_READ; + break; + case 'w': + case 'W': + perm |= MAY_WRITE; + break; + case 'x': + case 'X': + perm |= MAY_EXEC; + break; + case 'a': + case 'A': + perm |= MAY_APPEND; + break; + case 't': + case 'T': + perm |= MAY_TRANSMUTE; + break; + default: + return perm; + } } /** * smk_fill_rule - Fill Smack rule from strings * @subject: subject label string * @object: object label string - * @access: access string + * @access1: access string + * @access2: string with permissions to be removed * @rule: Smack rule * @import: if non-zero, import labels * @len: label length limit @@ -221,8 +298,9 @@ static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list, * Returns 0 on success, -1 on failure */ static int smk_fill_rule(const char *subject, const char *object, - const char *access, struct smack_rule *rule, - int import, int len) + const char *access1, const char *access2, + struct smack_parsed_rule *rule, int import, + int len) { const char *cp; struct smack_known *skp; @@ -255,36 +333,11 @@ static int smk_fill_rule(const char *subject, const char *object, rule->smk_object = skp->smk_known; } - rule->smk_access = 0; - - for (cp = access; *cp != '\0'; cp++) { - switch (*cp) { - case '-': - break; - case 'r': - case 'R': - rule->smk_access |= MAY_READ; - break; - case 'w': - case 'W': - rule->smk_access |= MAY_WRITE; - break; - case 'x': - case 'X': - rule->smk_access |= MAY_EXEC; - break; - case 'a': - case 'A': - rule->smk_access |= MAY_APPEND; - break; - case 't': - case 'T': - rule->smk_access |= MAY_TRANSMUTE; - break; - default: - return 0; - } - } + rule->smk_access1 = smk_perm_from_str(access1); + if (access2) + rule->smk_access2 = smk_perm_from_str(access2); + else + rule->smk_access2 = ~rule->smk_access1; return 0; } @@ -297,30 +350,33 @@ static int smk_fill_rule(const char *subject, const char *object, * * Returns 0 on success, -1 on errors. */ -static int smk_parse_rule(const char *data, struct smack_rule *rule, int import) +static int smk_parse_rule(const char *data, struct smack_parsed_rule *rule, + int import) { int rc; rc = smk_fill_rule(data, data + SMK_LABELLEN, - data + SMK_LABELLEN + SMK_LABELLEN, rule, import, - SMK_LABELLEN); + data + SMK_LABELLEN + SMK_LABELLEN, NULL, rule, + import, SMK_LABELLEN); return rc; } /** * smk_parse_long_rule - parse Smack rule from rule string * @data: string to be parsed, null terminated - * @rule: Smack rule + * @rule: Will be filled with Smack parsed rule * @import: if non-zero, import labels + * @change: if non-zero, data is from /smack/change-rule * * Returns 0 on success, -1 on failure */ -static int smk_parse_long_rule(const char *data, struct smack_rule *rule, - int import) +static int smk_parse_long_rule(const char *data, struct smack_parsed_rule *rule, + int import, int change) { char *subject; char *object; - char *access; + char *access1; + char *access2; int datalen; int rc = -1; @@ -334,14 +390,27 @@ static int smk_parse_long_rule(const char *data, struct smack_rule *rule, object = kzalloc(datalen, GFP_KERNEL); if (object == NULL) goto free_out_s; - access = kzalloc(datalen, GFP_KERNEL); - if (access == NULL) + access1 = kzalloc(datalen, GFP_KERNEL); + if (access1 == NULL) goto free_out_o; + access2 = kzalloc(datalen, GFP_KERNEL); + if (access2 == NULL) + goto free_out_a; + + if (change) { + if (sscanf(data, "%s %s %s %s", + subject, object, access1, access2) == 4) + rc = smk_fill_rule(subject, object, access1, access2, + rule, import, 0); + } else { + if (sscanf(data, "%s %s %s", subject, object, access1) == 3) + rc = smk_fill_rule(subject, object, access1, NULL, + rule, import, 0); + } - if (sscanf(data, "%s %s %s", subject, object, access) == 3) - rc = smk_fill_rule(subject, object, access, rule, import, 0); - - kfree(access); + kfree(access2); +free_out_a: + kfree(access1); free_out_o: kfree(object); free_out_s: @@ -351,6 +420,7 @@ free_out_s: #define SMK_FIXED24_FMT 0 /* Fixed 24byte label format */ #define SMK_LONG_FMT 1 /* Variable long label format */ +#define SMK_CHANGE_FMT 2 /* Rule modification format */ /** * smk_write_rules_list - write() for any /smack rule file * @file: file pointer, not actually used @@ -359,22 +429,24 @@ free_out_s: * @ppos: where to start - must be 0 * @rule_list: the list of rules to write to * @rule_lock: lock for the rule list - * @format: /smack/load or /smack/load2 format. + * @format: /smack/load or /smack/load2 or /smack/change-rule format. * * Get one smack access rule from above. * The format for SMK_LONG_FMT is: * "subject<whitespace>object<whitespace>access[<whitespace>...]" * The format for SMK_FIXED24_FMT is exactly: * "subject object rwxat" + * The format for SMK_CHANGE_FMT is: + * "subject<whitespace>object<whitespace> + * acc_enable<whitespace>acc_disable[<whitespace>...]" */ static ssize_t smk_write_rules_list(struct file *file, const char __user *buf, size_t count, loff_t *ppos, struct list_head *rule_list, struct mutex *rule_lock, int format) { - struct smack_master_list *smlp; struct smack_known *skp; - struct smack_rule *rule; + struct smack_parsed_rule *rule; char *data; int datalen; int rc = -EINVAL; @@ -417,7 +489,11 @@ static ssize_t smk_write_rules_list(struct file *file, const char __user *buf, * Be sure the data string is terminated. */ data[count] = '\0'; - if (smk_parse_long_rule(data, rule, 1)) + if (smk_parse_long_rule(data, rule, 1, 0)) + goto out_free_rule; + } else if (format == SMK_CHANGE_FMT) { + data[count] = '\0'; + if (smk_parse_long_rule(data, rule, 1, 1)) goto out_free_rule; } else { /* @@ -437,22 +513,9 @@ static ssize_t smk_write_rules_list(struct file *file, const char __user *buf, rule_lock = &skp->smk_rules_lock; } - rc = count; - /* - * If this is a global as opposed to self and a new rule - * it needs to get added for reporting. - * smk_set_access returns true if there was already a rule - * for the subject/object pair, and false if it was new. - */ - if (!smk_set_access(rule, rule_list, rule_lock)) { - if (load) { - smlp = kzalloc(sizeof(*smlp), GFP_KERNEL); - if (smlp != NULL) { - smlp->smk_rule = rule; - list_add_rcu(&smlp->list, &smack_rule_list); - } else - rc = -ENOMEM; - } + rc = smk_set_access(rule, rule_list, rule_lock, load); + if (rc == 0) { + rc = count; goto out; } @@ -1774,7 +1837,7 @@ static const struct file_operations smk_load_self_ops = { static ssize_t smk_user_access(struct file *file, const char __user *buf, size_t count, loff_t *ppos, int format) { - struct smack_rule rule; + struct smack_parsed_rule rule; char *data; char *cod; int res; @@ -1796,14 +1859,14 @@ static ssize_t smk_user_access(struct file *file, const char __user *buf, return -ENOMEM; memcpy(cod, data, count); cod[count] = '\0'; - res = smk_parse_long_rule(cod, &rule, 0); + res = smk_parse_long_rule(cod, &rule, 0, 0); kfree(cod); } if (res) return -EINVAL; - res = smk_access(rule.smk_subject, rule.smk_object, rule.smk_access, + res = smk_access(rule.smk_subject, rule.smk_object, rule.smk_access1, NULL); data[0] = res == 0 ? '1' : '0'; data[1] = '\0'; @@ -2035,10 +2098,8 @@ static ssize_t smk_write_revoke_subj(struct file *file, const char __user *buf, } skp = smk_find_entry(cp); - if (skp == NULL) { - rc = -EINVAL; + if (skp == NULL) goto free_out; - } rule_list = &skp->smk_rules; rule_lock = &skp->smk_rules_lock; @@ -2077,6 +2138,33 @@ static int smk_init_sysfs(void) } /** + * smk_write_change_rule - write() for /smack/change-rule + * @file: file pointer + * @buf: data from user space + * @count: bytes sent + * @ppos: where to start - must be 0 + */ +static ssize_t smk_write_change_rule(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* + * Must have privilege. + */ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + return smk_write_rules_list(file, buf, count, ppos, NULL, NULL, + SMK_CHANGE_FMT); +} + +static const struct file_operations smk_change_rule_ops = { + .write = smk_write_change_rule, + .read = simple_transaction_read, + .release = simple_transaction_release, + .llseek = generic_file_llseek, +}; + +/** * smk_fill_super - fill the /smackfs superblock * @sb: the empty superblock * @data: unused @@ -2125,6 +2213,8 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent) [SMK_REVOKE_SUBJ] = { "revoke-subject", &smk_revoke_subj_ops, S_IRUGO|S_IWUSR}, + [SMK_CHANGE_RULE] = { + "change-rule", &smk_change_rule_ops, S_IRUGO|S_IWUSR}, /* last one */ {""} }; diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index a2ee362546a..f0b756e27fe 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -536,7 +536,7 @@ static struct security_operations tomoyo_security_ops = { }; /* Lock for GC. */ -struct srcu_struct tomoyo_ss; +DEFINE_SRCU(tomoyo_ss); /** * tomoyo_init - Register TOMOYO Linux as a LSM module. @@ -550,8 +550,7 @@ static int __init tomoyo_init(void) if (!security_module_enable(&tomoyo_security_ops)) return 0; /* register ourselves with the security framework */ - if (register_security(&tomoyo_security_ops) || - init_srcu_struct(&tomoyo_ss)) + if (register_security(&tomoyo_security_ops)) panic("Failure registering TOMOYO Linux"); printk(KERN_INFO "TOMOYO Linux initialized\n"); cred->security = &tomoyo_kernel_domain; |