aboutsummaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
Diffstat (limited to 'security')
-rw-r--r--security/capability.c3
-rw-r--r--security/device_cgroup.c267
-rw-r--r--security/integrity/ima/ima_main.c14
-rw-r--r--security/keys/request_key.c13
-rw-r--r--security/security.c4
-rw-r--r--security/selinux/hooks.c39
-rw-r--r--security/smack/smack.h5
-rw-r--r--security/smack/smack_access.c2
-rw-r--r--security/smack/smack_lsm.c4
-rw-r--r--security/smack/smackfs.c254
-rw-r--r--security/tomoyo/tomoyo.c5
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;