summaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
Diffstat (limited to 'security')
-rw-r--r--security/Kconfig11
-rw-r--r--security/apparmor/Makefile8
-rw-r--r--security/apparmor/apparmorfs.c1672
-rw-r--r--security/apparmor/audit.c27
-rw-r--r--security/apparmor/capability.c61
-rw-r--r--security/apparmor/context.c87
-rw-r--r--security/apparmor/domain.c1393
-rw-r--r--security/apparmor/file.c517
-rw-r--r--security/apparmor/include/apparmor.h6
-rw-r--r--security/apparmor/include/apparmorfs.h67
-rw-r--r--security/apparmor/include/audit.h17
-rw-r--r--security/apparmor/include/capability.h8
-rw-r--r--security/apparmor/include/context.h201
-rw-r--r--security/apparmor/include/domain.h13
-rw-r--r--security/apparmor/include/file.h114
-rw-r--r--security/apparmor/include/ipc.h16
-rw-r--r--security/apparmor/include/label.h441
-rw-r--r--security/apparmor/include/lib.h120
-rw-r--r--security/apparmor/include/path.h7
-rw-r--r--security/apparmor/include/perms.h155
-rw-r--r--security/apparmor/include/policy.h131
-rw-r--r--security/apparmor/include/policy_ns.h21
-rw-r--r--security/apparmor/include/policy_unpack.h68
-rw-r--r--security/apparmor/include/procattr.h8
-rw-r--r--security/apparmor/include/resource.h6
-rw-r--r--security/apparmor/ipc.c140
-rw-r--r--security/apparmor/label.c2120
-rw-r--r--security/apparmor/lib.c368
-rw-r--r--security/apparmor/lsm.c245
-rw-r--r--security/apparmor/match.c2
-rw-r--r--security/apparmor/path.c130
-rw-r--r--security/apparmor/policy.c392
-rw-r--r--security/apparmor/policy_ns.c80
-rw-r--r--security/apparmor/policy_unpack.c98
-rw-r--r--security/apparmor/procattr.c71
-rw-r--r--security/apparmor/resource.c116
-rw-r--r--security/inode.c144
-rw-r--r--security/integrity/digsig_asymmetric.c4
-rw-r--r--security/integrity/evm/evm_crypto.c2
-rw-r--r--security/integrity/iint.c2
-rw-r--r--security/integrity/ima/Kconfig16
-rw-r--r--security/integrity/ima/ima.h31
-rw-r--r--security/integrity/ima/ima_appraise.c16
-rw-r--r--security/integrity/ima/ima_fs.c13
-rw-r--r--security/integrity/ima/ima_policy.c118
-rw-r--r--security/integrity/ima/ima_queue.c2
-rw-r--r--security/integrity/ima/ima_template.c124
-rw-r--r--security/integrity/ima/ima_template_lib.c61
-rw-r--r--security/integrity/ima/ima_template_lib.h6
-rw-r--r--security/integrity/integrity.h7
-rw-r--r--security/keys/Kconfig6
-rw-r--r--security/keys/dh.c300
-rw-r--r--security/keys/encrypted-keys/encrypted.c206
-rw-r--r--security/keys/encrypted-keys/masterkey_trusted.c2
-rw-r--r--security/keys/gc.c4
-rw-r--r--security/keys/internal.h1
-rw-r--r--security/keys/key.c16
-rw-r--r--security/keys/keyctl.c16
-rw-r--r--security/keys/keyring.c12
-rw-r--r--security/keys/process_keys.c7
-rw-r--r--security/keys/request_key.c2
-rw-r--r--security/keys/request_key_auth.c2
-rw-r--r--security/keys/trusted.c52
-rw-r--r--security/keys/user_defined.c16
-rw-r--r--security/lsm_audit.c16
-rw-r--r--security/security.c74
-rw-r--r--security/selinux/Makefile2
-rw-r--r--security/selinux/hooks.c217
-rw-r--r--security/selinux/ibpkey.c245
-rw-r--r--security/selinux/include/classmap.h6
-rw-r--r--security/selinux/include/ibpkey.h31
-rw-r--r--security/selinux/include/objsec.h11
-rw-r--r--security/selinux/include/security.h9
-rw-r--r--security/selinux/nlmsgtab.c3
-rw-r--r--security/selinux/selinuxfs.c15
-rw-r--r--security/selinux/ss/ebitmap.c26
-rw-r--r--security/selinux/ss/ebitmap.h3
-rw-r--r--security/selinux/ss/policydb.c127
-rw-r--r--security/selinux/ss/policydb.h27
-rw-r--r--security/selinux/ss/services.c108
-rw-r--r--security/selinux/ss/sidtab.c27
-rw-r--r--security/smack/smack.h2
-rw-r--r--security/smack/smack_access.c19
-rw-r--r--security/smack/smack_lsm.c2
-rw-r--r--security/smack/smack_netfilter.c26
-rw-r--r--security/yama/Kconfig3
86 files changed, 8687 insertions, 2437 deletions
diff --git a/security/Kconfig b/security/Kconfig
index 93027fdf47d1..d540bfe73190 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -54,6 +54,15 @@ config SECURITY_NETWORK
implement socket and networking access controls.
If you are unsure how to answer this question, answer N.
+config SECURITY_INFINIBAND
+ bool "Infiniband Security Hooks"
+ depends on SECURITY && INFINIBAND
+ help
+ This enables the Infiniband security hooks.
+ If enabled, a security module can use these hooks to
+ implement Infiniband access controls.
+ If you are unsure how to answer this question, answer N.
+
config SECURITY_NETWORK_XFRM
bool "XFRM (IPSec) Networking Security Hooks"
depends on XFRM && SECURITY_NETWORK
@@ -139,7 +148,7 @@ config HARDENED_USERCOPY
copying memory to/from the kernel (via copy_to_user() and
copy_from_user() functions) by rejecting memory ranges that
are larger than the specified heap object, span multiple
- separately allocates pages, are not on the process stack,
+ separately allocated pages, are not on the process stack,
or are part of the kernel text. This kills entire classes
of heap overflow exploits and similar kernel memory exposures.
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index ad369a7aac24..a16b195274de 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
- resource.o secid.o file.o policy_ns.o
+ resource.o secid.o file.o policy_ns.o label.o
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
clean-files := capability_names.h rlim_names.h
@@ -20,7 +20,7 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\
sed $< >>$@ -r -n -e '/CAP_FS_MASK/d' \
-e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\
echo "};" >> $@ ;\
- echo -n '\#define AA_FS_CAPS_MASK "' >> $@ ;\
+ printf '%s' '\#define AA_SFS_CAPS_MASK "' >> $@ ;\
sed $< -r -n -e '/CAP_FS_MASK/d' \
-e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
@@ -46,7 +46,7 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\
# #define RLIMIT_FSIZE 1 /* Maximum filesize */
# #define RLIMIT_STACK 3 /* max stack size */
# to
-# #define AA_FS_RLIMIT_MASK "fsize stack"
+# #define AA_SFS_RLIMIT_MASK "fsize stack"
quiet_cmd_make-rlim = GEN $@
cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \
> $@ ;\
@@ -56,7 +56,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \
echo "static const int rlim_map[RLIM_NLIMITS] = {" >> $@ ;\
sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\
echo "};" >> $@ ; \
- echo -n '\#define AA_FS_RLIMIT_MASK "' >> $@ ;\
+ printf '%s' '\#define AA_SFS_RLIMIT_MASK "' >> $@ ;\
sed -r -n 's/^\# ?define[ \t]+RLIMIT_([A-Z0-9_]+).*/\L\1/p' $< | \
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 4f6ac9dbc65d..853c2ec8e0c9 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -22,19 +22,52 @@
#include <linux/namei.h>
#include <linux/capability.h>
#include <linux/rcupdate.h>
-#include <uapi/linux/major.h>
#include <linux/fs.h>
+#include <linux/poll.h>
+#include <uapi/linux/major.h>
+#include <uapi/linux/magic.h>
#include "include/apparmor.h"
#include "include/apparmorfs.h"
#include "include/audit.h"
#include "include/context.h"
#include "include/crypto.h"
+#include "include/policy_ns.h"
+#include "include/label.h"
#include "include/policy.h"
#include "include/policy_ns.h"
#include "include/resource.h"
#include "include/policy_unpack.h"
+/*
+ * The apparmor filesystem interface used for policy load and introspection
+ * The interface is split into two main components based on their function
+ * a securityfs component:
+ * used for static files that are always available, and which allows
+ * userspace to specificy the location of the security filesystem.
+ *
+ * fns and data are prefixed with
+ * aa_sfs_
+ *
+ * an apparmorfs component:
+ * used loaded policy content and introspection. It is not part of a
+ * regular mounted filesystem and is available only through the magic
+ * policy symlink in the root of the securityfs apparmor/ directory.
+ * Tasks queries will be magically redirected to the correct portion
+ * of the policy tree based on their confinement.
+ *
+ * fns and data are prefixed with
+ * aafs_
+ *
+ * The aa_fs_ prefix is used to indicate the fn is used by both the
+ * securityfs and apparmorfs filesystems.
+ */
+
+
+/*
+ * support fns
+ */
+
/**
* aa_mangle_name - mangle a profile name to std profile layout form
* @name: profile name to mangle (NOT NULL)
@@ -74,6 +107,265 @@ static int mangle_name(const char *name, char *target)
return t - target;
}
+
+/*
+ * aafs - core fns and data for the policy tree
+ */
+
+#define AAFS_NAME "apparmorfs"
+static struct vfsmount *aafs_mnt;
+static int aafs_count;
+
+
+static int aafs_show_path(struct seq_file *seq, struct dentry *dentry)
+{
+ struct inode *inode = d_inode(dentry);
+
+ seq_printf(seq, "%s:[%lu]", AAFS_NAME, inode->i_ino);
+ return 0;
+}
+
+static void aafs_evict_inode(struct inode *inode)
+{
+ truncate_inode_pages_final(&inode->i_data);
+ clear_inode(inode);
+ if (S_ISLNK(inode->i_mode))
+ kfree(inode->i_link);
+}
+
+static const struct super_operations aafs_super_ops = {
+ .statfs = simple_statfs,
+ .evict_inode = aafs_evict_inode,
+ .show_path = aafs_show_path,
+};
+
+static int fill_super(struct super_block *sb, void *data, int silent)
+{
+ static struct tree_descr files[] = { {""} };
+ int error;
+
+ error = simple_fill_super(sb, AAFS_MAGIC, files);
+ if (error)
+ return error;
+ sb->s_op = &aafs_super_ops;
+
+ return 0;
+}
+
+static struct dentry *aafs_mount(struct file_system_type *fs_type,
+ int flags, const char *dev_name, void *data)
+{
+ return mount_single(fs_type, flags, data, fill_super);
+}
+
+static struct file_system_type aafs_ops = {
+ .owner = THIS_MODULE,
+ .name = AAFS_NAME,
+ .mount = aafs_mount,
+ .kill_sb = kill_anon_super,
+};
+
+/**
+ * __aafs_setup_d_inode - basic inode setup for apparmorfs
+ * @dir: parent directory for the dentry
+ * @dentry: dentry we are seting the inode up for
+ * @mode: permissions the file should have
+ * @data: data to store on inode.i_private, available in open()
+ * @link: if symlink, symlink target string
+ * @fops: struct file_operations that should be used
+ * @iops: struct of inode_operations that should be used
+ */
+static int __aafs_setup_d_inode(struct inode *dir, struct dentry *dentry,
+ umode_t mode, void *data, char *link,
+ const struct file_operations *fops,
+ const struct inode_operations *iops)
+{
+ struct inode *inode = new_inode(dir->i_sb);
+
+ AA_BUG(!dir);
+ AA_BUG(!dentry);
+
+ if (!inode)
+ return -ENOMEM;
+
+ inode->i_ino = get_next_ino();
+ inode->i_mode = mode;
+ inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
+ inode->i_private = data;
+ if (S_ISDIR(mode)) {
+ inode->i_op = iops ? iops : &simple_dir_inode_operations;
+ inode->i_fop = &simple_dir_operations;
+ inc_nlink(inode);
+ inc_nlink(dir);
+ } else if (S_ISLNK(mode)) {
+ inode->i_op = iops ? iops : &simple_symlink_inode_operations;
+ inode->i_link = link;
+ } else {
+ inode->i_fop = fops;
+ }
+ d_instantiate(dentry, inode);
+ dget(dentry);
+
+ return 0;
+}
+
+/**
+ * aafs_create - create a dentry in the apparmorfs filesystem
+ *
+ * @name: name of dentry to create
+ * @mode: permissions the file should have
+ * @parent: parent directory for this dentry
+ * @data: data to store on inode.i_private, available in open()
+ * @link: if symlink, symlink target string
+ * @fops: struct file_operations that should be used for
+ * @iops: struct of inode_operations that should be used
+ *
+ * This is the basic "create a xxx" function for apparmorfs.
+ *
+ * Returns a pointer to a dentry if it succeeds, that must be free with
+ * aafs_remove(). Will return ERR_PTR on failure.
+ */
+static struct dentry *aafs_create(const char *name, umode_t mode,
+ struct dentry *parent, void *data, void *link,
+ const struct file_operations *fops,
+ const struct inode_operations *iops)
+{
+ struct dentry *dentry;
+ struct inode *dir;
+ int error;
+
+ AA_BUG(!name);
+ AA_BUG(!parent);
+
+ if (!(mode & S_IFMT))
+ mode = (mode & S_IALLUGO) | S_IFREG;
+
+ error = simple_pin_fs(&aafs_ops, &aafs_mnt, &aafs_count);
+ if (error)
+ return ERR_PTR(error);
+
+ dir = d_inode(parent);
+
+ inode_lock(dir);
+ dentry = lookup_one_len(name, parent, strlen(name));
+ if (IS_ERR(dentry))
+ goto fail_lock;
+
+ if (d_really_is_positive(dentry)) {
+ error = -EEXIST;
+ goto fail_dentry;
+ }
+
+ error = __aafs_setup_d_inode(dir, dentry, mode, data, link, fops, iops);
+ if (error)
+ goto fail_dentry;
+ inode_unlock(dir);
+
+ return dentry;
+
+fail_dentry:
+ dput(dentry);
+
+fail_lock:
+ inode_unlock(dir);
+ simple_release_fs(&aafs_mnt, &aafs_count);
+
+ return ERR_PTR(error);
+}
+
+/**
+ * aafs_create_file - create a file in the apparmorfs filesystem
+ *
+ * @name: name of dentry to create
+ * @mode: permissions the file should have
+ * @parent: parent directory for this dentry
+ * @data: data to store on inode.i_private, available in open()
+ * @fops: struct file_operations that should be used for
+ *
+ * see aafs_create
+ */
+static struct dentry *aafs_create_file(const char *name, umode_t mode,
+ struct dentry *parent, void *data,
+ const struct file_operations *fops)
+{
+ return aafs_create(name, mode, parent, data, NULL, fops, NULL);
+}
+
+/**
+ * aafs_create_dir - create a directory in the apparmorfs filesystem
+ *
+ * @name: name of dentry to create
+ * @parent: parent directory for this dentry
+ *
+ * see aafs_create
+ */
+static struct dentry *aafs_create_dir(const char *name, struct dentry *parent)
+{
+ return aafs_create(name, S_IFDIR | 0755, parent, NULL, NULL, NULL,
+ NULL);
+}
+
+/**
+ * aafs_create_symlink - create a symlink in the apparmorfs filesystem
+ * @name: name of dentry to create
+ * @parent: parent directory for this dentry
+ * @target: if symlink, symlink target string
+ * @iops: struct of inode_operations that should be used
+ *
+ * If @target parameter is %NULL, then the @iops parameter needs to be
+ * setup to handle .readlink and .get_link inode_operations.
+ */
+static struct dentry *aafs_create_symlink(const char *name,
+ struct dentry *parent,
+ const char *target,
+ const struct inode_operations *iops)
+{
+ struct dentry *dent;
+ char *link = NULL;
+
+ if (target) {
+ link = kstrdup(target, GFP_KERNEL);
+ if (!link)
+ return ERR_PTR(-ENOMEM);
+ }
+ dent = aafs_create(name, S_IFLNK | 0444, parent, NULL, link, NULL,
+ iops);
+ if (IS_ERR(dent))
+ kfree(link);
+
+ return dent;
+}
+
+/**
+ * aafs_remove - removes a file or directory from the apparmorfs filesystem
+ *
+ * @dentry: dentry of the file/directory/symlink to removed.
+ */
+static void aafs_remove(struct dentry *dentry)
+{
+ struct inode *dir;
+
+ if (!dentry || IS_ERR(dentry))
+ return;
+
+ dir = d_inode(dentry->d_parent);
+ inode_lock(dir);
+ if (simple_positive(dentry)) {
+ if (d_is_dir(dentry))
+ simple_rmdir(dir, dentry);
+ else
+ simple_unlink(dir, dentry);
+ dput(dentry);
+ }
+ inode_unlock(dir);
+ simple_release_fs(&aafs_mnt, &aafs_count);
+}
+
+
+/*
+ * aa_fs - policy load/replace/remove
+ */
+
/**
* aa_simple_write_to_buffer - common routine for getting policy from user
* @userbuf: user buffer to copy data from (NOT NULL)
@@ -98,14 +390,11 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf,
return ERR_PTR(-ESPIPE);
/* freed by caller to simple_write_to_buffer */
- data = kvmalloc(sizeof(*data) + alloc_size, GFP_KERNEL);
- if (data == NULL)
- return ERR_PTR(-ENOMEM);
- kref_init(&data->count);
- data->size = copy_size;
- data->hash = NULL;
- data->abi = 0;
+ data = aa_loaddata_alloc(alloc_size);
+ if (IS_ERR(data))
+ return data;
+ data->size = copy_size;
if (copy_from_user(data->data, userbuf, copy_size)) {
kvfree(data);
return ERR_PTR(-EFAULT);
@@ -114,27 +403,29 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf,
return data;
}
-static ssize_t policy_update(int binop, const char __user *buf, size_t size,
+static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
loff_t *pos, struct aa_ns *ns)
{
- ssize_t error;
struct aa_loaddata *data;
- struct aa_profile *profile = aa_current_profile();
- const char *op = binop == PROF_ADD ? OP_PROF_LOAD : OP_PROF_REPL;
+ struct aa_label *label;
+ ssize_t error;
+
+ label = begin_current_label_crit_section();
+
/* high level check about policy management - fine grained in
* below after unpack
*/
- error = aa_may_manage_policy(profile, ns, op);
+ error = aa_may_manage_policy(label, ns, mask);
if (error)
return error;
data = aa_simple_write_to_buffer(buf, size, size, pos);
error = PTR_ERR(data);
if (!IS_ERR(data)) {
- error = aa_replace_profiles(ns ? ns : profile->ns, profile,
- binop, data);
+ error = aa_replace_profiles(ns, label, mask, data);
aa_put_loaddata(data);
}
+ end_current_label_crit_section(label);
return error;
}
@@ -144,7 +435,7 @@ static ssize_t profile_load(struct file *f, const char __user *buf, size_t size,
loff_t *pos)
{
struct aa_ns *ns = aa_get_ns(f->f_inode->i_private);
- int error = policy_update(PROF_ADD, buf, size, pos, ns);
+ int error = policy_update(AA_MAY_LOAD_POLICY, buf, size, pos, ns);
aa_put_ns(ns);
@@ -161,8 +452,8 @@ static ssize_t profile_replace(struct file *f, const char __user *buf,
size_t size, loff_t *pos)
{
struct aa_ns *ns = aa_get_ns(f->f_inode->i_private);
- int error = policy_update(PROF_REPLACE, buf, size, pos, ns);
-
+ int error = policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY,
+ buf, size, pos, ns);
aa_put_ns(ns);
return error;
@@ -178,15 +469,15 @@ static ssize_t profile_remove(struct file *f, const char __user *buf,
size_t size, loff_t *pos)
{
struct aa_loaddata *data;
- struct aa_profile *profile;
+ struct aa_label *label;
ssize_t error;
struct aa_ns *ns = aa_get_ns(f->f_inode->i_private);
- profile = aa_current_profile();
+ label = begin_current_label_crit_section();
/* high level check about policy management - fine grained in
* below after unpack
*/
- error = aa_may_manage_policy(profile, ns, OP_PROF_RM);
+ error = aa_may_manage_policy(label, ns, AA_MAY_REMOVE_POLICY);
if (error)
goto out;
@@ -199,11 +490,11 @@ static ssize_t profile_remove(struct file *f, const char __user *buf,
error = PTR_ERR(data);
if (!IS_ERR(data)) {
data->data[size] = 0;
- error = aa_remove_profiles(ns ? ns : profile->ns, profile,
- data->data, size);
+ error = aa_remove_profiles(ns, label, data->data, size);
aa_put_loaddata(data);
}
out:
+ end_current_label_crit_section(label);
aa_put_ns(ns);
return error;
}
@@ -213,6 +504,136 @@ static const struct file_operations aa_fs_profile_remove = {
.llseek = default_llseek,
};
+struct aa_revision {
+ struct aa_ns *ns;
+ long last_read;
+};
+
+/* revision file hook fn for policy loads */
+static int ns_revision_release(struct inode *inode, struct file *file)
+{
+ struct aa_revision *rev = file->private_data;
+
+ if (rev) {
+ aa_put_ns(rev->ns);
+ kfree(rev);
+ }
+
+ return 0;
+}
+
+static ssize_t ns_revision_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ struct aa_revision *rev = file->private_data;
+ char buffer[32];
+ long last_read;
+ int avail;
+
+ mutex_lock(&rev->ns->lock);
+ last_read = rev->last_read;
+ if (last_read == rev->ns->revision) {
+ mutex_unlock(&rev->ns->lock);
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ if (wait_event_interruptible(rev->ns->wait,
+ last_read !=
+ READ_ONCE(rev->ns->revision)))
+ return -ERESTARTSYS;
+ mutex_lock(&rev->ns->lock);
+ }
+
+ avail = sprintf(buffer, "%ld\n", rev->ns->revision);
+ if (*ppos + size > avail) {
+ rev->last_read = rev->ns->revision;
+ *ppos = 0;
+ }
+ mutex_unlock(&rev->ns->lock);
+
+ return simple_read_from_buffer(buf, size, ppos, buffer, avail);
+}
+
+static int ns_revision_open(struct inode *inode, struct file *file)
+{
+ struct aa_revision *rev = kzalloc(sizeof(*rev), GFP_KERNEL);
+
+ if (!rev)
+ return -ENOMEM;
+
+ rev->ns = aa_get_ns(inode->i_private);
+ if (!rev->ns)
+ rev->ns = aa_get_current_ns();
+ file->private_data = rev;
+
+ return 0;
+}
+
+static unsigned int ns_revision_poll(struct file *file, poll_table *pt)
+{
+ struct aa_revision *rev = file->private_data;
+ unsigned int mask = 0;
+
+ if (rev) {
+ mutex_lock(&rev->ns->lock);
+ poll_wait(file, &rev->ns->wait, pt);
+ if (rev->last_read < rev->ns->revision)
+ mask |= POLLIN | POLLRDNORM;
+ mutex_unlock(&rev->ns->lock);
+ }
+
+ return mask;
+}
+
+void __aa_bump_ns_revision(struct aa_ns *ns)
+{
+ ns->revision++;
+ wake_up_interruptible(&ns->wait);
+}
+
+static const struct file_operations aa_fs_ns_revision_fops = {
+ .owner = THIS_MODULE,
+ .open = ns_revision_open,
+ .poll = ns_revision_poll,
+ .read = ns_revision_read,
+ .llseek = generic_file_llseek,
+ .release = ns_revision_release,
+};
+
+static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,
+ const char *match_str, size_t match_len)
+{
+ struct aa_perms tmp;
+ struct aa_dfa *dfa;
+ unsigned int state = 0;
+
+ if (profile_unconfined(profile))
+ return;
+ if (profile->file.dfa && *match_str == AA_CLASS_FILE) {
+ dfa = profile->file.dfa;
+ state = aa_dfa_match_len(dfa, profile->file.start,
+ match_str + 1, match_len - 1);
+ tmp = nullperms;
+ if (state) {
+ struct path_cond cond = { };
+
+ tmp = aa_compute_fperms(dfa, state, &cond);
+ }
+ } else if (profile->policy.dfa) {
+ if (!PROFILE_MEDIATES_SAFE(profile, *match_str))
+ return; /* no change to current perms */
+ dfa = profile->policy.dfa;
+ state = aa_dfa_match_len(dfa, profile->policy.start[0],
+ match_str, match_len);
+ if (state)
+ aa_compute_perms(dfa, state, &tmp);
+ else
+ tmp = nullperms;
+ }
+ aa_apply_modes_to_perms(profile, &tmp);
+ aa_perms_accum_raw(perms, &tmp);
+}
+
+
/**
* query_data - queries a policy and writes its data to buf
* @buf: the resulting data is stored here (NOT NULL)
@@ -236,6 +657,8 @@ static ssize_t query_data(char *buf, size_t buf_len,
{
char *out;
const char *key;
+ struct label_it i;
+ struct aa_label *label, *curr;
struct aa_profile *profile;
struct aa_data *data;
u32 bytes, blocks;
@@ -253,7 +676,11 @@ static ssize_t query_data(char *buf, size_t buf_len,
if (buf_len < sizeof(bytes) + sizeof(blocks))
return -EINVAL; /* not enough space */
- profile = aa_current_profile();
+ curr = begin_current_label_crit_section();
+ label = aa_label_parse(curr, query, GFP_KERNEL, false, false);
+ end_current_label_crit_section(curr);
+ if (IS_ERR(label))
+ return PTR_ERR(label);
/* We are going to leave space for two numbers. The first is the total
* number of bytes we are writing after the first number. This is so
@@ -267,13 +694,19 @@ static ssize_t query_data(char *buf, size_t buf_len,
out = buf + sizeof(bytes) + sizeof(blocks);
blocks = 0;
- if (profile->data) {
+ label_for_each_confined(i, label, profile) {
+ if (!profile->data)
+ continue;
+
data = rhashtable_lookup_fast(profile->data, &key,
profile->data->p);
if (data) {
- if (out + sizeof(outle32) + data->size > buf + buf_len)
+ if (out + sizeof(outle32) + data->size > buf +
+ buf_len) {
+ aa_put_label(label);
return -EINVAL; /* not enough space */
+ }
outle32 = __cpu_to_le32(data->size);
memcpy(out, &outle32, sizeof(outle32));
out += sizeof(outle32);
@@ -282,6 +715,7 @@ static ssize_t query_data(char *buf, size_t buf_len,
blocks++;
}
}
+ aa_put_label(label);
outle32 = __cpu_to_le32(out - buf - sizeof(bytes));
memcpy(buf, &outle32, sizeof(outle32));
@@ -291,6 +725,182 @@ static ssize_t query_data(char *buf, size_t buf_len,
return out - buf;
}
+/**
+ * query_label - queries a label and writes permissions to buf
+ * @buf: the resulting permissions string is stored here (NOT NULL)
+ * @buf_len: size of buf
+ * @query: binary query string to match against the dfa
+ * @query_len: size of query
+ * @view_only: only compute for querier's view
+ *
+ * The buffers pointed to by buf and query may overlap. The query buffer is
+ * parsed before buf is written to.
+ *
+ * The query should look like "LABEL_NAME\0DFA_STRING" where LABEL_NAME is
+ * the name of the label, in the current namespace, that is to be queried and
+ * DFA_STRING is a binary string to match against the label(s)'s DFA.
+ *
+ * LABEL_NAME must be NUL terminated. DFA_STRING may contain NUL characters
+ * but must *not* be NUL terminated.
+ *
+ * Returns: number of characters written to buf or -errno on failure
+ */
+static ssize_t query_label(char *buf, size_t buf_len,
+ char *query, size_t query_len, bool view_only)
+{
+ struct aa_profile *profile;
+ struct aa_label *label, *curr;
+ char *label_name, *match_str;
+ size_t label_name_len, match_len;
+ struct aa_perms perms;
+ struct label_it i;
+
+ if (!query_len)
+ return -EINVAL;
+
+ label_name = query;
+ label_name_len = strnlen(query, query_len);
+ if (!label_name_len || label_name_len == query_len)
+ return -EINVAL;
+
+ /**
+ * The extra byte is to account for the null byte between the
+ * profile name and dfa string. profile_name_len is greater
+ * than zero and less than query_len, so a byte can be safely
+ * added or subtracted.
+ */
+ match_str = label_name + label_name_len + 1;
+ match_len = query_len - label_name_len - 1;
+
+ curr = begin_current_label_crit_section();
+ label = aa_label_parse(curr, label_name, GFP_KERNEL, false, false);
+ end_current_label_crit_section(curr);
+ if (IS_ERR(label))
+ return PTR_ERR(label);
+
+ perms = allperms;
+ if (view_only) {
+ label_for_each_in_ns(i, labels_ns(label), label, profile) {
+ profile_query_cb(profile, &perms, match_str, match_len);
+ }
+ } else {
+ label_for_each(i, label, profile) {
+ profile_query_cb(profile, &perms, match_str, match_len);
+ }
+ }
+ aa_put_label(label);
+
+ return scnprintf(buf, buf_len,
+ "allow 0x%08x\ndeny 0x%08x\naudit 0x%08x\nquiet 0x%08x\n",
+ perms.allow, perms.deny, perms.audit, perms.quiet);
+}
+
+/*
+ * Transaction based IO.
+ * The file expects a write which triggers the transaction, and then
+ * possibly a read(s) which collects the result - which is stored in a
+ * file-local buffer. Once a new write is performed, a new set of results
+ * are stored in the file-local buffer.
+ */
+struct multi_transaction {
+ struct kref count;
+ ssize_t size;
+ char data[0];
+};
+
+#define MULTI_TRANSACTION_LIMIT (PAGE_SIZE - sizeof(struct multi_transaction))
+/* TODO: replace with per file lock */
+static DEFINE_SPINLOCK(multi_transaction_lock);
+
+static void multi_transaction_kref(struct kref *kref)
+{
+ struct multi_transaction *t;
+
+ t = container_of(kref, struct multi_transaction, count);
+ free_page((unsigned long) t);
+}
+
+static struct multi_transaction *
+get_multi_transaction(struct multi_transaction *t)
+{
+ if (t)
+ kref_get(&(t->count));
+
+ return t;
+}
+
+static void put_multi_transaction(struct multi_transaction *t)
+{
+ if (t)
+ kref_put(&(t->count), multi_transaction_kref);
+}
+
+/* does not increment @new's count */
+static void multi_transaction_set(struct file *file,
+ struct multi_transaction *new, size_t n)
+{
+ struct multi_transaction *old;
+
+ AA_BUG(n > MULTI_TRANSACTION_LIMIT);
+
+ new->size = n;
+ spin_lock(&multi_transaction_lock);
+ old = (struct multi_transaction *) file->private_data;
+ file->private_data = new;
+ spin_unlock(&multi_transaction_lock);
+ put_multi_transaction(old);
+}
+
+static struct multi_transaction *multi_transaction_new(struct file *file,
+ const char __user *buf,
+ size_t size)
+{
+ struct multi_transaction *t;
+
+ if (size > MULTI_TRANSACTION_LIMIT - 1)
+ return ERR_PTR(-EFBIG);
+
+ t = (struct multi_transaction *)get_zeroed_page(GFP_KERNEL);
+ if (!t)
+ return ERR_PTR(-ENOMEM);
+ kref_init(&t->count);
+ if (copy_from_user(t->data, buf, size))
+ return ERR_PTR(-EFAULT);
+
+ return t;
+}
+
+static ssize_t multi_transaction_read(struct file *file, char __user *buf,
+ size_t size, loff_t *pos)
+{
+ struct multi_transaction *t;
+ ssize_t ret;
+
+ spin_lock(&multi_transaction_lock);
+ t = get_multi_transaction(file->private_data);
+ spin_unlock(&multi_transaction_lock);
+ if (!t)
+ return 0;
+
+ ret = simple_read_from_buffer(buf, size, pos, t->data, t->size);
+ put_multi_transaction(t);
+
+ return ret;
+}
+
+static int multi_transaction_release(struct inode *inode, struct file *file)
+{
+ put_multi_transaction(file->private_data);
+
+ return 0;
+}
+
+#define QUERY_CMD_LABEL "label\0"
+#define QUERY_CMD_LABEL_LEN 6
+#define QUERY_CMD_PROFILE "profile\0"
+#define QUERY_CMD_PROFILE_LEN 8
+#define QUERY_CMD_LABELALL "labelall\0"
+#define QUERY_CMD_LABELALL_LEN 9
#define QUERY_CMD_DATA "data\0"
#define QUERY_CMD_DATA_LEN 5
@@ -318,54 +928,72 @@ static ssize_t query_data(char *buf, size_t buf_len,
static ssize_t aa_write_access(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
- char *buf;
+ struct multi_transaction *t;
ssize_t len;
if (*ppos)
return -ESPIPE;
- buf = simple_transaction_get(file, ubuf, count);
- if (IS_ERR(buf))
- return PTR_ERR(buf);
-
- if (count > QUERY_CMD_DATA_LEN &&
- !memcmp(buf, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) {
- len = query_data(buf, SIMPLE_TRANSACTION_LIMIT,
- buf + QUERY_CMD_DATA_LEN,
+ t = multi_transaction_new(file, ubuf, count);
+ if (IS_ERR(t))
+ return PTR_ERR(t);
+
+ if (count > QUERY_CMD_PROFILE_LEN &&
+ !memcmp(t->data, QUERY_CMD_PROFILE, QUERY_CMD_PROFILE_LEN)) {
+ len = query_label(t->data, MULTI_TRANSACTION_LIMIT,
+ t->data + QUERY_CMD_PROFILE_LEN,
+ count - QUERY_CMD_PROFILE_LEN, true);
+ } else if (count > QUERY_CMD_LABEL_LEN &&
+ !memcmp(t->data, QUERY_CMD_LABEL, QUERY_CMD_LABEL_LEN)) {
+ len = query_label(t->data, MULTI_TRANSACTION_LIMIT,
+ t->data + QUERY_CMD_LABEL_LEN,
+ count - QUERY_CMD_LABEL_LEN, true);
+ } else if (count > QUERY_CMD_LABELALL_LEN &&
+ !memcmp(t->data, QUERY_CMD_LABELALL,
+ QUERY_CMD_LABELALL_LEN)) {
+ len = query_label(t->data, MULTI_TRANSACTION_LIMIT,
+ t->data + QUERY_CMD_LABELALL_LEN,
+ count - QUERY_CMD_LABELALL_LEN, false);
+ } else if (count > QUERY_CMD_DATA_LEN &&
+ !memcmp(t->data, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) {
+ len = query_data(t->data, MULTI_TRANSACTION_LIMIT,
+ t->data + QUERY_CMD_DATA_LEN,
count - QUERY_CMD_DATA_LEN);
} else
len = -EINVAL;
- if (len < 0)
+ if (len < 0) {
+ put_multi_transaction(t);
return len;
+ }
- simple_transaction_set(file, len);
+ multi_transaction_set(file, t, len);
return count;
}
-static const struct file_operations aa_fs_access = {
+static const struct file_operations aa_sfs_access = {
.write = aa_write_access,
- .read = simple_transaction_read,
- .release = simple_transaction_release,
+ .read = multi_transaction_read,
+ .release = multi_transaction_release,
.llseek = generic_file_llseek,
};
-static int aa_fs_seq_show(struct seq_file *seq, void *v)
+static int aa_sfs_seq_show(struct seq_file *seq, void *v)
{
- struct aa_fs_entry *fs_file = seq->private;
+ struct aa_sfs_entry *fs_file = seq->private;
if (!fs_file)
return 0;
switch (fs_file->v_type) {
- case AA_FS_TYPE_BOOLEAN:
+ case AA_SFS_TYPE_BOOLEAN:
seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no");
break;
- case AA_FS_TYPE_STRING:
+ case AA_SFS_TYPE_STRING:
seq_printf(seq, "%s\n", fs_file->v.string);
break;
- case AA_FS_TYPE_U64:
+ case AA_SFS_TYPE_U64:
seq_printf(seq, "%#08lx\n", fs_file->v.u64);
break;
default:
@@ -376,21 +1004,40 @@ static int aa_fs_seq_show(struct seq_file *seq, void *v)
return 0;
}
-static int aa_fs_seq_open(struct inode *inode, struct file *file)
+static int aa_sfs_seq_open(struct inode *inode, struct file *file)
{
- return single_open(file, aa_fs_seq_show, inode->i_private);
+ return single_open(file, aa_sfs_seq_show, inode->i_private);
}
-const struct file_operations aa_fs_seq_file_ops = {
+const struct file_operations aa_sfs_seq_file_ops = {
.owner = THIS_MODULE,
- .open = aa_fs_seq_open,
+ .open = aa_sfs_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
-static int aa_fs_seq_profile_open(struct inode *inode, struct file *file,
- int (*show)(struct seq_file *, void *))
+/*
+ * profile based file operations
+ * policy/profiles/XXXX/profiles/ *
+ */
+
+#define SEQ_PROFILE_FOPS(NAME) \
+static int seq_profile_ ##NAME ##_open(struct inode *inode, struct file *file)\
+{ \
+ return seq_profile_open(inode, file, seq_profile_ ##NAME ##_show); \
+} \
+ \
+static const struct file_operations seq_profile_ ##NAME ##_fops = { \
+ .owner = THIS_MODULE, \
+ .open = seq_profile_ ##NAME ##_open, \
+ .read = seq_read, \
+ .llseek = seq_lseek, \
+ .release = seq_profile_release, \
+} \
+
+static int seq_profile_open(struct inode *inode, struct file *file,
+ int (*show)(struct seq_file *, void *))
{
struct aa_proxy *proxy = aa_get_proxy(inode->i_private);
int error = single_open(file, show, proxy);
@@ -403,7 +1050,7 @@ static int aa_fs_seq_profile_open(struct inode *inode, struct file *file,
return error;
}
-static int aa_fs_seq_profile_release(struct inode *inode, struct file *file)
+static int seq_profile_release(struct inode *inode, struct file *file)
{
struct seq_file *seq = (struct seq_file *) file->private_data;
if (seq)
@@ -411,217 +1058,229 @@ static int aa_fs_seq_profile_release(struct inode *inode, struct file *file)
return single_release(inode, file);
}
-static int aa_fs_seq_profname_show(struct seq_file *seq, void *v)
+static int seq_profile_name_show(struct seq_file *seq, void *v)
{
struct aa_proxy *proxy = seq->private;
- struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile);
+ struct aa_label *label = aa_get_label_rcu(&proxy->label);
+ struct aa_profile *profile = labels_profile(label);
seq_printf(seq, "%s\n", profile->base.name);
- aa_put_profile(profile);
+ aa_put_label(label);
return 0;
}
-static int aa_fs_seq_profname_open(struct inode *inode, struct file *file)
-{
- return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profname_show);
-}
-
-static const struct file_operations aa_fs_profname_fops = {
- .owner = THIS_MODULE,
- .open = aa_fs_seq_profname_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = aa_fs_seq_profile_release,
-};
-
-static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v)
+static int seq_profile_mode_show(struct seq_file *seq, void *v)
{
struct aa_proxy *proxy = seq->private;
- struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile);
+ struct aa_label *label = aa_get_label_rcu(&proxy->label);
+ struct aa_profile *profile = labels_profile(label);
seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]);
- aa_put_profile(profile);
+ aa_put_label(label);
return 0;
}
-static int aa_fs_seq_profmode_open(struct inode *inode, struct file *file)
-{
- return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profmode_show);
-}
-
-static const struct file_operations aa_fs_profmode_fops = {
- .owner = THIS_MODULE,
- .open = aa_fs_seq_profmode_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = aa_fs_seq_profile_release,
-};
-
-static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v)
+static int seq_profile_attach_show(struct seq_file *seq, void *v)
{
struct aa_proxy *proxy = seq->private;
- struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile);
+ struct aa_label *label = aa_get_label_rcu(&proxy->label);
+ struct aa_profile *profile = labels_profile(label);
if (profile->attach)
seq_printf(seq, "%s\n", profile->attach);
else if (profile->xmatch)
seq_puts(seq, "<unknown>\n");
else
seq_printf(seq, "%s\n", profile->base.name);
- aa_put_profile(profile);
+ aa_put_label(label);
return 0;
}
-static int aa_fs_seq_profattach_open(struct inode *inode, struct file *file)
-{
- return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profattach_show);
-}
-
-static const struct file_operations aa_fs_profattach_fops = {
- .owner = THIS_MODULE,
- .open = aa_fs_seq_profattach_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = aa_fs_seq_profile_release,
-};
-
-static int aa_fs_seq_hash_show(struct seq_file *seq, void *v)
+static int seq_profile_hash_show(struct seq_file *seq, void *v)
{
struct aa_proxy *proxy = seq->private;
- struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile);
+ struct aa_label *label = aa_get_label_rcu(&proxy->label);
+ struct aa_profile *profile = labels_profile(label);
unsigned int i, size = aa_hash_size();
if (profile->hash) {
for (i = 0; i < size; i++)
seq_printf(seq, "%.2x", profile->hash[i]);
- seq_puts(seq, "\n");
+ seq_putc(seq, '\n');
}
- aa_put_profile(profile);
+ aa_put_label(label);
return 0;
}
-static int aa_fs_seq_hash_open(struct inode *inode, struct file *file)
+SEQ_PROFILE_FOPS(name);
+SEQ_PROFILE_FOPS(mode);
+SEQ_PROFILE_FOPS(attach);
+SEQ_PROFILE_FOPS(hash);
+
+/*
+ * namespace based files
+ * several root files and
+ * policy/ *
+ */
+
+#define SEQ_NS_FOPS(NAME) \
+static int seq_ns_ ##NAME ##_open(struct inode *inode, struct file *file) \
+{ \
+ return single_open(file, seq_ns_ ##NAME ##_show, inode->i_private); \
+} \
+ \
+static const struct file_operations seq_ns_ ##NAME ##_fops = { \
+ .owner = THIS_MODULE, \
+ .open = seq_ns_ ##NAME ##_open, \
+ .read = seq_read, \
+ .llseek = seq_lseek, \
+ .release = single_release, \
+} \
+
+static int seq_ns_stacked_show(struct seq_file *seq, void *v)
{
- return single_open(file, aa_fs_seq_hash_show, inode->i_private);
-}
+ struct aa_label *label;
-static const struct file_operations aa_fs_seq_hash_fops = {
- .owner = THIS_MODULE,
- .open = aa_fs_seq_hash_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = single_release,
-};
+ label = begin_current_label_crit_section();
+ seq_printf(seq, "%s\n", label->size > 1 ? "yes" : "no");
+ end_current_label_crit_section(label);
+ return 0;
+}
-static int aa_fs_seq_show_ns_level(struct seq_file *seq, void *v)
+static int seq_ns_nsstacked_show(struct seq_file *seq, void *v)
{
- struct aa_ns *ns = aa_current_profile()->ns;
+ struct aa_label *label;
+ struct aa_profile *profile;
+ struct label_it it;
+ int count = 1;
- seq_printf(seq, "%d\n", ns->level);
+ label = begin_current_label_crit_section();
+
+ if (label->size > 1) {
+ label_for_each(it, label, profile)
+ if (profile->ns != labels_ns(label)) {
+ count++;
+ break;
+ }
+ }
+
+ seq_printf(seq, "%s\n", count > 1 ? "yes" : "no");
+ end_current_label_crit_section(label);
return 0;
}
-static int aa_fs_seq_open_ns_level(struct inode *inode, struct file *file)
+static int seq_ns_level_show(struct seq_file *seq, void *v)
{
- return single_open(file, aa_fs_seq_show_ns_level, inode->i_private);
-}
+ struct aa_label *label;
-static const struct file_operations aa_fs_ns_level = {
- .owner = THIS_MODULE,
- .open = aa_fs_seq_open_ns_level,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = single_release,
-};
+ label = begin_current_label_crit_section();
+ seq_printf(seq, "%d\n", labels_ns(label)->level);
+ end_current_label_crit_section(label);
+
+ return 0;
+}
-static int aa_fs_seq_show_ns_name(struct seq_file *seq, void *v)
+static int seq_ns_name_show(struct seq_file *seq, void *v)
{
- struct aa_ns *ns = aa_current_profile()->ns;
+ struct aa_label *label = begin_current_label_crit_section();
- seq_printf(seq, "%s\n", ns->base.name);
+ seq_printf(seq, "%s\n", aa_ns_name(labels_ns(label),
+ labels_ns(label), true));
+ end_current_label_crit_section(label);
return 0;
}
-static int aa_fs_seq_open_ns_name(struct inode *inode, struct file *file)
+SEQ_NS_FOPS(stacked);
+SEQ_NS_FOPS(nsstacked);
+SEQ_NS_FOPS(level);
+SEQ_NS_FOPS(name);
+
+
+/* policy/raw_data/ * file ops */
+
+#define SEQ_RAWDATA_FOPS(NAME) \
+static int seq_rawdata_ ##NAME ##_open(struct inode *inode, struct file *file)\
+{ \
+ return seq_rawdata_open(inode, file, seq_rawdata_ ##NAME ##_show); \
+} \
+ \
+static const struct file_operations seq_rawdata_ ##NAME ##_fops = { \
+ .owner = THIS_MODULE, \
+ .open = seq_rawdata_ ##NAME ##_open, \
+ .read = seq_read, \
+ .llseek = seq_lseek, \
+ .release = seq_rawdata_release, \
+} \
+
+static int seq_rawdata_open(struct inode *inode, struct file *file,
+ int (*show)(struct seq_file *, void *))
{
- return single_open(file, aa_fs_seq_show_ns_name, inode->i_private);
-}
+ struct aa_loaddata *data = __aa_get_loaddata(inode->i_private);
+ int error;
-static const struct file_operations aa_fs_ns_name = {
- .owner = THIS_MODULE,
- .open = aa_fs_seq_open_ns_name,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = single_release,
-};
+ if (!data)
+ /* lost race this ent is being reaped */
+ return -ENOENT;
-static int rawdata_release(struct inode *inode, struct file *file)
+ error = single_open(file, show, data);
+ if (error) {
+ AA_BUG(file->private_data &&
+ ((struct seq_file *)file->private_data)->private);
+ aa_put_loaddata(data);
+ }
+
+ return error;
+}
+
+static int seq_rawdata_release(struct inode *inode, struct file *file)
{
- /* TODO: switch to loaddata when profile switched to symlink */
- aa_put_loaddata(file->private_data);
+ struct seq_file *seq = (struct seq_file *) file->private_data;
- return 0;
+ if (seq)
+ aa_put_loaddata(seq->private);
+
+ return single_release(inode, file);
}
-static int aa_fs_seq_raw_abi_show(struct seq_file *seq, void *v)
+static int seq_rawdata_abi_show(struct seq_file *seq, void *v)
{
- struct aa_proxy *proxy = seq->private;
- struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile);
+ struct aa_loaddata *data = seq->private;
- if (profile->rawdata->abi) {
- seq_printf(seq, "v%d", profile->rawdata->abi);
- seq_puts(seq, "\n");
- }
- aa_put_profile(profile);
+ seq_printf(seq, "v%d\n", data->abi);
return 0;
}
-static int aa_fs_seq_raw_abi_open(struct inode *inode, struct file *file)
+static int seq_rawdata_revision_show(struct seq_file *seq, void *v)
{
- return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_abi_show);
-}
+ struct aa_loaddata *data = seq->private;
-static const struct file_operations aa_fs_seq_raw_abi_fops = {
- .owner = THIS_MODULE,
- .open = aa_fs_seq_raw_abi_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = aa_fs_seq_profile_release,
-};
+ seq_printf(seq, "%ld\n", data->revision);
+
+ return 0;
+}
-static int aa_fs_seq_raw_hash_show(struct seq_file *seq, void *v)
+static int seq_rawdata_hash_show(struct seq_file *seq, void *v)
{
- struct aa_proxy *proxy = seq->private;
- struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile);
+ struct aa_loaddata *data = seq->private;
unsigned int i, size = aa_hash_size();
- if (profile->rawdata->hash) {
+ if (data->hash) {
for (i = 0; i < size; i++)
- seq_printf(seq, "%.2x", profile->rawdata->hash[i]);
- seq_puts(seq, "\n");
+ seq_printf(seq, "%.2x", data->hash[i]);
+ seq_putc(seq, '\n');
}
- aa_put_profile(profile);
return 0;
}
-static int aa_fs_seq_raw_hash_open(struct inode *inode, struct file *file)
-{
- return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_hash_show);
-}
-
-static const struct file_operations aa_fs_seq_raw_hash_fops = {
- .owner = THIS_MODULE,
- .open = aa_fs_seq_raw_hash_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = aa_fs_seq_profile_release,
-};
+SEQ_RAWDATA_FOPS(abi);
+SEQ_RAWDATA_FOPS(revision);
+SEQ_RAWDATA_FOPS(hash);
static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size,
loff_t *ppos)
@@ -632,29 +1291,127 @@ static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size,
rawdata->size);
}
-static int rawdata_open(struct inode *inode, struct file *file)
+static int rawdata_release(struct inode *inode, struct file *file)
{
- struct aa_proxy *proxy = inode->i_private;
- struct aa_profile *profile;
+ aa_put_loaddata(file->private_data);
+ return 0;
+}
+
+static int rawdata_open(struct inode *inode, struct file *file)
+{
if (!policy_view_capable(NULL))
return -EACCES;
- profile = aa_get_profile_rcu(&proxy->profile);
- file->private_data = aa_get_loaddata(profile->rawdata);
- aa_put_profile(profile);
+ file->private_data = __aa_get_loaddata(inode->i_private);
+ if (!file->private_data)
+ /* lost race: this entry is being reaped */
+ return -ENOENT;
return 0;
}
-static const struct file_operations aa_fs_rawdata_fops = {
+static const struct file_operations rawdata_fops = {
.open = rawdata_open,
.read = rawdata_read,
.llseek = generic_file_llseek,
.release = rawdata_release,
};
+static void remove_rawdata_dents(struct aa_loaddata *rawdata)
+{
+ int i;
+
+ for (i = 0; i < AAFS_LOADDATA_NDENTS; i++) {
+ if (!IS_ERR_OR_NULL(rawdata->dents[i])) {
+ /* no refcounts on i_private */
+ aafs_remove(rawdata->dents[i]);
+ rawdata->dents[i] = NULL;
+ }
+ }
+}
+
+void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata)
+{
+ AA_BUG(rawdata->ns && !mutex_is_locked(&rawdata->ns->lock));
+
+ if (rawdata->ns) {
+ remove_rawdata_dents(rawdata);
+ list_del_init(&rawdata->list);
+ aa_put_ns(rawdata->ns);
+ rawdata->ns = NULL;
+ }
+}
+
+int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata)
+{
+ struct dentry *dent, *dir;
+
+ AA_BUG(!ns);
+ AA_BUG(!rawdata);
+ AA_BUG(!mutex_is_locked(&ns->lock));
+ AA_BUG(!ns_subdata_dir(ns));
+
+ /*
+ * just use ns revision dir was originally created at. This is
+ * under ns->lock and if load is successful revision will be
+ * bumped and is guaranteed to be unique
+ */
+ rawdata->name = kasprintf(GFP_KERNEL, "%ld", ns->revision);
+ if (!rawdata->name)
+ return -ENOMEM;
+
+ dir = aafs_create_dir(rawdata->name, ns_subdata_dir(ns));
+ if (IS_ERR(dir))
+ /* ->name freed when rawdata freed */
+ return PTR_ERR(dir);
+ rawdata->dents[AAFS_LOADDATA_DIR] = dir;
+
+ dent = aafs_create_file("abi", S_IFREG | 0444, dir, rawdata,
+ &seq_rawdata_abi_fops);
+ if (IS_ERR(dent))
+ goto fail;
+ rawdata->dents[AAFS_LOADDATA_ABI] = dent;
+
+ dent = aafs_create_file("revision", S_IFREG | 0444, dir, rawdata,
+ &seq_rawdata_revision_fops);
+ if (IS_ERR(dent))
+ goto fail;
+ rawdata->dents[AAFS_LOADDATA_REVISION] = dent;
+
+ if (aa_g_hash_policy) {
+ dent = aafs_create_file("sha1", S_IFREG | 0444, dir,
+ rawdata, &seq_rawdata_hash_fops);
+ if (IS_ERR(dent))
+ goto fail;
+ rawdata->dents[AAFS_LOADDATA_HASH] = dent;
+ }
+
+ dent = aafs_create_file("raw_data", S_IFREG | 0444,
+ dir, rawdata, &rawdata_fops);
+ if (IS_ERR(dent))
+ goto fail;
+ rawdata->dents[AAFS_LOADDATA_DATA] = dent;
+ d_inode(dent)->i_size = rawdata->size;
+
+ rawdata->ns = aa_get_ns(ns);
+ list_add(&rawdata->list, &ns->rawdata_list);
+ /* no refcount on inode rawdata */
+
+ return 0;
+
+fail:
+ remove_rawdata_dents(rawdata);
+
+ return PTR_ERR(dent);
+}
+
/** fns to setup dynamic per profile/namespace files **/
-void __aa_fs_profile_rmdir(struct aa_profile *profile)
+
+/**
+ *
+ * Requires: @profile->ns->lock held
+ */
+void __aafs_profile_rmdir(struct aa_profile *profile)
{
struct aa_profile *child;
int i;
@@ -663,7 +1420,7 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile)
return;
list_for_each_entry(child, &profile->base.profiles, base.list)
- __aa_fs_profile_rmdir(child);
+ __aafs_profile_rmdir(child);
for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) {
struct aa_proxy *proxy;
@@ -671,14 +1428,18 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile)
continue;
proxy = d_inode(profile->dents[i])->i_private;
- securityfs_remove(profile->dents[i]);
+ aafs_remove(profile->dents[i]);
aa_put_proxy(proxy);
profile->dents[i] = NULL;
}
}
-void __aa_fs_profile_migrate_dents(struct aa_profile *old,
- struct aa_profile *new)
+/**
+ *
+ * Requires: @old->ns->lock held
+ */
+void __aafs_profile_migrate_dents(struct aa_profile *old,
+ struct aa_profile *new)
{
int i;
@@ -694,18 +1455,52 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name,
struct aa_profile *profile,
const struct file_operations *fops)
{
- struct aa_proxy *proxy = aa_get_proxy(profile->proxy);
+ struct aa_proxy *proxy = aa_get_proxy(profile->label.proxy);
struct dentry *dent;
- dent = securityfs_create_file(name, S_IFREG | 0444, dir, proxy, fops);
+ dent = aafs_create_file(name, S_IFREG | 0444, dir, proxy, fops);
if (IS_ERR(dent))
aa_put_proxy(proxy);
return dent;
}
-/* requires lock be held */
-int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
+static int profile_depth(struct aa_profile *profile)
+{
+ int depth = 0;
+
+ rcu_read_lock();
+ for (depth = 0; profile; profile = rcu_access_pointer(profile->parent))
+ depth++;
+ rcu_read_unlock();
+
+ return depth;
+}
+
+static int gen_symlink_name(char *buffer, size_t bsize, int depth,
+ const char *dirname, const char *fname)
+{
+ int error;
+
+ for (; depth > 0; depth--) {
+ if (bsize < 7)
+ return -ENAMETOOLONG;
+ strcpy(buffer, "../../");
+ buffer += 6;
+ bsize -= 6;
+ }
+
+ error = snprintf(buffer, bsize, "raw_data/%s/%s", dirname, fname);
+ if (error >= bsize || error < 0)
+ return -ENAMETOOLONG;
+
+ return 0;
+}
+
+/*
+ * Requires: @profile->ns->lock held
+ */
+int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
{
struct aa_profile *child;
struct dentry *dent = NULL, *dir;
@@ -716,7 +1511,7 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
p = aa_deref_parent(profile);
dent = prof_dir(p);
/* adding to parent that previously didn't have children */
- dent = securityfs_create_dir("profiles", dent);
+ dent = aafs_create_dir("profiles", dent);
if (IS_ERR(dent))
goto fail;
prof_child_dir(p) = parent = dent;
@@ -728,67 +1523,80 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
id_len = snprintf(NULL, 0, ".%ld", profile->ns->uniq_id);
profile->dirname = kmalloc(len + id_len + 1, GFP_KERNEL);
- if (!profile->dirname)
- goto fail;
+ if (!profile->dirname) {
+ error = -ENOMEM;
+ goto fail2;
+ }
mangle_name(profile->base.name, profile->dirname);
sprintf(profile->dirname + len, ".%ld", profile->ns->uniq_id++);
}
- dent = securityfs_create_dir(profile->dirname, parent);
+ dent = aafs_create_dir(profile->dirname, parent);
if (IS_ERR(dent))
goto fail;
prof_dir(profile) = dir = dent;
- dent = create_profile_file(dir, "name", profile, &aa_fs_profname_fops);
+ dent = create_profile_file(dir, "name", profile,
+ &seq_profile_name_fops);
if (IS_ERR(dent))
goto fail;
profile->dents[AAFS_PROF_NAME] = dent;
- dent = create_profile_file(dir, "mode", profile, &aa_fs_profmode_fops);
+ dent = create_profile_file(dir, "mode", profile,
+ &seq_profile_mode_fops);
if (IS_ERR(dent))
goto fail;
profile->dents[AAFS_PROF_MODE] = dent;
dent = create_profile_file(dir, "attach", profile,
- &aa_fs_profattach_fops);
+ &seq_profile_attach_fops);
if (IS_ERR(dent))
goto fail;
profile->dents[AAFS_PROF_ATTACH] = dent;
if (profile->hash) {
dent = create_profile_file(dir, "sha1", profile,
- &aa_fs_seq_hash_fops);
+ &seq_profile_hash_fops);
if (IS_ERR(dent))
goto fail;
profile->dents[AAFS_PROF_HASH] = dent;
}
if (profile->rawdata) {
- dent = create_profile_file(dir, "raw_sha1", profile,
- &aa_fs_seq_raw_hash_fops);
+ char target[64];
+ int depth = profile_depth(profile);
+
+ error = gen_symlink_name(target, sizeof(target), depth,
+ profile->rawdata->name, "sha1");
+ if (error < 0)
+ goto fail2;
+ dent = aafs_create_symlink("raw_sha1", dir, target, NULL);
if (IS_ERR(dent))
goto fail;
profile->dents[AAFS_PROF_RAW_HASH] = dent;
- dent = create_profile_file(dir, "raw_abi", profile,
- &aa_fs_seq_raw_abi_fops);
+ error = gen_symlink_name(target, sizeof(target), depth,
+ profile->rawdata->name, "abi");
+ if (error < 0)
+ goto fail2;
+ dent = aafs_create_symlink("raw_abi", dir, target, NULL);
if (IS_ERR(dent))
goto fail;
profile->dents[AAFS_PROF_RAW_ABI] = dent;
- dent = securityfs_create_file("raw_data", S_IFREG | 0444, dir,
- profile->proxy,
- &aa_fs_rawdata_fops);
+ error = gen_symlink_name(target, sizeof(target), depth,
+ profile->rawdata->name, "raw_data");
+ if (error < 0)
+ goto fail2;
+ dent = aafs_create_symlink("raw_data", dir, target, NULL);
if (IS_ERR(dent))
goto fail;
profile->dents[AAFS_PROF_RAW_DATA] = dent;
- d_inode(dent)->i_size = profile->rawdata->size;
- aa_get_proxy(profile->proxy);
}
list_for_each_entry(child, &profile->base.profiles, base.list) {
- error = __aa_fs_profile_mkdir(child, prof_child_dir(profile));
+ error = __aafs_profile_mkdir(child, prof_child_dir(profile));
if (error)
goto fail2;
}
@@ -799,12 +1607,123 @@ fail:
error = PTR_ERR(dent);
fail2:
- __aa_fs_profile_rmdir(profile);
+ __aafs_profile_rmdir(profile);
return error;
}
-void __aa_fs_ns_rmdir(struct aa_ns *ns)
+static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode)
+{
+ struct aa_ns *ns, *parent;
+ /* TODO: improve permission check */
+ struct aa_label *label;
+ int error;
+
+ label = begin_current_label_crit_section();
+ error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY);
+ end_current_label_crit_section(label);
+ if (error)
+ return error;
+
+ parent = aa_get_ns(dir->i_private);
+ AA_BUG(d_inode(ns_subns_dir(parent)) != dir);
+
+ /* we have to unlock and then relock to get locking order right
+ * for pin_fs
+ */
+ inode_unlock(dir);
+ error = simple_pin_fs(&aafs_ops, &aafs_mnt, &aafs_count);
+ mutex_lock(&parent->lock);
+ inode_lock_nested(dir, I_MUTEX_PARENT);
+ if (error)
+ goto out;
+
+ error = __aafs_setup_d_inode(dir, dentry, mode | S_IFDIR, NULL,
+ NULL, NULL, NULL);
+ if (error)
+ goto out_pin;
+
+ ns = __aa_find_or_create_ns(parent, READ_ONCE(dentry->d_name.name),
+ dentry);
+ if (IS_ERR(ns)) {
+ error = PTR_ERR(ns);
+ ns = NULL;
+ }
+
+ aa_put_ns(ns); /* list ref remains */
+out_pin:
+ if (error)
+ simple_release_fs(&aafs_mnt, &aafs_count);
+out:
+ mutex_unlock(&parent->lock);
+ aa_put_ns(parent);
+
+ return error;
+}
+
+static int ns_rmdir_op(struct inode *dir, struct dentry *dentry)
+{
+ struct aa_ns *ns, *parent;
+ /* TODO: improve permission check */
+ struct aa_label *label;
+ int error;
+
+ label = begin_current_label_crit_section();
+ error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY);
+ end_current_label_crit_section(label);
+ if (error)
+ return error;
+
+ parent = aa_get_ns(dir->i_private);
+ /* rmdir calls the generic securityfs functions to remove files
+ * from the apparmor dir. It is up to the apparmor ns locking
+ * to avoid races.
+ */
+ inode_unlock(dir);
+ inode_unlock(dentry->d_inode);
+
+ mutex_lock(&parent->lock);
+ ns = aa_get_ns(__aa_findn_ns(&parent->sub_ns, dentry->d_name.name,
+ dentry->d_name.len));
+ if (!ns) {
+ error = -ENOENT;
+ goto out;
+ }
+ AA_BUG(ns_dir(ns) != dentry);
+
+ __aa_remove_ns(ns);
+ aa_put_ns(ns);
+
+out:
+ mutex_unlock(&parent->lock);
+ inode_lock_nested(dir, I_MUTEX_PARENT);
+ inode_lock(dentry->d_inode);
+ aa_put_ns(parent);
+
+ return error;
+}
+
+static const struct inode_operations ns_dir_inode_operations = {
+ .lookup = simple_lookup,
+ .mkdir = ns_mkdir_op,
+ .rmdir = ns_rmdir_op,
+};
+
+static void __aa_fs_list_remove_rawdata(struct aa_ns *ns)
+{
+ struct aa_loaddata *ent, *tmp;
+
+ AA_BUG(!mutex_is_locked(&ns->lock));
+
+ list_for_each_entry_safe(ent, tmp, &ns->rawdata_list, list)
+ __aa_fs_remove_rawdata(ent);
+}
+
+/**
+ *
+ * Requires: @ns->lock held
+ */
+void __aafs_ns_rmdir(struct aa_ns *ns)
{
struct aa_ns *sub;
struct aa_profile *child;
@@ -814,14 +1733,16 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns)
return;
list_for_each_entry(child, &ns->base.profiles, base.list)
- __aa_fs_profile_rmdir(child);
+ __aafs_profile_rmdir(child);
list_for_each_entry(sub, &ns->sub_ns, base.list) {
mutex_lock(&sub->lock);
- __aa_fs_ns_rmdir(sub);
+ __aafs_ns_rmdir(sub);
mutex_unlock(&sub->lock);
}
+ __aa_fs_list_remove_rawdata(ns);
+
if (ns_subns_dir(ns)) {
sub = d_inode(ns_subns_dir(ns))->i_private;
aa_put_ns(sub);
@@ -838,53 +1759,66 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns)
sub = d_inode(ns_subremove(ns))->i_private;
aa_put_ns(sub);
}
+ if (ns_subrevision(ns)) {
+ sub = d_inode(ns_subrevision(ns))->i_private;
+ aa_put_ns(sub);
+ }
for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) {
- securityfs_remove(ns->dents[i]);
+ aafs_remove(ns->dents[i]);
ns->dents[i] = NULL;
}
}
/* assumes cleanup in caller */
-static int __aa_fs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir)
+static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir)
{
struct dentry *dent;
AA_BUG(!ns);
AA_BUG(!dir);
- dent = securityfs_create_dir("profiles", dir);
+ dent = aafs_create_dir("profiles", dir);
if (IS_ERR(dent))
return PTR_ERR(dent);
ns_subprofs_dir(ns) = dent;
- dent = securityfs_create_dir("raw_data", dir);
+ dent = aafs_create_dir("raw_data", dir);
if (IS_ERR(dent))
return PTR_ERR(dent);
ns_subdata_dir(ns) = dent;
- dent = securityfs_create_file(".load", 0640, dir, ns,
+ dent = aafs_create_file("revision", 0444, dir, ns,
+ &aa_fs_ns_revision_fops);
+ if (IS_ERR(dent))
+ return PTR_ERR(dent);
+ aa_get_ns(ns);
+ ns_subrevision(ns) = dent;
+
+ dent = aafs_create_file(".load", 0640, dir, ns,
&aa_fs_profile_load);
if (IS_ERR(dent))
return PTR_ERR(dent);
aa_get_ns(ns);
ns_subload(ns) = dent;
- dent = securityfs_create_file(".replace", 0640, dir, ns,
+ dent = aafs_create_file(".replace", 0640, dir, ns,
&aa_fs_profile_replace);
if (IS_ERR(dent))
return PTR_ERR(dent);
aa_get_ns(ns);
ns_subreplace(ns) = dent;
- dent = securityfs_create_file(".remove", 0640, dir, ns,
+ dent = aafs_create_file(".remove", 0640, dir, ns,
&aa_fs_profile_remove);
if (IS_ERR(dent))
return PTR_ERR(dent);
aa_get_ns(ns);
ns_subremove(ns) = dent;
- dent = securityfs_create_dir("namespaces", dir);
+ /* use create_dentry so we can supply private data */
+ dent = aafs_create("namespaces", S_IFDIR | 0755, dir, ns, NULL, NULL,
+ &ns_dir_inode_operations);
if (IS_ERR(dent))
return PTR_ERR(dent);
aa_get_ns(ns);
@@ -893,11 +1827,15 @@ static int __aa_fs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir)
return 0;
}
-int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name)
+/*
+ * Requires: @ns->lock held
+ */
+int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name,
+ struct dentry *dent)
{
struct aa_ns *sub;
struct aa_profile *child;
- struct dentry *dent, *dir;
+ struct dentry *dir;
int error;
AA_BUG(!ns);
@@ -907,19 +1845,21 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name)
if (!name)
name = ns->base.name;
- /* create ns dir if it doesn't already exist */
- dent = securityfs_create_dir(name, parent);
- if (IS_ERR(dent))
- goto fail;
-
+ if (!dent) {
+ /* create ns dir if it doesn't already exist */
+ dent = aafs_create_dir(name, parent);
+ if (IS_ERR(dent))
+ goto fail;
+ } else
+ dget(dent);
ns_dir(ns) = dir = dent;
- error = __aa_fs_ns_mkdir_entries(ns, dir);
+ error = __aafs_ns_mkdir_entries(ns, dir);
if (error)
goto fail2;
/* profiles */
list_for_each_entry(child, &ns->base.profiles, base.list) {
- error = __aa_fs_profile_mkdir(child, ns_subprofs_dir(ns));
+ error = __aafs_profile_mkdir(child, ns_subprofs_dir(ns));
if (error)
goto fail2;
}
@@ -927,7 +1867,7 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name)
/* subnamespaces */
list_for_each_entry(sub, &ns->sub_ns, base.list) {
mutex_lock(&sub->lock);
- error = __aa_fs_ns_mkdir(sub, ns_subns_dir(ns), NULL);
+ error = __aafs_ns_mkdir(sub, ns_subns_dir(ns), NULL, NULL);
mutex_unlock(&sub->lock);
if (error)
goto fail2;
@@ -939,7 +1879,7 @@ fail:
error = PTR_ERR(dent);
fail2:
- __aa_fs_ns_rmdir(ns);
+ __aafs_ns_rmdir(ns);
return error;
}
@@ -1074,10 +2014,9 @@ static struct aa_profile *next_profile(struct aa_ns *root,
static void *p_start(struct seq_file *f, loff_t *pos)
{
struct aa_profile *profile = NULL;
- struct aa_ns *root = aa_current_profile()->ns;
+ struct aa_ns *root = aa_get_current_ns();
loff_t l = *pos;
- f->private = aa_get_ns(root);
-
+ f->private = root;
/* find the first profile */
mutex_lock(&root->lock);
@@ -1141,15 +2080,14 @@ static int seq_show_profile(struct seq_file *f, void *p)
struct aa_profile *profile = (struct aa_profile *)p;
struct aa_ns *root = f->private;
- if (profile->ns != root)
- seq_printf(f, ":%s://", aa_ns_name(root, profile->ns, true));
- seq_printf(f, "%s (%s)\n", profile->base.hname,
- aa_profile_mode_names[profile->mode]);
+ aa_label_seq_xprint(f, root, &profile->label,
+ FLAG_SHOW_MODE | FLAG_VIEW_SUBNS, GFP_KERNEL);
+ seq_putc(f, '\n');
return 0;
}
-static const struct seq_operations aa_fs_profiles_op = {
+static const struct seq_operations aa_sfs_profiles_op = {
.start = p_start,
.next = p_next,
.stop = p_stop,
@@ -1161,7 +2099,7 @@ static int profiles_open(struct inode *inode, struct file *file)
if (!policy_view_capable(NULL))
return -EACCES;
- return seq_open(file, &aa_fs_profiles_op);
+ return seq_open(file, &aa_sfs_profiles_op);
}
static int profiles_release(struct inode *inode, struct file *file)
@@ -1169,7 +2107,7 @@ static int profiles_release(struct inode *inode, struct file *file)
return seq_release(inode, file);
}
-static const struct file_operations aa_fs_profiles_fops = {
+static const struct file_operations aa_sfs_profiles_fops = {
.open = profiles_open,
.read = seq_read,
.llseek = seq_lseek,
@@ -1178,64 +2116,94 @@ static const struct file_operations aa_fs_profiles_fops = {
/** Base file system setup **/
-static struct aa_fs_entry aa_fs_entry_file[] = {
- AA_FS_FILE_STRING("mask", "create read write exec append mmap_exec " \
- "link lock"),
+static struct aa_sfs_entry aa_sfs_entry_file[] = {
+ AA_SFS_FILE_STRING("mask",
+ "create read write exec append mmap_exec link lock"),
+ { }
+};
+
+static struct aa_sfs_entry aa_sfs_entry_ptrace[] = {
+ AA_SFS_FILE_STRING("mask", "read trace"),
+ { }
+};
+
+static struct aa_sfs_entry aa_sfs_entry_domain[] = {
+ AA_SFS_FILE_BOOLEAN("change_hat", 1),
+ AA_SFS_FILE_BOOLEAN("change_hatv", 1),
+ AA_SFS_FILE_BOOLEAN("change_onexec", 1),
+ AA_SFS_FILE_BOOLEAN("change_profile", 1),
+ AA_SFS_FILE_BOOLEAN("stack", 1),
+ AA_SFS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1),
+ AA_SFS_FILE_STRING("version", "1.2"),
+ { }
+};
+
+static struct aa_sfs_entry aa_sfs_entry_versions[] = {
+ AA_SFS_FILE_BOOLEAN("v5", 1),
+ AA_SFS_FILE_BOOLEAN("v6", 1),
+ AA_SFS_FILE_BOOLEAN("v7", 1),
{ }
};
-static struct aa_fs_entry aa_fs_entry_domain[] = {
- AA_FS_FILE_BOOLEAN("change_hat", 1),
- AA_FS_FILE_BOOLEAN("change_hatv", 1),
- AA_FS_FILE_BOOLEAN("change_onexec", 1),
- AA_FS_FILE_BOOLEAN("change_profile", 1),
- AA_FS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1),
- AA_FS_FILE_STRING("version", "1.2"),
+static struct aa_sfs_entry aa_sfs_entry_policy[] = {
+ AA_SFS_DIR("versions", aa_sfs_entry_versions),
+ AA_SFS_FILE_BOOLEAN("set_load", 1),
{ }
};
-static struct aa_fs_entry aa_fs_entry_versions[] = {
- AA_FS_FILE_BOOLEAN("v5", 1),
+static struct aa_sfs_entry aa_sfs_entry_ns[] = {
+ AA_SFS_FILE_BOOLEAN("profile", 1),
+ AA_SFS_FILE_BOOLEAN("pivot_root", 1),
{ }
};
-static struct aa_fs_entry aa_fs_entry_policy[] = {
- AA_FS_DIR("versions", aa_fs_entry_versions),
- AA_FS_FILE_BOOLEAN("set_load", 1),
+static struct aa_sfs_entry aa_sfs_entry_query_label[] = {
+ AA_SFS_FILE_STRING("perms", "allow deny audit quiet"),
+ AA_SFS_FILE_BOOLEAN("data", 1),
+ AA_SFS_FILE_BOOLEAN("multi_transaction", 1),
{ }
};
-static struct aa_fs_entry aa_fs_entry_features[] = {
- AA_FS_DIR("policy", aa_fs_entry_policy),
- AA_FS_DIR("domain", aa_fs_entry_domain),
- AA_FS_DIR("file", aa_fs_entry_file),
- AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK),
- AA_FS_DIR("rlimit", aa_fs_entry_rlimit),
- AA_FS_DIR("caps", aa_fs_entry_caps),
+static struct aa_sfs_entry aa_sfs_entry_query[] = {
+ AA_SFS_DIR("label", aa_sfs_entry_query_label),
+ { }
+};
+static struct aa_sfs_entry aa_sfs_entry_features[] = {
+ AA_SFS_DIR("policy", aa_sfs_entry_policy),
+ AA_SFS_DIR("domain", aa_sfs_entry_domain),
+ AA_SFS_DIR("file", aa_sfs_entry_file),
+ AA_SFS_DIR("namespaces", aa_sfs_entry_ns),
+ AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK),
+ AA_SFS_DIR("rlimit", aa_sfs_entry_rlimit),
+ AA_SFS_DIR("caps", aa_sfs_entry_caps),
+ AA_SFS_DIR("ptrace", aa_sfs_entry_ptrace),
+ AA_SFS_DIR("query", aa_sfs_entry_query),
{ }
};
-static struct aa_fs_entry aa_fs_entry_apparmor[] = {
- AA_FS_FILE_FOPS(".access", 0640, &aa_fs_access),
- AA_FS_FILE_FOPS(".ns_level", 0666, &aa_fs_ns_level),
- AA_FS_FILE_FOPS(".ns_name", 0640, &aa_fs_ns_name),
- AA_FS_FILE_FOPS("profiles", 0440, &aa_fs_profiles_fops),
- AA_FS_DIR("features", aa_fs_entry_features),
+static struct aa_sfs_entry aa_sfs_entry_apparmor[] = {
+ AA_SFS_FILE_FOPS(".access", 0640, &aa_sfs_access),
+ AA_SFS_FILE_FOPS(".stacked", 0444, &seq_ns_stacked_fops),
+ AA_SFS_FILE_FOPS(".ns_stacked", 0444, &seq_ns_nsstacked_fops),
+ AA_SFS_FILE_FOPS(".ns_level", 0666, &seq_ns_level_fops),
+ AA_SFS_FILE_FOPS(".ns_name", 0640, &seq_ns_name_fops),
+ AA_SFS_FILE_FOPS("profiles", 0440, &aa_sfs_profiles_fops),
+ AA_SFS_DIR("features", aa_sfs_entry_features),
{ }
};
-static struct aa_fs_entry aa_fs_entry =
- AA_FS_DIR("apparmor", aa_fs_entry_apparmor);
+static struct aa_sfs_entry aa_sfs_entry =
+ AA_SFS_DIR("apparmor", aa_sfs_entry_apparmor);
/**
- * aafs_create_file - create a file entry in the apparmor securityfs
- * @fs_file: aa_fs_entry to build an entry for (NOT NULL)
+ * entry_create_file - create a file entry in the apparmor securityfs
+ * @fs_file: aa_sfs_entry to build an entry for (NOT NULL)
* @parent: the parent dentry in the securityfs
*
- * Use aafs_remove_file to remove entries created with this fn.
+ * Use entry_remove_file to remove entries created with this fn.
*/
-static int __init aafs_create_file(struct aa_fs_entry *fs_file,
- struct dentry *parent)
+static int __init entry_create_file(struct aa_sfs_entry *fs_file,
+ struct dentry *parent)
{
int error = 0;
@@ -1250,18 +2218,18 @@ static int __init aafs_create_file(struct aa_fs_entry *fs_file,
return error;
}
-static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir);
+static void __init entry_remove_dir(struct aa_sfs_entry *fs_dir);
/**
- * aafs_create_dir - recursively create a directory entry in the securityfs
- * @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL)
+ * entry_create_dir - recursively create a directory entry in the securityfs
+ * @fs_dir: aa_sfs_entry (and all child entries) to build (NOT NULL)
* @parent: the parent dentry in the securityfs
*
- * Use aafs_remove_dir to remove entries created with this fn.
+ * Use entry_remove_dir to remove entries created with this fn.
*/
-static int __init aafs_create_dir(struct aa_fs_entry *fs_dir,
- struct dentry *parent)
+static int __init entry_create_dir(struct aa_sfs_entry *fs_dir,
+ struct dentry *parent)
{
- struct aa_fs_entry *fs_file;
+ struct aa_sfs_entry *fs_file;
struct dentry *dir;
int error;
@@ -1271,10 +2239,10 @@ static int __init aafs_create_dir(struct aa_fs_entry *fs_dir,
fs_dir->dentry = dir;
for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) {
- if (fs_file->v_type == AA_FS_TYPE_DIR)
- error = aafs_create_dir(fs_file, fs_dir->dentry);
+ if (fs_file->v_type == AA_SFS_TYPE_DIR)
+ error = entry_create_dir(fs_file, fs_dir->dentry);
else
- error = aafs_create_file(fs_file, fs_dir->dentry);
+ error = entry_create_file(fs_file, fs_dir->dentry);
if (error)
goto failed;
}
@@ -1282,16 +2250,16 @@ static int __init aafs_create_dir(struct aa_fs_entry *fs_dir,
return 0;
failed:
- aafs_remove_dir(fs_dir);
+ entry_remove_dir(fs_dir);
return error;
}
/**
- * aafs_remove_file - drop a single file entry in the apparmor securityfs
- * @fs_file: aa_fs_entry to detach from the securityfs (NOT NULL)
+ * entry_remove_file - drop a single file entry in the apparmor securityfs
+ * @fs_file: aa_sfs_entry to detach from the securityfs (NOT NULL)
*/
-static void __init aafs_remove_file(struct aa_fs_entry *fs_file)
+static void __init entry_remove_file(struct aa_sfs_entry *fs_file)
{
if (!fs_file->dentry)
return;
@@ -1301,21 +2269,21 @@ static void __init aafs_remove_file(struct aa_fs_entry *fs_file)
}
/**
- * aafs_remove_dir - recursively drop a directory entry from the securityfs
- * @fs_dir: aa_fs_entry (and all child entries) to detach (NOT NULL)
+ * entry_remove_dir - recursively drop a directory entry from the securityfs
+ * @fs_dir: aa_sfs_entry (and all child entries) to detach (NOT NULL)
*/
-static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir)
+static void __init entry_remove_dir(struct aa_sfs_entry *fs_dir)
{
- struct aa_fs_entry *fs_file;
+ struct aa_sfs_entry *fs_file;
for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) {
- if (fs_file->v_type == AA_FS_TYPE_DIR)
- aafs_remove_dir(fs_file);
+ if (fs_file->v_type == AA_SFS_TYPE_DIR)
+ entry_remove_dir(fs_file);
else
- aafs_remove_file(fs_file);
+ entry_remove_file(fs_file);
}
- aafs_remove_file(fs_dir);
+ entry_remove_file(fs_dir);
}
/**
@@ -1325,7 +2293,7 @@ static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir)
*/
void __init aa_destroy_aafs(void)
{
- aafs_remove_dir(&aa_fs_entry);
+ entry_remove_dir(&aa_sfs_entry);
}
@@ -1374,6 +2342,59 @@ out:
return error;
}
+
+
+static const char *policy_get_link(struct dentry *dentry,
+ struct inode *inode,
+ struct delayed_call *done)
+{
+ struct aa_ns *ns;
+ struct path path;
+
+ if (!dentry)
+ return ERR_PTR(-ECHILD);
+ ns = aa_get_current_ns();
+ path.mnt = mntget(aafs_mnt);
+ path.dentry = dget(ns_dir(ns));
+ nd_jump_link(&path);
+ aa_put_ns(ns);
+
+ return NULL;
+}
+
+static int ns_get_name(char *buf, size_t size, struct aa_ns *ns,
+ struct inode *inode)
+{
+ int res = snprintf(buf, size, "%s:[%lu]", AAFS_NAME, inode->i_ino);
+
+ if (res < 0 || res >= size)
+ res = -ENOENT;
+
+ return res;
+}
+
+static int policy_readlink(struct dentry *dentry, char __user *buffer,
+ int buflen)
+{
+ struct aa_ns *ns;
+ char name[32];
+ int res;
+
+ ns = aa_get_current_ns();
+ res = ns_get_name(name, sizeof(name), ns, d_inode(dentry));
+ if (res >= 0)
+ res = readlink_copy(buffer, buflen, name);
+ aa_put_ns(ns);
+
+ return res;
+}
+
+static const struct inode_operations policy_link_iops = {
+ .readlink = policy_readlink,
+ .get_link = policy_get_link,
+};
+
+
/**
* aa_create_aafs - create the apparmor security filesystem
*
@@ -1389,17 +2410,23 @@ static int __init aa_create_aafs(void)
if (!apparmor_initialized)
return 0;
- if (aa_fs_entry.dentry) {
+ if (aa_sfs_entry.dentry) {
AA_ERROR("%s: AppArmor securityfs already exists\n", __func__);
return -EEXIST;
}
+ /* setup apparmorfs used to virtualize policy/ */
+ aafs_mnt = kern_mount(&aafs_ops);
+ if (IS_ERR(aafs_mnt))
+ panic("can't set apparmorfs up\n");
+ aafs_mnt->mnt_sb->s_flags &= ~MS_NOUSER;
+
/* Populate fs tree. */
- error = aafs_create_dir(&aa_fs_entry, NULL);
+ error = entry_create_dir(&aa_sfs_entry, NULL);
if (error)
goto error;
- dent = securityfs_create_file(".load", 0666, aa_fs_entry.dentry,
+ dent = securityfs_create_file(".load", 0666, aa_sfs_entry.dentry,
NULL, &aa_fs_profile_load);
if (IS_ERR(dent)) {
error = PTR_ERR(dent);
@@ -1407,7 +2434,7 @@ static int __init aa_create_aafs(void)
}
ns_subload(root_ns) = dent;
- dent = securityfs_create_file(".replace", 0666, aa_fs_entry.dentry,
+ dent = securityfs_create_file(".replace", 0666, aa_sfs_entry.dentry,
NULL, &aa_fs_profile_replace);
if (IS_ERR(dent)) {
error = PTR_ERR(dent);
@@ -1415,7 +2442,7 @@ static int __init aa_create_aafs(void)
}
ns_subreplace(root_ns) = dent;
- dent = securityfs_create_file(".remove", 0666, aa_fs_entry.dentry,
+ dent = securityfs_create_file(".remove", 0666, aa_sfs_entry.dentry,
NULL, &aa_fs_profile_remove);
if (IS_ERR(dent)) {
error = PTR_ERR(dent);
@@ -1423,14 +2450,31 @@ static int __init aa_create_aafs(void)
}
ns_subremove(root_ns) = dent;
+ dent = securityfs_create_file("revision", 0444, aa_sfs_entry.dentry,
+ NULL, &aa_fs_ns_revision_fops);
+ if (IS_ERR(dent)) {
+ error = PTR_ERR(dent);
+ goto error;
+ }
+ ns_subrevision(root_ns) = dent;
+
+ /* policy tree referenced by magic policy symlink */
mutex_lock(&root_ns->lock);
- error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy");
+ error = __aafs_ns_mkdir(root_ns, aafs_mnt->mnt_root, ".policy",
+ aafs_mnt->mnt_root);
mutex_unlock(&root_ns->lock);
-
if (error)
goto error;
- error = aa_mk_null_file(aa_fs_entry.dentry);
+ /* magic symlink similar to nsfs redirects based on task policy */
+ dent = securityfs_create_symlink("policy", aa_sfs_entry.dentry,
+ NULL, &policy_link_iops);
+ if (IS_ERR(dent)) {
+ error = PTR_ERR(dent);
+ goto error;
+ }
+
+ error = aa_mk_null_file(aa_sfs_entry.dentry);
if (error)
goto error;
diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c
index 87f40fa8c431..8f9ecac7f8de 100644
--- a/security/apparmor/audit.c
+++ b/security/apparmor/audit.c
@@ -77,14 +77,24 @@ static void audit_pre(struct audit_buffer *ab, void *ca)
audit_log_format(ab, " error=%d", aad(sa)->error);
}
- if (aad(sa)->profile) {
- struct aa_profile *profile = aad(sa)->profile;
- if (profile->ns != root_ns) {
- audit_log_format(ab, " namespace=");
- audit_log_untrustedstring(ab, profile->ns->base.hname);
+ if (aad(sa)->label) {
+ struct aa_label *label = aad(sa)->label;
+
+ if (label_isprofile(label)) {
+ struct aa_profile *profile = labels_profile(label);
+
+ if (profile->ns != root_ns) {
+ audit_log_format(ab, " namespace=");
+ audit_log_untrustedstring(ab,
+ profile->ns->base.hname);
+ }
+ audit_log_format(ab, " profile=");
+ audit_log_untrustedstring(ab, profile->base.hname);
+ } else {
+ audit_log_format(ab, " label=");
+ aa_label_xaudit(ab, root_ns, label, FLAG_VIEW_SUBNS,
+ GFP_ATOMIC);
}
- audit_log_format(ab, " profile=");
- audit_log_untrustedstring(ab, profile->base.hname);
}
if (aad(sa)->name) {
@@ -139,8 +149,7 @@ int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED)
type = AUDIT_APPARMOR_KILL;
- if (!unconfined(profile))
- aad(sa)->profile = profile;
+ aad(sa)->label = &profile->label;
aa_audit_msg(type, sa, cb);
diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c
index ed0a3e6b8022..67e347192a55 100644
--- a/security/apparmor/capability.c
+++ b/security/apparmor/capability.c
@@ -28,8 +28,8 @@
*/
#include "capability_names.h"
-struct aa_fs_entry aa_fs_entry_caps[] = {
- AA_FS_FILE_STRING("mask", AA_FS_CAPS_MASK),
+struct aa_sfs_entry aa_sfs_entry_caps[] = {
+ AA_SFS_FILE_STRING("mask", AA_SFS_CAPS_MASK),
{ }
};
@@ -48,15 +48,16 @@ static DEFINE_PER_CPU(struct audit_cache, audit_cache);
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
+
audit_log_format(ab, " capname=");
audit_log_untrustedstring(ab, capability_names[sa->u.cap]);
}
/**
* audit_caps - audit a capability
+ * @sa: audit data
* @profile: profile being tested for confinement (NOT NULL)
* @cap: capability tested
- @audit: whether an audit record should be generated
* @error: error code returned by test
*
* Do auditing of capability and handle, audit/complain/kill modes switching
@@ -64,16 +65,13 @@ static void audit_cb(struct audit_buffer *ab, void *va)
*
* Returns: 0 or sa->error on success, error code on failure
*/
-static int audit_caps(struct aa_profile *profile, int cap, int audit,
- int error)
+static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
+ int cap, int error)
{
struct audit_cache *ent;
int type = AUDIT_APPARMOR_AUTO;
- DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE);
- sa.u.cap = cap;
- aad(&sa)->error = error;
- if (audit == SECURITY_CAP_NOAUDIT)
- aad(&sa)->info = "optional: no audit";
+
+ aad(sa)->error = error;
if (likely(!error)) {
/* test if auditing is being forced */
@@ -105,24 +103,44 @@ static int audit_caps(struct aa_profile *profile, int cap, int audit,
}
put_cpu_var(audit_cache);
- return aa_audit(type, profile, &sa, audit_cb);
+ return aa_audit(type, profile, sa, audit_cb);
}
/**
* profile_capable - test if profile allows use of capability @cap
* @profile: profile being enforced (NOT NULL, NOT unconfined)
* @cap: capability to test if allowed
+ * @audit: whether an audit record should be generated
+ * @sa: audit data (MAY BE NULL indicating no auditing)
*
* Returns: 0 if allowed else -EPERM
*/
-static int profile_capable(struct aa_profile *profile, int cap)
+static int profile_capable(struct aa_profile *profile, int cap, int audit,
+ struct common_audit_data *sa)
{
- return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM;
+ int error;
+
+ if (cap_raised(profile->caps.allow, cap) &&
+ !cap_raised(profile->caps.denied, cap))
+ error = 0;
+ else
+ error = -EPERM;
+
+ if (audit == SECURITY_CAP_NOAUDIT) {
+ if (!COMPLAIN_MODE(profile))
+ return error;
+ /* audit the cap request in complain mode but note that it
+ * should be optional.
+ */
+ aad(sa)->info = "optional: no audit";
+ }
+
+ return audit_caps(sa, profile, cap, error);
}
/**
* aa_capable - test permission to use capability
- * @profile: profile being tested against (NOT NULL)
+ * @label: label being tested for capability (NOT NULL)
* @cap: capability to be tested
* @audit: whether an audit record should be generated
*
@@ -130,14 +148,15 @@ static int profile_capable(struct aa_profile *profile, int cap)
*
* Returns: 0 on success, or else an error code.
*/
-int aa_capable(struct aa_profile *profile, int cap, int audit)
+int aa_capable(struct aa_label *label, int cap, int audit)
{
- int error = profile_capable(profile, cap);
+ struct aa_profile *profile;
+ int error = 0;
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE);
- if (audit == SECURITY_CAP_NOAUDIT) {
- if (!COMPLAIN_MODE(profile))
- return error;
- }
+ sa.u.cap = cap;
+ error = fn_for_each_confined(label, profile,
+ profile_capable(profile, cap, audit, &sa));
- return audit_caps(profile, cap, audit, error);
+ return error;
}
diff --git a/security/apparmor/context.c b/security/apparmor/context.c
index 1fc16b88efbf..c95f1ac6190b 100644
--- a/security/apparmor/context.c
+++ b/security/apparmor/context.c
@@ -14,9 +14,9 @@
*
*
* AppArmor sets confinement on every task, via the the aa_task_ctx and
- * the aa_task_ctx.profile, both of which are required and are not allowed
+ * the aa_task_ctx.label, both of which are required and are not allowed
* to be NULL. The aa_task_ctx is not reference counted and is unique
- * to each cred (which is reference count). The profile pointed to by
+ * to each cred (which is reference count). The label pointed to by
* the task_ctx is reference counted.
*
* TODO
@@ -47,9 +47,9 @@ struct aa_task_ctx *aa_alloc_task_context(gfp_t flags)
void aa_free_task_context(struct aa_task_ctx *ctx)
{
if (ctx) {
- aa_put_profile(ctx->profile);
- aa_put_profile(ctx->previous);
- aa_put_profile(ctx->onexec);
+ aa_put_label(ctx->label);
+ aa_put_label(ctx->previous);
+ aa_put_label(ctx->onexec);
kzfree(ctx);
}
@@ -63,41 +63,41 @@ void aa_free_task_context(struct aa_task_ctx *ctx)
void aa_dup_task_context(struct aa_task_ctx *new, const struct aa_task_ctx *old)
{
*new = *old;
- aa_get_profile(new->profile);
- aa_get_profile(new->previous);
- aa_get_profile(new->onexec);
+ aa_get_label(new->label);
+ aa_get_label(new->previous);
+ aa_get_label(new->onexec);
}
/**
- * aa_get_task_profile - Get another task's profile
+ * aa_get_task_label - Get another task's label
* @task: task to query (NOT NULL)
*
- * Returns: counted reference to @task's profile
+ * Returns: counted reference to @task's label
*/
-struct aa_profile *aa_get_task_profile(struct task_struct *task)
+struct aa_label *aa_get_task_label(struct task_struct *task)
{
- struct aa_profile *p;
+ struct aa_label *p;
rcu_read_lock();
- p = aa_get_profile(__aa_task_profile(task));
+ p = aa_get_newest_label(__aa_task_raw_label(task));
rcu_read_unlock();
return p;
}
/**
- * aa_replace_current_profile - replace the current tasks profiles
- * @profile: new profile (NOT NULL)
+ * aa_replace_current_label - replace the current tasks label
+ * @label: new label (NOT NULL)
*
* Returns: 0 or error on failure
*/
-int aa_replace_current_profile(struct aa_profile *profile)
+int aa_replace_current_label(struct aa_label *label)
{
struct aa_task_ctx *ctx = current_ctx();
struct cred *new;
- AA_BUG(!profile);
+ AA_BUG(!label);
- if (ctx->profile == profile)
+ if (ctx->label == label)
return 0;
if (current_cred() != current_real_cred())
@@ -108,8 +108,8 @@ int aa_replace_current_profile(struct aa_profile *profile)
return -ENOMEM;
ctx = cred_ctx(new);
- if (unconfined(profile) || (ctx->profile->ns != profile->ns))
- /* if switching to unconfined or a different profile namespace
+ if (unconfined(label) || (labels_ns(ctx->label) != labels_ns(label)))
+ /* if switching to unconfined or a different label namespace
* clear out context state
*/
aa_clear_task_ctx_trans(ctx);
@@ -120,9 +120,9 @@ int aa_replace_current_profile(struct aa_profile *profile)
* keeping @profile valid, so make sure to get its reference before
* dropping the reference on ctx->profile
*/
- aa_get_profile(profile);
- aa_put_profile(ctx->profile);
- ctx->profile = profile;
+ aa_get_label(label);
+ aa_put_label(ctx->label);
+ ctx->label = label;
commit_creds(new);
return 0;
@@ -130,11 +130,11 @@ int aa_replace_current_profile(struct aa_profile *profile)
/**
* aa_set_current_onexec - set the tasks change_profile to happen onexec
- * @profile: system profile to set at exec (MAYBE NULL to clear value)
- *
+ * @label: system label to set at exec (MAYBE NULL to clear value)
+ * @stack: whether stacking should be done
* Returns: 0 or error on failure
*/
-int aa_set_current_onexec(struct aa_profile *profile)
+int aa_set_current_onexec(struct aa_label *label, bool stack)
{
struct aa_task_ctx *ctx;
struct cred *new = prepare_creds();
@@ -142,9 +142,10 @@ int aa_set_current_onexec(struct aa_profile *profile)
return -ENOMEM;
ctx = cred_ctx(new);
- aa_get_profile(profile);
- aa_put_profile(ctx->onexec);
- ctx->onexec = profile;
+ aa_get_label(label);
+ aa_clear_task_ctx_trans(ctx);
+ ctx->onexec = label;
+ ctx->token = stack;
commit_creds(new);
return 0;
@@ -152,7 +153,7 @@ int aa_set_current_onexec(struct aa_profile *profile)
/**
* aa_set_current_hat - set the current tasks hat
- * @profile: profile to set as the current hat (NOT NULL)
+ * @label: label to set as the current hat (NOT NULL)
* @token: token value that must be specified to change from the hat
*
* Do switch of tasks hat. If the task is currently in a hat
@@ -160,29 +161,29 @@ int aa_set_current_onexec(struct aa_profile *profile)
*
* Returns: 0 or error on failure
*/
-int aa_set_current_hat(struct aa_profile *profile, u64 token)
+int aa_set_current_hat(struct aa_label *label, u64 token)
{
struct aa_task_ctx *ctx;
struct cred *new = prepare_creds();
if (!new)
return -ENOMEM;
- AA_BUG(!profile);
+ AA_BUG(!label);
ctx = cred_ctx(new);
if (!ctx->previous) {
/* transfer refcount */
- ctx->previous = ctx->profile;
+ ctx->previous = ctx->label;
ctx->token = token;
} else if (ctx->token == token) {
- aa_put_profile(ctx->profile);
+ aa_put_label(ctx->label);
} else {
/* previous_profile && ctx->token != token */
abort_creds(new);
return -EACCES;
}
- ctx->profile = aa_get_newest_profile(profile);
+ ctx->label = aa_get_newest_label(label);
/* clear exec on switching context */
- aa_put_profile(ctx->onexec);
+ aa_put_label(ctx->onexec);
ctx->onexec = NULL;
commit_creds(new);
@@ -190,15 +191,15 @@ int aa_set_current_hat(struct aa_profile *profile, u64 token)
}
/**
- * aa_restore_previous_profile - exit from hat context restoring the profile
+ * aa_restore_previous_label - exit from hat context restoring previous label
* @token: the token that must be matched to exit hat context
*
- * Attempt to return out of a hat to the previous profile. The token
+ * Attempt to return out of a hat to the previous label. The token
* must match the stored token value.
*
* Returns: 0 or error of failure
*/
-int aa_restore_previous_profile(u64 token)
+int aa_restore_previous_label(u64 token)
{
struct aa_task_ctx *ctx;
struct cred *new = prepare_creds();
@@ -210,15 +211,15 @@ int aa_restore_previous_profile(u64 token)
abort_creds(new);
return -EACCES;
}
- /* ignore restores when there is no saved profile */
+ /* ignore restores when there is no saved label */
if (!ctx->previous) {
abort_creds(new);
return 0;
}
- aa_put_profile(ctx->profile);
- ctx->profile = aa_get_newest_profile(ctx->previous);
- AA_BUG(!ctx->profile);
+ aa_put_label(ctx->label);
+ ctx->label = aa_get_newest_label(ctx->previous);
+ AA_BUG(!ctx->label);
/* clear exec && prev information when restoring to previous context */
aa_clear_task_ctx_trans(ctx);
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 001e133a3c8c..d0594446ae3f 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -51,76 +51,254 @@ void aa_free_domain_entries(struct aa_domain *domain)
/**
* may_change_ptraced_domain - check if can change profile on ptraced task
- * @to_profile: profile to change to (NOT NULL)
+ * @to_label: profile to change to (NOT NULL)
+ * @info: message if there is an error
*
* Check if current is ptraced and if so if the tracing task is allowed
* to trace the new domain
*
* Returns: %0 or error if change not allowed
*/
-static int may_change_ptraced_domain(struct aa_profile *to_profile)
+static int may_change_ptraced_domain(struct aa_label *to_label,
+ const char **info)
{
struct task_struct *tracer;
- struct aa_profile *tracerp = NULL;
+ struct aa_label *tracerl = NULL;
int error = 0;
rcu_read_lock();
tracer = ptrace_parent(current);
if (tracer)
/* released below */
- tracerp = aa_get_task_profile(tracer);
+ tracerl = aa_get_task_label(tracer);
/* not ptraced */
- if (!tracer || unconfined(tracerp))
+ if (!tracer || unconfined(tracerl))
goto out;
- error = aa_may_ptrace(tracerp, to_profile, PTRACE_MODE_ATTACH);
+ error = aa_may_ptrace(tracerl, to_label, PTRACE_MODE_ATTACH);
out:
rcu_read_unlock();
- aa_put_profile(tracerp);
+ aa_put_label(tracerl);
+ if (error)
+ *info = "ptrace prevents transition";
return error;
}
+/**** TODO: dedup to aa_label_match - needs perm and dfa, merging
+ * specifically this is an exact copy of aa_label_match except
+ * aa_compute_perms is replaced with aa_compute_fperms
+ * and policy.dfa with file.dfa
+ ****/
+/* match a profile and its associated ns component if needed
+ * Assumes visibility test has already been done.
+ * If a subns profile is not to be matched should be prescreened with
+ * visibility test.
+ */
+static inline unsigned int match_component(struct aa_profile *profile,
+ struct aa_profile *tp,
+ bool stack, unsigned int state)
+{
+ const char *ns_name;
+
+ if (stack)
+ state = aa_dfa_match(profile->file.dfa, state, "&");
+ if (profile->ns == tp->ns)
+ return aa_dfa_match(profile->file.dfa, state, tp->base.hname);
+
+ /* try matching with namespace name and then profile */
+ ns_name = aa_ns_name(profile->ns, tp->ns, true);
+ state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
+ state = aa_dfa_match(profile->file.dfa, state, ns_name);
+ state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
+ return aa_dfa_match(profile->file.dfa, state, tp->base.hname);
+}
+
+/**
+ * label_compound_match - find perms for full compound label
+ * @profile: profile to find perms for
+ * @label: label to check access permissions for
+ * @stack: whether this is a stacking request
+ * @start: state to start match in
+ * @subns: whether to do permission checks on components in a subns
+ * @request: permissions to request
+ * @perms: perms struct to set
+ *
+ * Returns: 0 on success else ERROR
+ *
+ * For the label A//&B//&C this does the perm match for A//&B//&C
+ * @perms should be preinitialized with allperms OR a previous permission
+ * check to be stacked.
+ */
+static int label_compound_match(struct aa_profile *profile,
+ struct aa_label *label, bool stack,
+ unsigned int state, bool subns, u32 request,
+ struct aa_perms *perms)
+{
+ struct aa_profile *tp;
+ struct label_it i;
+ struct path_cond cond = { };
+
+ /* find first subcomponent that is visible */
+ label_for_each(i, label, tp) {
+ if (!aa_ns_visible(profile->ns, tp->ns, subns))
+ continue;
+ state = match_component(profile, tp, stack, state);
+ if (!state)
+ goto fail;
+ goto next;
+ }
+
+ /* no component visible */
+ *perms = allperms;
+ return 0;
+
+next:
+ label_for_each_cont(i, label, tp) {
+ if (!aa_ns_visible(profile->ns, tp->ns, subns))
+ continue;
+ state = aa_dfa_match(profile->file.dfa, state, "//&");
+ state = match_component(profile, tp, false, state);
+ if (!state)
+ goto fail;
+ }
+ *perms = aa_compute_fperms(profile->file.dfa, state, &cond);
+ aa_apply_modes_to_perms(profile, perms);
+ if ((perms->allow & request) != request)
+ return -EACCES;
+
+ return 0;
+
+fail:
+ *perms = nullperms;
+ return -EACCES;
+}
+
+/**
+ * label_components_match - find perms for all subcomponents of a label
+ * @profile: profile to find perms for
+ * @label: label to check access permissions for
+ * @stack: whether this is a stacking request
+ * @start: state to start match in
+ * @subns: whether to do permission checks on components in a subns
+ * @request: permissions to request
+ * @perms: an initialized perms struct to add accumulation to
+ *
+ * Returns: 0 on success else ERROR
+ *
+ * For the label A//&B//&C this does the perm match for each of A and B and C
+ * @perms should be preinitialized with allperms OR a previous permission
+ * check to be stacked.
+ */
+static int label_components_match(struct aa_profile *profile,
+ struct aa_label *label, bool stack,
+ unsigned int start, bool subns, u32 request,
+ struct aa_perms *perms)
+{
+ struct aa_profile *tp;
+ struct label_it i;
+ struct aa_perms tmp;
+ struct path_cond cond = { };
+ unsigned int state = 0;
+
+ /* find first subcomponent to test */
+ label_for_each(i, label, tp) {
+ if (!aa_ns_visible(profile->ns, tp->ns, subns))
+ continue;
+ state = match_component(profile, tp, stack, start);
+ if (!state)
+ goto fail;
+ goto next;
+ }
+
+ /* no subcomponents visible - no change in perms */
+ return 0;
+
+next:
+ tmp = aa_compute_fperms(profile->file.dfa, state, &cond);
+ aa_apply_modes_to_perms(profile, &tmp);
+ aa_perms_accum(perms, &tmp);
+ label_for_each_cont(i, label, tp) {
+ if (!aa_ns_visible(profile->ns, tp->ns, subns))
+ continue;
+ state = match_component(profile, tp, stack, start);
+ if (!state)
+ goto fail;
+ tmp = aa_compute_fperms(profile->file.dfa, state, &cond);
+ aa_apply_modes_to_perms(profile, &tmp);
+ aa_perms_accum(perms, &tmp);
+ }
+
+ if ((perms->allow & request) != request)
+ return -EACCES;
+
+ return 0;
+
+fail:
+ *perms = nullperms;
+ return -EACCES;
+}
+
+/**
+ * label_match - do a multi-component label match
+ * @profile: profile to match against (NOT NULL)
+ * @label: label to match (NOT NULL)
+ * @stack: whether this is a stacking request
+ * @state: state to start in
+ * @subns: whether to match subns components
+ * @request: permission request
+ * @perms: Returns computed perms (NOT NULL)
+ *
+ * Returns: the state the match finished in, may be the none matching state
+ */
+static int label_match(struct aa_profile *profile, struct aa_label *label,
+ bool stack, unsigned int state, bool subns, u32 request,
+ struct aa_perms *perms)
+{
+ int error;
+
+ *perms = nullperms;
+ error = label_compound_match(profile, label, stack, state, subns,
+ request, perms);
+ if (!error)
+ return error;
+
+ *perms = allperms;
+ return label_components_match(profile, label, stack, state, subns,
+ request, perms);
+}
+
+/******* end TODO: dedup *****/
+
/**
* change_profile_perms - find permissions for change_profile
* @profile: the current profile (NOT NULL)
- * @ns: the namespace being switched to (NOT NULL)
- * @name: the name of the profile to change to (NOT NULL)
+ * @target: label to transition to (NOT NULL)
+ * @stack: whether this is a stacking request
* @request: requested perms
* @start: state to start matching in
*
+ *
* Returns: permission set
+ *
+ * currently only matches full label A//&B//&C or individual components A, B, C
+ * not arbitrary combinations. Eg. A//&B, C
*/
-static struct file_perms change_profile_perms(struct aa_profile *profile,
- struct aa_ns *ns,
- const char *name, u32 request,
- unsigned int start)
+static int change_profile_perms(struct aa_profile *profile,
+ struct aa_label *target, bool stack,
+ u32 request, unsigned int start,
+ struct aa_perms *perms)
{
- struct file_perms perms;
- struct path_cond cond = { };
- unsigned int state;
-
- if (unconfined(profile)) {
- perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
- perms.audit = perms.quiet = perms.kill = 0;
- return perms;
- } else if (!profile->file.dfa) {
- return nullperms;
- } else if ((ns == profile->ns)) {
- /* try matching against rules with out namespace prepended */
- aa_str_perms(profile->file.dfa, start, name, &cond, &perms);
- if (COMBINED_PERM_MASK(perms) & request)
- return perms;
+ if (profile_unconfined(profile)) {
+ perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
+ perms->audit = perms->quiet = perms->kill = 0;
+ return 0;
}
- /* try matching with namespace name and then profile */
- state = aa_dfa_match(profile->file.dfa, start, ns->base.name);
- state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
- aa_str_perms(profile->file.dfa, state, name, &cond, &perms);
-
- return perms;
+ /* TODO: add profile in ns screening */
+ return label_match(profile, target, stack, start, true, request, perms);
}
/**
@@ -144,7 +322,7 @@ static struct aa_profile *__attach_match(const char *name,
struct aa_profile *profile, *candidate = NULL;
list_for_each_entry_rcu(profile, head, base.list) {
- if (profile->flags & PFLAG_NULL)
+ if (profile->label.flags & FLAG_NULL)
continue;
if (profile->xmatch && profile->xmatch_len > len) {
unsigned int state = aa_dfa_match(profile->xmatch,
@@ -169,10 +347,10 @@ static struct aa_profile *__attach_match(const char *name,
* @list: list to search (NOT NULL)
* @name: the executable name to match against (NOT NULL)
*
- * Returns: profile or NULL if no match found
+ * Returns: label or NULL if no match found
*/
-static struct aa_profile *find_attach(struct aa_ns *ns,
- struct list_head *list, const char *name)
+static struct aa_label *find_attach(struct aa_ns *ns, struct list_head *list,
+ const char *name)
{
struct aa_profile *profile;
@@ -180,49 +358,7 @@ static struct aa_profile *find_attach(struct aa_ns *ns,
profile = aa_get_profile(__attach_match(name, list));
rcu_read_unlock();
- return profile;
-}
-
-/**
- * separate_fqname - separate the namespace and profile names
- * @fqname: the fqname name to split (NOT NULL)
- * @ns_name: the namespace name if it exists (NOT NULL)
- *
- * This is the xtable equivalent routine of aa_split_fqname. It finds the
- * split in an xtable fqname which contains an embedded \0 instead of a :
- * if a namespace is specified. This is done so the xtable is constant and
- * isn't re-split on every lookup.
- *
- * Either the profile or namespace name may be optional but if the namespace
- * is specified the profile name termination must be present. This results
- * in the following possible encodings:
- * profile_name\0
- * :ns_name\0profile_name\0
- * :ns_name\0\0
- *
- * NOTE: the xtable fqname is pre-validated at load time in unpack_trans_table
- *
- * Returns: profile name if it is specified else NULL
- */
-static const char *separate_fqname(const char *fqname, const char **ns_name)
-{
- const char *name;
-
- if (fqname[0] == ':') {
- /* In this case there is guaranteed to be two \0 terminators
- * in the string. They are verified at load time by
- * by unpack_trans_table
- */
- *ns_name = fqname + 1; /* skip : */
- name = *ns_name + strlen(*ns_name) + 1;
- if (!*name)
- name = NULL;
- } else {
- *ns_name = NULL;
- name = fqname;
- }
-
- return name;
+ return profile ? &profile->label : NULL;
}
static const char *next_name(int xtype, const char *name)
@@ -234,290 +370,477 @@ static const char *next_name(int xtype, const char *name)
* x_table_lookup - lookup an x transition name via transition table
* @profile: current profile (NOT NULL)
* @xindex: index into x transition table
+ * @name: returns: name tested to find label (NOT NULL)
*
- * Returns: refcounted profile, or NULL on failure (MAYBE NULL)
+ * Returns: refcounted label, or NULL on failure (MAYBE NULL)
*/
-static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex)
+static struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
+ const char **name)
{
- struct aa_profile *new_profile = NULL;
- struct aa_ns *ns = profile->ns;
+ struct aa_label *label = NULL;
u32 xtype = xindex & AA_X_TYPE_MASK;
int index = xindex & AA_X_INDEX_MASK;
- const char *name;
- /* index is guaranteed to be in range, validated at load time */
- for (name = profile->file.trans.table[index]; !new_profile && name;
- name = next_name(xtype, name)) {
- struct aa_ns *new_ns;
- const char *xname = NULL;
+ AA_BUG(!name);
- new_ns = NULL;
+ /* index is guaranteed to be in range, validated at load time */
+ /* TODO: move lookup parsing to unpack time so this is a straight
+ * index into the resultant label
+ */
+ for (*name = profile->file.trans.table[index]; !label && *name;
+ *name = next_name(xtype, *name)) {
if (xindex & AA_X_CHILD) {
+ struct aa_profile *new_profile;
/* release by caller */
- new_profile = aa_find_child(profile, name);
+ new_profile = aa_find_child(profile, *name);
+ if (new_profile)
+ label = &new_profile->label;
continue;
- } else if (*name == ':') {
- /* switching namespace */
- const char *ns_name;
- xname = name = separate_fqname(name, &ns_name);
- if (!xname)
- /* no name so use profile name */
- xname = profile->base.hname;
- if (*ns_name == '@') {
- /* TODO: variable support */
- ;
- }
- /* released below */
- new_ns = aa_find_ns(ns, ns_name);
- if (!new_ns)
- continue;
- } else if (*name == '@') {
- /* TODO: variable support */
- continue;
- } else {
- /* basic namespace lookup */
- xname = name;
}
-
- /* released by caller */
- new_profile = aa_lookup_profile(new_ns ? new_ns : ns, xname);
- aa_put_ns(new_ns);
+ label = aa_label_parse(&profile->label, *name, GFP_ATOMIC,
+ true, false);
+ if (IS_ERR(label))
+ label = NULL;
}
/* released by caller */
- return new_profile;
+
+ return label;
}
/**
- * x_to_profile - get target profile for a given xindex
+ * x_to_label - get target label for a given xindex
* @profile: current profile (NOT NULL)
* @name: name to lookup (NOT NULL)
* @xindex: index into x transition table
+ * @lookupname: returns: name used in lookup if one was specified (NOT NULL)
*
- * find profile for a transition index
+ * find label for a transition index
*
- * Returns: refcounted profile or NULL if not found available
+ * Returns: refcounted label or NULL if not found available
*/
-static struct aa_profile *x_to_profile(struct aa_profile *profile,
- const char *name, u32 xindex)
+static struct aa_label *x_to_label(struct aa_profile *profile,
+ const char *name, u32 xindex,
+ const char **lookupname,
+ const char **info)
{
- struct aa_profile *new_profile = NULL;
+ struct aa_label *new = NULL;
struct aa_ns *ns = profile->ns;
u32 xtype = xindex & AA_X_TYPE_MASK;
+ const char *stack = NULL;
switch (xtype) {
case AA_X_NONE:
/* fail exec unless ix || ux fallback - handled by caller */
- return NULL;
+ *lookupname = NULL;
+ break;
+ case AA_X_TABLE:
+ /* TODO: fix when perm mapping done at unload */
+ stack = profile->file.trans.table[xindex & AA_X_INDEX_MASK];
+ if (*stack != '&') {
+ /* released by caller */
+ new = x_table_lookup(profile, xindex, lookupname);
+ stack = NULL;
+ break;
+ }
+ /* fall through to X_NAME */
case AA_X_NAME:
if (xindex & AA_X_CHILD)
/* released by caller */
- new_profile = find_attach(ns, &profile->base.profiles,
- name);
+ new = find_attach(ns, &profile->base.profiles,
+ name);
else
/* released by caller */
- new_profile = find_attach(ns, &ns->base.profiles,
- name);
- break;
- case AA_X_TABLE:
- /* released by caller */
- new_profile = x_table_lookup(profile, xindex);
+ new = find_attach(ns, &ns->base.profiles,
+ name);
+ *lookupname = name;
break;
}
+ if (!new) {
+ if (xindex & AA_X_INHERIT) {
+ /* (p|c|n)ix - don't change profile but do
+ * use the newest version
+ */
+ *info = "ix fallback";
+ /* no profile && no error */
+ new = aa_get_newest_label(&profile->label);
+ } else if (xindex & AA_X_UNCONFINED) {
+ new = aa_get_newest_label(ns_unconfined(profile->ns));
+ *info = "ux fallback";
+ }
+ }
+
+ if (new && stack) {
+ /* base the stack on post domain transition */
+ struct aa_label *base = new;
+
+ new = aa_label_parse(base, stack, GFP_ATOMIC, true, false);
+ if (IS_ERR(new))
+ new = NULL;
+ aa_put_label(base);
+ }
+
/* released by caller */
- return new_profile;
+ return new;
}
-/**
- * apparmor_bprm_set_creds - set the new creds on the bprm struct
- * @bprm: binprm for the exec (NOT NULL)
- *
- * Returns: %0 or error on failure
- */
-int apparmor_bprm_set_creds(struct linux_binprm *bprm)
+static struct aa_label *profile_transition(struct aa_profile *profile,
+ const struct linux_binprm *bprm,
+ char *buffer, struct path_cond *cond,
+ bool *secure_exec)
{
- struct aa_task_ctx *ctx;
- struct aa_profile *profile, *new_profile = NULL;
- struct aa_ns *ns;
- char *buffer = NULL;
- unsigned int state;
- struct file_perms perms = {};
- struct path_cond cond = {
- file_inode(bprm->file)->i_uid,
- file_inode(bprm->file)->i_mode
- };
- const char *name = NULL, *info = NULL;
+ struct aa_label *new = NULL;
+ const char *info = NULL, *name = NULL, *target = NULL;
+ unsigned int state = profile->file.start;
+ struct aa_perms perms = {};
+ bool nonewprivs = false;
int error = 0;
- if (bprm->cred_prepared)
- return 0;
-
- ctx = cred_ctx(bprm->cred);
- AA_BUG(!ctx);
-
- profile = aa_get_newest_profile(ctx->profile);
- /*
- * get the namespace from the replacement profile as replacement
- * can change the namespace
- */
- ns = profile->ns;
- state = profile->file.start;
+ AA_BUG(!profile);
+ AA_BUG(!bprm);
+ AA_BUG(!buffer);
- /* buffer freed below, name is pointer into buffer */
- error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer,
- &name, &info);
+ error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
+ &name, &info, profile->disconnected);
if (error) {
- if (unconfined(profile) ||
- (profile->flags & PFLAG_IX_ON_NAME_ERROR))
+ if (profile_unconfined(profile) ||
+ (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
+ AA_DEBUG("name lookup ix on error");
error = 0;
+ new = aa_get_newest_label(&profile->label);
+ }
name = bprm->filename;
goto audit;
}
- /* Test for onexec first as onexec directives override other
- * x transitions.
- */
- if (unconfined(profile)) {
- /* unconfined task */
- if (ctx->onexec)
- /* change_profile on exec already been granted */
- new_profile = aa_get_profile(ctx->onexec);
- else
- new_profile = find_attach(ns, &ns->base.profiles, name);
- if (!new_profile)
- goto cleanup;
- /*
- * NOTE: Domain transitions from unconfined are allowed
- * even when no_new_privs is set because this aways results
- * in a further reduction of permissions.
- */
- goto apply;
+ if (profile_unconfined(profile)) {
+ new = find_attach(profile->ns, &profile->ns->base.profiles,
+ name);
+ if (new) {
+ AA_DEBUG("unconfined attached to new label");
+ return new;
+ }
+ AA_DEBUG("unconfined exec no attachment");
+ return aa_get_newest_label(&profile->label);
}
/* find exec permissions for name */
- state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms);
- if (ctx->onexec) {
- struct file_perms cp;
- info = "change_profile onexec";
- new_profile = aa_get_newest_profile(ctx->onexec);
- if (!(perms.allow & AA_MAY_ONEXEC))
- goto audit;
-
- /* test if this exec can be paired with change_profile onexec.
- * onexec permission is linked to exec with a standard pairing
- * exec\0change_profile
- */
- state = aa_dfa_null_transition(profile->file.dfa, state);
- cp = change_profile_perms(profile, ctx->onexec->ns,
- ctx->onexec->base.name,
- AA_MAY_ONEXEC, state);
-
- if (!(cp.allow & AA_MAY_ONEXEC))
- goto audit;
- goto apply;
- }
-
+ state = aa_str_perms(profile->file.dfa, state, name, cond, &perms);
if (perms.allow & MAY_EXEC) {
/* exec permission determine how to transition */
- new_profile = x_to_profile(profile, name, perms.xindex);
- if (!new_profile) {
- if (perms.xindex & AA_X_INHERIT) {
- /* (p|c|n)ix - don't change profile but do
- * use the newest version, which was picked
- * up above when getting profile
- */
- info = "ix fallback";
- new_profile = aa_get_profile(profile);
- goto x_clear;
- } else if (perms.xindex & AA_X_UNCONFINED) {
- new_profile = aa_get_newest_profile(ns->unconfined);
- info = "ux fallback";
- } else {
- error = -EACCES;
- info = "profile not found";
- /* remove MAY_EXEC to audit as failure */
- perms.allow &= ~MAY_EXEC;
- }
+ new = x_to_label(profile, name, perms.xindex, &target, &info);
+ if (new && new->proxy == profile->label.proxy && info) {
+ /* hack ix fallback - improve how this is detected */
+ goto audit;
+ } else if (!new) {
+ error = -EACCES;
+ info = "profile transition not found";
+ /* remove MAY_EXEC to audit as failure */
+ perms.allow &= ~MAY_EXEC;
}
} else if (COMPLAIN_MODE(profile)) {
- /* no exec permission - are we in learning mode */
- new_profile = aa_new_null_profile(profile, false, name,
- GFP_ATOMIC);
+ /* no exec permission - learning mode */
+ struct aa_profile *new_profile = aa_new_null_profile(profile,
+ false, name,
+ GFP_ATOMIC);
if (!new_profile) {
error = -ENOMEM;
info = "could not create null profile";
- } else
+ } else {
error = -EACCES;
+ new = &new_profile->label;
+ }
perms.xindex |= AA_X_UNSAFE;
} else
/* fail exec */
error = -EACCES;
- /*
- * Policy has specified a domain transition, if no_new_privs then
- * fail the exec.
+ if (!new)
+ goto audit;
+
+ /* Policy has specified a domain transitions. if no_new_privs and
+ * confined and not transitioning to the current domain fail.
+ *
+ * NOTE: Domain transitions from unconfined and to stritly stacked
+ * subsets are allowed even when no_new_privs is set because this
+ * aways results in a further reduction of permissions.
*/
- if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) {
+ if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) &&
+ !profile_unconfined(profile) &&
+ !aa_label_is_subset(new, &profile->label)) {
error = -EPERM;
- goto cleanup;
+ info = "no new privs";
+ nonewprivs = true;
+ perms.allow &= ~MAY_EXEC;
+ goto audit;
+ }
+
+ if (!(perms.xindex & AA_X_UNSAFE)) {
+ if (DEBUG_ON) {
+ dbg_printk("apparmor: scrubbing environment variables"
+ " for %s profile=", name);
+ aa_label_printk(new, GFP_ATOMIC);
+ dbg_printk("\n");
+ }
+ *secure_exec = true;
+ }
+
+audit:
+ aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, target, new,
+ cond->uid, info, error);
+ if (!new || nonewprivs) {
+ aa_put_label(new);
+ return ERR_PTR(error);
}
- if (!new_profile)
+ return new;
+}
+
+static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
+ bool stack, const struct linux_binprm *bprm,
+ char *buffer, struct path_cond *cond,
+ bool *secure_exec)
+{
+ unsigned int state = profile->file.start;
+ struct aa_perms perms = {};
+ const char *xname = NULL, *info = "change_profile onexec";
+ int error = -EACCES;
+
+ AA_BUG(!profile);
+ AA_BUG(!onexec);
+ AA_BUG(!bprm);
+ AA_BUG(!buffer);
+
+ if (profile_unconfined(profile)) {
+ /* change_profile on exec already granted */
+ /*
+ * NOTE: Domain transitions from unconfined are allowed
+ * even when no_new_privs is set because this aways results
+ * in a further reduction of permissions.
+ */
+ return 0;
+ }
+
+ error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
+ &xname, &info, profile->disconnected);
+ if (error) {
+ if (profile_unconfined(profile) ||
+ (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
+ AA_DEBUG("name lookup ix on error");
+ error = 0;
+ }
+ xname = bprm->filename;
+ goto audit;
+ }
+
+ /* find exec permissions for name */
+ state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms);
+ if (!(perms.allow & AA_MAY_ONEXEC)) {
+ info = "no change_onexec valid for executable";
+ goto audit;
+ }
+ /* test if this exec can be paired with change_profile onexec.
+ * onexec permission is linked to exec with a standard pairing
+ * exec\0change_profile
+ */
+ state = aa_dfa_null_transition(profile->file.dfa, state);
+ error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC,
+ state, &perms);
+ if (error) {
+ perms.allow &= ~AA_MAY_ONEXEC;
goto audit;
+ }
+ /* Policy has specified a domain transitions. if no_new_privs and
+ * confined and not transitioning to the current domain fail.
+ *
+ * NOTE: Domain transitions from unconfined and to stritly stacked
+ * subsets are allowed even when no_new_privs is set because this
+ * aways results in a further reduction of permissions.
+ */
+ if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) &&
+ !profile_unconfined(profile) &&
+ !aa_label_is_subset(onexec, &profile->label)) {
+ error = -EPERM;
+ info = "no new privs";
+ perms.allow &= ~AA_MAY_ONEXEC;
+ goto audit;
+ }
+
+ if (!(perms.xindex & AA_X_UNSAFE)) {
+ if (DEBUG_ON) {
+ dbg_printk("apparmor: scrubbing environment "
+ "variables for %s label=", xname);
+ aa_label_printk(onexec, GFP_ATOMIC);
+ dbg_printk("\n");
+ }
+ *secure_exec = true;
+ }
+
+audit:
+ return aa_audit_file(profile, &perms, OP_EXEC, AA_MAY_ONEXEC, xname,
+ NULL, onexec, cond->uid, info, error);
+}
+
+/* ensure none ns domain transitions are correctly applied with onexec */
+
+static struct aa_label *handle_onexec(struct aa_label *label,
+ struct aa_label *onexec, bool stack,
+ const struct linux_binprm *bprm,
+ char *buffer, struct path_cond *cond,
+ bool *unsafe)
+{
+ struct aa_profile *profile;
+ struct aa_label *new;
+ int error;
+
+ AA_BUG(!label);
+ AA_BUG(!onexec);
+ AA_BUG(!bprm);
+ AA_BUG(!buffer);
+
+ if (!stack) {
+ error = fn_for_each_in_ns(label, profile,
+ profile_onexec(profile, onexec, stack,
+ bprm, buffer, cond, unsafe));
+ if (error)
+ return ERR_PTR(error);
+ new = fn_label_build_in_ns(label, profile, GFP_ATOMIC,
+ aa_get_newest_label(onexec),
+ profile_transition(profile, bprm, buffer,
+ cond, unsafe));
+
+ } else {
+ /* TODO: determine how much we want to losen this */
+ error = fn_for_each_in_ns(label, profile,
+ profile_onexec(profile, onexec, stack, bprm,
+ buffer, cond, unsafe));
+ if (error)
+ return ERR_PTR(error);
+ new = fn_label_build_in_ns(label, profile, GFP_ATOMIC,
+ aa_label_merge(&profile->label, onexec,
+ GFP_ATOMIC),
+ profile_transition(profile, bprm, buffer,
+ cond, unsafe));
+ }
+
+ if (new)
+ return new;
+
+ /* TODO: get rid of GLOBAL_ROOT_UID */
+ error = fn_for_each_in_ns(label, profile,
+ aa_audit_file(profile, &nullperms, OP_CHANGE_ONEXEC,
+ AA_MAY_ONEXEC, bprm->filename, NULL,
+ onexec, GLOBAL_ROOT_UID,
+ "failed to build target label", -ENOMEM));
+ return ERR_PTR(error);
+}
+
+/**
+ * apparmor_bprm_set_creds - set the new creds on the bprm struct
+ * @bprm: binprm for the exec (NOT NULL)
+ *
+ * Returns: %0 or error on failure
+ *
+ * TODO: once the other paths are done see if we can't refactor into a fn
+ */
+int apparmor_bprm_set_creds(struct linux_binprm *bprm)
+{
+ struct aa_task_ctx *ctx;
+ struct aa_label *label, *new = NULL;
+ struct aa_profile *profile;
+ char *buffer = NULL;
+ const char *info = NULL;
+ int error = 0;
+ bool unsafe = false;
+ struct path_cond cond = {
+ file_inode(bprm->file)->i_uid,
+ file_inode(bprm->file)->i_mode
+ };
+
+ if (bprm->cred_prepared)
+ return 0;
+
+ ctx = cred_ctx(bprm->cred);
+ AA_BUG(!ctx);
+
+ label = aa_get_newest_label(ctx->label);
+
+ /* buffer freed below, name is pointer into buffer */
+ get_buffers(buffer);
+ /* Test for onexec first as onexec override other x transitions. */
+ if (ctx->onexec)
+ new = handle_onexec(label, ctx->onexec, ctx->token,
+ bprm, buffer, &cond, &unsafe);
+ else
+ new = fn_label_build(label, profile, GFP_ATOMIC,
+ profile_transition(profile, bprm, buffer,
+ &cond, &unsafe));
+
+ AA_BUG(!new);
+ if (IS_ERR(new)) {
+ error = PTR_ERR(new);
+ goto done;
+ } else if (!new) {
+ error = -ENOMEM;
+ goto done;
+ }
+
+ /* TODO: Add ns level no_new_privs subset test */
if (bprm->unsafe & LSM_UNSAFE_SHARE) {
/* FIXME: currently don't mediate shared state */
;
}
- if (bprm->unsafe & LSM_UNSAFE_PTRACE) {
- error = may_change_ptraced_domain(new_profile);
+ if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) {
+ /* TODO: test needs to be profile of label to new */
+ error = may_change_ptraced_domain(new, &info);
if (error)
goto audit;
}
- /* Determine if secure exec is needed.
- * Can be at this point for the following reasons:
- * 1. unconfined switching to confined
- * 2. confined switching to different confinement
- * 3. confined switching to unconfined
- *
- * Cases 2 and 3 are marked as requiring secure exec
- * (unless policy specified "unsafe exec")
- *
- * bprm->unsafe is used to cache the AA_X_UNSAFE permission
- * to avoid having to recompute in secureexec
- */
- if (!(perms.xindex & AA_X_UNSAFE)) {
- AA_DEBUG("scrubbing environment variables for %s profile=%s\n",
- name, new_profile->base.hname);
+ if (unsafe) {
+ if (DEBUG_ON) {
+ dbg_printk("scrubbing environment variables for %s "
+ "label=", bprm->filename);
+ aa_label_printk(new, GFP_ATOMIC);
+ dbg_printk("\n");
+ }
bprm->unsafe |= AA_SECURE_X_NEEDED;
}
-apply:
- /* when transitioning profiles clear unsafe personality bits */
- bprm->per_clear |= PER_CLEAR_ON_SETID;
-x_clear:
- aa_put_profile(ctx->profile);
- /* transfer new profile reference will be released when ctx is freed */
- ctx->profile = new_profile;
- new_profile = NULL;
+ if (label->proxy != new->proxy) {
+ /* when transitioning clear unsafe personality bits */
+ if (DEBUG_ON) {
+ dbg_printk("apparmor: clearing unsafe personality "
+ "bits. %s label=", bprm->filename);
+ aa_label_printk(new, GFP_ATOMIC);
+ dbg_printk("\n");
+ }
+ bprm->per_clear |= PER_CLEAR_ON_SETID;
+ }
+ aa_put_label(ctx->label);
+ /* transfer reference, released when ctx is freed */
+ ctx->label = new;
- /* clear out all temporary/transitional state from the context */
+done:
+ /* clear out temporary/transitional state from the context */
aa_clear_task_ctx_trans(ctx);
-audit:
- error = aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name,
- new_profile ? new_profile->base.hname : NULL,
- cond.uid, info, error);
-
-cleanup:
- aa_put_profile(new_profile);
- aa_put_profile(profile);
- kfree(buffer);
+ aa_put_label(label);
+ put_buffers(buffer);
return error;
+
+audit:
+ error = fn_for_each(label, profile,
+ aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC,
+ bprm->filename, NULL, new,
+ file_inode(bprm->file)->i_uid, info,
+ error));
+ aa_put_label(new);
+ goto done;
}
/**
@@ -537,53 +860,157 @@ int apparmor_bprm_secureexec(struct linux_binprm *bprm)
return 0;
}
-/**
- * apparmor_bprm_committing_creds - do task cleanup on committing new creds
- * @bprm: binprm for the exec (NOT NULL)
+/*
+ * Functions for self directed profile change
*/
-void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
-{
- struct aa_profile *profile = __aa_current_profile();
- struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred);
- /* bail out if unconfined or not changing profile */
- if ((new_ctx->profile == profile) ||
- (unconfined(new_ctx->profile)))
- return;
- current->pdeath_signal = 0;
-
- /* reset soft limits and set hard limits for the new profile */
- __aa_transition_rlimits(profile, new_ctx->profile);
-}
-
-/**
- * apparmor_bprm_commited_cred - do cleanup after new creds committed
- * @bprm: binprm for the exec (NOT NULL)
+/* helper fn for change_hat
+ *
+ * Returns: label for hat transition OR ERR_PTR. Does NOT return NULL
*/
-void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
+static struct aa_label *build_change_hat(struct aa_profile *profile,
+ const char *name, bool sibling)
{
- /* TODO: cleanup signals - ipc mediation */
- return;
-}
+ struct aa_profile *root, *hat = NULL;
+ const char *info = NULL;
+ int error = 0;
-/*
- * Functions for self directed profile change
- */
+ if (sibling && PROFILE_IS_HAT(profile)) {
+ root = aa_get_profile_rcu(&profile->parent);
+ } else if (!sibling && !PROFILE_IS_HAT(profile)) {
+ root = aa_get_profile(profile);
+ } else {
+ info = "conflicting target types";
+ error = -EPERM;
+ goto audit;
+ }
-/**
- * new_compound_name - create an hname with @n2 appended to @n1
- * @n1: base of hname (NOT NULL)
- * @n2: name to append (NOT NULL)
+ hat = aa_find_child(root, name);
+ if (!hat) {
+ error = -ENOENT;
+ if (COMPLAIN_MODE(profile)) {
+ hat = aa_new_null_profile(profile, true, name,
+ GFP_KERNEL);
+ if (!hat) {
+ info = "failed null profile create";
+ error = -ENOMEM;
+ }
+ }
+ }
+ aa_put_profile(root);
+
+audit:
+ aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT,
+ name, hat ? hat->base.hname : NULL,
+ hat ? &hat->label : NULL, GLOBAL_ROOT_UID, NULL,
+ error);
+ if (!hat || (error && error != -ENOENT))
+ return ERR_PTR(error);
+ /* if hat && error - complain mode, already audited and we adjust for
+ * complain mode allow by returning hat->label
+ */
+ return &hat->label;
+}
+
+/* helper fn for changing into a hat
*
- * Returns: new name or NULL on error
+ * Returns: label for hat transition or ERR_PTR. Does not return NULL
*/
-static char *new_compound_name(const char *n1, const char *n2)
+static struct aa_label *change_hat(struct aa_label *label, const char *hats[],
+ int count, int flags)
{
- char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL);
- if (name)
- sprintf(name, "%s//%s", n1, n2);
- return name;
+ struct aa_profile *profile, *root, *hat = NULL;
+ struct aa_label *new;
+ struct label_it it;
+ bool sibling = false;
+ const char *name, *info = NULL;
+ int i, error;
+
+ AA_BUG(!label);
+ AA_BUG(!hats);
+ AA_BUG(count < 1);
+
+ if (PROFILE_IS_HAT(labels_profile(label)))
+ sibling = true;
+
+ /*find first matching hat */
+ for (i = 0; i < count && !hat; i++) {
+ name = hats[i];
+ label_for_each_in_ns(it, labels_ns(label), label, profile) {
+ if (sibling && PROFILE_IS_HAT(profile)) {
+ root = aa_get_profile_rcu(&profile->parent);
+ } else if (!sibling && !PROFILE_IS_HAT(profile)) {
+ root = aa_get_profile(profile);
+ } else { /* conflicting change type */
+ info = "conflicting targets types";
+ error = -EPERM;
+ goto fail;
+ }
+ hat = aa_find_child(root, name);
+ aa_put_profile(root);
+ if (!hat) {
+ if (!COMPLAIN_MODE(profile))
+ goto outer_continue;
+ /* complain mode succeed as if hat */
+ } else if (!PROFILE_IS_HAT(hat)) {
+ info = "target not hat";
+ error = -EPERM;
+ aa_put_profile(hat);
+ goto fail;
+ }
+ aa_put_profile(hat);
+ }
+ /* found a hat for all profiles in ns */
+ goto build;
+outer_continue:
+ ;
+ }
+ /* no hats that match, find appropriate error
+ *
+ * In complain mode audit of the failure is based off of the first
+ * hat supplied. This is done due how userspace interacts with
+ * change_hat.
+ */
+ name = NULL;
+ label_for_each_in_ns(it, labels_ns(label), label, profile) {
+ if (!list_empty(&profile->base.profiles)) {
+ info = "hat not found";
+ error = -ENOENT;
+ goto fail;
+ }
+ }
+ info = "no hats defined";
+ error = -ECHILD;
+
+fail:
+ label_for_each_in_ns(it, labels_ns(label), label, profile) {
+ /*
+ * no target as it has failed to be found or built
+ *
+ * change_hat uses probing and should not log failures
+ * related to missing hats
+ */
+ /* TODO: get rid of GLOBAL_ROOT_UID */
+ if (count > 1 || COMPLAIN_MODE(profile)) {
+ aa_audit_file(profile, &nullperms, OP_CHANGE_HAT,
+ AA_MAY_CHANGEHAT, name, NULL, NULL,
+ GLOBAL_ROOT_UID, info, error);
+ }
+ }
+ return ERR_PTR(error);
+
+build:
+ new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
+ build_change_hat(profile, name, sibling),
+ aa_get_label(&profile->label));
+ if (!new) {
+ info = "label build failed";
+ error = -ENOMEM;
+ goto fail;
+ } /* else if (IS_ERR) build_change_hat has logged error so return new */
+
+ return new;
}
/**
@@ -591,24 +1018,26 @@ static char *new_compound_name(const char *n1, const char *n2)
* @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0)
* @count: number of hat names in @hats
* @token: magic value to validate the hat change
- * @permtest: true if this is just a permission test
+ * @flags: flags affecting behavior of the change
+ *
+ * Returns %0 on success, error otherwise.
*
* Change to the first profile specified in @hats that exists, and store
* the @hat_magic in the current task context. If the count == 0 and the
* @token matches that stored in the current task context, return to the
* top level profile.
*
- * Returns %0 on success, error otherwise.
+ * change_hat only applies to profiles in the current ns, and each profile
+ * in the ns must make the same transition otherwise change_hat will fail.
*/
-int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
+int aa_change_hat(const char *hats[], int count, u64 token, int flags)
{
const struct cred *cred;
struct aa_task_ctx *ctx;
- struct aa_profile *profile, *previous_profile, *hat = NULL;
- char *name = NULL;
- int i;
- struct file_perms perms = {};
- const char *target = NULL, *info = NULL;
+ struct aa_label *label, *previous, *new = NULL, *target = NULL;
+ struct aa_profile *profile;
+ struct aa_perms perms = {};
+ const char *info = NULL;
int error = 0;
/*
@@ -616,122 +1045,120 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
* There is no exception for unconfined as change_hat is not
* available.
*/
- if (task_no_new_privs(current))
+ if (task_no_new_privs(current)) {
+ /* not an apparmor denial per se, so don't log it */
+ AA_DEBUG("no_new_privs - change_hat denied");
return -EPERM;
+ }
/* released below */
cred = get_current_cred();
ctx = cred_ctx(cred);
- profile = aa_get_newest_profile(aa_cred_profile(cred));
- previous_profile = aa_get_newest_profile(ctx->previous);
+ label = aa_get_newest_cred_label(cred);
+ previous = aa_get_newest_label(ctx->previous);
- if (unconfined(profile)) {
- info = "unconfined";
+ if (unconfined(label)) {
+ info = "unconfined can not change_hat";
error = -EPERM;
- goto audit;
+ goto fail;
}
if (count) {
- /* attempting to change into a new hat or switch to a sibling */
- struct aa_profile *root;
- if (PROFILE_IS_HAT(profile))
- root = aa_get_profile_rcu(&profile->parent);
- else
- root = aa_get_profile(profile);
-
- /* find first matching hat */
- for (i = 0; i < count && !hat; i++)
- /* released below */
- hat = aa_find_child(root, hats[i]);
- if (!hat) {
- if (!COMPLAIN_MODE(root) || permtest) {
- if (list_empty(&root->base.profiles))
- error = -ECHILD;
- else
- error = -ENOENT;
- aa_put_profile(root);
- goto out;
- }
-
- /*
- * In complain mode and failed to match any hats.
- * Audit the failure is based off of the first hat
- * supplied. This is done due how userspace
- * interacts with change_hat.
- *
- * TODO: Add logging of all failed hats
- */
-
- /* freed below */
- name = new_compound_name(root->base.hname, hats[0]);
- aa_put_profile(root);
- target = name;
- /* released below */
- hat = aa_new_null_profile(profile, true, hats[0],
- GFP_KERNEL);
- if (!hat) {
- info = "failed null profile create";
- error = -ENOMEM;
- goto audit;
- }
- } else {
- aa_put_profile(root);
- target = hat->base.hname;
- if (!PROFILE_IS_HAT(hat)) {
- info = "target not hat";
- error = -EPERM;
- goto audit;
- }
+ new = change_hat(label, hats, count, flags);
+ AA_BUG(!new);
+ if (IS_ERR(new)) {
+ error = PTR_ERR(new);
+ new = NULL;
+ /* already audited */
+ goto out;
}
- error = may_change_ptraced_domain(hat);
+ error = may_change_ptraced_domain(new, &info);
+ if (error)
+ goto fail;
+
+ if (flags & AA_CHANGE_TEST)
+ goto out;
+
+ target = new;
+ error = aa_set_current_hat(new, token);
+ if (error == -EACCES)
+ /* kill task in case of brute force attacks */
+ goto kill;
+ } else if (previous && !(flags & AA_CHANGE_TEST)) {
+ /* Return to saved label. Kill task if restore fails
+ * to avoid brute force attacks
+ */
+ target = previous;
+ error = aa_restore_previous_label(token);
if (error) {
- info = "ptraced";
- error = -EPERM;
- goto audit;
- }
-
- if (!permtest) {
- error = aa_set_current_hat(hat, token);
if (error == -EACCES)
- /* kill task in case of brute force attacks */
- perms.kill = AA_MAY_CHANGEHAT;
- else if (name && !error)
- /* reset error for learning of new hats */
- error = -ENOENT;
+ goto kill;
+ goto fail;
}
- } else if (previous_profile) {
- /* Return to saved profile. Kill task if restore fails
- * to avoid brute force attacks
- */
- target = previous_profile->base.hname;
- error = aa_restore_previous_profile(token);
- perms.kill = AA_MAY_CHANGEHAT;
- } else
- /* ignore restores when there is no saved profile */
- goto out;
-
-audit:
- if (!permtest)
- error = aa_audit_file(profile, &perms, OP_CHANGE_HAT,
- AA_MAY_CHANGEHAT, NULL, target,
- GLOBAL_ROOT_UID, info, error);
+ } /* else ignore @flags && restores when there is no saved profile */
out:
- aa_put_profile(hat);
- kfree(name);
- aa_put_profile(profile);
- aa_put_profile(previous_profile);
+ aa_put_label(new);
+ aa_put_label(previous);
+ aa_put_label(label);
put_cred(cred);
return error;
+
+kill:
+ info = "failed token match";
+ perms.kill = AA_MAY_CHANGEHAT;
+
+fail:
+ fn_for_each_in_ns(label, profile,
+ aa_audit_file(profile, &perms, OP_CHANGE_HAT,
+ AA_MAY_CHANGEHAT, NULL, NULL, target,
+ GLOBAL_ROOT_UID, info, error));
+
+ goto out;
+}
+
+
+static int change_profile_perms_wrapper(const char *op, const char *name,
+ struct aa_profile *profile,
+ struct aa_label *target, bool stack,
+ u32 request, struct aa_perms *perms)
+{
+ const char *info = NULL;
+ int error = 0;
+
+ /*
+ * Fail explicitly requested domain transitions when no_new_privs
+ * and not unconfined OR the transition results in a stack on
+ * the current label.
+ * Stacking domain transitions and transitions from unconfined are
+ * allowed even when no_new_privs is set because this aways results
+ * in a reduction of permissions.
+ */
+ if (task_no_new_privs(current) && !stack &&
+ !profile_unconfined(profile) &&
+ !aa_label_is_subset(target, &profile->label)) {
+ info = "no new privs";
+ error = -EPERM;
+ }
+
+ if (!error)
+ error = change_profile_perms(profile, target, stack, request,
+ profile->file.start, perms);
+ if (error)
+ error = aa_audit_file(profile, perms, op, request, name,
+ NULL, target, GLOBAL_ROOT_UID, info,
+ error);
+
+ return error;
}
/**
* aa_change_profile - perform a one-way profile transition
* @fqname: name of profile may include namespace (NOT NULL)
* @onexec: whether this transition is to take place immediately or at exec
- * @permtest: true if this is just a permission test
+ * @flags: flags affecting change behavior
*
* Change to new profile @name. Unlike with hats, there is no way
* to change back. If @name isn't specified the current profile name is
@@ -741,14 +1168,16 @@ out:
*
* Returns %0 on success, error otherwise.
*/
-int aa_change_profile(const char *fqname, bool onexec,
- bool permtest, bool stack)
+int aa_change_profile(const char *fqname, int flags)
{
- const struct cred *cred;
- struct aa_profile *profile, *target = NULL;
- struct file_perms perms = {};
- const char *info = NULL, *op;
+ struct aa_label *label, *new = NULL, *target = NULL;
+ struct aa_profile *profile;
+ struct aa_perms perms = {};
+ const char *info = NULL;
+ const char *auditname = fqname; /* retain leading & if stack */
+ bool stack = flags & AA_CHANGE_STACK;
int error = 0;
+ char *op;
u32 request;
if (!fqname || !*fqname) {
@@ -756,74 +1185,118 @@ int aa_change_profile(const char *fqname, bool onexec,
return -EINVAL;
}
- if (onexec) {
+ if (flags & AA_CHANGE_ONEXEC) {
request = AA_MAY_ONEXEC;
- op = OP_CHANGE_ONEXEC;
+ if (stack)
+ op = OP_STACK_ONEXEC;
+ else
+ op = OP_CHANGE_ONEXEC;
} else {
request = AA_MAY_CHANGE_PROFILE;
- op = OP_CHANGE_PROFILE;
+ if (stack)
+ op = OP_STACK;
+ else
+ op = OP_CHANGE_PROFILE;
}
- cred = get_current_cred();
- profile = aa_cred_profile(cred);
+ label = aa_get_current_label();
- /*
- * Fail explicitly requested domain transitions if no_new_privs
- * and not unconfined.
- * Domain transitions from unconfined are allowed even when
- * no_new_privs is set because this aways results in a reduction
- * of permissions.
- */
- if (task_no_new_privs(current) && !unconfined(profile)) {
- put_cred(cred);
- return -EPERM;
+ if (*fqname == '&') {
+ stack = true;
+ /* don't have label_parse() do stacking */
+ fqname++;
}
+ target = aa_label_parse(label, fqname, GFP_KERNEL, true, false);
+ if (IS_ERR(target)) {
+ struct aa_profile *tprofile;
- target = aa_fqlookupn_profile(profile, fqname, strlen(fqname));
- if (!target) {
- info = "profile not found";
- error = -ENOENT;
- if (permtest || !COMPLAIN_MODE(profile))
+ info = "label not found";
+ error = PTR_ERR(target);
+ target = NULL;
+ /*
+ * TODO: fixme using labels_profile is not right - do profile
+ * per complain profile
+ */
+ if ((flags & AA_CHANGE_TEST) ||
+ !COMPLAIN_MODE(labels_profile(label)))
goto audit;
/* released below */
- target = aa_new_null_profile(profile, false, fqname,
- GFP_KERNEL);
- if (!target) {
+ tprofile = aa_new_null_profile(labels_profile(label), false,
+ fqname, GFP_KERNEL);
+ if (!tprofile) {
info = "failed null profile create";
error = -ENOMEM;
goto audit;
}
+ target = &tprofile->label;
+ goto check;
}
- perms = change_profile_perms(profile, target->ns, target->base.hname,
- request, profile->file.start);
- if (!(perms.allow & request)) {
- error = -EACCES;
- goto audit;
- }
+ /*
+ * self directed transitions only apply to current policy ns
+ * TODO: currently requiring perms for stacking and straight change
+ * stacking doesn't strictly need this. Determine how much
+ * we want to loosen this restriction for stacking
+ *
+ * if (!stack) {
+ */
+ error = fn_for_each_in_ns(label, profile,
+ change_profile_perms_wrapper(op, auditname,
+ profile, target, stack,
+ request, &perms));
+ if (error)
+ /* auditing done in change_profile_perms_wrapper */
+ goto out;
+
+ /* } */
+check:
/* check if tracing task is allowed to trace target domain */
- error = may_change_ptraced_domain(target);
- if (error) {
- info = "ptrace prevents transition";
+ error = may_change_ptraced_domain(target, &info);
+ if (error && !fn_for_each_in_ns(label, profile,
+ COMPLAIN_MODE(profile)))
goto audit;
- }
- if (permtest)
- goto audit;
+ /* TODO: add permission check to allow this
+ * if ((flags & AA_CHANGE_ONEXEC) && !current_is_single_threaded()) {
+ * info = "not a single threaded task";
+ * error = -EACCES;
+ * goto audit;
+ * }
+ */
+ if (flags & AA_CHANGE_TEST)
+ goto out;
- if (onexec)
- error = aa_set_current_onexec(target);
- else
- error = aa_replace_current_profile(target);
+ if (!(flags & AA_CHANGE_ONEXEC)) {
+ /* only transition profiles in the current ns */
+ if (stack)
+ new = aa_label_merge(label, target, GFP_KERNEL);
+ else
+ new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
+ aa_get_label(target),
+ aa_get_label(&profile->label));
+ if (IS_ERR_OR_NULL(new)) {
+ info = "failed to build target label";
+ error = PTR_ERR(new);
+ new = NULL;
+ perms.allow = 0;
+ goto audit;
+ }
+ error = aa_replace_current_label(new);
+ } else
+ /* full transition will be built in exec path */
+ error = aa_set_current_onexec(target, stack);
audit:
- if (!permtest)
- error = aa_audit_file(profile, &perms, op, request, NULL,
- fqname, GLOBAL_ROOT_UID, info, error);
+ error = fn_for_each_in_ns(label, profile,
+ aa_audit_file(profile, &perms, op, request, auditname,
+ NULL, new ? new : target,
+ GLOBAL_ROOT_UID, info, error));
- aa_put_profile(target);
- put_cred(cred);
+out:
+ aa_put_label(new);
+ aa_put_label(target);
+ aa_put_label(label);
return error;
}
diff --git a/security/apparmor/file.c b/security/apparmor/file.c
index 750564c3ab71..3382518b87fa 100644
--- a/security/apparmor/file.c
+++ b/security/apparmor/file.c
@@ -12,15 +12,30 @@
* License.
*/
+#include <linux/tty.h>
+#include <linux/fdtable.h>
+#include <linux/file.h>
+
#include "include/apparmor.h"
#include "include/audit.h"
+#include "include/context.h"
#include "include/file.h"
#include "include/match.h"
#include "include/path.h"
#include "include/policy.h"
+#include "include/label.h"
-struct file_perms nullperms;
+static u32 map_mask_to_chr_mask(u32 mask)
+{
+ u32 m = mask & PERMS_CHRS_MASK;
+ if (mask & AA_MAY_GETATTR)
+ m |= MAY_READ;
+ if (mask & (AA_MAY_SETATTR | AA_MAY_CHMOD | AA_MAY_CHOWN))
+ m |= MAY_WRITE;
+
+ return m;
+}
/**
* audit_file_mask - convert mask to permission string
@@ -31,29 +46,7 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask)
{
char str[10];
- char *m = str;
-
- if (mask & AA_EXEC_MMAP)
- *m++ = 'm';
- if (mask & (MAY_READ | AA_MAY_META_READ))
- *m++ = 'r';
- if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD |
- AA_MAY_CHOWN))
- *m++ = 'w';
- else if (mask & MAY_APPEND)
- *m++ = 'a';
- if (mask & AA_MAY_CREATE)
- *m++ = 'c';
- if (mask & AA_MAY_DELETE)
- *m++ = 'd';
- if (mask & AA_MAY_LINK)
- *m++ = 'l';
- if (mask & AA_MAY_LOCK)
- *m++ = 'k';
- if (mask & MAY_EXEC)
- *m++ = 'x';
- *m = '\0';
-
+ aa_perm_mask_to_str(str, aa_file_perm_chrs, map_mask_to_chr_mask(mask));
audit_log_string(ab, str);
}
@@ -67,22 +60,26 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
struct common_audit_data *sa = va;
kuid_t fsuid = current_fsuid();
- if (aad(sa)->fs.request & AA_AUDIT_FILE_MASK) {
+ if (aad(sa)->request & AA_AUDIT_FILE_MASK) {
audit_log_format(ab, " requested_mask=");
- audit_file_mask(ab, aad(sa)->fs.request);
+ audit_file_mask(ab, aad(sa)->request);
}
- if (aad(sa)->fs.denied & AA_AUDIT_FILE_MASK) {
+ if (aad(sa)->denied & AA_AUDIT_FILE_MASK) {
audit_log_format(ab, " denied_mask=");
- audit_file_mask(ab, aad(sa)->fs.denied);
+ audit_file_mask(ab, aad(sa)->denied);
}
- if (aad(sa)->fs.request & AA_AUDIT_FILE_MASK) {
+ if (aad(sa)->request & AA_AUDIT_FILE_MASK) {
audit_log_format(ab, " fsuid=%d",
from_kuid(&init_user_ns, fsuid));
audit_log_format(ab, " ouid=%d",
from_kuid(&init_user_ns, aad(sa)->fs.ouid));
}
- if (aad(sa)->fs.target) {
+ if (aad(sa)->peer) {
+ audit_log_format(ab, " target=");
+ aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
+ FLAG_VIEW_SUBNS, GFP_ATOMIC);
+ } else if (aad(sa)->fs.target) {
audit_log_format(ab, " target=");
audit_log_untrustedstring(ab, aad(sa)->fs.target);
}
@@ -92,28 +89,30 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
* aa_audit_file - handle the auditing of file operations
* @profile: the profile being enforced (NOT NULL)
* @perms: the permissions computed for the request (NOT NULL)
- * @gfp: allocation flags
* @op: operation being mediated
* @request: permissions requested
* @name: name of object being mediated (MAYBE NULL)
* @target: name of target (MAYBE NULL)
+ * @tlabel: target label (MAY BE NULL)
* @ouid: object uid
* @info: extra information message (MAYBE NULL)
* @error: 0 if operation allowed else failure error code
*
* Returns: %0 or error on failure
*/
-int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
+int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
const char *op, u32 request, const char *name,
- const char *target, kuid_t ouid, const char *info, int error)
+ const char *target, struct aa_label *tlabel,
+ kuid_t ouid, const char *info, int error)
{
int type = AUDIT_APPARMOR_AUTO;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, op);
sa.u.tsk = NULL;
- aad(&sa)->fs.request = request;
+ aad(&sa)->request = request;
aad(&sa)->name = name;
aad(&sa)->fs.target = target;
+ aad(&sa)->peer = tlabel;
aad(&sa)->fs.ouid = ouid;
aad(&sa)->info = info;
aad(&sa)->error = error;
@@ -126,34 +125,67 @@ int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
mask = 0xffff;
/* mask off perms that are not being force audited */
- aad(&sa)->fs.request &= mask;
+ aad(&sa)->request &= mask;
- if (likely(!aad(&sa)->fs.request))
+ if (likely(!aad(&sa)->request))
return 0;
type = AUDIT_APPARMOR_AUDIT;
} else {
/* only report permissions that were denied */
- aad(&sa)->fs.request = aad(&sa)->fs.request & ~perms->allow;
- AA_BUG(!aad(&sa)->fs.request);
+ aad(&sa)->request = aad(&sa)->request & ~perms->allow;
+ AA_BUG(!aad(&sa)->request);
- if (aad(&sa)->fs.request & perms->kill)
+ if (aad(&sa)->request & perms->kill)
type = AUDIT_APPARMOR_KILL;
/* quiet known rejects, assumes quiet and kill do not overlap */
- if ((aad(&sa)->fs.request & perms->quiet) &&
+ if ((aad(&sa)->request & perms->quiet) &&
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
AUDIT_MODE(profile) != AUDIT_ALL)
- aad(&sa)->fs.request &= ~perms->quiet;
+ aad(&sa)->request &= ~perms->quiet;
- if (!aad(&sa)->fs.request)
- return COMPLAIN_MODE(profile) ? 0 : aad(&sa)->error;
+ if (!aad(&sa)->request)
+ return aad(&sa)->error;
}
- aad(&sa)->fs.denied = aad(&sa)->fs.request & ~perms->allow;
+ aad(&sa)->denied = aad(&sa)->request & ~perms->allow;
return aa_audit(type, profile, &sa, file_audit_cb);
}
/**
+ * is_deleted - test if a file has been completely unlinked
+ * @dentry: dentry of file to test for deletion (NOT NULL)
+ *
+ * Returns: %1 if deleted else %0
+ */
+static inline bool is_deleted(struct dentry *dentry)
+{
+ if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0)
+ return 1;
+ return 0;
+}
+
+static int path_name(const char *op, struct aa_label *label,
+ const struct path *path, int flags, char *buffer,
+ const char **name, struct path_cond *cond, u32 request)
+{
+ struct aa_profile *profile;
+ const char *info = NULL;
+ int error;
+
+ error = aa_path_name(path, flags, buffer, name, &info,
+ labels_profile(label)->disconnected);
+ if (error) {
+ fn_for_each_confined(label, profile,
+ aa_audit_file(profile, &nullperms, op, request, *name,
+ NULL, NULL, cond->uid, info, error));
+ return error;
+ }
+
+ return 0;
+}
+
+/**
* map_old_perms - map old file perms layout to the new layout
* @old: permission set in old mapping
*
@@ -163,10 +195,10 @@ static u32 map_old_perms(u32 old)
{
u32 new = old & 0xf;
if (old & MAY_READ)
- new |= AA_MAY_META_READ;
+ new |= AA_MAY_GETATTR | AA_MAY_OPEN;
if (old & MAY_WRITE)
- new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE |
- AA_MAY_CHMOD | AA_MAY_CHOWN;
+ new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
+ AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN;
if (old & 0x10)
new |= AA_MAY_LINK;
/* the old mapping lock and link_subset flags where overlaid
@@ -181,7 +213,7 @@ static u32 map_old_perms(u32 old)
}
/**
- * compute_perms - convert dfa compressed perms to internal perms
+ * aa_compute_fperms - convert dfa compressed perms to internal perms
* @dfa: dfa to compute perms for (NOT NULL)
* @state: state in dfa
* @cond: conditions to consider (NOT NULL)
@@ -191,17 +223,21 @@ static u32 map_old_perms(u32 old)
*
* Returns: computed permission set
*/
-static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
- struct path_cond *cond)
+struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state,
+ struct path_cond *cond)
{
- struct file_perms perms;
+ struct aa_perms perms;
/* FIXME: change over to new dfa format
* currently file perms are encoded in the dfa, new format
* splits the permissions from the dfa. This mapping can be
* done at profile load
*/
- perms.kill = 0;
+ perms.deny = 0;
+ perms.kill = perms.stop = 0;
+ perms.complain = perms.cond = 0;
+ perms.hide = 0;
+ perms.prompt = 0;
if (uid_eq(current_fsuid(), cond->uid)) {
perms.allow = map_old_perms(dfa_user_allow(dfa, state));
@@ -214,7 +250,7 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
perms.xindex = dfa_other_xindex(dfa, state);
}
- perms.allow |= AA_MAY_META_READ;
+ perms.allow |= AA_MAY_GETATTR;
/* change_profile wasn't determined by ownership in old mapping */
if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
@@ -237,37 +273,55 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
*/
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
const char *name, struct path_cond *cond,
- struct file_perms *perms)
+ struct aa_perms *perms)
{
unsigned int state;
- if (!dfa) {
- *perms = nullperms;
- return DFA_NOMATCH;
- }
-
state = aa_dfa_match(dfa, start, name);
- *perms = compute_perms(dfa, state, cond);
+ *perms = aa_compute_fperms(dfa, state, cond);
return state;
}
-/**
- * is_deleted - test if a file has been completely unlinked
- * @dentry: dentry of file to test for deletion (NOT NULL)
- *
- * Returns: %1 if deleted else %0
- */
-static inline bool is_deleted(struct dentry *dentry)
+int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name,
+ u32 request, struct path_cond *cond, int flags,
+ struct aa_perms *perms)
{
- if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0)
- return 1;
- return 0;
+ int e = 0;
+
+ if (profile_unconfined(profile))
+ return 0;
+ aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms);
+ if (request & ~perms->allow)
+ e = -EACCES;
+ return aa_audit_file(profile, perms, op, request, name, NULL, NULL,
+ cond->uid, NULL, e);
+}
+
+
+static int profile_path_perm(const char *op, struct aa_profile *profile,
+ const struct path *path, char *buffer, u32 request,
+ struct path_cond *cond, int flags,
+ struct aa_perms *perms)
+{
+ const char *name;
+ int error;
+
+ if (profile_unconfined(profile))
+ return 0;
+
+ error = path_name(op, &profile->label, path,
+ flags | profile->path_flags, buffer, &name, cond,
+ request);
+ if (error)
+ return error;
+ return __aa_path_perm(op, profile, name, request, cond, flags,
+ perms);
}
/**
* aa_path_perm - do permissions check & audit for @path
* @op: operation being checked
- * @profile: profile being enforced (NOT NULL)
+ * @label: profile being enforced (NOT NULL)
* @path: path to check permissions of (NOT NULL)
* @flags: any additional path flags beyond what the profile specifies
* @request: requested permissions
@@ -275,35 +329,23 @@ static inline bool is_deleted(struct dentry *dentry)
*
* Returns: %0 else error if access denied or other error
*/
-int aa_path_perm(const char *op, struct aa_profile *profile,
+int aa_path_perm(const char *op, struct aa_label *label,
const struct path *path, int flags, u32 request,
struct path_cond *cond)
{
+ struct aa_perms perms = {};
+ struct aa_profile *profile;
char *buffer = NULL;
- struct file_perms perms = {};
- const char *name, *info = NULL;
int error;
- flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
- error = aa_path_name(path, flags, &buffer, &name, &info);
- if (error) {
- if (error == -ENOENT && is_deleted(path->dentry)) {
- /* Access to open files that are deleted are
- * give a pass (implicit delegation)
- */
- error = 0;
- info = NULL;
- perms.allow = request;
- }
- } else {
- aa_str_perms(profile->file.dfa, profile->file.start, name, cond,
- &perms);
- if (request & ~perms.allow)
- error = -EACCES;
- }
- error = aa_audit_file(profile, &perms, op, request, name, NULL,
- cond->uid, info, error);
- kfree(buffer);
+ flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR :
+ 0);
+ get_buffers(buffer);
+ error = fn_for_each_confined(label, profile,
+ profile_path_perm(op, profile, path, buffer, request,
+ cond, flags, &perms));
+
+ put_buffers(buffer);
return error;
}
@@ -328,65 +370,40 @@ static inline bool xindex_is_subset(u32 link, u32 target)
return 1;
}
-/**
- * aa_path_link - Handle hard link permission check
- * @profile: the profile being enforced (NOT NULL)
- * @old_dentry: the target dentry (NOT NULL)
- * @new_dir: directory the new link will be created in (NOT NULL)
- * @new_dentry: the link being created (NOT NULL)
- *
- * Handle the permission test for a link & target pair. Permission
- * is encoded as a pair where the link permission is determined
- * first, and if allowed, the target is tested. The target test
- * is done from the point of the link match (not start of DFA)
- * making the target permission dependent on the link permission match.
- *
- * The subset test if required forces that permissions granted
- * on link are a subset of the permission granted to target.
- *
- * Returns: %0 if allowed else error
- */
-int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
- const struct path *new_dir, struct dentry *new_dentry)
+static int profile_path_link(struct aa_profile *profile,
+ const struct path *link, char *buffer,
+ const struct path *target, char *buffer2,
+ struct path_cond *cond)
{
- struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry };
- struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry };
- struct path_cond cond = {
- d_backing_inode(old_dentry)->i_uid,
- d_backing_inode(old_dentry)->i_mode
- };
- char *buffer = NULL, *buffer2 = NULL;
- const char *lname, *tname = NULL, *info = NULL;
- struct file_perms lperms, perms;
+ const char *lname, *tname = NULL;
+ struct aa_perms lperms = {}, perms;
+ const char *info = NULL;
u32 request = AA_MAY_LINK;
unsigned int state;
int error;
- lperms = nullperms;
-
- /* buffer freed below, lname is pointer in buffer */
- error = aa_path_name(&link, profile->path_flags, &buffer, &lname,
- &info);
+ error = path_name(OP_LINK, &profile->label, link, profile->path_flags,
+ buffer, &lname, cond, AA_MAY_LINK);
if (error)
goto audit;
/* buffer2 freed below, tname is pointer in buffer2 */
- error = aa_path_name(&target, profile->path_flags, &buffer2, &tname,
- &info);
+ error = path_name(OP_LINK, &profile->label, target, profile->path_flags,
+ buffer2, &tname, cond, AA_MAY_LINK);
if (error)
goto audit;
error = -EACCES;
/* aa_str_perms - handles the case of the dfa being NULL */
state = aa_str_perms(profile->file.dfa, profile->file.start, lname,
- &cond, &lperms);
+ cond, &lperms);
if (!(lperms.allow & AA_MAY_LINK))
goto audit;
/* test to see if target can be paired with link */
state = aa_dfa_null_transition(profile->file.dfa, state);
- aa_str_perms(profile->file.dfa, state, tname, &cond, &perms);
+ aa_str_perms(profile->file.dfa, state, tname, cond, &perms);
/* force audit/quiet masks for link are stored in the second entry
* in the link pair.
@@ -397,6 +414,7 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
if (!(perms.allow & AA_MAY_LINK)) {
info = "target restricted";
+ lperms = perms;
goto audit;
}
@@ -404,10 +422,10 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
if (!(perms.allow & AA_LINK_SUBSET))
goto done_tests;
- /* Do link perm subset test requiring allowed permission on link are a
- * subset of the allowed permissions on target.
+ /* Do link perm subset test requiring allowed permission on link are
+ * a subset of the allowed permissions on target.
*/
- aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond,
+ aa_str_perms(profile->file.dfa, profile->file.start, tname, cond,
&perms);
/* AA_MAY_LINK is not considered in the subset test */
@@ -429,10 +447,121 @@ done_tests:
error = 0;
audit:
- error = aa_audit_file(profile, &lperms, OP_LINK, request,
- lname, tname, cond.uid, info, error);
- kfree(buffer);
- kfree(buffer2);
+ return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname,
+ NULL, cond->uid, info, error);
+}
+
+/**
+ * aa_path_link - Handle hard link permission check
+ * @label: the label being enforced (NOT NULL)
+ * @old_dentry: the target dentry (NOT NULL)
+ * @new_dir: directory the new link will be created in (NOT NULL)
+ * @new_dentry: the link being created (NOT NULL)
+ *
+ * Handle the permission test for a link & target pair. Permission
+ * is encoded as a pair where the link permission is determined
+ * first, and if allowed, the target is tested. The target test
+ * is done from the point of the link match (not start of DFA)
+ * making the target permission dependent on the link permission match.
+ *
+ * The subset test if required forces that permissions granted
+ * on link are a subset of the permission granted to target.
+ *
+ * Returns: %0 if allowed else error
+ */
+int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
+ const struct path *new_dir, struct dentry *new_dentry)
+{
+ struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry };
+ struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry };
+ struct path_cond cond = {
+ d_backing_inode(old_dentry)->i_uid,
+ d_backing_inode(old_dentry)->i_mode
+ };
+ char *buffer = NULL, *buffer2 = NULL;
+ struct aa_profile *profile;
+ int error;
+
+ /* buffer freed below, lname is pointer in buffer */
+ get_buffers(buffer, buffer2);
+ error = fn_for_each_confined(label, profile,
+ profile_path_link(profile, &link, buffer, &target,
+ buffer2, &cond));
+ put_buffers(buffer, buffer2);
+
+ return error;
+}
+
+static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label,
+ u32 request)
+{
+ struct aa_label *l, *old;
+
+ /* update caching of label on file_ctx */
+ spin_lock(&fctx->lock);
+ old = rcu_dereference_protected(fctx->label,
+ spin_is_locked(&fctx->lock));
+ l = aa_label_merge(old, label, GFP_ATOMIC);
+ if (l) {
+ if (l != old) {
+ rcu_assign_pointer(fctx->label, l);
+ aa_put_label(old);
+ } else
+ aa_put_label(l);
+ fctx->allow |= request;
+ }
+ spin_unlock(&fctx->lock);
+}
+
+static int __file_path_perm(const char *op, struct aa_label *label,
+ struct aa_label *flabel, struct file *file,
+ u32 request, u32 denied)
+{
+ struct aa_profile *profile;
+ struct aa_perms perms = {};
+ struct path_cond cond = {
+ .uid = file_inode(file)->i_uid,
+ .mode = file_inode(file)->i_mode
+ };
+ char *buffer;
+ int flags, error;
+
+ /* revalidation due to label out of date. No revocation at this time */
+ if (!denied && aa_label_is_subset(flabel, label))
+ /* TODO: check for revocation on stale profiles */
+ return 0;
+
+ flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0);
+ get_buffers(buffer);
+
+ /* check every profile in task label not in current cache */
+ error = fn_for_each_not_in_set(flabel, label, profile,
+ profile_path_perm(op, profile, &file->f_path, buffer,
+ request, &cond, flags, &perms));
+ if (denied && !error) {
+ /*
+ * check every profile in file label that was not tested
+ * in the initial check above.
+ *
+ * TODO: cache full perms so this only happens because of
+ * conditionals
+ * TODO: don't audit here
+ */
+ if (label == flabel)
+ error = fn_for_each(label, profile,
+ profile_path_perm(op, profile, &file->f_path,
+ buffer, request, &cond, flags,
+ &perms));
+ else
+ error = fn_for_each_not_in_set(label, flabel, profile,
+ profile_path_perm(op, profile, &file->f_path,
+ buffer, request, &cond, flags,
+ &perms));
+ }
+ if (!error)
+ update_file_ctx(file_ctx(file), label, request);
+
+ put_buffers(buffer);
return error;
}
@@ -440,20 +569,114 @@ audit:
/**
* aa_file_perm - do permission revalidation check & audit for @file
* @op: operation being checked
- * @profile: profile being enforced (NOT NULL)
+ * @label: label being enforced (NOT NULL)
* @file: file to revalidate access permissions on (NOT NULL)
* @request: requested permissions
*
* Returns: %0 if access allowed else error
*/
-int aa_file_perm(const char *op, struct aa_profile *profile, struct file *file,
+int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
u32 request)
{
- struct path_cond cond = {
- .uid = file_inode(file)->i_uid,
- .mode = file_inode(file)->i_mode
- };
+ struct aa_file_ctx *fctx;
+ struct aa_label *flabel;
+ u32 denied;
+ int error = 0;
+
+ AA_BUG(!label);
+ AA_BUG(!file);
+
+ fctx = file_ctx(file);
+
+ rcu_read_lock();
+ flabel = rcu_dereference(fctx->label);
+ AA_BUG(!flabel);
+
+ /* revalidate access, if task is unconfined, or the cached cred
+ * doesn't match or if the request is for more permissions than
+ * was granted.
+ *
+ * Note: the test for !unconfined(flabel) is to handle file
+ * delegation from unconfined tasks
+ */
+ denied = request & ~fctx->allow;
+ if (unconfined(label) || unconfined(flabel) ||
+ (!denied && aa_label_is_subset(flabel, label)))
+ goto done;
+
+ /* TODO: label cross check */
+
+ if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry))
+ error = __file_path_perm(op, label, flabel, file, request,
+ denied);
+
+done:
+ rcu_read_unlock();
+
+ return error;
+}
+
+static void revalidate_tty(struct aa_label *label)
+{
+ struct tty_struct *tty;
+ int drop_tty = 0;
+
+ tty = get_current_tty();
+ if (!tty)
+ return;
+
+ spin_lock(&tty->files_lock);
+ if (!list_empty(&tty->tty_files)) {
+ struct tty_file_private *file_priv;
+ struct file *file;
+ /* TODO: Revalidate access to controlling tty. */
+ file_priv = list_first_entry(&tty->tty_files,
+ struct tty_file_private, list);
+ file = file_priv->file;
+
+ if (aa_file_perm(OP_INHERIT, label, file, MAY_READ | MAY_WRITE))
+ drop_tty = 1;
+ }
+ spin_unlock(&tty->files_lock);
+ tty_kref_put(tty);
- return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED,
- request, &cond);
+ if (drop_tty)
+ no_tty();
+}
+
+static int match_file(const void *p, struct file *file, unsigned int fd)
+{
+ struct aa_label *label = (struct aa_label *)p;
+
+ if (aa_file_perm(OP_INHERIT, label, file, aa_map_file_to_perms(file)))
+ return fd + 1;
+ return 0;
+}
+
+
+/* based on selinux's flush_unauthorized_files */
+void aa_inherit_files(const struct cred *cred, struct files_struct *files)
+{
+ struct aa_label *label = aa_get_newest_cred_label(cred);
+ struct file *devnull = NULL;
+ unsigned int n;
+
+ revalidate_tty(label);
+
+ /* Revalidate access to inherited open files. */
+ n = iterate_fd(files, 0, match_file, label);
+ if (!n) /* none found? */
+ goto out;
+
+ devnull = dentry_open(&aa_null, O_RDWR, cred);
+ if (IS_ERR(devnull))
+ devnull = NULL;
+ /* replace all the matching ones with this */
+ do {
+ replace_fd(n - 1, devnull, 0);
+ } while ((n = iterate_fd(files, n, match_file, label)) != 0);
+ if (devnull)
+ fput(devnull);
+out:
+ aa_put_label(label);
}
diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h
index 1750cc0721c1..aaf893f4e4f5 100644
--- a/security/apparmor/include/apparmor.h
+++ b/security/apparmor/include/apparmor.h
@@ -4,7 +4,7 @@
* This file contains AppArmor basic global
*
* Copyright (C) 1998-2008 Novell/SUSE
- * Copyright 2009-2010 Canonical Ltd.
+ * Copyright 2009-2017 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -27,8 +27,10 @@
#define AA_CLASS_NET 4
#define AA_CLASS_RLIMITS 5
#define AA_CLASS_DOMAIN 6
+#define AA_CLASS_PTRACE 9
+#define AA_CLASS_LABEL 16
-#define AA_CLASS_LAST AA_CLASS_DOMAIN
+#define AA_CLASS_LAST AA_CLASS_LABEL
/* Control parameters settable through module/boot flags */
extern enum audit_mode aa_g_audit;
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
index 120a798b5bb0..bd689114bf93 100644
--- a/security/apparmor/include/apparmorfs.h
+++ b/security/apparmor/include/apparmorfs.h
@@ -17,49 +17,49 @@
extern struct path aa_null;
-enum aa_fs_type {
- AA_FS_TYPE_BOOLEAN,
- AA_FS_TYPE_STRING,
- AA_FS_TYPE_U64,
- AA_FS_TYPE_FOPS,
- AA_FS_TYPE_DIR,
+enum aa_sfs_type {
+ AA_SFS_TYPE_BOOLEAN,
+ AA_SFS_TYPE_STRING,
+ AA_SFS_TYPE_U64,
+ AA_SFS_TYPE_FOPS,
+ AA_SFS_TYPE_DIR,
};
-struct aa_fs_entry;
+struct aa_sfs_entry;
-struct aa_fs_entry {
+struct aa_sfs_entry {
const char *name;
struct dentry *dentry;
umode_t mode;
- enum aa_fs_type v_type;
+ enum aa_sfs_type v_type;
union {
bool boolean;
char *string;
unsigned long u64;
- struct aa_fs_entry *files;
+ struct aa_sfs_entry *files;
} v;
const struct file_operations *file_ops;
};
-extern const struct file_operations aa_fs_seq_file_ops;
+extern const struct file_operations aa_sfs_seq_file_ops;
-#define AA_FS_FILE_BOOLEAN(_name, _value) \
+#define AA_SFS_FILE_BOOLEAN(_name, _value) \
{ .name = (_name), .mode = 0444, \
- .v_type = AA_FS_TYPE_BOOLEAN, .v.boolean = (_value), \
- .file_ops = &aa_fs_seq_file_ops }
-#define AA_FS_FILE_STRING(_name, _value) \
+ .v_type = AA_SFS_TYPE_BOOLEAN, .v.boolean = (_value), \
+ .file_ops = &aa_sfs_seq_file_ops }
+#define AA_SFS_FILE_STRING(_name, _value) \
{ .name = (_name), .mode = 0444, \
- .v_type = AA_FS_TYPE_STRING, .v.string = (_value), \
- .file_ops = &aa_fs_seq_file_ops }
-#define AA_FS_FILE_U64(_name, _value) \
+ .v_type = AA_SFS_TYPE_STRING, .v.string = (_value), \
+ .file_ops = &aa_sfs_seq_file_ops }
+#define AA_SFS_FILE_U64(_name, _value) \
{ .name = (_name), .mode = 0444, \
- .v_type = AA_FS_TYPE_U64, .v.u64 = (_value), \
- .file_ops = &aa_fs_seq_file_ops }
-#define AA_FS_FILE_FOPS(_name, _mode, _fops) \
- { .name = (_name), .v_type = AA_FS_TYPE_FOPS, \
+ .v_type = AA_SFS_TYPE_U64, .v.u64 = (_value), \
+ .file_ops = &aa_sfs_seq_file_ops }
+#define AA_SFS_FILE_FOPS(_name, _mode, _fops) \
+ { .name = (_name), .v_type = AA_SFS_TYPE_FOPS, \
.mode = (_mode), .file_ops = (_fops) }
-#define AA_FS_DIR(_name, _value) \
- { .name = (_name), .v_type = AA_FS_TYPE_DIR, .v.files = (_value) }
+#define AA_SFS_DIR(_name, _value) \
+ { .name = (_name), .v_type = AA_SFS_TYPE_DIR, .v.files = (_value) }
extern void __init aa_destroy_aafs(void);
@@ -74,6 +74,7 @@ enum aafs_ns_type {
AAFS_NS_LOAD,
AAFS_NS_REPLACE,
AAFS_NS_REMOVE,
+ AAFS_NS_REVISION,
AAFS_NS_COUNT,
AAFS_NS_MAX_COUNT,
AAFS_NS_SIZE,
@@ -102,16 +103,22 @@ enum aafs_prof_type {
#define ns_subload(X) ((X)->dents[AAFS_NS_LOAD])
#define ns_subreplace(X) ((X)->dents[AAFS_NS_REPLACE])
#define ns_subremove(X) ((X)->dents[AAFS_NS_REMOVE])
+#define ns_subrevision(X) ((X)->dents[AAFS_NS_REVISION])
#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])
-void __aa_fs_profile_rmdir(struct aa_profile *profile);
-void __aa_fs_profile_migrate_dents(struct aa_profile *old,
+void __aa_bump_ns_revision(struct aa_ns *ns);
+void __aafs_profile_rmdir(struct aa_profile *profile);
+void __aafs_profile_migrate_dents(struct aa_profile *old,
struct aa_profile *new);
-int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent);
-void __aa_fs_ns_rmdir(struct aa_ns *ns);
-int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent,
- const char *name);
+int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent);
+void __aafs_ns_rmdir(struct aa_ns *ns);
+int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name,
+ struct dentry *dent);
+
+struct aa_loaddata;
+void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata);
+int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata);
#endif /* __AA_APPARMORFS_H */
diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h
index fdc4774318ba..c68839a44351 100644
--- a/security/apparmor/include/audit.h
+++ b/security/apparmor/include/audit.h
@@ -22,8 +22,7 @@
#include <linux/slab.h>
#include "file.h"
-
-struct aa_profile;
+#include "label.h"
extern const char *const audit_mode_names[];
#define AUDIT_MAX_INDEX 5
@@ -65,10 +64,12 @@ enum audit_type {
#define OP_GETATTR "getattr"
#define OP_OPEN "open"
+#define OP_FRECEIVE "file_receive"
#define OP_FPERM "file_perm"
#define OP_FLOCK "file_lock"
#define OP_FMMAP "file_mmap"
#define OP_FMPROT "file_mprotect"
+#define OP_INHERIT "file_inherit"
#define OP_CREATE "create"
#define OP_POST_CREATE "post_create"
@@ -91,6 +92,8 @@ enum audit_type {
#define OP_CHANGE_HAT "change_hat"
#define OP_CHANGE_PROFILE "change_profile"
#define OP_CHANGE_ONEXEC "change_onexec"
+#define OP_STACK "stack"
+#define OP_STACK_ONEXEC "stack_onexec"
#define OP_SETPROCATTR "setprocattr"
#define OP_SETRLIMIT "setrlimit"
@@ -102,19 +105,19 @@ enum audit_type {
struct apparmor_audit_data {
int error;
- const char *op;
int type;
- void *profile;
+ const char *op;
+ struct aa_label *label;
const char *name;
const char *info;
+ u32 request;
+ u32 denied;
union {
/* these entries require a custom callback fn */
struct {
- struct aa_profile *peer;
+ struct aa_label *peer;
struct {
const char *target;
- u32 request;
- u32 denied;
kuid_t ouid;
} fs;
};
diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h
index fc3fa381d850..e0304e2aeb7f 100644
--- a/security/apparmor/include/capability.h
+++ b/security/apparmor/include/capability.h
@@ -19,11 +19,12 @@
#include "apparmorfs.h"
-struct aa_profile;
+struct aa_label;
/* aa_caps - confinement data for capabilities
* @allowed: capabilities mask
* @audit: caps that are to be audited
+ * @denied: caps that are explicitly denied
* @quiet: caps that should not be audited
* @kill: caps that when requested will result in the task being killed
* @extended: caps that are subject finer grained mediation
@@ -31,14 +32,15 @@ struct aa_profile;
struct aa_caps {
kernel_cap_t allow;
kernel_cap_t audit;
+ kernel_cap_t denied;
kernel_cap_t quiet;
kernel_cap_t kill;
kernel_cap_t extended;
};
-extern struct aa_fs_entry aa_fs_entry_caps[];
+extern struct aa_sfs_entry aa_sfs_entry_caps[];
-int aa_capable(struct aa_profile *profile, int cap, int audit);
+int aa_capable(struct aa_label *label, int cap, int audit);
static inline void aa_free_cap_rules(struct aa_caps *caps)
{
diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h
index 5b18fedab4c8..6ae07e9aaa17 100644
--- a/security/apparmor/include/context.h
+++ b/security/apparmor/include/context.h
@@ -19,60 +19,28 @@
#include <linux/slab.h>
#include <linux/sched.h>
-#include "policy.h"
+#include "label.h"
#include "policy_ns.h"
#define cred_ctx(X) ((X)->security)
#define current_ctx() cred_ctx(current_cred())
-/* struct aa_file_ctx - the AppArmor context the file was opened in
- * @perms: the permission the file was opened with
- *
- * The file_ctx could currently be directly stored in file->f_security
- * as the profile reference is now stored in the f_cred. However the
- * ctx struct will expand in the future so we keep the struct.
- */
-struct aa_file_ctx {
- u16 allow;
-};
-
-/**
- * aa_alloc_file_context - allocate file_ctx
- * @gfp: gfp flags for allocation
- *
- * Returns: file_ctx or NULL on failure
- */
-static inline struct aa_file_ctx *aa_alloc_file_context(gfp_t gfp)
-{
- return kzalloc(sizeof(struct aa_file_ctx), gfp);
-}
-
-/**
- * aa_free_file_context - free a file_ctx
- * @ctx: file_ctx to free (MAYBE_NULL)
- */
-static inline void aa_free_file_context(struct aa_file_ctx *ctx)
-{
- if (ctx)
- kzfree(ctx);
-}
-
/**
* struct aa_task_ctx - primary label for confined tasks
- * @profile: the current profile (NOT NULL)
- * @exec: profile to transition to on next exec (MAYBE NULL)
- * @previous: profile the task may return to (MAYBE NULL)
- * @token: magic value the task must know for returning to @previous_profile
+ * @label: the current label (NOT NULL)
+ * @exec: label to transition to on next exec (MAYBE NULL)
+ * @previous: label the task may return to (MAYBE NULL)
+ * @token: magic value the task must know for returning to @previous
*
- * Contains the task's current profile (which could change due to
+ * Contains the task's current label (which could change due to
* change_hat). Plus the hat_magic needed during change_hat.
*
* TODO: make so a task can be confined by a stack of contexts
*/
struct aa_task_ctx {
- struct aa_profile *profile;
- struct aa_profile *onexec;
- struct aa_profile *previous;
+ struct aa_label *label;
+ struct aa_label *onexec;
+ struct aa_label *previous;
u64 token;
};
@@ -80,40 +48,51 @@ struct aa_task_ctx *aa_alloc_task_context(gfp_t flags);
void aa_free_task_context(struct aa_task_ctx *ctx);
void aa_dup_task_context(struct aa_task_ctx *new,
const struct aa_task_ctx *old);
-int aa_replace_current_profile(struct aa_profile *profile);
-int aa_set_current_onexec(struct aa_profile *profile);
-int aa_set_current_hat(struct aa_profile *profile, u64 token);
-int aa_restore_previous_profile(u64 cookie);
-struct aa_profile *aa_get_task_profile(struct task_struct *task);
+int aa_replace_current_label(struct aa_label *label);
+int aa_set_current_onexec(struct aa_label *label, bool stack);
+int aa_set_current_hat(struct aa_label *label, u64 token);
+int aa_restore_previous_label(u64 cookie);
+struct aa_label *aa_get_task_label(struct task_struct *task);
/**
- * aa_cred_profile - obtain cred's profiles
- * @cred: cred to obtain profiles from (NOT NULL)
+ * aa_cred_raw_label - obtain cred's label
+ * @cred: cred to obtain label from (NOT NULL)
*
- * Returns: confining profile
+ * Returns: confining label
*
* does NOT increment reference count
*/
-static inline struct aa_profile *aa_cred_profile(const struct cred *cred)
+static inline struct aa_label *aa_cred_raw_label(const struct cred *cred)
{
struct aa_task_ctx *ctx = cred_ctx(cred);
- AA_BUG(!ctx || !ctx->profile);
- return ctx->profile;
+ AA_BUG(!ctx || !ctx->label);
+ return ctx->label;
+}
+
+/**
+ * aa_get_newest_cred_label - obtain the newest label on a cred
+ * @cred: cred to obtain label from (NOT NULL)
+ *
+ * Returns: newest version of confining label
+ */
+static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred)
+{
+ return aa_get_newest_label(aa_cred_raw_label(cred));
}
/**
- * __aa_task_profile - retrieve another task's profile
+ * __aa_task_raw_label - retrieve another task's label
* @task: task to query (NOT NULL)
*
- * Returns: @task's profile without incrementing its ref count
+ * Returns: @task's label without incrementing its ref count
*
* If @task != current needs to be called in RCU safe critical section
*/
-static inline struct aa_profile *__aa_task_profile(struct task_struct *task)
+static inline struct aa_label *__aa_task_raw_label(struct task_struct *task)
{
- return aa_cred_profile(__task_cred(task));
+ return aa_cred_raw_label(__task_cred(task));
}
/**
@@ -124,50 +103,114 @@ static inline struct aa_profile *__aa_task_profile(struct task_struct *task)
*/
static inline bool __aa_task_is_confined(struct task_struct *task)
{
- return !unconfined(__aa_task_profile(task));
+ return !unconfined(__aa_task_raw_label(task));
}
/**
- * __aa_current_profile - find the current tasks confining profile
+ * aa_current_raw_label - find the current tasks confining label
*
- * Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
+ * Returns: up to date confining label or the ns unconfined label (NOT NULL)
*
* This fn will not update the tasks cred to the most up to date version
- * of the profile so it is safe to call when inside of locks.
+ * of the label so it is safe to call when inside of locks.
*/
-static inline struct aa_profile *__aa_current_profile(void)
+static inline struct aa_label *aa_current_raw_label(void)
{
- return aa_cred_profile(current_cred());
+ return aa_cred_raw_label(current_cred());
}
/**
- * aa_current_profile - find the current tasks confining profile and do updates
+ * aa_get_current_label - get the newest version of the current tasks label
+ *
+ * Returns: newest version of confining label (NOT NULL)
*
- * Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
+ * This fn will not update the tasks cred, so it is safe inside of locks
*
- * This fn will update the tasks cred structure if the profile has been
- * replaced. Not safe to call inside locks
+ * The returned reference must be put with aa_put_label()
*/
-static inline struct aa_profile *aa_current_profile(void)
+static inline struct aa_label *aa_get_current_label(void)
{
- const struct aa_task_ctx *ctx = current_ctx();
- struct aa_profile *profile;
+ struct aa_label *l = aa_current_raw_label();
- AA_BUG(!ctx || !ctx->profile);
+ if (label_is_stale(l))
+ return aa_get_newest_label(l);
+ return aa_get_label(l);
+}
+
+#define __end_current_label_crit_section(X) end_current_label_crit_section(X)
- if (profile_is_stale(ctx->profile)) {
- profile = aa_get_newest_profile(ctx->profile);
- aa_replace_current_profile(profile);
- aa_put_profile(profile);
- ctx = current_ctx();
+/**
+ * end_label_crit_section - put a reference found with begin_current_label..
+ * @label: label reference to put
+ *
+ * Should only be used with a reference obtained with
+ * begin_current_label_crit_section and never used in situations where the
+ * task cred may be updated
+ */
+static inline void end_current_label_crit_section(struct aa_label *label)
+{
+ if (label != aa_current_raw_label())
+ aa_put_label(label);
+}
+
+/**
+ * __begin_current_label_crit_section - current's confining label
+ *
+ * Returns: up to date confining label or the ns unconfined label (NOT NULL)
+ *
+ * safe to call inside locks
+ *
+ * The returned reference must be put with __end_current_label_crit_section()
+ * This must NOT be used if the task cred could be updated within the
+ * critical section between __begin_current_label_crit_section() ..
+ * __end_current_label_crit_section()
+ */
+static inline struct aa_label *__begin_current_label_crit_section(void)
+{
+ struct aa_label *label = aa_current_raw_label();
+
+ if (label_is_stale(label))
+ label = aa_get_newest_label(label);
+
+ return label;
+}
+
+/**
+ * begin_current_label_crit_section - current's confining label and update it
+ *
+ * Returns: up to date confining label or the ns unconfined label (NOT NULL)
+ *
+ * Not safe to call inside locks
+ *
+ * The returned reference must be put with end_current_label_crit_section()
+ * This must NOT be used if the task cred could be updated within the
+ * critical section between begin_current_label_crit_section() ..
+ * end_current_label_crit_section()
+ */
+static inline struct aa_label *begin_current_label_crit_section(void)
+{
+ struct aa_label *label = aa_current_raw_label();
+
+ if (label_is_stale(label)) {
+ label = aa_get_newest_label(label);
+ if (aa_replace_current_label(label) == 0)
+ /* task cred will keep the reference */
+ aa_put_label(label);
}
- return ctx->profile;
+ return label;
}
static inline struct aa_ns *aa_get_current_ns(void)
{
- return aa_get_ns(__aa_current_profile()->ns);
+ struct aa_label *label;
+ struct aa_ns *ns;
+
+ label = __begin_current_label_crit_section();
+ ns = aa_get_ns(labels_ns(label));
+ __end_current_label_crit_section(label);
+
+ return ns;
}
/**
@@ -176,8 +219,8 @@ static inline struct aa_ns *aa_get_current_ns(void)
*/
static inline void aa_clear_task_ctx_trans(struct aa_task_ctx *ctx)
{
- aa_put_profile(ctx->previous);
- aa_put_profile(ctx->onexec);
+ aa_put_label(ctx->previous);
+ aa_put_label(ctx->onexec);
ctx->previous = NULL;
ctx->onexec = NULL;
ctx->token = 0;
diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h
index 30544729878a..bab5810b6e9a 100644
--- a/security/apparmor/include/domain.h
+++ b/security/apparmor/include/domain.h
@@ -23,14 +23,17 @@ struct aa_domain {
char **table;
};
+#define AA_CHANGE_NOFLAGS 0
+#define AA_CHANGE_TEST 1
+#define AA_CHANGE_CHILD 2
+#define AA_CHANGE_ONEXEC 4
+#define AA_CHANGE_STACK 8
+
int apparmor_bprm_set_creds(struct linux_binprm *bprm);
int apparmor_bprm_secureexec(struct linux_binprm *bprm);
-void apparmor_bprm_committing_creds(struct linux_binprm *bprm);
-void apparmor_bprm_committed_creds(struct linux_binprm *bprm);
void aa_free_domain_entries(struct aa_domain *domain);
-int aa_change_hat(const char *hats[], int count, u64 token, bool permtest);
-int aa_change_profile(const char *fqname, bool onexec, bool permtest,
- bool stack);
+int aa_change_hat(const char *hats[], int count, u64 token, int flags);
+int aa_change_profile(const char *fqname, int flags);
#endif /* __AA_DOMAIN_H */
diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h
index 38f821bf49b6..001e40073ff9 100644
--- a/security/apparmor/include/file.h
+++ b/security/apparmor/include/file.h
@@ -15,38 +15,73 @@
#ifndef __AA_FILE_H
#define __AA_FILE_H
+#include <linux/spinlock.h>
+
#include "domain.h"
#include "match.h"
+#include "perms.h"
struct aa_profile;
struct path;
-/*
- * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags
- * for profile permissions
- */
-#define AA_MAY_CREATE 0x0010
-#define AA_MAY_DELETE 0x0020
-#define AA_MAY_META_WRITE 0x0040
-#define AA_MAY_META_READ 0x0080
-
-#define AA_MAY_CHMOD 0x0100
-#define AA_MAY_CHOWN 0x0200
-#define AA_MAY_LOCK 0x0400
-#define AA_EXEC_MMAP 0x0800
-
-#define AA_MAY_LINK 0x1000
-#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
-#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */
-#define AA_MAY_CHANGE_PROFILE 0x80000000
-#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */
+#define mask_mode_t(X) (X & (MAY_EXEC | MAY_WRITE | MAY_READ | MAY_APPEND))
#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\
AA_MAY_CREATE | AA_MAY_DELETE | \
- AA_MAY_META_READ | AA_MAY_META_WRITE | \
+ AA_MAY_GETATTR | AA_MAY_SETATTR | \
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \
AA_EXEC_MMAP | AA_MAY_LINK)
+#define file_ctx(X) ((struct aa_file_ctx *)(X)->f_security)
+
+/* struct aa_file_ctx - the AppArmor context the file was opened in
+ * @lock: lock to update the ctx
+ * @label: label currently cached on the ctx
+ * @perms: the permission the file was opened with
+ */
+struct aa_file_ctx {
+ spinlock_t lock;
+ struct aa_label __rcu *label;
+ u32 allow;
+};
+
+/**
+ * aa_alloc_file_ctx - allocate file_ctx
+ * @label: initial label of task creating the file
+ * @gfp: gfp flags for allocation
+ *
+ * Returns: file_ctx or NULL on failure
+ */
+static inline struct aa_file_ctx *aa_alloc_file_ctx(struct aa_label *label,
+ gfp_t gfp)
+{
+ struct aa_file_ctx *ctx;
+
+ ctx = kzalloc(sizeof(struct aa_file_ctx), gfp);
+ if (ctx) {
+ spin_lock_init(&ctx->lock);
+ rcu_assign_pointer(ctx->label, aa_get_label(label));
+ }
+ return ctx;
+}
+
+/**
+ * aa_free_file_ctx - free a file_ctx
+ * @ctx: file_ctx to free (MAYBE_NULL)
+ */
+static inline void aa_free_file_ctx(struct aa_file_ctx *ctx)
+{
+ if (ctx) {
+ aa_put_label(rcu_access_pointer(ctx->label));
+ kzfree(ctx);
+ }
+}
+
+static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx)
+{
+ return aa_get_label_rcu(&ctx->label);
+}
+
/*
* The xindex is broken into 3 parts
* - index - an index into either the exec name table or the variable table
@@ -75,25 +110,6 @@ struct path_cond {
umode_t mode;
};
-/* struct file_perms - file permission
- * @allow: mask of permissions that are allowed
- * @audit: mask of permissions to force an audit message for
- * @quiet: mask of permissions to quiet audit messages for
- * @kill: mask of permissions that when matched will kill the task
- * @xindex: exec transition index if @allow contains MAY_EXEC
- *
- * The @audit and @queit mask should be mutually exclusive.
- */
-struct file_perms {
- u32 allow;
- u32 audit;
- u32 quiet;
- u32 kill;
- u16 xindex;
-};
-
-extern struct file_perms nullperms;
-
#define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill)
/* FIXME: split perms from dfa and match this to description
@@ -144,9 +160,10 @@ static inline u16 dfa_map_xindex(u16 mask)
#define dfa_other_xindex(dfa, state) \
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
-int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
+int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
const char *op, u32 request, const char *name,
- const char *target, kuid_t ouid, const char *info, int error);
+ const char *target, struct aa_label *tlabel, kuid_t ouid,
+ const char *info, int error);
/**
* struct aa_file_rules - components used for file rule permissions
@@ -167,20 +184,27 @@ struct aa_file_rules {
/* TODO: add delegate table */
};
+struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state,
+ struct path_cond *cond);
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
const char *name, struct path_cond *cond,
- struct file_perms *perms);
+ struct aa_perms *perms);
-int aa_path_perm(const char *op, struct aa_profile *profile,
+int __aa_path_perm(const char *op, struct aa_profile *profile,
+ const char *name, u32 request, struct path_cond *cond,
+ int flags, struct aa_perms *perms);
+int aa_path_perm(const char *op, struct aa_label *label,
const struct path *path, int flags, u32 request,
struct path_cond *cond);
-int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
+int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
const struct path *new_dir, struct dentry *new_dentry);
-int aa_file_perm(const char *op, struct aa_profile *profile, struct file *file,
+int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
u32 request);
+void aa_inherit_files(const struct cred *cred, struct files_struct *files);
+
static inline void aa_free_file_rules(struct aa_file_rules *rules)
{
aa_put_dfa(rules->dfa);
diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h
index 288ca76e2fb1..656fdb81c8a0 100644
--- a/security/apparmor/include/ipc.h
+++ b/security/apparmor/include/ipc.h
@@ -4,7 +4,7 @@
* This file contains AppArmor ipc mediation function definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
- * Copyright 2009-2010 Canonical Ltd.
+ * Copyright 2009-2017 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -19,10 +19,16 @@
struct aa_profile;
-int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee,
- unsigned int mode);
+#define AA_PTRACE_TRACE MAY_WRITE
+#define AA_PTRACE_READ MAY_READ
+#define AA_MAY_BE_TRACED AA_MAY_APPEND
+#define AA_MAY_BE_READ AA_MAY_CREATE
+#define PTRACE_PERM_SHIFT 2
-int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
- unsigned int mode);
+#define AA_PTRACE_PERM_MASK (AA_PTRACE_READ | AA_PTRACE_TRACE | \
+ AA_MAY_BE_READ | AA_MAY_BE_TRACED)
+
+int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee,
+ u32 request);
#endif /* __AA_IPC_H */
diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h
new file mode 100644
index 000000000000..9a283b722755
--- /dev/null
+++ b/security/apparmor/include/label.h
@@ -0,0 +1,441 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor label definitions
+ *
+ * Copyright 2017 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#ifndef __AA_LABEL_H
+#define __AA_LABEL_H
+
+#include <linux/atomic.h>
+#include <linux/audit.h>
+#include <linux/rbtree.h>
+#include <linux/rcupdate.h>
+
+#include "apparmor.h"
+#include "lib.h"
+
+struct aa_ns;
+
+#define LOCAL_VEC_ENTRIES 8
+#define DEFINE_VEC(T, V) \
+ struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES]; \
+ struct aa_ ## T **(V)
+
+#define vec_setup(T, V, N, GFP) \
+({ \
+ if ((N) <= LOCAL_VEC_ENTRIES) { \
+ typeof(N) i; \
+ (V) = (_ ## V ## _localtmp); \
+ for (i = 0; i < (N); i++) \
+ (V)[i] = NULL; \
+ } else \
+ (V) = kzalloc(sizeof(struct aa_ ## T *) * (N), (GFP)); \
+ (V) ? 0 : -ENOMEM; \
+})
+
+#define vec_cleanup(T, V, N) \
+do { \
+ int i; \
+ for (i = 0; i < (N); i++) { \
+ if (!IS_ERR_OR_NULL((V)[i])) \
+ aa_put_ ## T((V)[i]); \
+ } \
+ if ((V) != _ ## V ## _localtmp) \
+ kfree(V); \
+} while (0)
+
+#define vec_last(VEC, SIZE) ((VEC)[(SIZE) - 1])
+#define vec_ns(VEC, SIZE) (vec_last((VEC), (SIZE))->ns)
+#define vec_labelset(VEC, SIZE) (&vec_ns((VEC), (SIZE))->labels)
+#define cleanup_domain_vec(V, L) cleanup_label_vec((V), (L)->size)
+
+struct aa_profile;
+#define VEC_FLAG_TERMINATE 1
+int aa_vec_unique(struct aa_profile **vec, int n, int flags);
+struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len,
+ gfp_t gfp);
+#define aa_sort_and_merge_vec(N, V) \
+ aa_sort_and_merge_profiles((N), (struct aa_profile **)(V))
+
+
+/* struct aa_labelset - set of labels for a namespace
+ *
+ * Labels are reference counted; aa_labelset does not contribute to label
+ * reference counts. Once a label's last refcount is put it is removed from
+ * the set.
+ */
+struct aa_labelset {
+ rwlock_t lock;
+
+ struct rb_root root;
+};
+
+#define __labelset_for_each(LS, N) \
+ for ((N) = rb_first(&(LS)->root); (N); (N) = rb_next(N))
+
+void aa_labelset_destroy(struct aa_labelset *ls);
+void aa_labelset_init(struct aa_labelset *ls);
+
+
+enum label_flags {
+ FLAG_HAT = 1, /* profile is a hat */
+ FLAG_UNCONFINED = 2, /* label unconfined only if all */
+ FLAG_NULL = 4, /* profile is null learning profile */
+ FLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */
+ FLAG_IMMUTIBLE = 0x10, /* don't allow changes/replacement */
+ FLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */
+ FLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
+ FLAG_NS_COUNT = 0x80, /* carries NS ref count */
+ FLAG_IN_TREE = 0x100, /* label is in tree */
+ FLAG_PROFILE = 0x200, /* label is a profile */
+ FLAG_EXPLICIT = 0x400, /* explicit static label */
+ FLAG_STALE = 0x800, /* replaced/removed */
+ FLAG_RENAMED = 0x1000, /* label has renaming in it */
+ FLAG_REVOKED = 0x2000, /* label has revocation in it */
+
+ /* These flags must correspond with PATH_flags */
+ /* TODO: add new path flags */
+};
+
+struct aa_label;
+struct aa_proxy {
+ struct kref count;
+ struct aa_label __rcu *label;
+};
+
+struct label_it {
+ int i, j;
+};
+
+/* struct aa_label - lazy labeling struct
+ * @count: ref count of active users
+ * @node: rbtree position
+ * @rcu: rcu callback struct
+ * @proxy: is set to the label that replaced this label
+ * @hname: text representation of the label (MAYBE_NULL)
+ * @flags: stale and other flags - values may change under label set lock
+ * @secid: secid that references this label
+ * @size: number of entries in @ent[]
+ * @ent: set of profiles for label, actual size determined by @size
+ */
+struct aa_label {
+ struct kref count;
+ struct rb_node node;
+ struct rcu_head rcu;
+ struct aa_proxy *proxy;
+ __counted char *hname;
+ long flags;
+ u32 secid;
+ int size;
+ struct aa_profile *vec[];
+};
+
+#define last_error(E, FN) \
+do { \
+ int __subE = (FN); \
+ if (__subE) \
+ (E) = __subE; \
+} while (0)
+
+#define label_isprofile(X) ((X)->flags & FLAG_PROFILE)
+#define label_unconfined(X) ((X)->flags & FLAG_UNCONFINED)
+#define unconfined(X) label_unconfined(X)
+#define label_is_stale(X) ((X)->flags & FLAG_STALE)
+#define __label_make_stale(X) ((X)->flags |= FLAG_STALE)
+#define labels_ns(X) (vec_ns(&((X)->vec[0]), (X)->size))
+#define labels_set(X) (&labels_ns(X)->labels)
+#define labels_profile(X) ((X)->vec[(X)->size - 1])
+
+
+int aa_label_next_confined(struct aa_label *l, int i);
+
+/* for each profile in a label */
+#define label_for_each(I, L, P) \
+ for ((I).i = 0; ((P) = (L)->vec[(I).i]); ++((I).i))
+
+/* assumes break/goto ended label_for_each */
+#define label_for_each_cont(I, L, P) \
+ for (++((I).i); ((P) = (L)->vec[(I).i]); ++((I).i))
+
+#define next_comb(I, L1, L2) \
+do { \
+ (I).j++; \
+ if ((I).j >= (L2)->size) { \
+ (I).i++; \
+ (I).j = 0; \
+ } \
+} while (0)
+
+
+/* for each combination of P1 in L1, and P2 in L2 */
+#define label_for_each_comb(I, L1, L2, P1, P2) \
+for ((I).i = (I).j = 0; \
+ ((P1) = (L1)->vec[(I).i]) && ((P2) = (L2)->vec[(I).j]); \
+ (I) = next_comb(I, L1, L2))
+
+#define fn_for_each_comb(L1, L2, P1, P2, FN) \
+({ \
+ struct label_it i; \
+ int __E = 0; \
+ label_for_each_comb(i, (L1), (L2), (P1), (P2)) { \
+ last_error(__E, (FN)); \
+ } \
+ __E; \
+})
+
+/* for each profile that is enforcing confinement in a label */
+#define label_for_each_confined(I, L, P) \
+ for ((I).i = aa_label_next_confined((L), 0); \
+ ((P) = (L)->vec[(I).i]); \
+ (I).i = aa_label_next_confined((L), (I).i + 1))
+
+#define label_for_each_in_merge(I, A, B, P) \
+ for ((I).i = (I).j = 0; \
+ ((P) = aa_label_next_in_merge(&(I), (A), (B))); \
+ )
+
+#define label_for_each_not_in_set(I, SET, SUB, P) \
+ for ((I).i = (I).j = 0; \
+ ((P) = __aa_label_next_not_in_set(&(I), (SET), (SUB))); \
+ )
+
+#define next_in_ns(i, NS, L) \
+({ \
+ typeof(i) ___i = (i); \
+ while ((L)->vec[___i] && (L)->vec[___i]->ns != (NS)) \
+ (___i)++; \
+ (___i); \
+})
+
+#define label_for_each_in_ns(I, NS, L, P) \
+ for ((I).i = next_in_ns(0, (NS), (L)); \
+ ((P) = (L)->vec[(I).i]); \
+ (I).i = next_in_ns((I).i + 1, (NS), (L)))
+
+#define fn_for_each_in_ns(L, P, FN) \
+({ \
+ struct label_it __i; \
+ struct aa_ns *__ns = labels_ns(L); \
+ int __E = 0; \
+ label_for_each_in_ns(__i, __ns, (L), (P)) { \
+ last_error(__E, (FN)); \
+ } \
+ __E; \
+})
+
+
+#define fn_for_each_XXX(L, P, FN, ...) \
+({ \
+ struct label_it i; \
+ int __E = 0; \
+ label_for_each ## __VA_ARGS__(i, (L), (P)) { \
+ last_error(__E, (FN)); \
+ } \
+ __E; \
+})
+
+#define fn_for_each(L, P, FN) fn_for_each_XXX(L, P, FN)
+#define fn_for_each_confined(L, P, FN) fn_for_each_XXX(L, P, FN, _confined)
+
+#define fn_for_each2_XXX(L1, L2, P, FN, ...) \
+({ \
+ struct label_it i; \
+ int __E = 0; \
+ label_for_each ## __VA_ARGS__(i, (L1), (L2), (P)) { \
+ last_error(__E, (FN)); \
+ } \
+ __E; \
+})
+
+#define fn_for_each_in_merge(L1, L2, P, FN) \
+ fn_for_each2_XXX((L1), (L2), P, FN, _in_merge)
+#define fn_for_each_not_in_set(L1, L2, P, FN) \
+ fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set)
+
+#define LABEL_MEDIATES(L, C) \
+({ \
+ struct aa_profile *profile; \
+ struct label_it i; \
+ int ret = 0; \
+ label_for_each(i, (L), profile) { \
+ if (PROFILE_MEDIATES(profile, (C))) { \
+ ret = 1; \
+ break; \
+ } \
+ } \
+ ret; \
+})
+
+
+void aa_labelset_destroy(struct aa_labelset *ls);
+void aa_labelset_init(struct aa_labelset *ls);
+void __aa_labelset_update_subtree(struct aa_ns *ns);
+
+void aa_label_free(struct aa_label *label);
+void aa_label_kref(struct kref *kref);
+bool aa_label_init(struct aa_label *label, int size);
+struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp);
+
+bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub);
+struct aa_profile *__aa_label_next_not_in_set(struct label_it *I,
+ struct aa_label *set,
+ struct aa_label *sub);
+bool aa_label_remove(struct aa_label *label);
+struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l);
+bool aa_label_replace(struct aa_label *old, struct aa_label *new);
+bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old,
+ struct aa_label *new);
+
+struct aa_label *aa_label_find(struct aa_label *l);
+
+struct aa_profile *aa_label_next_in_merge(struct label_it *I,
+ struct aa_label *a,
+ struct aa_label *b);
+struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b);
+struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b,
+ gfp_t gfp);
+
+
+bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp);
+
+#define FLAGS_NONE 0
+#define FLAG_SHOW_MODE 1
+#define FLAG_VIEW_SUBNS 2
+#define FLAG_HIDDEN_UNCONFINED 4
+int aa_label_snxprint(char *str, size_t size, struct aa_ns *view,
+ struct aa_label *label, int flags);
+int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label,
+ int flags, gfp_t gfp);
+int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns,
+ struct aa_label *label, int flags, gfp_t gfp);
+void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns,
+ struct aa_label *label, int flags, gfp_t gfp);
+void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns,
+ struct aa_label *label, int flags, gfp_t gfp);
+void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags,
+ gfp_t gfp);
+void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp);
+void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp);
+void aa_label_printk(struct aa_label *label, gfp_t gfp);
+
+struct aa_label *aa_label_parse(struct aa_label *base, const char *str,
+ gfp_t gfp, bool create, bool force_stack);
+
+
+struct aa_perms;
+int aa_label_match(struct aa_profile *profile, struct aa_label *label,
+ unsigned int state, bool subns, u32 request,
+ struct aa_perms *perms);
+
+
+/**
+ * __aa_get_label - get a reference count to uncounted label reference
+ * @l: reference to get a count on
+ *
+ * Returns: pointer to reference OR NULL if race is lost and reference is
+ * being repeated.
+ * Requires: lock held, and the return code MUST be checked
+ */
+static inline struct aa_label *__aa_get_label(struct aa_label *l)
+{
+ if (l && kref_get_unless_zero(&l->count))
+ return l;
+
+ return NULL;
+}
+
+static inline struct aa_label *aa_get_label(struct aa_label *l)
+{
+ if (l)
+ kref_get(&(l->count));
+
+ return l;
+}
+
+
+/**
+ * aa_get_label_rcu - increment refcount on a label that can be replaced
+ * @l: pointer to label that can be replaced (NOT NULL)
+ *
+ * Returns: pointer to a refcounted label.
+ * else NULL if no label
+ */
+static inline struct aa_label *aa_get_label_rcu(struct aa_label __rcu **l)
+{
+ struct aa_label *c;
+
+ rcu_read_lock();
+ do {
+ c = rcu_dereference(*l);
+ } while (c && !kref_get_unless_zero(&c->count));
+ rcu_read_unlock();
+
+ return c;
+}
+
+/**
+ * aa_get_newest_label - find the newest version of @l
+ * @l: the label to check for newer versions of
+ *
+ * Returns: refcounted newest version of @l taking into account
+ * replacement, renames and removals
+ * return @l.
+ */
+static inline struct aa_label *aa_get_newest_label(struct aa_label *l)
+{
+ if (!l)
+ return NULL;
+
+ if (label_is_stale(l)) {
+ struct aa_label *tmp;
+
+ AA_BUG(!l->proxy);
+ AA_BUG(!l->proxy->label);
+ /* BUG: only way this can happen is @l ref count and its
+ * replacement count have gone to 0 and are on their way
+ * to destruction. ie. we have a refcounting error
+ */
+ tmp = aa_get_label_rcu(&l->proxy->label);
+ AA_BUG(!tmp);
+
+ return tmp;
+ }
+
+ return aa_get_label(l);
+}
+
+static inline void aa_put_label(struct aa_label *l)
+{
+ if (l)
+ kref_put(&l->count, aa_label_kref);
+}
+
+
+struct aa_proxy *aa_alloc_proxy(struct aa_label *l, gfp_t gfp);
+void aa_proxy_kref(struct kref *kref);
+
+static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *proxy)
+{
+ if (proxy)
+ kref_get(&(proxy->count));
+
+ return proxy;
+}
+
+static inline void aa_put_proxy(struct aa_proxy *proxy)
+{
+ if (proxy)
+ kref_put(&proxy->count, aa_proxy_kref);
+}
+
+void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new);
+
+#endif /* __AA_LABEL_H */
diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h
index 550a700563b4..436b3a722357 100644
--- a/security/apparmor/include/lib.h
+++ b/security/apparmor/include/lib.h
@@ -60,6 +60,7 @@
extern int apparmor_initialized;
/* fn's in lib */
+const char *skipn_spaces(const char *str, size_t n);
char *aa_split_fqname(char *args, char **ns_name);
const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name,
size_t *ns_len);
@@ -99,6 +100,36 @@ static inline bool path_mediated_fs(struct dentry *dentry)
return !(dentry->d_sb->s_flags & MS_NOUSER);
}
+
+struct counted_str {
+ struct kref count;
+ char name[];
+};
+
+#define str_to_counted(str) \
+ ((struct counted_str *)(str - offsetof(struct counted_str, name)))
+
+#define __counted /* atm just a notation */
+
+void aa_str_kref(struct kref *kref);
+char *aa_str_alloc(int size, gfp_t gfp);
+
+
+static inline __counted char *aa_get_str(__counted char *str)
+{
+ if (str)
+ kref_get(&(str_to_counted(str)->count));
+
+ return str;
+}
+
+static inline void aa_put_str(__counted char *str)
+{
+ if (str)
+ kref_put(&str_to_counted(str)->count, aa_str_kref);
+}
+
+
/* struct aa_policy - common part of both namespaces and profiles
* @name: name of the object
* @hname - The hierarchical name
@@ -107,7 +138,7 @@ static inline bool path_mediated_fs(struct dentry *dentry)
*/
struct aa_policy {
const char *name;
- const char *hname;
+ __counted char *hname;
struct list_head list;
struct list_head profiles;
};
@@ -180,4 +211,89 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix,
const char *name, gfp_t gfp);
void aa_policy_destroy(struct aa_policy *policy);
-#endif /* AA_LIB_H */
+
+/*
+ * fn_label_build - abstract out the build of a label transition
+ * @L: label the transition is being computed for
+ * @P: profile parameter derived from L by this macro, can be passed to FN
+ * @GFP: memory allocation type to use
+ * @FN: fn to call for each profile transition. @P is set to the profile
+ *
+ * Returns: new label on success
+ * ERR_PTR if build @FN fails
+ * NULL if label_build fails due to low memory conditions
+ *
+ * @FN must return a label or ERR_PTR on failure. NULL is not allowed
+ */
+#define fn_label_build(L, P, GFP, FN) \
+({ \
+ __label__ __cleanup, __done; \
+ struct aa_label *__new_; \
+ \
+ if ((L)->size > 1) { \
+ /* TODO: add cache of transitions already done */ \
+ struct label_it __i; \
+ int __j, __k, __count; \
+ DEFINE_VEC(label, __lvec); \
+ DEFINE_VEC(profile, __pvec); \
+ if (vec_setup(label, __lvec, (L)->size, (GFP))) { \
+ __new_ = NULL; \
+ goto __done; \
+ } \
+ __j = 0; \
+ label_for_each(__i, (L), (P)) { \
+ __new_ = (FN); \
+ AA_BUG(!__new_); \
+ if (IS_ERR(__new_)) \
+ goto __cleanup; \
+ __lvec[__j++] = __new_; \
+ } \
+ for (__j = __count = 0; __j < (L)->size; __j++) \
+ __count += __lvec[__j]->size; \
+ if (!vec_setup(profile, __pvec, __count, (GFP))) { \
+ for (__j = __k = 0; __j < (L)->size; __j++) { \
+ label_for_each(__i, __lvec[__j], (P)) \
+ __pvec[__k++] = aa_get_profile(P); \
+ } \
+ __count -= aa_vec_unique(__pvec, __count, 0); \
+ if (__count > 1) { \
+ __new_ = aa_vec_find_or_create_label(__pvec,\
+ __count, (GFP)); \
+ /* only fails if out of Mem */ \
+ if (!__new_) \
+ __new_ = NULL; \
+ } else \
+ __new_ = aa_get_label(&__pvec[0]->label); \
+ vec_cleanup(profile, __pvec, __count); \
+ } else \
+ __new_ = NULL; \
+__cleanup: \
+ vec_cleanup(label, __lvec, (L)->size); \
+ } else { \
+ (P) = labels_profile(L); \
+ __new_ = (FN); \
+ } \
+__done: \
+ if (!__new_) \
+ AA_DEBUG("label build failed\n"); \
+ (__new_); \
+})
+
+
+#define __fn_build_in_ns(NS, P, NS_FN, OTHER_FN) \
+({ \
+ struct aa_label *__new; \
+ if ((P)->ns != (NS)) \
+ __new = (OTHER_FN); \
+ else \
+ __new = (NS_FN); \
+ (__new); \
+})
+
+#define fn_label_build_in_ns(L, P, GFP, NS_FN, OTHER_FN) \
+({ \
+ fn_label_build((L), (P), (GFP), \
+ __fn_build_in_ns(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \
+})
+
+#endif /* __AA_LIB_H */
diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h
index 0444fdde3918..05fb3305671e 100644
--- a/security/apparmor/include/path.h
+++ b/security/apparmor/include/path.h
@@ -23,11 +23,12 @@ enum path_flags {
PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */
PATH_DELEGATE_DELETED = 0x08000, /* delegate deleted files */
- PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */
+ PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */
};
-int aa_path_name(const struct path *path, int flags, char **buffer,
- const char **name, const char **info);
+int aa_path_name(const struct path *path, int flags, char *buffer,
+ const char **name, const char **info,
+ const char *disconnected);
#define MAX_PATH_BUFFERS 2
diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h
new file mode 100644
index 000000000000..2b27bb79aec4
--- /dev/null
+++ b/security/apparmor/include/perms.h
@@ -0,0 +1,155 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor basic permission sets definitions.
+ *
+ * Copyright 2017 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#ifndef __AA_PERM_H
+#define __AA_PERM_H
+
+#include <linux/fs.h>
+#include "label.h"
+
+#define AA_MAY_EXEC MAY_EXEC
+#define AA_MAY_WRITE MAY_WRITE
+#define AA_MAY_READ MAY_READ
+#define AA_MAY_APPEND MAY_APPEND
+
+#define AA_MAY_CREATE 0x0010
+#define AA_MAY_DELETE 0x0020
+#define AA_MAY_OPEN 0x0040
+#define AA_MAY_RENAME 0x0080 /* pair */
+
+#define AA_MAY_SETATTR 0x0100 /* meta write */
+#define AA_MAY_GETATTR 0x0200 /* meta read */
+#define AA_MAY_SETCRED 0x0400 /* security cred/attr */
+#define AA_MAY_GETCRED 0x0800
+
+#define AA_MAY_CHMOD 0x1000 /* pair */
+#define AA_MAY_CHOWN 0x2000 /* pair */
+#define AA_MAY_CHGRP 0x4000 /* pair */
+#define AA_MAY_LOCK 0x8000 /* LINK_SUBSET overlaid */
+
+#define AA_EXEC_MMAP 0x00010000
+#define AA_MAY_MPROT 0x00020000 /* extend conditions */
+#define AA_MAY_LINK 0x00040000 /* pair */
+#define AA_MAY_SNAPSHOT 0x00080000 /* pair */
+
+#define AA_MAY_DELEGATE
+#define AA_CONT_MATCH 0x08000000
+
+#define AA_MAY_STACK 0x10000000
+#define AA_MAY_ONEXEC 0x20000000 /* either stack or change_profile */
+#define AA_MAY_CHANGE_PROFILE 0x40000000
+#define AA_MAY_CHANGEHAT 0x80000000
+
+#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
+
+
+#define PERMS_CHRS_MASK (MAY_READ | MAY_WRITE | AA_MAY_CREATE | \
+ AA_MAY_DELETE | AA_MAY_LINK | AA_MAY_LOCK | \
+ AA_MAY_EXEC | AA_EXEC_MMAP | AA_MAY_APPEND)
+
+#define PERMS_NAMES_MASK (PERMS_CHRS_MASK | AA_MAY_OPEN | AA_MAY_RENAME | \
+ AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_SETCRED | \
+ AA_MAY_GETCRED | AA_MAY_CHMOD | AA_MAY_CHOWN | \
+ AA_MAY_CHGRP | AA_MAY_MPROT | AA_MAY_SNAPSHOT | \
+ AA_MAY_STACK | AA_MAY_ONEXEC | \
+ AA_MAY_CHANGE_PROFILE | AA_MAY_CHANGEHAT)
+
+extern const char aa_file_perm_chrs[];
+extern const char *aa_file_perm_names[];
+
+struct aa_perms {
+ u32 allow;
+ u32 audit; /* set only when allow is set */
+
+ u32 deny; /* explicit deny, or conflict if allow also set */
+ u32 quiet; /* set only when ~allow | deny */
+ u32 kill; /* set only when ~allow | deny */
+ u32 stop; /* set only when ~allow | deny */
+
+ u32 complain; /* accumulates only used when ~allow & ~deny */
+ u32 cond; /* set only when ~allow and ~deny */
+
+ u32 hide; /* set only when ~allow | deny */
+ u32 prompt; /* accumulates only used when ~allow & ~deny */
+
+ /* Reserved:
+ * u32 subtree; / * set only when allow is set * /
+ */
+ u16 xindex;
+};
+
+#define ALL_PERMS_MASK 0xffffffff
+extern struct aa_perms nullperms;
+extern struct aa_perms allperms;
+
+
+#define xcheck(FN1, FN2) \
+({ \
+ int e, error = FN1; \
+ e = FN2; \
+ if (e) \
+ error = e; \
+ error; \
+})
+
+
+/*
+ * TODO: update for labels pointing to labels instead of profiles
+ * TODO: optimize the walk, currently does subwalk of L2 for each P in L1
+ * gah this doesn't allow for label compound check!!!!
+ */
+#define xcheck_ns_profile_profile(P1, P2, FN, args...) \
+({ \
+ int ____e = 0; \
+ if (P1->ns == P2->ns) \
+ ____e = FN((P1), (P2), args); \
+ (____e); \
+})
+
+#define xcheck_ns_profile_label(P, L, FN, args...) \
+({ \
+ struct aa_profile *__p2; \
+ fn_for_each((L), __p2, \
+ xcheck_ns_profile_profile((P), __p2, (FN), args)); \
+})
+
+#define xcheck_ns_labels(L1, L2, FN, args...) \
+({ \
+ struct aa_profile *__p1; \
+ fn_for_each((L1), __p1, FN(__p1, (L2), args)); \
+})
+
+/* Do the cross check but applying FN at the profiles level */
+#define xcheck_labels_profiles(L1, L2, FN, args...) \
+ xcheck_ns_labels((L1), (L2), xcheck_ns_profile_label, (FN), args)
+
+
+void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask);
+void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask);
+void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
+ u32 chrsmask, const char **names, u32 namesmask);
+void aa_apply_modes_to_perms(struct aa_profile *profile,
+ struct aa_perms *perms);
+void aa_compute_perms(struct aa_dfa *dfa, unsigned int state,
+ struct aa_perms *perms);
+void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend);
+void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend);
+void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label,
+ int type, u32 request, struct aa_perms *perms);
+int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
+ u32 request, int type, u32 *deny,
+ struct common_audit_data *sa);
+int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
+ u32 request, struct common_audit_data *sa,
+ void (*cb)(struct audit_buffer *, void *));
+#endif /* __AA_PERM_H */
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index 67bc96afe541..17fe41a9cac3 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -29,6 +29,8 @@
#include "domain.h"
#include "file.h"
#include "lib.h"
+#include "label.h"
+#include "perms.h"
#include "resource.h"
@@ -47,9 +49,9 @@ extern const char *const aa_profile_mode_names[];
#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL)
-#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT)
+#define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT)
-#define profile_is_stale(_profile) ((_profile)->flags & PFLAG_STALE)
+#define profile_is_stale(_profile) (label_is_stale(&(_profile)->label))
#define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2)
@@ -66,22 +68,6 @@ enum profile_mode {
APPARMOR_UNCONFINED, /* profile set to unconfined */
};
-enum profile_flags {
- PFLAG_HAT = 1, /* profile is a hat */
- PFLAG_NULL = 4, /* profile is null learning profile */
- PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */
- PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */
- PFLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */
- PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
- PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */
- PFLAG_STALE = 0x200, /* profile replaced/removed */
- PFLAG_NS_COUNT = 0x400, /* carries NS ref count */
-
- /* These flags must correspond with PATH_flags */
- PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */
-};
-
-struct aa_profile;
/* struct aa_policydb - match engine for a policy
* dfa: dfa pattern match
@@ -94,11 +80,6 @@ struct aa_policydb {
};
-struct aa_proxy {
- struct kref count;
- struct aa_profile __rcu *profile;
-};
-
/* struct aa_data - generic data structure
* key: name for retrieving this data
* size: size of data in bytes
@@ -115,19 +96,17 @@ struct aa_data {
/* struct aa_profile - basic confinement data
* @base - base components of the profile (name, refcount, lists, lock ...)
- * @count: reference count of the obj
- * @rcu: rcu head used when removing from @list
+ * @label - label this profile is an extension of
* @parent: parent of profile
* @ns: namespace the profile is in
- * @proxy: is set to the profile that replaced this profile
* @rename: optional profile name that this profile renamed
* @attach: human readable attachment string
* @xmatch: optional extended matching for unconfined executables names
* @xmatch_len: xmatch prefix len, used to determine xmatch priority
* @audit: the auditing mode of the profile
* @mode: the enforcement mode of the profile
- * @flags: flags controlling profile behavior
* @path_flags: flags controlling path generation behavior
+ * @disconnected: what to prepend if attach_disconnected is specified
* @size: the memory consumed by this profiles rules
* @policy: general match rules governing policy
* @file: The set of rules governing basic file access and domain transitions
@@ -143,8 +122,6 @@ struct aa_data {
* used to determine profile attachment against unconfined tasks. All other
* attachments are determined by profile X transition rules.
*
- * The @proxy struct is write protected by the profile lock.
- *
* Profiles have a hierarchy where hats and children profiles keep
* a reference to their parent.
*
@@ -154,12 +131,9 @@ struct aa_data {
*/
struct aa_profile {
struct aa_policy base;
- struct kref count;
- struct rcu_head rcu;
struct aa_profile __rcu *parent;
struct aa_ns *ns;
- struct aa_proxy *proxy;
const char *rename;
const char *attach;
@@ -167,8 +141,8 @@ struct aa_profile {
int xmatch_len;
enum audit_mode audit;
long mode;
- long flags;
u32 path_flags;
+ const char *disconnected;
int size;
struct aa_policydb policy;
@@ -181,17 +155,24 @@ struct aa_profile {
char *dirname;
struct dentry *dents[AAFS_PROF_SIZEOF];
struct rhashtable *data;
+ struct aa_label label;
};
extern enum profile_mode aa_g_profile_mode;
-void __aa_update_proxy(struct aa_profile *orig, struct aa_profile *new);
+#define AA_MAY_LOAD_POLICY AA_MAY_APPEND
+#define AA_MAY_REPLACE_POLICY AA_MAY_WRITE
+#define AA_MAY_REMOVE_POLICY AA_MAY_DELETE
+
+#define profiles_ns(P) ((P)->ns)
+#define name_is_shared(A, B) ((A)->hname && (A)->hname == (B)->hname)
void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
void aa_free_proxy_kref(struct kref *kref);
-struct aa_profile *aa_alloc_profile(const char *name, gfp_t gfp);
+struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy,
+ gfp_t gfp);
struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat,
const char *base, gfp_t gfp);
void aa_free_profile(struct aa_profile *profile);
@@ -200,21 +181,44 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname,
size_t n);
struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *name);
-struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base,
+struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
const char *fqname, size_t n);
struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name);
-ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
- bool noreplace, struct aa_loaddata *udata);
-ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *profile,
- char *name, size_t size);
+ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label,
+ u32 mask, struct aa_loaddata *udata);
+ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label,
+ char *name, size_t size);
void __aa_profile_list_release(struct list_head *head);
#define PROF_ADD 1
#define PROF_REPLACE 0
-#define unconfined(X) ((X)->mode == APPARMOR_UNCONFINED)
+#define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED)
+
+/**
+ * aa_get_newest_profile - simple wrapper fn to wrap the label version
+ * @p: profile (NOT NULL)
+ *
+ * Returns refcount to newest version of the profile (maybe @p)
+ *
+ * Requires: @p must be held with a valid refcount
+ */
+static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
+{
+ return labels_profile(aa_get_newest_label(&p->label));
+}
+#define PROFILE_MEDIATES(P, T) ((P)->policy.start[(T)])
+/* safe version of POLICY_MEDIATES for full range input */
+static inline unsigned int PROFILE_MEDIATES_SAFE(struct aa_profile *profile,
+ unsigned char class)
+{
+ if (profile->policy.dfa)
+ return aa_dfa_match_len(profile->policy.dfa,
+ profile->policy.start[0], &class, 1);
+ return 0;
+}
/**
* aa_get_profile - increment refcount on profile @p
@@ -226,7 +230,7 @@ void __aa_profile_list_release(struct list_head *head);
static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
{
if (p)
- kref_get(&(p->count));
+ kref_get(&(p->label.count));
return p;
}
@@ -240,7 +244,7 @@ static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
*/
static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p)
{
- if (p && kref_get_unless_zero(&p->count))
+ if (p && kref_get_unless_zero(&p->label.count))
return p;
return NULL;
@@ -260,53 +264,20 @@ static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p)
rcu_read_lock();
do {
c = rcu_dereference(*p);
- } while (c && !kref_get_unless_zero(&c->count));
+ } while (c && !kref_get_unless_zero(&c->label.count));
rcu_read_unlock();
return c;
}
/**
- * aa_get_newest_profile - find the newest version of @profile
- * @profile: the profile to check for newer versions of
- *
- * Returns: refcounted newest version of @profile taking into account
- * replacement, renames and removals
- * return @profile.
- */
-static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
-{
- if (!p)
- return NULL;
-
- if (profile_is_stale(p))
- return aa_get_profile_rcu(&p->proxy->profile);
-
- return aa_get_profile(p);
-}
-
-/**
* aa_put_profile - decrement refcount on profile @p
* @p: profile (MAYBE NULL)
*/
static inline void aa_put_profile(struct aa_profile *p)
{
if (p)
- kref_put(&p->count, aa_free_profile_kref);
-}
-
-static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *p)
-{
- if (p)
- kref_get(&(p->count));
-
- return p;
-}
-
-static inline void aa_put_proxy(struct aa_proxy *p)
-{
- if (p)
- kref_put(&p->count, aa_free_proxy_kref);
+ kref_put(&p->label.count, aa_label_kref);
}
static inline int AUDIT_MODE(struct aa_profile *profile)
@@ -319,7 +290,7 @@ static inline int AUDIT_MODE(struct aa_profile *profile)
bool policy_view_capable(struct aa_ns *ns);
bool policy_admin_capable(struct aa_ns *ns);
-int aa_may_manage_policy(struct aa_profile *profile, struct aa_ns *ns,
- const char *op);
+int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns,
+ u32 mask);
#endif /* __AA_POLICY_H */
diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h
index 89cffddd7e75..9605f18624e2 100644
--- a/security/apparmor/include/policy_ns.h
+++ b/security/apparmor/include/policy_ns.h
@@ -19,6 +19,7 @@
#include "apparmor.h"
#include "apparmorfs.h"
+#include "label.h"
#include "policy.h"
@@ -68,6 +69,11 @@ struct aa_ns {
atomic_t uniq_null;
long uniq_id;
int level;
+ long revision;
+ wait_queue_head_t wait;
+
+ struct aa_labelset labels;
+ struct list_head rawdata_list;
struct dentry *dents[AAFS_NS_SIZEOF];
};
@@ -76,6 +82,8 @@ extern struct aa_ns *root_ns;
extern const char *aa_hidden_ns_name;
+#define ns_unconfined(NS) (&(NS)->unconfined->label)
+
bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns);
const char *aa_ns_name(struct aa_ns *parent, struct aa_ns *child, bool subns);
void aa_free_ns(struct aa_ns *ns);
@@ -85,6 +93,8 @@ void aa_free_ns_kref(struct kref *kref);
struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name);
struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n);
+struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n);
+struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n);
struct aa_ns *__aa_find_or_create_ns(struct aa_ns *parent, const char *name,
struct dentry *dir);
struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name);
@@ -144,4 +154,15 @@ static inline struct aa_ns *__aa_find_ns(struct list_head *head,
return __aa_findn_ns(head, name, strlen(name));
}
+static inline struct aa_ns *__aa_lookup_ns(struct aa_ns *base,
+ const char *hname)
+{
+ return __aa_lookupn_ns(base, hname, strlen(hname));
+}
+
+static inline struct aa_ns *aa_lookup_ns(struct aa_ns *view, const char *name)
+{
+ return aa_lookupn_ns(view, name, strlen(name));
+}
+
#endif /* AA_NAMESPACE_H */
diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h
index 4c1319eebc42..be6cd69ac319 100644
--- a/security/apparmor/include/policy_unpack.h
+++ b/security/apparmor/include/policy_unpack.h
@@ -17,6 +17,8 @@
#include <linux/list.h>
#include <linux/kref.h>
+#include <linux/dcache.h>
+#include <linux/workqueue.h>
struct aa_load_ent {
struct list_head list;
@@ -36,26 +38,84 @@ struct aa_load_ent *aa_load_ent_alloc(void);
#define PACKED_MODE_KILL 2
#define PACKED_MODE_UNCONFINED 3
-/* struct aa_loaddata - buffer of policy load data set */
+struct aa_ns;
+
+enum {
+ AAFS_LOADDATA_ABI = 0,
+ AAFS_LOADDATA_REVISION,
+ AAFS_LOADDATA_HASH,
+ AAFS_LOADDATA_DATA,
+ AAFS_LOADDATA_DIR, /* must be last actual entry */
+ AAFS_LOADDATA_NDENTS /* count of entries */
+};
+
+/*
+ * struct aa_loaddata - buffer of policy raw_data set
+ *
+ * there is no loaddata ref for being on ns list, nor a ref from
+ * d_inode(@dentry) when grab a ref from these, @ns->lock must be held
+ * && __aa_get_loaddata() needs to be used, and the return value
+ * checked, if NULL the loaddata is already being reaped and should be
+ * considered dead.
+ */
struct aa_loaddata {
struct kref count;
+ struct list_head list;
+ struct work_struct work;
+ struct dentry *dents[AAFS_LOADDATA_NDENTS];
+ struct aa_ns *ns;
+ char *name;
size_t size;
+ long revision; /* the ns policy revision this caused */
int abi;
unsigned char *hash;
+
char data[];
};
int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns);
+/**
+ * __aa_get_loaddata - get a reference count to uncounted data reference
+ * @data: reference to get a count on
+ *
+ * Returns: pointer to reference OR NULL if race is lost and reference is
+ * being repeated.
+ * Requires: @data->ns->lock held, and the return code MUST be checked
+ *
+ * Use only from inode->i_private and @data->list found references
+ */
+static inline struct aa_loaddata *
+__aa_get_loaddata(struct aa_loaddata *data)
+{
+ if (data && kref_get_unless_zero(&(data->count)))
+ return data;
+
+ return NULL;
+}
+
+/**
+ * aa_get_loaddata - get a reference count from a counted data reference
+ * @data: reference to get a count on
+ *
+ * Returns: point to reference
+ * Requires: @data to have a valid reference count on it. It is a bug
+ * if the race to reap can be encountered when it is used.
+ */
static inline struct aa_loaddata *
aa_get_loaddata(struct aa_loaddata *data)
{
- if (data)
- kref_get(&(data->count));
- return data;
+ struct aa_loaddata *tmp = __aa_get_loaddata(data);
+
+ AA_BUG(data && !tmp);
+
+ return tmp;
}
+void __aa_loaddata_update(struct aa_loaddata *data, long revision);
+bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r);
void aa_loaddata_kref(struct kref *kref);
+struct aa_loaddata *aa_loaddata_alloc(size_t size);
static inline void aa_put_loaddata(struct aa_loaddata *data)
{
if (data)
diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h
index 6bd5f33d9533..c8fd99c9357d 100644
--- a/security/apparmor/include/procattr.h
+++ b/security/apparmor/include/procattr.h
@@ -15,11 +15,7 @@
#ifndef __AA_PROCATTR_H
#define __AA_PROCATTR_H
-#define AA_DO_TEST 1
-#define AA_ONEXEC 1
-
-int aa_getprocattr(struct aa_profile *profile, char **string);
-int aa_setprocattr_changehat(char *args, size_t size, int test);
-int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test);
+int aa_getprocattr(struct aa_label *label, char **string);
+int aa_setprocattr_changehat(char *args, size_t size, int flags);
#endif /* __AA_PROCATTR_H */
diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h
index d3f4cf027957..76f1586c9adb 100644
--- a/security/apparmor/include/resource.h
+++ b/security/apparmor/include/resource.h
@@ -34,13 +34,13 @@ struct aa_rlimit {
struct rlimit limits[RLIM_NLIMITS];
};
-extern struct aa_fs_entry aa_fs_entry_rlimit[];
+extern struct aa_sfs_entry aa_sfs_entry_rlimit[];
int aa_map_resource(int resource);
-int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *,
+int aa_task_setrlimit(struct aa_label *label, struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim);
-void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new);
+void __aa_transition_rlimits(struct aa_label *old, struct aa_label *new);
static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims)
{
diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c
index edac790923c3..11e66b5bbc42 100644
--- a/security/apparmor/ipc.c
+++ b/security/apparmor/ipc.c
@@ -4,7 +4,7 @@
* This file contains AppArmor ipc mediation
*
* Copyright (C) 1998-2008 Novell/SUSE
- * Copyright 2009-2010 Canonical Ltd.
+ * Copyright 2009-2017 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -21,87 +21,103 @@
#include "include/policy.h"
#include "include/ipc.h"
+/**
+ * audit_ptrace_mask - convert mask to permission string
+ * @buffer: buffer to write string to (NOT NULL)
+ * @mask: permission mask to convert
+ */
+static void audit_ptrace_mask(struct audit_buffer *ab, u32 mask)
+{
+ switch (mask) {
+ case MAY_READ:
+ audit_log_string(ab, "read");
+ break;
+ case MAY_WRITE:
+ audit_log_string(ab, "trace");
+ break;
+ case AA_MAY_BE_READ:
+ audit_log_string(ab, "readby");
+ break;
+ case AA_MAY_BE_TRACED:
+ audit_log_string(ab, "tracedby");
+ break;
+ }
+}
+
/* call back to audit ptrace fields */
-static void audit_cb(struct audit_buffer *ab, void *va)
+static void audit_ptrace_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
+
+ if (aad(sa)->request & AA_PTRACE_PERM_MASK) {
+ audit_log_format(ab, " requested_mask=");
+ audit_ptrace_mask(ab, aad(sa)->request);
+
+ if (aad(sa)->denied & AA_PTRACE_PERM_MASK) {
+ audit_log_format(ab, " denied_mask=");
+ audit_ptrace_mask(ab, aad(sa)->denied);
+ }
+ }
audit_log_format(ab, " peer=");
- audit_log_untrustedstring(ab, aad(sa)->peer->base.hname);
+ aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
+ FLAGS_NONE, GFP_ATOMIC);
}
-/**
- * aa_audit_ptrace - do auditing for ptrace
- * @profile: profile being enforced (NOT NULL)
- * @target: profile being traced (NOT NULL)
- * @error: error condition
- *
- * Returns: %0 or error code
- */
-static int aa_audit_ptrace(struct aa_profile *profile,
- struct aa_profile *target, int error)
+/* TODO: conditionals */
+static int profile_ptrace_perm(struct aa_profile *profile,
+ struct aa_profile *peer, u32 request,
+ struct common_audit_data *sa)
{
- DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE);
+ struct aa_perms perms = { };
- aad(&sa)->peer = target;
- aad(&sa)->error = error;
+ /* need because of peer in cross check */
+ if (profile_unconfined(profile) ||
+ !PROFILE_MEDIATES(profile, AA_CLASS_PTRACE))
+ return 0;
- return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb);
+ aad(sa)->peer = &peer->label;
+ aa_profile_match_label(profile, &peer->label, AA_CLASS_PTRACE, request,
+ &perms);
+ aa_apply_modes_to_perms(profile, &perms);
+ return aa_check_perms(profile, &perms, request, sa, audit_ptrace_cb);
}
-/**
- * aa_may_ptrace - test if tracer task can trace the tracee
- * @tracer: profile of the task doing the tracing (NOT NULL)
- * @tracee: task to be traced
- * @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH
- *
- * Returns: %0 else error code if permission denied or error
- */
-int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee,
- unsigned int mode)
+static int cross_ptrace_perm(struct aa_profile *tracer,
+ struct aa_profile *tracee, u32 request,
+ struct common_audit_data *sa)
{
- /* TODO: currently only based on capability, not extended ptrace
- * rules,
- * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH
- */
-
- if (unconfined(tracer) || tracer == tracee)
+ if (PROFILE_MEDIATES(tracer, AA_CLASS_PTRACE))
+ return xcheck(profile_ptrace_perm(tracer, tracee, request, sa),
+ profile_ptrace_perm(tracee, tracer,
+ request << PTRACE_PERM_SHIFT,
+ sa));
+ /* policy uses the old style capability check for ptrace */
+ if (profile_unconfined(tracer) || tracer == tracee)
return 0;
- /* log this capability request */
- return aa_capable(tracer, CAP_SYS_PTRACE, 1);
+
+ aad(sa)->label = &tracer->label;
+ aad(sa)->peer = &tracee->label;
+ aad(sa)->request = 0;
+ aad(sa)->error = aa_capable(&tracer->label, CAP_SYS_PTRACE, 1);
+
+ return aa_audit(AUDIT_APPARMOR_AUTO, tracer, sa, audit_ptrace_cb);
}
/**
- * aa_ptrace - do ptrace permission check and auditing
- * @tracer: task doing the tracing (NOT NULL)
- * @tracee: task being traced (NOT NULL)
- * @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH
+ * aa_may_ptrace - test if tracer task can trace the tracee
+ * @tracer: label of the task doing the tracing (NOT NULL)
+ * @tracee: task label to be traced
+ * @request: permission request
*
* Returns: %0 else error code if permission denied or error
*/
-int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
- unsigned int mode)
+int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee,
+ u32 request)
{
- /*
- * tracer can ptrace tracee when
- * - tracer is unconfined ||
- * - tracer is in complain mode
- * - tracer has rules allowing it to trace tracee currently this is:
- * - confined by the same profile ||
- * - tracer profile has CAP_SYS_PTRACE
- */
-
- struct aa_profile *tracer_p = aa_get_task_profile(tracer);
- int error = 0;
-
- if (!unconfined(tracer_p)) {
- struct aa_profile *tracee_p = aa_get_task_profile(tracee);
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE);
- error = aa_may_ptrace(tracer_p, tracee_p, mode);
- error = aa_audit_ptrace(tracer_p, tracee_p, error);
+ return xcheck_labels_profiles(tracer, tracee, cross_ptrace_perm,
+ request, &sa);
+}
- aa_put_profile(tracee_p);
- }
- aa_put_profile(tracer_p);
- return error;
-}
diff --git a/security/apparmor/label.c b/security/apparmor/label.c
new file mode 100644
index 000000000000..e052eaba1cf6
--- /dev/null
+++ b/security/apparmor/label.c
@@ -0,0 +1,2120 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor label definitions
+ *
+ * Copyright 2017 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#include <linux/audit.h>
+#include <linux/seq_file.h>
+#include <linux/sort.h>
+
+#include "include/apparmor.h"
+#include "include/context.h"
+#include "include/label.h"
+#include "include/policy.h"
+#include "include/secid.h"
+
+
+/*
+ * the aa_label represents the set of profiles confining an object
+ *
+ * Labels maintain a reference count to the set of pointers they reference
+ * Labels are ref counted by
+ * tasks and object via the security field/security context off the field
+ * code - will take a ref count on a label if it needs the label
+ * beyond what is possible with an rcu_read_lock.
+ * profiles - each profile is a label
+ * secids - a pinned secid will keep a refcount of the label it is
+ * referencing
+ * objects - inode, files, sockets, ...
+ *
+ * Labels are not ref counted by the label set, so they maybe removed and
+ * freed when no longer in use.
+ *
+ */
+
+#define PROXY_POISON 97
+#define LABEL_POISON 100
+
+static void free_proxy(struct aa_proxy *proxy)
+{
+ if (proxy) {
+ /* p->label will not updated any more as p is dead */
+ aa_put_label(rcu_dereference_protected(proxy->label, true));
+ memset(proxy, 0, sizeof(*proxy));
+ proxy->label = (struct aa_label *) PROXY_POISON;
+ kfree(proxy);
+ }
+}
+
+void aa_proxy_kref(struct kref *kref)
+{
+ struct aa_proxy *proxy = container_of(kref, struct aa_proxy, count);
+
+ free_proxy(proxy);
+}
+
+struct aa_proxy *aa_alloc_proxy(struct aa_label *label, gfp_t gfp)
+{
+ struct aa_proxy *new;
+
+ new = kzalloc(sizeof(struct aa_proxy), gfp);
+ if (new) {
+ kref_init(&new->count);
+ rcu_assign_pointer(new->label, aa_get_label(label));
+ }
+ return new;
+}
+
+/* requires profile list write lock held */
+void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new)
+{
+ struct aa_label *tmp;
+
+ AA_BUG(!orig);
+ AA_BUG(!new);
+ AA_BUG(!write_is_locked(&labels_set(orig)->lock));
+
+ tmp = rcu_dereference_protected(orig->proxy->label,
+ &labels_ns(orig)->lock);
+ rcu_assign_pointer(orig->proxy->label, aa_get_label(new));
+ orig->flags |= FLAG_STALE;
+ aa_put_label(tmp);
+}
+
+static void __proxy_share(struct aa_label *old, struct aa_label *new)
+{
+ struct aa_proxy *proxy = new->proxy;
+
+ new->proxy = aa_get_proxy(old->proxy);
+ __aa_proxy_redirect(old, new);
+ aa_put_proxy(proxy);
+}
+
+
+/**
+ * ns_cmp - compare ns for label set ordering
+ * @a: ns to compare (NOT NULL)
+ * @b: ns to compare (NOT NULL)
+ *
+ * Returns: <0 if a < b
+ * ==0 if a == b
+ * >0 if a > b
+ */
+static int ns_cmp(struct aa_ns *a, struct aa_ns *b)
+{
+ int res;
+
+ AA_BUG(!a);
+ AA_BUG(!b);
+ AA_BUG(!a->base.hname);
+ AA_BUG(!b->base.hname);
+
+ if (a == b)
+ return 0;
+
+ res = a->level - b->level;
+ if (res)
+ return res;
+
+ return strcmp(a->base.hname, b->base.hname);
+}
+
+/**
+ * profile_cmp - profile comparision for set ordering
+ * @a: profile to compare (NOT NULL)
+ * @b: profile to compare (NOT NULL)
+ *
+ * Returns: <0 if a < b
+ * ==0 if a == b
+ * >0 if a > b
+ */
+static int profile_cmp(struct aa_profile *a, struct aa_profile *b)
+{
+ int res;
+
+ AA_BUG(!a);
+ AA_BUG(!b);
+ AA_BUG(!a->ns);
+ AA_BUG(!b->ns);
+ AA_BUG(!a->base.hname);
+ AA_BUG(!b->base.hname);
+
+ if (a == b || a->base.hname == b->base.hname)
+ return 0;
+ res = ns_cmp(a->ns, b->ns);
+ if (res)
+ return res;
+
+ return strcmp(a->base.hname, b->base.hname);
+}
+
+/**
+ * vec_cmp - label comparision for set ordering
+ * @a: label to compare (NOT NULL)
+ * @vec: vector of profiles to compare (NOT NULL)
+ * @n: length of @vec
+ *
+ * Returns: <0 if a < vec
+ * ==0 if a == vec
+ * >0 if a > vec
+ */
+static int vec_cmp(struct aa_profile **a, int an, struct aa_profile **b, int bn)
+{
+ int i;
+
+ AA_BUG(!a);
+ AA_BUG(!*a);
+ AA_BUG(!b);
+ AA_BUG(!*b);
+ AA_BUG(an <= 0);
+ AA_BUG(bn <= 0);
+
+ for (i = 0; i < an && i < bn; i++) {
+ int res = profile_cmp(a[i], b[i]);
+
+ if (res != 0)
+ return res;
+ }
+
+ return an - bn;
+}
+
+static bool vec_is_stale(struct aa_profile **vec, int n)
+{
+ int i;
+
+ AA_BUG(!vec);
+
+ for (i = 0; i < n; i++) {
+ if (profile_is_stale(vec[i]))
+ return true;
+ }
+
+ return false;
+}
+
+static bool vec_unconfined(struct aa_profile **vec, int n)
+{
+ int i;
+
+ AA_BUG(!vec);
+
+ for (i = 0; i < n; i++) {
+ if (!profile_unconfined(vec[i]))
+ return false;
+ }
+
+ return true;
+}
+
+static int sort_cmp(const void *a, const void *b)
+{
+ return profile_cmp(*(struct aa_profile **)a, *(struct aa_profile **)b);
+}
+
+/*
+ * assumes vec is sorted
+ * Assumes @vec has null terminator at vec[n], and will null terminate
+ * vec[n - dups]
+ */
+static inline int unique(struct aa_profile **vec, int n)
+{
+ int i, pos, dups = 0;
+
+ AA_BUG(n < 1);
+ AA_BUG(!vec);
+
+ pos = 0;
+ for (i = 1; i < n; i++) {
+ int res = profile_cmp(vec[pos], vec[i]);
+
+ AA_BUG(res > 0, "vec not sorted");
+ if (res == 0) {
+ /* drop duplicate */
+ aa_put_profile(vec[i]);
+ dups++;
+ continue;
+ }
+ pos++;
+ if (dups)
+ vec[pos] = vec[i];
+ }
+
+ AA_BUG(dups < 0);
+
+ return dups;
+}
+
+/**
+ * aa_vec_unique - canonical sort and unique a list of profiles
+ * @n: number of refcounted profiles in the list (@n > 0)
+ * @vec: list of profiles to sort and merge
+ *
+ * Returns: the number of duplicates eliminated == references put
+ *
+ * If @flags & VEC_FLAG_TERMINATE @vec has null terminator at vec[n], and will
+ * null terminate vec[n - dups]
+ */
+int aa_vec_unique(struct aa_profile **vec, int n, int flags)
+{
+ int i, dups = 0;
+
+ AA_BUG(n < 1);
+ AA_BUG(!vec);
+
+ /* vecs are usually small and inorder, have a fallback for larger */
+ if (n > 8) {
+ sort(vec, n, sizeof(struct aa_profile *), sort_cmp, NULL);
+ dups = unique(vec, n);
+ goto out;
+ }
+
+ /* insertion sort + unique in one */
+ for (i = 1; i < n; i++) {
+ struct aa_profile *tmp = vec[i];
+ int pos, j;
+
+ for (pos = i - 1 - dups; pos >= 0; pos--) {
+ int res = profile_cmp(vec[pos], tmp);
+
+ if (res == 0) {
+ /* drop duplicate entry */
+ aa_put_profile(tmp);
+ dups++;
+ goto continue_outer;
+ } else if (res < 0)
+ break;
+ }
+ /* pos is at entry < tmp, or index -1. Set to insert pos */
+ pos++;
+
+ for (j = i - dups; j > pos; j--)
+ vec[j] = vec[j - 1];
+ vec[pos] = tmp;
+continue_outer:
+ ;
+ }
+
+ AA_BUG(dups < 0);
+
+out:
+ if (flags & VEC_FLAG_TERMINATE)
+ vec[n - dups] = NULL;
+
+ return dups;
+}
+
+
+static void label_destroy(struct aa_label *label)
+{
+ struct aa_label *tmp;
+
+ AA_BUG(!label);
+
+ if (!label_isprofile(label)) {
+ struct aa_profile *profile;
+ struct label_it i;
+
+ aa_put_str(label->hname);
+
+ label_for_each(i, label, profile) {
+ aa_put_profile(profile);
+ label->vec[i.i] = (struct aa_profile *)
+ (LABEL_POISON + (long) i.i);
+ }
+ }
+
+ if (rcu_dereference_protected(label->proxy->label, true) == label)
+ rcu_assign_pointer(label->proxy->label, NULL);
+
+ aa_free_secid(label->secid);
+
+ tmp = rcu_dereference_protected(label->proxy->label, true);
+ if (tmp == label)
+ rcu_assign_pointer(label->proxy->label, NULL);
+
+ aa_put_proxy(label->proxy);
+ label->proxy = (struct aa_proxy *) PROXY_POISON + 1;
+}
+
+void aa_label_free(struct aa_label *label)
+{
+ if (!label)
+ return;
+
+ label_destroy(label);
+ kfree(label);
+}
+
+static void label_free_switch(struct aa_label *label)
+{
+ if (label->flags & FLAG_NS_COUNT)
+ aa_free_ns(labels_ns(label));
+ else if (label_isprofile(label))
+ aa_free_profile(labels_profile(label));
+ else
+ aa_label_free(label);
+}
+
+static void label_free_rcu(struct rcu_head *head)
+{
+ struct aa_label *label = container_of(head, struct aa_label, rcu);
+
+ if (label->flags & FLAG_IN_TREE)
+ (void) aa_label_remove(label);
+ label_free_switch(label);
+}
+
+void aa_label_kref(struct kref *kref)
+{
+ struct aa_label *label = container_of(kref, struct aa_label, count);
+ struct aa_ns *ns = labels_ns(label);
+
+ if (!ns) {
+ /* never live, no rcu callback needed, just using the fn */
+ label_free_switch(label);
+ return;
+ }
+ /* TODO: update labels_profile macro so it works here */
+ AA_BUG(label_isprofile(label) &&
+ on_list_rcu(&label->vec[0]->base.profiles));
+ AA_BUG(label_isprofile(label) &&
+ on_list_rcu(&label->vec[0]->base.list));
+
+ /* TODO: if compound label and not stale add to reclaim cache */
+ call_rcu(&label->rcu, label_free_rcu);
+}
+
+static void label_free_or_put_new(struct aa_label *label, struct aa_label *new)
+{
+ if (label != new)
+ /* need to free directly to break circular ref with proxy */
+ aa_label_free(new);
+ else
+ aa_put_label(new);
+}
+
+bool aa_label_init(struct aa_label *label, int size)
+{
+ AA_BUG(!label);
+ AA_BUG(size < 1);
+
+ label->secid = aa_alloc_secid();
+ if (label->secid == AA_SECID_INVALID)
+ return false;
+
+ label->size = size; /* doesn't include null */
+ label->vec[size] = NULL; /* null terminate */
+ kref_init(&label->count);
+ RB_CLEAR_NODE(&label->node);
+
+ return true;
+}
+
+/**
+ * aa_label_alloc - allocate a label with a profile vector of @size length
+ * @size: size of profile vector in the label
+ * @proxy: proxy to use OR null if to allocate a new one
+ * @gfp: memory allocation type
+ *
+ * Returns: new label
+ * else NULL if failed
+ */
+struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp)
+{
+ struct aa_label *new;
+
+ AA_BUG(size < 1);
+
+ /* + 1 for null terminator entry on vec */
+ new = kzalloc(sizeof(*new) + sizeof(struct aa_profile *) * (size + 1),
+ gfp);
+ AA_DEBUG("%s (%p)\n", __func__, new);
+ if (!new)
+ goto fail;
+
+ if (!aa_label_init(new, size))
+ goto fail;
+
+ if (!proxy) {
+ proxy = aa_alloc_proxy(new, gfp);
+ if (!proxy)
+ goto fail;
+ } else
+ aa_get_proxy(proxy);
+ /* just set new's proxy, don't redirect proxy here if it was passed in*/
+ new->proxy = proxy;
+
+ return new;
+
+fail:
+ kfree(new);
+
+ return NULL;
+}
+
+
+/**
+ * label_cmp - label comparision for set ordering
+ * @a: label to compare (NOT NULL)
+ * @b: label to compare (NOT NULL)
+ *
+ * Returns: <0 if a < b
+ * ==0 if a == b
+ * >0 if a > b
+ */
+static int label_cmp(struct aa_label *a, struct aa_label *b)
+{
+ AA_BUG(!b);
+
+ if (a == b)
+ return 0;
+
+ return vec_cmp(a->vec, a->size, b->vec, b->size);
+}
+
+/* helper fn for label_for_each_confined */
+int aa_label_next_confined(struct aa_label *label, int i)
+{
+ AA_BUG(!label);
+ AA_BUG(i < 0);
+
+ for (; i < label->size; i++) {
+ if (!profile_unconfined(label->vec[i]))
+ return i;
+ }
+
+ return i;
+}
+
+/**
+ * aa_label_next_not_in_set - return the next profile of @sub not in @set
+ * @I: label iterator
+ * @set: label to test against
+ * @sub: label to if is subset of @set
+ *
+ * Returns: profile in @sub that is not in @set, with iterator set pos after
+ * else NULL if @sub is a subset of @set
+ */
+struct aa_profile *__aa_label_next_not_in_set(struct label_it *I,
+ struct aa_label *set,
+ struct aa_label *sub)
+{
+ AA_BUG(!set);
+ AA_BUG(!I);
+ AA_BUG(I->i < 0);
+ AA_BUG(I->i > set->size);
+ AA_BUG(!sub);
+ AA_BUG(I->j < 0);
+ AA_BUG(I->j > sub->size);
+
+ while (I->j < sub->size && I->i < set->size) {
+ int res = profile_cmp(sub->vec[I->j], set->vec[I->i]);
+
+ if (res == 0) {
+ (I->j)++;
+ (I->i)++;
+ } else if (res > 0)
+ (I->i)++;
+ else
+ return sub->vec[(I->j)++];
+ }
+
+ if (I->j < sub->size)
+ return sub->vec[(I->j)++];
+
+ return NULL;
+}
+
+/**
+ * aa_label_is_subset - test if @sub is a subset of @set
+ * @set: label to test against
+ * @sub: label to test if is subset of @set
+ *
+ * Returns: true if @sub is subset of @set
+ * else false
+ */
+bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub)
+{
+ struct label_it i = { };
+
+ AA_BUG(!set);
+ AA_BUG(!sub);
+
+ if (sub == set)
+ return true;
+
+ return __aa_label_next_not_in_set(&i, set, sub) == NULL;
+}
+
+
+
+/**
+ * __label_remove - remove @label from the label set
+ * @l: label to remove
+ * @new: label to redirect to
+ *
+ * Requires: labels_set(@label)->lock write_lock
+ * Returns: true if the label was in the tree and removed
+ */
+static bool __label_remove(struct aa_label *label, struct aa_label *new)
+{
+ struct aa_labelset *ls = labels_set(label);
+
+ AA_BUG(!ls);
+ AA_BUG(!label);
+ AA_BUG(!write_is_locked(&ls->lock));
+
+ if (new)
+ __aa_proxy_redirect(label, new);
+
+ if (!label_is_stale(label))
+ __label_make_stale(label);
+
+ if (label->flags & FLAG_IN_TREE) {
+ rb_erase(&label->node, &ls->root);
+ label->flags &= ~FLAG_IN_TREE;
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * __label_replace - replace @old with @new in label set
+ * @old: label to remove from label set
+ * @new: label to replace @old with
+ *
+ * Requires: labels_set(@old)->lock write_lock
+ * valid ref count be held on @new
+ * Returns: true if @old was in set and replaced by @new
+ *
+ * Note: current implementation requires label set be order in such a way
+ * that @new directly replaces @old position in the set (ie.
+ * using pointer comparison of the label address would not work)
+ */
+static bool __label_replace(struct aa_label *old, struct aa_label *new)
+{
+ struct aa_labelset *ls = labels_set(old);
+
+ AA_BUG(!ls);
+ AA_BUG(!old);
+ AA_BUG(!new);
+ AA_BUG(!write_is_locked(&ls->lock));
+ AA_BUG(new->flags & FLAG_IN_TREE);
+
+ if (!label_is_stale(old))
+ __label_make_stale(old);
+
+ if (old->flags & FLAG_IN_TREE) {
+ rb_replace_node(&old->node, &new->node, &ls->root);
+ old->flags &= ~FLAG_IN_TREE;
+ new->flags |= FLAG_IN_TREE;
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * __label_insert - attempt to insert @l into a label set
+ * @ls: set of labels to insert @l into (NOT NULL)
+ * @label: new label to insert (NOT NULL)
+ * @replace: whether insertion should replace existing entry that is not stale
+ *
+ * Requires: @ls->lock
+ * caller to hold a valid ref on l
+ * if @replace is true l has a preallocated proxy associated
+ * Returns: @l if successful in inserting @l - with additional refcount
+ * else ref counted equivalent label that is already in the set,
+ * the else condition only happens if @replace is false
+ */
+static struct aa_label *__label_insert(struct aa_labelset *ls,
+ struct aa_label *label, bool replace)
+{
+ struct rb_node **new, *parent = NULL;
+
+ AA_BUG(!ls);
+ AA_BUG(!label);
+ AA_BUG(labels_set(label) != ls);
+ AA_BUG(!write_is_locked(&ls->lock));
+ AA_BUG(label->flags & FLAG_IN_TREE);
+
+ /* Figure out where to put new node */
+ new = &ls->root.rb_node;
+ while (*new) {
+ struct aa_label *this = rb_entry(*new, struct aa_label, node);
+ int result = label_cmp(label, this);
+
+ parent = *new;
+ if (result == 0) {
+ /* !__aa_get_label means queued for destruction,
+ * so replace in place, however the label has
+ * died before the replacement so do not share
+ * the proxy
+ */
+ if (!replace && !label_is_stale(this)) {
+ if (__aa_get_label(this))
+ return this;
+ } else
+ __proxy_share(this, label);
+ AA_BUG(!__label_replace(this, label));
+ return aa_get_label(label);
+ } else if (result < 0)
+ new = &((*new)->rb_left);
+ else /* (result > 0) */
+ new = &((*new)->rb_right);
+ }
+
+ /* Add new node and rebalance tree. */
+ rb_link_node(&label->node, parent, new);
+ rb_insert_color(&label->node, &ls->root);
+ label->flags |= FLAG_IN_TREE;
+
+ return aa_get_label(label);
+}
+
+/**
+ * __vec_find - find label that matches @vec in label set
+ * @vec: vec of profiles to find matching label for (NOT NULL)
+ * @n: length of @vec
+ *
+ * Requires: @vec_labelset(vec) lock held
+ * caller to hold a valid ref on l
+ *
+ * Returns: ref counted @label if matching label is in tree
+ * ref counted label that is equiv to @l in tree
+ * else NULL if @vec equiv is not in tree
+ */
+static struct aa_label *__vec_find(struct aa_profile **vec, int n)
+{
+ struct rb_node *node;
+
+ AA_BUG(!vec);
+ AA_BUG(!*vec);
+ AA_BUG(n <= 0);
+
+ node = vec_labelset(vec, n)->root.rb_node;
+ while (node) {
+ struct aa_label *this = rb_entry(node, struct aa_label, node);
+ int result = vec_cmp(this->vec, this->size, vec, n);
+
+ if (result > 0)
+ node = node->rb_left;
+ else if (result < 0)
+ node = node->rb_right;
+ else
+ return __aa_get_label(this);
+ }
+
+ return NULL;
+}
+
+/**
+ * __label_find - find label @label in label set
+ * @label: label to find (NOT NULL)
+ *
+ * Requires: labels_set(@label)->lock held
+ * caller to hold a valid ref on l
+ *
+ * Returns: ref counted @label if @label is in tree OR
+ * ref counted label that is equiv to @label in tree
+ * else NULL if @label or equiv is not in tree
+ */
+static struct aa_label *__label_find(struct aa_label *label)
+{
+ AA_BUG(!label);
+
+ return __vec_find(label->vec, label->size);
+}
+
+
+/**
+ * aa_label_remove - remove a label from the labelset
+ * @label: label to remove
+ *
+ * Returns: true if @label was removed from the tree
+ * else @label was not in tree so it could not be removed
+ */
+bool aa_label_remove(struct aa_label *label)
+{
+ struct aa_labelset *ls = labels_set(label);
+ unsigned long flags;
+ bool res;
+
+ AA_BUG(!ls);
+
+ write_lock_irqsave(&ls->lock, flags);
+ res = __label_remove(label, ns_unconfined(labels_ns(label)));
+ write_unlock_irqrestore(&ls->lock, flags);
+
+ return res;
+}
+
+/**
+ * aa_label_replace - replace a label @old with a new version @new
+ * @old: label to replace
+ * @new: label replacing @old
+ *
+ * Returns: true if @old was in tree and replaced
+ * else @old was not in tree, and @new was not inserted
+ */
+bool aa_label_replace(struct aa_label *old, struct aa_label *new)
+{
+ unsigned long flags;
+ bool res;
+
+ if (name_is_shared(old, new) && labels_ns(old) == labels_ns(new)) {
+ write_lock_irqsave(&labels_set(old)->lock, flags);
+ if (old->proxy != new->proxy)
+ __proxy_share(old, new);
+ else
+ __aa_proxy_redirect(old, new);
+ res = __label_replace(old, new);
+ write_unlock_irqrestore(&labels_set(old)->lock, flags);
+ } else {
+ struct aa_label *l;
+ struct aa_labelset *ls = labels_set(old);
+
+ write_lock_irqsave(&ls->lock, flags);
+ res = __label_remove(old, new);
+ if (labels_ns(old) != labels_ns(new)) {
+ write_unlock_irqrestore(&ls->lock, flags);
+ ls = labels_set(new);
+ write_lock_irqsave(&ls->lock, flags);
+ }
+ l = __label_insert(ls, new, true);
+ res = (l == new);
+ write_unlock_irqrestore(&ls->lock, flags);
+ aa_put_label(l);
+ }
+
+ return res;
+}
+
+/**
+ * vec_find - find label @l in label set
+ * @vec: array of profiles to find equiv label for (NOT NULL)
+ * @n: length of @vec
+ *
+ * Returns: refcounted label if @vec equiv is in tree
+ * else NULL if @vec equiv is not in tree
+ */
+static struct aa_label *vec_find(struct aa_profile **vec, int n)
+{
+ struct aa_labelset *ls;
+ struct aa_label *label;
+ unsigned long flags;
+
+ AA_BUG(!vec);
+ AA_BUG(!*vec);
+ AA_BUG(n <= 0);
+
+ ls = vec_labelset(vec, n);
+ read_lock_irqsave(&ls->lock, flags);
+ label = __vec_find(vec, n);
+ read_unlock_irqrestore(&ls->lock, flags);
+
+ return label;
+}
+
+/* requires sort and merge done first */
+static struct aa_label *vec_create_and_insert_label(struct aa_profile **vec,
+ int len, gfp_t gfp)
+{
+ struct aa_label *label = NULL;
+ struct aa_labelset *ls;
+ unsigned long flags;
+ struct aa_label *new;
+ int i;
+
+ AA_BUG(!vec);
+
+ if (len == 1)
+ return aa_get_label(&vec[0]->label);
+
+ ls = labels_set(&vec[len - 1]->label);
+
+ /* TODO: enable when read side is lockless
+ * check if label exists before taking locks
+ */
+ new = aa_label_alloc(len, NULL, gfp);
+ if (!new)
+ return NULL;
+
+ for (i = 0; i < len; i++)
+ new->vec[i] = aa_get_profile(vec[i]);
+
+ write_lock_irqsave(&ls->lock, flags);
+ label = __label_insert(ls, new, false);
+ write_unlock_irqrestore(&ls->lock, flags);
+ label_free_or_put_new(label, new);
+
+ return label;
+}
+
+struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len,
+ gfp_t gfp)
+{
+ struct aa_label *label = vec_find(vec, len);
+
+ if (label)
+ return label;
+
+ return vec_create_and_insert_label(vec, len, gfp);
+}
+
+/**
+ * aa_label_find - find label @label in label set
+ * @label: label to find (NOT NULL)
+ *
+ * Requires: caller to hold a valid ref on l
+ *
+ * Returns: refcounted @label if @label is in tree
+ * refcounted label that is equiv to @label in tree
+ * else NULL if @label or equiv is not in tree
+ */
+struct aa_label *aa_label_find(struct aa_label *label)
+{
+ AA_BUG(!label);
+
+ return vec_find(label->vec, label->size);
+}
+
+
+/**
+ * aa_label_insert - insert label @label into @ls or return existing label
+ * @ls - labelset to insert @label into
+ * @label - label to insert
+ *
+ * Requires: caller to hold a valid ref on @label
+ *
+ * Returns: ref counted @label if successful in inserting @label
+ * else ref counted equivalent label that is already in the set
+ */
+struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *label)
+{
+ struct aa_label *l;
+ unsigned long flags;
+
+ AA_BUG(!ls);
+ AA_BUG(!label);
+
+ /* check if label exists before taking lock */
+ if (!label_is_stale(label)) {
+ read_lock_irqsave(&ls->lock, flags);
+ l = __label_find(label);
+ read_unlock_irqrestore(&ls->lock, flags);
+ if (l)
+ return l;
+ }
+
+ write_lock_irqsave(&ls->lock, flags);
+ l = __label_insert(ls, label, false);
+ write_unlock_irqrestore(&ls->lock, flags);
+
+ return l;
+}
+
+
+/**
+ * aa_label_next_in_merge - find the next profile when merging @a and @b
+ * @I: label iterator
+ * @a: label to merge
+ * @b: label to merge
+ *
+ * Returns: next profile
+ * else null if no more profiles
+ */
+struct aa_profile *aa_label_next_in_merge(struct label_it *I,
+ struct aa_label *a,
+ struct aa_label *b)
+{
+ AA_BUG(!a);
+ AA_BUG(!b);
+ AA_BUG(!I);
+ AA_BUG(I->i < 0);
+ AA_BUG(I->i > a->size);
+ AA_BUG(I->j < 0);
+ AA_BUG(I->j > b->size);
+
+ if (I->i < a->size) {
+ if (I->j < b->size) {
+ int res = profile_cmp(a->vec[I->i], b->vec[I->j]);
+
+ if (res > 0)
+ return b->vec[(I->j)++];
+ if (res == 0)
+ (I->j)++;
+ }
+
+ return a->vec[(I->i)++];
+ }
+
+ if (I->j < b->size)
+ return b->vec[(I->j)++];
+
+ return NULL;
+}
+
+/**
+ * label_merge_cmp - cmp of @a merging with @b against @z for set ordering
+ * @a: label to merge then compare (NOT NULL)
+ * @b: label to merge then compare (NOT NULL)
+ * @z: label to compare merge against (NOT NULL)
+ *
+ * Assumes: using the most recent versions of @a, @b, and @z
+ *
+ * Returns: <0 if a < b
+ * ==0 if a == b
+ * >0 if a > b
+ */
+static int label_merge_cmp(struct aa_label *a, struct aa_label *b,
+ struct aa_label *z)
+{
+ struct aa_profile *p = NULL;
+ struct label_it i = { };
+ int k;
+
+ AA_BUG(!a);
+ AA_BUG(!b);
+ AA_BUG(!z);
+
+ for (k = 0;
+ k < z->size && (p = aa_label_next_in_merge(&i, a, b));
+ k++) {
+ int res = profile_cmp(p, z->vec[k]);
+
+ if (res != 0)
+ return res;
+ }
+
+ if (p)
+ return 1;
+ else if (k < z->size)
+ return -1;
+ return 0;
+}
+
+/**
+ * label_merge_insert - create a new label by merging @a and @b
+ * @new: preallocated label to merge into (NOT NULL)
+ * @a: label to merge with @b (NOT NULL)
+ * @b: label to merge with @a (NOT NULL)
+ *
+ * Requires: preallocated proxy
+ *
+ * Returns: ref counted label either @new if merge is unique
+ * @a if @b is a subset of @a
+ * @b if @a is a subset of @b
+ *
+ * NOTE: will not use @new if the merge results in @new == @a or @b
+ *
+ * Must be used within labelset write lock to avoid racing with
+ * setting labels stale.
+ */
+static struct aa_label *label_merge_insert(struct aa_label *new,
+ struct aa_label *a,
+ struct aa_label *b)
+{
+ struct aa_label *label;
+ struct aa_labelset *ls;
+ struct aa_profile *next;
+ struct label_it i;
+ unsigned long flags;
+ int k = 0, invcount = 0;
+ bool stale = false;
+
+ AA_BUG(!a);
+ AA_BUG(a->size < 0);
+ AA_BUG(!b);
+ AA_BUG(b->size < 0);
+ AA_BUG(!new);
+ AA_BUG(new->size < a->size + b->size);
+
+ label_for_each_in_merge(i, a, b, next) {
+ AA_BUG(!next);
+ if (profile_is_stale(next)) {
+ new->vec[k] = aa_get_newest_profile(next);
+ AA_BUG(!new->vec[k]->label.proxy);
+ AA_BUG(!new->vec[k]->label.proxy->label);
+ if (next->label.proxy != new->vec[k]->label.proxy)
+ invcount++;
+ k++;
+ stale = true;
+ } else
+ new->vec[k++] = aa_get_profile(next);
+ }
+ /* set to actual size which is <= allocated len */
+ new->size = k;
+ new->vec[k] = NULL;
+
+ if (invcount) {
+ new->size -= aa_vec_unique(&new->vec[0], new->size,
+ VEC_FLAG_TERMINATE);
+ /* TODO: deal with reference labels */
+ if (new->size == 1) {
+ label = aa_get_label(&new->vec[0]->label);
+ return label;
+ }
+ } else if (!stale) {
+ /*
+ * merge could be same as a || b, note: it is not possible
+ * for new->size == a->size == b->size unless a == b
+ */
+ if (k == a->size)
+ return aa_get_label(a);
+ else if (k == b->size)
+ return aa_get_label(b);
+ }
+ if (vec_unconfined(new->vec, new->size))
+ new->flags |= FLAG_UNCONFINED;
+ ls = labels_set(new);
+ write_lock_irqsave(&ls->lock, flags);
+ label = __label_insert(labels_set(new), new, false);
+ write_unlock_irqrestore(&ls->lock, flags);
+
+ return label;
+}
+
+/**
+ * labelset_of_merge - find which labelset a merged label should be inserted
+ * @a: label to merge and insert
+ * @b: label to merge and insert
+ *
+ * Returns: labelset that the merged label should be inserted into
+ */
+static struct aa_labelset *labelset_of_merge(struct aa_label *a,
+ struct aa_label *b)
+{
+ struct aa_ns *nsa = labels_ns(a);
+ struct aa_ns *nsb = labels_ns(b);
+
+ if (ns_cmp(nsa, nsb) <= 0)
+ return &nsa->labels;
+ return &nsb->labels;
+}
+
+/**
+ * __label_find_merge - find label that is equiv to merge of @a and @b
+ * @ls: set of labels to search (NOT NULL)
+ * @a: label to merge with @b (NOT NULL)
+ * @b: label to merge with @a (NOT NULL)
+ *
+ * Requires: ls->lock read_lock held
+ *
+ * Returns: ref counted label that is equiv to merge of @a and @b
+ * else NULL if merge of @a and @b is not in set
+ */
+static struct aa_label *__label_find_merge(struct aa_labelset *ls,
+ struct aa_label *a,
+ struct aa_label *b)
+{
+ struct rb_node *node;
+
+ AA_BUG(!ls);
+ AA_BUG(!a);
+ AA_BUG(!b);
+
+ if (a == b)
+ return __label_find(a);
+
+ node = ls->root.rb_node;
+ while (node) {
+ struct aa_label *this = container_of(node, struct aa_label,
+ node);
+ int result = label_merge_cmp(a, b, this);
+
+ if (result < 0)
+ node = node->rb_left;
+ else if (result > 0)
+ node = node->rb_right;
+ else
+ return __aa_get_label(this);
+ }
+
+ return NULL;
+}
+
+
+/**
+ * aa_label_find_merge - find label that is equiv to merge of @a and @b
+ * @a: label to merge with @b (NOT NULL)
+ * @b: label to merge with @a (NOT NULL)
+ *
+ * Requires: labels be fully constructed with a valid ns
+ *
+ * Returns: ref counted label that is equiv to merge of @a and @b
+ * else NULL if merge of @a and @b is not in set
+ */
+struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b)
+{
+ struct aa_labelset *ls;
+ struct aa_label *label, *ar = NULL, *br = NULL;
+ unsigned long flags;
+
+ AA_BUG(!a);
+ AA_BUG(!b);
+
+ if (label_is_stale(a))
+ a = ar = aa_get_newest_label(a);
+ if (label_is_stale(b))
+ b = br = aa_get_newest_label(b);
+ ls = labelset_of_merge(a, b);
+ read_lock_irqsave(&ls->lock, flags);
+ label = __label_find_merge(ls, a, b);
+ read_unlock_irqrestore(&ls->lock, flags);
+ aa_put_label(ar);
+ aa_put_label(br);
+
+ return label;
+}
+
+/**
+ * aa_label_merge - attempt to insert new merged label of @a and @b
+ * @ls: set of labels to insert label into (NOT NULL)
+ * @a: label to merge with @b (NOT NULL)
+ * @b: label to merge with @a (NOT NULL)
+ * @gfp: memory allocation type
+ *
+ * Requires: caller to hold valid refs on @a and @b
+ * labels be fully constructed with a valid ns
+ *
+ * Returns: ref counted new label if successful in inserting merge of a & b
+ * else ref counted equivalent label that is already in the set.
+ * else NULL if could not create label (-ENOMEM)
+ */
+struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b,
+ gfp_t gfp)
+{
+ struct aa_label *label = NULL;
+
+ AA_BUG(!a);
+ AA_BUG(!b);
+
+ if (a == b)
+ return aa_get_newest_label(a);
+
+ /* TODO: enable when read side is lockless
+ * check if label exists before taking locks
+ if (!label_is_stale(a) && !label_is_stale(b))
+ label = aa_label_find_merge(a, b);
+ */
+
+ if (!label) {
+ struct aa_label *new;
+
+ a = aa_get_newest_label(a);
+ b = aa_get_newest_label(b);
+
+ /* could use label_merge_len(a, b), but requires double
+ * comparison for small savings
+ */
+ new = aa_label_alloc(a->size + b->size, NULL, gfp);
+ if (!new)
+ goto out;
+
+ label = label_merge_insert(new, a, b);
+ label_free_or_put_new(label, new);
+out:
+ aa_put_label(a);
+ aa_put_label(b);
+ }
+
+ return label;
+}
+
+static inline bool label_is_visible(struct aa_profile *profile,
+ struct aa_label *label)
+{
+ return aa_ns_visible(profile->ns, labels_ns(label), true);
+}
+
+/* match a profile and its associated ns component if needed
+ * Assumes visibility test has already been done.
+ * If a subns profile is not to be matched should be prescreened with
+ * visibility test.
+ */
+static inline unsigned int match_component(struct aa_profile *profile,
+ struct aa_profile *tp,
+ unsigned int state)
+{
+ const char *ns_name;
+
+ if (profile->ns == tp->ns)
+ return aa_dfa_match(profile->policy.dfa, state, tp->base.hname);
+
+ /* try matching with namespace name and then profile */
+ ns_name = aa_ns_name(profile->ns, tp->ns, true);
+ state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1);
+ state = aa_dfa_match(profile->policy.dfa, state, ns_name);
+ state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1);
+ return aa_dfa_match(profile->policy.dfa, state, tp->base.hname);
+}
+
+/**
+ * label_compound_match - find perms for full compound label
+ * @profile: profile to find perms for
+ * @label: label to check access permissions for
+ * @start: state to start match in
+ * @subns: whether to do permission checks on components in a subns
+ * @request: permissions to request
+ * @perms: perms struct to set
+ *
+ * Returns: 0 on success else ERROR
+ *
+ * For the label A//&B//&C this does the perm match for A//&B//&C
+ * @perms should be preinitialized with allperms OR a previous permission
+ * check to be stacked.
+ */
+static int label_compound_match(struct aa_profile *profile,
+ struct aa_label *label,
+ unsigned int state, bool subns, u32 request,
+ struct aa_perms *perms)
+{
+ struct aa_profile *tp;
+ struct label_it i;
+
+ /* find first subcomponent that is visible */
+ label_for_each(i, label, tp) {
+ if (!aa_ns_visible(profile->ns, tp->ns, subns))
+ continue;
+ state = match_component(profile, tp, state);
+ if (!state)
+ goto fail;
+ goto next;
+ }
+
+ /* no component visible */
+ *perms = allperms;
+ return 0;
+
+next:
+ label_for_each_cont(i, label, tp) {
+ if (!aa_ns_visible(profile->ns, tp->ns, subns))
+ continue;
+ state = aa_dfa_match(profile->policy.dfa, state, "//&");
+ state = match_component(profile, tp, state);
+ if (!state)
+ goto fail;
+ }
+ aa_compute_perms(profile->policy.dfa, state, perms);
+ aa_apply_modes_to_perms(profile, perms);
+ if ((perms->allow & request) != request)
+ return -EACCES;
+
+ return 0;
+
+fail:
+ *perms = nullperms;
+ return state;
+}
+
+/**
+ * label_components_match - find perms for all subcomponents of a label
+ * @profile: profile to find perms for
+ * @label: label to check access permissions for
+ * @start: state to start match in
+ * @subns: whether to do permission checks on components in a subns
+ * @request: permissions to request
+ * @perms: an initialized perms struct to add accumulation to
+ *
+ * Returns: 0 on success else ERROR
+ *
+ * For the label A//&B//&C this does the perm match for each of A and B and C
+ * @perms should be preinitialized with allperms OR a previous permission
+ * check to be stacked.
+ */
+static int label_components_match(struct aa_profile *profile,
+ struct aa_label *label, unsigned int start,
+ bool subns, u32 request,
+ struct aa_perms *perms)
+{
+ struct aa_profile *tp;
+ struct label_it i;
+ struct aa_perms tmp;
+ unsigned int state = 0;
+
+ /* find first subcomponent to test */
+ label_for_each(i, label, tp) {
+ if (!aa_ns_visible(profile->ns, tp->ns, subns))
+ continue;
+ state = match_component(profile, tp, start);
+ if (!state)
+ goto fail;
+ goto next;
+ }
+
+ /* no subcomponents visible - no change in perms */
+ return 0;
+
+next:
+ aa_compute_perms(profile->policy.dfa, state, &tmp);
+ aa_apply_modes_to_perms(profile, &tmp);
+ aa_perms_accum(perms, &tmp);
+ label_for_each_cont(i, label, tp) {
+ if (!aa_ns_visible(profile->ns, tp->ns, subns))
+ continue;
+ state = match_component(profile, tp, start);
+ if (!state)
+ goto fail;
+ aa_compute_perms(profile->policy.dfa, state, &tmp);
+ aa_apply_modes_to_perms(profile, &tmp);
+ aa_perms_accum(perms, &tmp);
+ }
+
+ if ((perms->allow & request) != request)
+ return -EACCES;
+
+ return 0;
+
+fail:
+ *perms = nullperms;
+ return -EACCES;
+}
+
+/**
+ * aa_label_match - do a multi-component label match
+ * @profile: profile to match against (NOT NULL)
+ * @label: label to match (NOT NULL)
+ * @state: state to start in
+ * @subns: whether to match subns components
+ * @request: permission request
+ * @perms: Returns computed perms (NOT NULL)
+ *
+ * Returns: the state the match finished in, may be the none matching state
+ */
+int aa_label_match(struct aa_profile *profile, struct aa_label *label,
+ unsigned int state, bool subns, u32 request,
+ struct aa_perms *perms)
+{
+ int error = label_compound_match(profile, label, state, subns, request,
+ perms);
+ if (!error)
+ return error;
+
+ *perms = allperms;
+ return label_components_match(profile, label, state, subns, request,
+ perms);
+}
+
+
+/**
+ * aa_update_label_name - update a label to have a stored name
+ * @ns: ns being viewed from (NOT NULL)
+ * @label: label to update (NOT NULL)
+ * @gfp: type of memory allocation
+ *
+ * Requires: labels_set(label) not locked in caller
+ *
+ * note: only updates the label name if it does not have a name already
+ * and if it is in the labelset
+ */
+bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp)
+{
+ struct aa_labelset *ls;
+ unsigned long flags;
+ char __counted *name;
+ bool res = false;
+
+ AA_BUG(!ns);
+ AA_BUG(!label);
+
+ if (label->hname || labels_ns(label) != ns)
+ return res;
+
+ if (aa_label_acntsxprint(&name, ns, label, FLAGS_NONE, gfp) == -1)
+ return res;
+
+ ls = labels_set(label);
+ write_lock_irqsave(&ls->lock, flags);
+ if (!label->hname && label->flags & FLAG_IN_TREE) {
+ label->hname = name;
+ res = true;
+ } else
+ aa_put_str(name);
+ write_unlock_irqrestore(&ls->lock, flags);
+
+ return res;
+}
+
+/*
+ * cached label name is present and visible
+ * @label->hname only exists if label is namespace hierachical
+ */
+static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label)
+{
+ if (label->hname && labels_ns(label) == ns)
+ return true;
+
+ return false;
+}
+
+/* helper macro for snprint routines */
+#define update_for_len(total, len, size, str) \
+do { \
+ AA_BUG(len < 0); \
+ total += len; \
+ len = min(len, size); \
+ size -= len; \
+ str += len; \
+} while (0)
+
+/**
+ * aa_profile_snxprint - print a profile name to a buffer
+ * @str: buffer to write to. (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @view: namespace profile is being viewed from
+ * @profile: profile to view (NOT NULL)
+ * @flags: whether to include the mode string
+ * @prev_ns: last ns printed when used in compound print
+ *
+ * Returns: size of name written or would be written if larger than
+ * available buffer
+ *
+ * Note: will not print anything if the profile is not visible
+ */
+static int aa_profile_snxprint(char *str, size_t size, struct aa_ns *view,
+ struct aa_profile *profile, int flags,
+ struct aa_ns **prev_ns)
+{
+ const char *ns_name = NULL;
+
+ AA_BUG(!str && size != 0);
+ AA_BUG(!profile);
+
+ if (!view)
+ view = profiles_ns(profile);
+
+ if (view != profile->ns &&
+ (!prev_ns || (prev_ns && *prev_ns != profile->ns))) {
+ if (prev_ns)
+ *prev_ns = profile->ns;
+ ns_name = aa_ns_name(view, profile->ns,
+ flags & FLAG_VIEW_SUBNS);
+ if (ns_name == aa_hidden_ns_name) {
+ if (flags & FLAG_HIDDEN_UNCONFINED)
+ return snprintf(str, size, "%s", "unconfined");
+ return snprintf(str, size, "%s", ns_name);
+ }
+ }
+
+ if ((flags & FLAG_SHOW_MODE) && profile != profile->ns->unconfined) {
+ const char *modestr = aa_profile_mode_names[profile->mode];
+
+ if (ns_name)
+ return snprintf(str, size, ":%s:%s (%s)", ns_name,
+ profile->base.hname, modestr);
+ return snprintf(str, size, "%s (%s)", profile->base.hname,
+ modestr);
+ }
+
+ if (ns_name)
+ return snprintf(str, size, ":%s:%s", ns_name,
+ profile->base.hname);
+ return snprintf(str, size, "%s", profile->base.hname);
+}
+
+static const char *label_modename(struct aa_ns *ns, struct aa_label *label,
+ int flags)
+{
+ struct aa_profile *profile;
+ struct label_it i;
+ int mode = -1, count = 0;
+
+ label_for_each(i, label, profile) {
+ if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) {
+ if (profile->mode == APPARMOR_UNCONFINED)
+ /* special case unconfined so stacks with
+ * unconfined don't report as mixed. ie.
+ * profile_foo//&:ns1:unconfined (mixed)
+ */
+ continue;
+ count++;
+ if (mode == -1)
+ mode = profile->mode;
+ else if (mode != profile->mode)
+ return "mixed";
+ }
+ }
+
+ if (count == 0)
+ return "-";
+ if (mode == -1)
+ /* everything was unconfined */
+ mode = APPARMOR_UNCONFINED;
+
+ return aa_profile_mode_names[mode];
+}
+
+/* if any visible label is not unconfined the display_mode returns true */
+static inline bool display_mode(struct aa_ns *ns, struct aa_label *label,
+ int flags)
+{
+ if ((flags & FLAG_SHOW_MODE)) {
+ struct aa_profile *profile;
+ struct label_it i;
+
+ label_for_each(i, label, profile) {
+ if (aa_ns_visible(ns, profile->ns,
+ flags & FLAG_VIEW_SUBNS) &&
+ profile != profile->ns->unconfined)
+ return true;
+ }
+ /* only ns->unconfined in set of profiles in ns */
+ return false;
+ }
+
+ return false;
+}
+
+/**
+ * aa_label_snxprint - print a label name to a string buffer
+ * @str: buffer to write to. (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @ns: namespace profile is being viewed from
+ * @label: label to view (NOT NULL)
+ * @flags: whether to include the mode string
+ *
+ * Returns: size of name written or would be written if larger than
+ * available buffer
+ *
+ * Note: labels do not have to be strictly hierarchical to the ns as
+ * objects may be shared across different namespaces and thus
+ * pickup labeling from each ns. If a particular part of the
+ * label is not visible it will just be excluded. And if none
+ * of the label is visible "---" will be used.
+ */
+int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns,
+ struct aa_label *label, int flags)
+{
+ struct aa_profile *profile;
+ struct aa_ns *prev_ns = NULL;
+ struct label_it i;
+ int count = 0, total = 0;
+ size_t len;
+
+ AA_BUG(!str && size != 0);
+ AA_BUG(!label);
+
+ if (!ns)
+ ns = labels_ns(label);
+
+ label_for_each(i, label, profile) {
+ if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) {
+ if (count > 0) {
+ len = snprintf(str, size, "//&");
+ update_for_len(total, len, size, str);
+ }
+ len = aa_profile_snxprint(str, size, ns, profile,
+ flags & FLAG_VIEW_SUBNS,
+ &prev_ns);
+ update_for_len(total, len, size, str);
+ count++;
+ }
+ }
+
+ if (count == 0) {
+ if (flags & FLAG_HIDDEN_UNCONFINED)
+ return snprintf(str, size, "%s", "unconfined");
+ return snprintf(str, size, "%s", aa_hidden_ns_name);
+ }
+
+ /* count == 1 && ... is for backwards compat where the mode
+ * is not displayed for 'unconfined' in the current ns
+ */
+ if (display_mode(ns, label, flags)) {
+ len = snprintf(str, size, " (%s)",
+ label_modename(ns, label, flags));
+ update_for_len(total, len, size, str);
+ }
+
+ return total;
+}
+#undef update_for_len
+
+/**
+ * aa_label_asxprint - allocate a string buffer and print label into it
+ * @strp: Returns - the allocated buffer with the label name. (NOT NULL)
+ * @ns: namespace profile is being viewed from
+ * @label: label to view (NOT NULL)
+ * @flags: flags controlling what label info is printed
+ * @gfp: kernel memory allocation type
+ *
+ * Returns: size of name written or would be written if larger than
+ * available buffer
+ */
+int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label,
+ int flags, gfp_t gfp)
+{
+ int size;
+
+ AA_BUG(!strp);
+ AA_BUG(!label);
+
+ size = aa_label_snxprint(NULL, 0, ns, label, flags);
+ if (size < 0)
+ return size;
+
+ *strp = kmalloc(size + 1, gfp);
+ if (!*strp)
+ return -ENOMEM;
+ return aa_label_snxprint(*strp, size + 1, ns, label, flags);
+}
+
+/**
+ * aa_label_acntsxprint - allocate a __counted string buffer and print label
+ * @strp: buffer to write to. (MAY BE NULL if @size == 0)
+ * @ns: namespace profile is being viewed from
+ * @label: label to view (NOT NULL)
+ * @flags: flags controlling what label info is printed
+ * @gfp: kernel memory allocation type
+ *
+ * Returns: size of name written or would be written if larger than
+ * available buffer
+ */
+int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns,
+ struct aa_label *label, int flags, gfp_t gfp)
+{
+ int size;
+
+ AA_BUG(!strp);
+ AA_BUG(!label);
+
+ size = aa_label_snxprint(NULL, 0, ns, label, flags);
+ if (size < 0)
+ return size;
+
+ *strp = aa_str_alloc(size + 1, gfp);
+ if (!*strp)
+ return -ENOMEM;
+ return aa_label_snxprint(*strp, size + 1, ns, label, flags);
+}
+
+
+void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns,
+ struct aa_label *label, int flags, gfp_t gfp)
+{
+ const char *str;
+ char *name = NULL;
+ int len;
+
+ AA_BUG(!ab);
+ AA_BUG(!label);
+
+ if (!ns)
+ ns = labels_ns(label);
+
+ if (!use_label_hname(ns, label) || display_mode(ns, label, flags)) {
+ len = aa_label_asxprint(&name, ns, label, flags, gfp);
+ if (len == -1) {
+ AA_DEBUG("label print error");
+ return;
+ }
+ str = name;
+ } else {
+ str = (char *) label->hname;
+ len = strlen(str);
+ }
+ if (audit_string_contains_control(str, len))
+ audit_log_n_hex(ab, str, len);
+ else
+ audit_log_n_string(ab, str, len);
+
+ kfree(name);
+}
+
+void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns,
+ struct aa_label *label, int flags, gfp_t gfp)
+{
+ AA_BUG(!f);
+ AA_BUG(!label);
+
+ if (!ns)
+ ns = labels_ns(label);
+
+ if (!use_label_hname(ns, label)) {
+ char *str;
+ int len;
+
+ len = aa_label_asxprint(&str, ns, label, flags, gfp);
+ if (len == -1) {
+ AA_DEBUG("label print error");
+ return;
+ }
+ seq_printf(f, "%s", str);
+ kfree(str);
+ } else if (display_mode(ns, label, flags))
+ seq_printf(f, "%s (%s)", label->hname,
+ label_modename(ns, label, flags));
+ else
+ seq_printf(f, "%s", label->hname);
+}
+
+void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags,
+ gfp_t gfp)
+{
+ AA_BUG(!label);
+
+ if (!ns)
+ ns = labels_ns(label);
+
+ if (!use_label_hname(ns, label)) {
+ char *str;
+ int len;
+
+ len = aa_label_asxprint(&str, ns, label, flags, gfp);
+ if (len == -1) {
+ AA_DEBUG("label print error");
+ return;
+ }
+ pr_info("%s", str);
+ kfree(str);
+ } else if (display_mode(ns, label, flags))
+ pr_info("%s (%s)", label->hname,
+ label_modename(ns, label, flags));
+ else
+ pr_info("%s", label->hname);
+}
+
+void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp)
+{
+ struct aa_ns *ns = aa_get_current_ns();
+
+ aa_label_xaudit(ab, ns, label, FLAG_VIEW_SUBNS, gfp);
+ aa_put_ns(ns);
+}
+
+void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp)
+{
+ struct aa_ns *ns = aa_get_current_ns();
+
+ aa_label_seq_xprint(f, ns, label, FLAG_VIEW_SUBNS, gfp);
+ aa_put_ns(ns);
+}
+
+void aa_label_printk(struct aa_label *label, gfp_t gfp)
+{
+ struct aa_ns *ns = aa_get_current_ns();
+
+ aa_label_xprintk(ns, label, FLAG_VIEW_SUBNS, gfp);
+ aa_put_ns(ns);
+}
+
+static int label_count_str_entries(const char *str)
+{
+ const char *split;
+ int count = 1;
+
+ AA_BUG(!str);
+
+ for (split = strstr(str, "//&"); split; split = strstr(str, "//&")) {
+ count++;
+ str = split + 3;
+ }
+
+ return count;
+}
+
+/*
+ * ensure stacks with components like
+ * :ns:A//&B
+ * have :ns: applied to both 'A' and 'B' by making the lookup relative
+ * to the base if the lookup specifies an ns, else making the stacked lookup
+ * relative to the last embedded ns in the string.
+ */
+static struct aa_profile *fqlookupn_profile(struct aa_label *base,
+ struct aa_label *currentbase,
+ const char *str, size_t n)
+{
+ const char *first = skipn_spaces(str, n);
+
+ if (first && *first == ':')
+ return aa_fqlookupn_profile(base, str, n);
+
+ return aa_fqlookupn_profile(currentbase, str, n);
+}
+
+/**
+ * aa_label_parse - parse, validate and convert a text string to a label
+ * @base: base label to use for lookups (NOT NULL)
+ * @str: null terminated text string (NOT NULL)
+ * @gfp: allocation type
+ * @create: true if should create compound labels if they don't exist
+ * @force_stack: true if should stack even if no leading &
+ *
+ * Returns: the matching refcounted label if present
+ * else ERRPTR
+ */
+struct aa_label *aa_label_parse(struct aa_label *base, const char *str,
+ gfp_t gfp, bool create, bool force_stack)
+{
+ DEFINE_VEC(profile, vec);
+ struct aa_label *label, *currbase = base;
+ int i, len, stack = 0, error;
+ char *split;
+
+ AA_BUG(!base);
+ AA_BUG(!str);
+
+ str = skip_spaces(str);
+ len = label_count_str_entries(str);
+ if (*str == '&' || force_stack) {
+ /* stack on top of base */
+ stack = base->size;
+ len += stack;
+ if (*str == '&')
+ str++;
+ }
+ error = vec_setup(profile, vec, len, gfp);
+ if (error)
+ return ERR_PTR(error);
+
+ for (i = 0; i < stack; i++)
+ vec[i] = aa_get_profile(base->vec[i]);
+
+ for (split = strstr(str, "//&"), i = stack; split && i < len; i++) {
+ vec[i] = fqlookupn_profile(base, currbase, str, split - str);
+ if (!vec[i])
+ goto fail;
+ /*
+ * if component specified a new ns it becomes the new base
+ * so that subsequent lookups are relative to it
+ */
+ if (vec[i]->ns != labels_ns(currbase))
+ currbase = &vec[i]->label;
+ str = split + 3;
+ split = strstr(str, "//&");
+ }
+ /* last element doesn't have a split */
+ if (i < len) {
+ vec[i] = fqlookupn_profile(base, currbase, str, strlen(str));
+ if (!vec[i])
+ goto fail;
+ }
+ if (len == 1)
+ /* no need to free vec as len < LOCAL_VEC_ENTRIES */
+ return &vec[0]->label;
+
+ len -= aa_vec_unique(vec, len, VEC_FLAG_TERMINATE);
+ /* TODO: deal with reference labels */
+ if (len == 1) {
+ label = aa_get_label(&vec[0]->label);
+ goto out;
+ }
+
+ if (create)
+ label = aa_vec_find_or_create_label(vec, len, gfp);
+ else
+ label = vec_find(vec, len);
+ if (!label)
+ goto fail;
+
+out:
+ /* use adjusted len from after vec_unique, not original */
+ vec_cleanup(profile, vec, len);
+ return label;
+
+fail:
+ label = ERR_PTR(-ENOENT);
+ goto out;
+}
+
+
+/**
+ * aa_labelset_destroy - remove all labels from the label set
+ * @ls: label set to cleanup (NOT NULL)
+ *
+ * Labels that are removed from the set may still exist beyond the set
+ * being destroyed depending on their reference counting
+ */
+void aa_labelset_destroy(struct aa_labelset *ls)
+{
+ struct rb_node *node;
+ unsigned long flags;
+
+ AA_BUG(!ls);
+
+ write_lock_irqsave(&ls->lock, flags);
+ for (node = rb_first(&ls->root); node; node = rb_first(&ls->root)) {
+ struct aa_label *this = rb_entry(node, struct aa_label, node);
+
+ if (labels_ns(this) != root_ns)
+ __label_remove(this,
+ ns_unconfined(labels_ns(this)->parent));
+ else
+ __label_remove(this, NULL);
+ }
+ write_unlock_irqrestore(&ls->lock, flags);
+}
+
+/*
+ * @ls: labelset to init (NOT NULL)
+ */
+void aa_labelset_init(struct aa_labelset *ls)
+{
+ AA_BUG(!ls);
+
+ rwlock_init(&ls->lock);
+ ls->root = RB_ROOT;
+}
+
+static struct aa_label *labelset_next_stale(struct aa_labelset *ls)
+{
+ struct aa_label *label;
+ struct rb_node *node;
+ unsigned long flags;
+
+ AA_BUG(!ls);
+
+ read_lock_irqsave(&ls->lock, flags);
+
+ __labelset_for_each(ls, node) {
+ label = rb_entry(node, struct aa_label, node);
+ if ((label_is_stale(label) ||
+ vec_is_stale(label->vec, label->size)) &&
+ __aa_get_label(label))
+ goto out;
+
+ }
+ label = NULL;
+
+out:
+ read_unlock_irqrestore(&ls->lock, flags);
+
+ return label;
+}
+
+/**
+ * __label_update - insert updated version of @label into labelset
+ * @label - the label to update/repace
+ *
+ * Returns: new label that is up to date
+ * else NULL on failure
+ *
+ * Requires: @ns lock be held
+ *
+ * Note: worst case is the stale @label does not get updated and has
+ * to be updated at a later time.
+ */
+static struct aa_label *__label_update(struct aa_label *label)
+{
+ struct aa_label *new, *tmp;
+ struct aa_labelset *ls;
+ unsigned long flags;
+ int i, invcount = 0;
+
+ AA_BUG(!label);
+ AA_BUG(!mutex_is_locked(&labels_ns(label)->lock));
+
+ new = aa_label_alloc(label->size, label->proxy, GFP_KERNEL);
+ if (!new)
+ return NULL;
+
+ /*
+ * while holding the ns_lock will stop profile replacement, removal,
+ * and label updates, label merging and removal can be occurring
+ */
+ ls = labels_set(label);
+ write_lock_irqsave(&ls->lock, flags);
+ for (i = 0; i < label->size; i++) {
+ AA_BUG(!label->vec[i]);
+ new->vec[i] = aa_get_newest_profile(label->vec[i]);
+ AA_BUG(!new->vec[i]);
+ AA_BUG(!new->vec[i]->label.proxy);
+ AA_BUG(!new->vec[i]->label.proxy->label);
+ if (new->vec[i]->label.proxy != label->vec[i]->label.proxy)
+ invcount++;
+ }
+
+ /* updated stale label by being removed/renamed from labelset */
+ if (invcount) {
+ new->size -= aa_vec_unique(&new->vec[0], new->size,
+ VEC_FLAG_TERMINATE);
+ /* TODO: deal with reference labels */
+ if (new->size == 1) {
+ tmp = aa_get_label(&new->vec[0]->label);
+ AA_BUG(tmp == label);
+ goto remove;
+ }
+ if (labels_set(label) != labels_set(new)) {
+ write_unlock_irqrestore(&ls->lock, flags);
+ tmp = aa_label_insert(labels_set(new), new);
+ write_lock_irqsave(&ls->lock, flags);
+ goto remove;
+ }
+ } else
+ AA_BUG(labels_ns(label) != labels_ns(new));
+
+ tmp = __label_insert(labels_set(label), new, true);
+remove:
+ /* ensure label is removed, and redirected correctly */
+ __label_remove(label, tmp);
+ write_unlock_irqrestore(&ls->lock, flags);
+ label_free_or_put_new(tmp, new);
+
+ return tmp;
+}
+
+/**
+ * __labelset_update - update labels in @ns
+ * @ns: namespace to update labels in (NOT NULL)
+ *
+ * Requires: @ns lock be held
+ *
+ * Walk the labelset ensuring that all labels are up to date and valid
+ * Any label that has a stale component is marked stale and replaced and
+ * by an updated version.
+ *
+ * If failures happen due to memory pressures then stale labels will
+ * be left in place until the next pass.
+ */
+static void __labelset_update(struct aa_ns *ns)
+{
+ struct aa_label *label;
+
+ AA_BUG(!ns);
+ AA_BUG(!mutex_is_locked(&ns->lock));
+
+ do {
+ label = labelset_next_stale(&ns->labels);
+ if (label) {
+ struct aa_label *l = __label_update(label);
+
+ aa_put_label(l);
+ aa_put_label(label);
+ }
+ } while (label);
+}
+
+/**
+ * __aa_labelset_udate_subtree - update all labels with a stale component
+ * @ns: ns to start update at (NOT NULL)
+ *
+ * Requires: @ns lock be held
+ *
+ * Invalidates labels based on @p in @ns and any children namespaces.
+ */
+void __aa_labelset_update_subtree(struct aa_ns *ns)
+{
+ struct aa_ns *child;
+
+ AA_BUG(!ns);
+ AA_BUG(!mutex_is_locked(&ns->lock));
+
+ __labelset_update(ns);
+
+ list_for_each_entry(child, &ns->sub_ns, base.list) {
+ mutex_lock(&child->lock);
+ __aa_labelset_update_subtree(child);
+ mutex_unlock(&child->lock);
+ }
+}
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c
index 7cd788a9445b..08ca26bcca77 100644
--- a/security/apparmor/lib.c
+++ b/security/apparmor/lib.c
@@ -21,8 +21,14 @@
#include "include/audit.h"
#include "include/apparmor.h"
#include "include/lib.h"
+#include "include/perms.h"
#include "include/policy.h"
+struct aa_perms nullperms;
+struct aa_perms allperms = { .allow = ALL_PERMS_MASK,
+ .quiet = ALL_PERMS_MASK,
+ .hide = ALL_PERMS_MASK };
+
/**
* aa_split_fqname - split a fqname into a profile and namespace name
* @fqname: a full qualified name in namespace profile format (NOT NULL)
@@ -69,7 +75,7 @@ char *aa_split_fqname(char *fqname, char **ns_name)
* if all whitespace will return NULL
*/
-static const char *skipn_spaces(const char *str, size_t n)
+const char *skipn_spaces(const char *str, size_t n)
{
for (; n && isspace(*str); --n)
++str;
@@ -128,11 +134,350 @@ void aa_info_message(const char *str)
printk(KERN_INFO "AppArmor: %s\n", str);
}
+__counted char *aa_str_alloc(int size, gfp_t gfp)
+{
+ struct counted_str *str;
+
+ str = kmalloc(sizeof(struct counted_str) + size, gfp);
+ if (!str)
+ return NULL;
+
+ kref_init(&str->count);
+ return str->name;
+}
+
+void aa_str_kref(struct kref *kref)
+{
+ kfree(container_of(kref, struct counted_str, count));
+}
+
+
+const char aa_file_perm_chrs[] = "xwracd km l ";
+const char *aa_file_perm_names[] = {
+ "exec",
+ "write",
+ "read",
+ "append",
+
+ "create",
+ "delete",
+ "open",
+ "rename",
+
+ "setattr",
+ "getattr",
+ "setcred",
+ "getcred",
+
+ "chmod",
+ "chown",
+ "chgrp",
+ "lock",
+
+ "mmap",
+ "mprot",
+ "link",
+ "snapshot",
+
+ "unknown",
+ "unknown",
+ "unknown",
+ "unknown",
+
+ "unknown",
+ "unknown",
+ "unknown",
+ "unknown",
+
+ "stack",
+ "change_onexec",
+ "change_profile",
+ "change_hat",
+};
+
+/**
+ * aa_perm_mask_to_str - convert a perm mask to its short string
+ * @str: character buffer to store string in (at least 10 characters)
+ * @mask: permission mask to convert
+ */
+void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask)
+{
+ unsigned int i, perm = 1;
+
+ for (i = 0; i < 32; perm <<= 1, i++) {
+ if (mask & perm)
+ *str++ = chrs[i];
+ }
+ *str = '\0';
+}
+
+void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask)
+{
+ const char *fmt = "%s";
+ unsigned int i, perm = 1;
+ bool prev = false;
+
+ for (i = 0; i < 32; perm <<= 1, i++) {
+ if (mask & perm) {
+ audit_log_format(ab, fmt, names[i]);
+ if (!prev) {
+ prev = true;
+ fmt = " %s";
+ }
+ }
+ }
+}
+
+void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
+ u32 chrsmask, const char **names, u32 namesmask)
+{
+ char str[33];
+
+ audit_log_format(ab, "\"");
+ if ((mask & chrsmask) && chrs) {
+ aa_perm_mask_to_str(str, chrs, mask & chrsmask);
+ mask &= ~chrsmask;
+ audit_log_format(ab, "%s", str);
+ if (mask & namesmask)
+ audit_log_format(ab, " ");
+ }
+ if ((mask & namesmask) && names)
+ aa_audit_perm_names(ab, names, mask & namesmask);
+ audit_log_format(ab, "\"");
+}
+
+/**
+ * aa_audit_perms_cb - generic callback fn for auditing perms
+ * @ab: audit buffer (NOT NULL)
+ * @va: audit struct to audit values of (NOT NULL)
+ */
+static void aa_audit_perms_cb(struct audit_buffer *ab, void *va)
+{
+ struct common_audit_data *sa = va;
+
+ if (aad(sa)->request) {
+ audit_log_format(ab, " requested_mask=");
+ aa_audit_perm_mask(ab, aad(sa)->request, aa_file_perm_chrs,
+ PERMS_CHRS_MASK, aa_file_perm_names,
+ PERMS_NAMES_MASK);
+ }
+ if (aad(sa)->denied) {
+ audit_log_format(ab, "denied_mask=");
+ aa_audit_perm_mask(ab, aad(sa)->denied, aa_file_perm_chrs,
+ PERMS_CHRS_MASK, aa_file_perm_names,
+ PERMS_NAMES_MASK);
+ }
+ audit_log_format(ab, " peer=");
+ aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
+ FLAGS_NONE, GFP_ATOMIC);
+}
+
+/**
+ * aa_apply_modes_to_perms - apply namespace and profile flags to perms
+ * @profile: that perms where computed from
+ * @perms: perms to apply mode modifiers to
+ *
+ * TODO: split into profile and ns based flags for when accumulating perms
+ */
+void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms)
+{
+ switch (AUDIT_MODE(profile)) {
+ case AUDIT_ALL:
+ perms->audit = ALL_PERMS_MASK;
+ /* fall through */
+ case AUDIT_NOQUIET:
+ perms->quiet = 0;
+ break;
+ case AUDIT_QUIET:
+ perms->audit = 0;
+ /* fall through */
+ case AUDIT_QUIET_DENIED:
+ perms->quiet = ALL_PERMS_MASK;
+ break;
+ }
+
+ if (KILL_MODE(profile))
+ perms->kill = ALL_PERMS_MASK;
+ else if (COMPLAIN_MODE(profile))
+ perms->complain = ALL_PERMS_MASK;
+/*
+ * TODO:
+ * else if (PROMPT_MODE(profile))
+ * perms->prompt = ALL_PERMS_MASK;
+ */
+}
+
+static u32 map_other(u32 x)
+{
+ return ((x & 0x3) << 8) | /* SETATTR/GETATTR */
+ ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */
+ ((x & 0x60) << 19); /* SETOPT/GETOPT */
+}
+
+void aa_compute_perms(struct aa_dfa *dfa, unsigned int state,
+ struct aa_perms *perms)
+{
+ perms->deny = 0;
+ perms->kill = perms->stop = 0;
+ perms->complain = perms->cond = 0;
+ perms->hide = 0;
+ perms->prompt = 0;
+ perms->allow = dfa_user_allow(dfa, state);
+ perms->audit = dfa_user_audit(dfa, state);
+ perms->quiet = dfa_user_quiet(dfa, state);
+
+ /* for v5 perm mapping in the policydb, the other set is used
+ * to extend the general perm set
+ */
+ perms->allow |= map_other(dfa_other_allow(dfa, state));
+ perms->audit |= map_other(dfa_other_audit(dfa, state));
+ perms->quiet |= map_other(dfa_other_quiet(dfa, state));
+// perms->xindex = dfa_user_xindex(dfa, state);
+}
+
+/**
+ * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms
+ * @accum - perms struct to accumulate into
+ * @addend - perms struct to add to @accum
+ */
+void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend)
+{
+ accum->deny |= addend->deny;
+ accum->allow &= addend->allow & ~addend->deny;
+ accum->audit |= addend->audit & addend->allow;
+ accum->quiet &= addend->quiet & ~addend->allow;
+ accum->kill |= addend->kill & ~addend->allow;
+ accum->stop |= addend->stop & ~addend->allow;
+ accum->complain |= addend->complain & ~addend->allow & ~addend->deny;
+ accum->cond |= addend->cond & ~addend->allow & ~addend->deny;
+ accum->hide &= addend->hide & ~addend->allow;
+ accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny;
+}
+
+/**
+ * aa_perms_accum - accumulate perms, masking off overlapping perms
+ * @accum - perms struct to accumulate into
+ * @addend - perms struct to add to @accum
+ */
+void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend)
+{
+ accum->deny |= addend->deny;
+ accum->allow &= addend->allow & ~accum->deny;
+ accum->audit |= addend->audit & accum->allow;
+ accum->quiet &= addend->quiet & ~accum->allow;
+ accum->kill |= addend->kill & ~accum->allow;
+ accum->stop |= addend->stop & ~accum->allow;
+ accum->complain |= addend->complain & ~accum->allow & ~accum->deny;
+ accum->cond |= addend->cond & ~accum->allow & ~accum->deny;
+ accum->hide &= addend->hide & ~accum->allow;
+ accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny;
+}
+
+void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label,
+ int type, u32 request, struct aa_perms *perms)
+{
+ /* TODO: doesn't yet handle extended types */
+ unsigned int state;
+
+ state = aa_dfa_next(profile->policy.dfa,
+ profile->policy.start[AA_CLASS_LABEL],
+ type);
+ aa_label_match(profile, label, state, false, request, perms);
+}
+
+
+/* currently unused */
+int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
+ u32 request, int type, u32 *deny,
+ struct common_audit_data *sa)
+{
+ struct aa_perms perms;
+
+ aad(sa)->label = &profile->label;
+ aad(sa)->peer = &target->label;
+ aad(sa)->request = request;
+
+ aa_profile_match_label(profile, &target->label, type, request, &perms);
+ aa_apply_modes_to_perms(profile, &perms);
+ *deny |= request & perms.deny;
+ return aa_check_perms(profile, &perms, request, sa, aa_audit_perms_cb);
+}
+
+/**
+ * aa_check_perms - do audit mode selection based on perms set
+ * @profile: profile being checked
+ * @perms: perms computed for the request
+ * @request: requested perms
+ * @deny: Returns: explicit deny set
+ * @sa: initialized audit structure (MAY BE NULL if not auditing)
+ * @cb: callback fn for tpye specific fields (MAY BE NULL)
+ *
+ * Returns: 0 if permission else error code
+ *
+ * Note: profile audit modes need to be set before calling by setting the
+ * perm masks appropriately.
+ *
+ * If not auditing then complain mode is not enabled and the
+ * error code will indicate whether there was an explicit deny
+ * with a positive value.
+ */
+int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
+ u32 request, struct common_audit_data *sa,
+ void (*cb)(struct audit_buffer *, void *))
+{
+ int type, error;
+ bool stop = false;
+ u32 denied = request & (~perms->allow | perms->deny);
+
+ if (likely(!denied)) {
+ /* mask off perms that are not being force audited */
+ request &= perms->audit;
+ if (!request || !sa)
+ return 0;
+
+ type = AUDIT_APPARMOR_AUDIT;
+ error = 0;
+ } else {
+ error = -EACCES;
+
+ if (denied & perms->kill)
+ type = AUDIT_APPARMOR_KILL;
+ else if (denied == (denied & perms->complain))
+ type = AUDIT_APPARMOR_ALLOWED;
+ else
+ type = AUDIT_APPARMOR_DENIED;
+
+ if (denied & perms->stop)
+ stop = true;
+ if (denied == (denied & perms->hide))
+ error = -ENOENT;
+
+ denied &= ~perms->quiet;
+ if (!sa || !denied)
+ return error;
+ }
+
+ if (sa) {
+ aad(sa)->label = &profile->label;
+ aad(sa)->request = request;
+ aad(sa)->denied = denied;
+ aad(sa)->error = error;
+ aa_audit_msg(type, sa, cb);
+ }
+
+ if (type == AUDIT_APPARMOR_ALLOWED)
+ error = 0;
+
+ return error;
+}
+
+
/**
* aa_policy_init - initialize a policy structure
* @policy: policy to initialize (NOT NULL)
* @prefix: prefix name if any is required. (MAYBE NULL)
* @name: name of the policy, init will make a copy of it (NOT NULL)
+ * @gfp: allocation mode
*
* Note: this fn creates a copy of strings passed in
*
@@ -141,16 +486,21 @@ void aa_info_message(const char *str)
bool aa_policy_init(struct aa_policy *policy, const char *prefix,
const char *name, gfp_t gfp)
{
+ char *hname;
+
/* freed by policy_free */
if (prefix) {
- policy->hname = kmalloc(strlen(prefix) + strlen(name) + 3,
- gfp);
- if (policy->hname)
- sprintf((char *)policy->hname, "%s//%s", prefix, name);
- } else
- policy->hname = kstrdup(name, gfp);
- if (!policy->hname)
+ hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3, gfp);
+ if (hname)
+ sprintf(hname, "%s//%s", prefix, name);
+ } else {
+ hname = aa_str_alloc(strlen(name) + 1, gfp);
+ if (hname)
+ strcpy(hname, name);
+ }
+ if (!hname)
return false;
+ policy->hname = hname;
/* base.name is a substring of fqname */
policy->name = basename(policy->hname);
INIT_LIST_HEAD(&policy->list);
@@ -169,5 +519,5 @@ void aa_policy_destroy(struct aa_policy *policy)
AA_BUG(on_list_rcu(&policy->list));
/* don't free name as its a subset of hname */
- kzfree(policy->hname);
+ aa_put_str(policy->hname);
}
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 8f3c0f7aca5a..867bcd154c7e 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -34,6 +34,7 @@
#include "include/file.h"
#include "include/ipc.h"
#include "include/path.h"
+#include "include/label.h"
#include "include/policy.h"
#include "include/policy_ns.h"
#include "include/procattr.h"
@@ -49,7 +50,7 @@ DEFINE_PER_CPU(struct aa_buffers, aa_buffers);
*/
/*
- * free the associated aa_task_ctx and put its profiles
+ * free the associated aa_task_ctx and put its labels
*/
static void apparmor_cred_free(struct cred *cred)
{
@@ -103,34 +104,63 @@ static void apparmor_cred_transfer(struct cred *new, const struct cred *old)
static int apparmor_ptrace_access_check(struct task_struct *child,
unsigned int mode)
{
- return aa_ptrace(current, child, mode);
+ struct aa_label *tracer, *tracee;
+ int error;
+
+ tracer = begin_current_label_crit_section();
+ tracee = aa_get_task_label(child);
+ error = aa_may_ptrace(tracer, tracee,
+ mode == PTRACE_MODE_READ ? AA_PTRACE_READ : AA_PTRACE_TRACE);
+ aa_put_label(tracee);
+ end_current_label_crit_section(tracer);
+
+ return error;
}
static int apparmor_ptrace_traceme(struct task_struct *parent)
{
- return aa_ptrace(parent, current, PTRACE_MODE_ATTACH);
+ struct aa_label *tracer, *tracee;
+ int error;
+
+ tracee = begin_current_label_crit_section();
+ tracer = aa_get_task_label(parent);
+ error = aa_may_ptrace(tracer, tracee, AA_PTRACE_TRACE);
+ aa_put_label(tracer);
+ end_current_label_crit_section(tracee);
+
+ return error;
}
/* Derived from security/commoncap.c:cap_capget */
static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
kernel_cap_t *inheritable, kernel_cap_t *permitted)
{
- struct aa_profile *profile;
+ struct aa_label *label;
const struct cred *cred;
rcu_read_lock();
cred = __task_cred(target);
- profile = aa_cred_profile(cred);
+ label = aa_get_newest_cred_label(cred);
/*
* cap_capget is stacked ahead of this and will
* initialize effective and permitted.
*/
- if (!unconfined(profile) && !COMPLAIN_MODE(profile)) {
- *effective = cap_intersect(*effective, profile->caps.allow);
- *permitted = cap_intersect(*permitted, profile->caps.allow);
+ if (!unconfined(label)) {
+ struct aa_profile *profile;
+ struct label_it i;
+
+ label_for_each_confined(i, label, profile) {
+ if (COMPLAIN_MODE(profile))
+ continue;
+ *effective = cap_intersect(*effective,
+ profile->caps.allow);
+ *permitted = cap_intersect(*permitted,
+ profile->caps.allow);
+ }
}
rcu_read_unlock();
+ aa_put_label(label);
return 0;
}
@@ -138,12 +168,14 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
static int apparmor_capable(const struct cred *cred, struct user_namespace *ns,
int cap, int audit)
{
- struct aa_profile *profile;
+ struct aa_label *label;
int error = 0;
- profile = aa_cred_profile(cred);
- if (!unconfined(profile))
- error = aa_capable(profile, cap, audit);
+ label = aa_get_newest_cred_label(cred);
+ if (!unconfined(label))
+ error = aa_capable(label, cap, audit);
+ aa_put_label(label);
+
return error;
}
@@ -159,12 +191,13 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns,
static int common_perm(const char *op, const struct path *path, u32 mask,
struct path_cond *cond)
{
- struct aa_profile *profile;
+ struct aa_label *label;
int error = 0;
- profile = __aa_current_profile();
- if (!unconfined(profile))
- error = aa_path_perm(op, profile, path, 0, mask, cond);
+ label = __begin_current_label_crit_section();
+ if (!unconfined(label))
+ error = aa_path_perm(op, label, path, 0, mask, cond);
+ __end_current_label_crit_section(label);
return error;
}
@@ -278,7 +311,7 @@ static int apparmor_path_mknod(const struct path *dir, struct dentry *dentry,
static int apparmor_path_truncate(const struct path *path)
{
- return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE);
+ return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR);
}
static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry,
@@ -291,29 +324,31 @@ static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry,
static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_dir,
struct dentry *new_dentry)
{
- struct aa_profile *profile;
+ struct aa_label *label;
int error = 0;
if (!path_mediated_fs(old_dentry))
return 0;
- profile = aa_current_profile();
- if (!unconfined(profile))
- error = aa_path_link(profile, old_dentry, new_dir, new_dentry);
+ label = begin_current_label_crit_section();
+ if (!unconfined(label))
+ error = aa_path_link(label, old_dentry, new_dir, new_dentry);
+ end_current_label_crit_section(label);
+
return error;
}
static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_dentry,
const struct path *new_dir, struct dentry *new_dentry)
{
- struct aa_profile *profile;
+ struct aa_label *label;
int error = 0;
if (!path_mediated_fs(old_dentry))
return 0;
- profile = aa_current_profile();
- if (!unconfined(profile)) {
+ label = begin_current_label_crit_section();
+ if (!unconfined(label)) {
struct path old_path = { .mnt = old_dir->mnt,
.dentry = old_dentry };
struct path new_path = { .mnt = new_dir->mnt,
@@ -322,16 +357,18 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d
d_backing_inode(old_dentry)->i_mode
};
- error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0,
- MAY_READ | AA_MAY_META_READ | MAY_WRITE |
- AA_MAY_META_WRITE | AA_MAY_DELETE,
+ error = aa_path_perm(OP_RENAME_SRC, label, &old_path, 0,
+ MAY_READ | AA_MAY_GETATTR | MAY_WRITE |
+ AA_MAY_SETATTR | AA_MAY_DELETE,
&cond);
if (!error)
- error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
- 0, MAY_WRITE | AA_MAY_META_WRITE |
+ error = aa_path_perm(OP_RENAME_DEST, label, &new_path,
+ 0, MAY_WRITE | AA_MAY_SETATTR |
AA_MAY_CREATE, &cond);
}
+ end_current_label_crit_section(label);
+
return error;
}
@@ -347,13 +384,13 @@ static int apparmor_path_chown(const struct path *path, kuid_t uid, kgid_t gid)
static int apparmor_inode_getattr(const struct path *path)
{
- return common_perm_cond(OP_GETATTR, path, AA_MAY_META_READ);
+ return common_perm_cond(OP_GETATTR, path, AA_MAY_GETATTR);
}
static int apparmor_file_open(struct file *file, const struct cred *cred)
{
- struct aa_file_ctx *fctx = file->f_security;
- struct aa_profile *profile;
+ struct aa_file_ctx *fctx = file_ctx(file);
+ struct aa_label *label;
int error = 0;
if (!path_mediated_fs(file->f_path.dentry))
@@ -369,65 +406,61 @@ static int apparmor_file_open(struct file *file, const struct cred *cred)
return 0;
}
- profile = aa_cred_profile(cred);
- if (!unconfined(profile)) {
+ label = aa_get_newest_cred_label(cred);
+ if (!unconfined(label)) {
struct inode *inode = file_inode(file);
struct path_cond cond = { inode->i_uid, inode->i_mode };
- error = aa_path_perm(OP_OPEN, profile, &file->f_path, 0,
+ error = aa_path_perm(OP_OPEN, label, &file->f_path, 0,
aa_map_file_to_perms(file), &cond);
/* todo cache full allowed permissions set and state */
fctx->allow = aa_map_file_to_perms(file);
}
+ aa_put_label(label);
return error;
}
static int apparmor_file_alloc_security(struct file *file)
{
+ int error = 0;
+
/* freed by apparmor_file_free_security */
- file->f_security = aa_alloc_file_context(GFP_KERNEL);
- if (!file->f_security)
- return -ENOMEM;
- return 0;
+ struct aa_label *label = begin_current_label_crit_section();
+ file->f_security = aa_alloc_file_ctx(label, GFP_KERNEL);
+ if (!file_ctx(file))
+ error = -ENOMEM;
+ end_current_label_crit_section(label);
+ return error;
}
static void apparmor_file_free_security(struct file *file)
{
- struct aa_file_ctx *ctx = file->f_security;
-
- aa_free_file_context(ctx);
+ aa_free_file_ctx(file_ctx(file));
}
static int common_file_perm(const char *op, struct file *file, u32 mask)
{
- struct aa_file_ctx *fctx = file->f_security;
- struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred);
+ struct aa_label *label;
int error = 0;
- AA_BUG(!fprofile);
+ /* don't reaudit files closed during inheritance */
+ if (file->f_path.dentry == aa_null.dentry)
+ return -EACCES;
- if (!file->f_path.mnt ||
- !path_mediated_fs(file->f_path.dentry))
- return 0;
-
- profile = __aa_current_profile();
-
- /* revalidate access, if task is unconfined, or the cached cred
- * doesn't match or if the request is for more permissions than
- * was granted.
- *
- * Note: the test for !unconfined(fprofile) is to handle file
- * delegation from unconfined tasks
- */
- if (!unconfined(profile) && !unconfined(fprofile) &&
- ((fprofile != profile) || (mask & ~fctx->allow)))
- error = aa_file_perm(op, profile, file, mask);
+ label = __begin_current_label_crit_section();
+ error = aa_file_perm(op, label, file, mask);
+ __end_current_label_crit_section(label);
return error;
}
+static int apparmor_file_receive(struct file *file)
+{
+ return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file));
+}
+
static int apparmor_file_permission(struct file *file, int mask)
{
return common_file_perm(OP_FPERM, file, mask);
@@ -448,7 +481,7 @@ static int common_mmap(const char *op, struct file *file, unsigned long prot,
{
int mask = 0;
- if (!file || !file->f_security)
+ if (!file || !file_ctx(file))
return 0;
if (prot & PROT_READ)
@@ -485,21 +518,21 @@ static int apparmor_getprocattr(struct task_struct *task, char *name,
/* released below */
const struct cred *cred = get_task_cred(task);
struct aa_task_ctx *ctx = cred_ctx(cred);
- struct aa_profile *profile = NULL;
+ struct aa_label *label = NULL;
if (strcmp(name, "current") == 0)
- profile = aa_get_newest_profile(ctx->profile);
+ label = aa_get_newest_label(ctx->label);
else if (strcmp(name, "prev") == 0 && ctx->previous)
- profile = aa_get_newest_profile(ctx->previous);
+ label = aa_get_newest_label(ctx->previous);
else if (strcmp(name, "exec") == 0 && ctx->onexec)
- profile = aa_get_newest_profile(ctx->onexec);
+ label = aa_get_newest_label(ctx->onexec);
else
error = -EINVAL;
- if (profile)
- error = aa_getprocattr(profile, value);
+ if (label)
+ error = aa_getprocattr(label, value);
- aa_put_profile(profile);
+ aa_put_label(label);
put_cred(cred);
return error;
@@ -539,22 +572,24 @@ static int apparmor_setprocattr(const char *name, void *value,
if (strcmp(name, "current") == 0) {
if (strcmp(command, "changehat") == 0) {
error = aa_setprocattr_changehat(args, arg_size,
- !AA_DO_TEST);
+ AA_CHANGE_NOFLAGS);
} else if (strcmp(command, "permhat") == 0) {
error = aa_setprocattr_changehat(args, arg_size,
- AA_DO_TEST);
+ AA_CHANGE_TEST);
} else if (strcmp(command, "changeprofile") == 0) {
- error = aa_change_profile(args, !AA_ONEXEC,
- !AA_DO_TEST, false);
+ error = aa_change_profile(args, AA_CHANGE_NOFLAGS);
} else if (strcmp(command, "permprofile") == 0) {
- error = aa_change_profile(args, !AA_ONEXEC, AA_DO_TEST,
- false);
+ error = aa_change_profile(args, AA_CHANGE_TEST);
+ } else if (strcmp(command, "stack") == 0) {
+ error = aa_change_profile(args, AA_CHANGE_STACK);
} else
goto fail;
} else if (strcmp(name, "exec") == 0) {
if (strcmp(command, "exec") == 0)
- error = aa_change_profile(args, AA_ONEXEC, !AA_DO_TEST,
- false);
+ error = aa_change_profile(args, AA_CHANGE_ONEXEC);
+ else if (strcmp(command, "stack") == 0)
+ error = aa_change_profile(args, (AA_CHANGE_ONEXEC |
+ AA_CHANGE_STACK));
else
goto fail;
} else
@@ -568,21 +603,55 @@ out:
return error;
fail:
- aad(&sa)->profile = aa_current_profile();
+ aad(&sa)->label = begin_current_label_crit_section();
aad(&sa)->info = name;
aad(&sa)->error = error = -EINVAL;
aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL);
+ end_current_label_crit_section(aad(&sa)->label);
goto out;
}
+/**
+ * apparmor_bprm_committing_creds - do task cleanup on committing new creds
+ * @bprm: binprm for the exec (NOT NULL)
+ */
+static void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
+{
+ struct aa_label *label = aa_current_raw_label();
+ struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred);
+
+ /* bail out if unconfined or not changing profile */
+ if ((new_ctx->label->proxy == label->proxy) ||
+ (unconfined(new_ctx->label)))
+ return;
+
+ aa_inherit_files(bprm->cred, current->files);
+
+ current->pdeath_signal = 0;
+
+ /* reset soft limits and set hard limits for the new label */
+ __aa_transition_rlimits(label, new_ctx->label);
+}
+
+/**
+ * apparmor_bprm_committed_cred - do cleanup after new creds committed
+ * @bprm: binprm for the exec (NOT NULL)
+ */
+static void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
+{
+ /* TODO: cleanup signals - ipc mediation */
+ return;
+}
+
static int apparmor_task_setrlimit(struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim)
{
- struct aa_profile *profile = __aa_current_profile();
+ struct aa_label *label = __begin_current_label_crit_section();
int error = 0;
- if (!unconfined(profile))
- error = aa_task_setrlimit(profile, task, resource, new_rlim);
+ if (!unconfined(label))
+ error = aa_task_setrlimit(label, task, resource, new_rlim);
+ __end_current_label_crit_section(label);
return error;
}
@@ -606,6 +675,7 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(inode_getattr, apparmor_inode_getattr),
LSM_HOOK_INIT(file_open, apparmor_file_open),
+ LSM_HOOK_INIT(file_receive, apparmor_file_receive),
LSM_HOOK_INIT(file_permission, apparmor_file_permission),
LSM_HOOK_INIT(file_alloc_security, apparmor_file_alloc_security),
LSM_HOOK_INIT(file_free_security, apparmor_file_free_security),
@@ -774,11 +844,18 @@ static int param_get_aabool(char *buffer, const struct kernel_param *kp)
static int param_set_aauint(const char *val, const struct kernel_param *kp)
{
+ int error;
+
if (!apparmor_enabled)
return -EINVAL;
- if (apparmor_initialized && !policy_admin_capable(NULL))
+ /* file is ro but enforce 2nd line check */
+ if (apparmor_initialized)
return -EPERM;
- return param_set_uint(val, kp);
+
+ error = param_set_uint(val, kp);
+ pr_info("AppArmor: buffer size set to %d bytes\n", aa_g_path_max);
+
+ return error;
}
static int param_get_aauint(char *buffer, const struct kernel_param *kp)
@@ -869,7 +946,7 @@ static int __init set_init_ctx(void)
if (!ctx)
return -ENOMEM;
- ctx->profile = aa_get_profile(root_ns->unconfined);
+ ctx->label = aa_get_label(ns_unconfined(root_ns));
cred_ctx(cred) = ctx;
return 0;
diff --git a/security/apparmor/match.c b/security/apparmor/match.c
index 960c913381e2..72c604350e80 100644
--- a/security/apparmor/match.c
+++ b/security/apparmor/match.c
@@ -226,7 +226,7 @@ void aa_dfa_free_kref(struct kref *kref)
* @flags: flags controlling what type of accept tables are acceptable
*
* Unpack a dfa that has been serialized. To find information on the dfa
- * format look in Documentation/security/apparmor.txt
+ * format look in Documentation/admin-guide/LSM/apparmor.rst
* Assumes the dfa @blob stream has been aligned on a 8 byte boundary
*
* Returns: an unpacked dfa ready for matching or ERR_PTR on failure
diff --git a/security/apparmor/path.c b/security/apparmor/path.c
index a8fc7d08c144..9d5de1d05be4 100644
--- a/security/apparmor/path.c
+++ b/security/apparmor/path.c
@@ -50,7 +50,7 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen)
* namespace root.
*/
static int disconnect(const struct path *path, char *buf, char **name,
- int flags)
+ int flags, const char *disconnected)
{
int error = 0;
@@ -63,9 +63,14 @@ static int disconnect(const struct path *path, char *buf, char **name,
error = -EACCES;
if (**name == '/')
*name = *name + 1;
- } else if (**name != '/')
- /* CONNECT_PATH with missing root */
- error = prepend(name, *name - buf, "/", 1);
+ } else {
+ if (**name != '/')
+ /* CONNECT_PATH with missing root */
+ error = prepend(name, *name - buf, "/", 1);
+ if (!error && disconnected)
+ error = prepend(name, *name - buf, disconnected,
+ strlen(disconnected));
+ }
return error;
}
@@ -74,9 +79,9 @@ static int disconnect(const struct path *path, char *buf, char **name,
* d_namespace_path - lookup a name associated with a given path
* @path: path to lookup (NOT NULL)
* @buf: buffer to store path to (NOT NULL)
- * @buflen: length of @buf
* @name: Returns - pointer for start of path name with in @buf (NOT NULL)
* @flags: flags controlling path lookup
+ * @disconnected: string to prefix to disconnected paths
*
* Handle path name lookup.
*
@@ -84,12 +89,14 @@ static int disconnect(const struct path *path, char *buf, char **name,
* When no error the path name is returned in @name which points to
* to a position in @buf
*/
-static int d_namespace_path(const struct path *path, char *buf, int buflen,
- char **name, int flags)
+static int d_namespace_path(const struct path *path, char *buf, char **name,
+ int flags, const char *disconnected)
{
char *res;
int error = 0;
int connected = 1;
+ int isdir = (flags & PATH_IS_DIR) ? 1 : 0;
+ int buflen = aa_g_path_max - isdir;
if (path->mnt->mnt_flags & MNT_INTERNAL) {
/* it's not mounted anywhere */
@@ -104,10 +111,12 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen,
/* TODO: convert over to using a per namespace
* control instead of hard coded /proc
*/
- return prepend(name, *name - buf, "/proc", 5);
+ error = prepend(name, *name - buf, "/proc", 5);
+ goto out;
} else
- return disconnect(path, buf, name, flags);
- return 0;
+ error = disconnect(path, buf, name, flags,
+ disconnected);
+ goto out;
}
/* resolve paths relative to chroot?*/
@@ -126,8 +135,11 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen,
* be returned.
*/
if (!res || IS_ERR(res)) {
- if (PTR_ERR(res) == -ENAMETOOLONG)
- return -ENAMETOOLONG;
+ if (PTR_ERR(res) == -ENAMETOOLONG) {
+ error = -ENAMETOOLONG;
+ *name = buf;
+ goto out;
+ }
connected = 0;
res = dentry_path_raw(path->dentry, buf, buflen);
if (IS_ERR(res)) {
@@ -140,6 +152,9 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen,
*name = res;
+ if (!connected)
+ error = disconnect(path, buf, name, flags, disconnected);
+
/* Handle two cases:
* 1. A deleted dentry && profile is not allowing mediation of deleted
* 2. On some filesystems, newly allocated dentries appear to the
@@ -147,62 +162,30 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen,
* allocated.
*/
if (d_unlinked(path->dentry) && d_is_positive(path->dentry) &&
- !(flags & PATH_MEDIATE_DELETED)) {
+ !(flags & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))) {
error = -ENOENT;
goto out;
}
- if (!connected)
- error = disconnect(path, buf, name, flags);
-
out:
- return error;
-}
-
-/**
- * get_name_to_buffer - get the pathname to a buffer ensure dir / is appended
- * @path: path to get name for (NOT NULL)
- * @flags: flags controlling path lookup
- * @buffer: buffer to put name in (NOT NULL)
- * @size: size of buffer
- * @name: Returns - contains position of path name in @buffer (NOT NULL)
- *
- * Returns: %0 else error on failure
- */
-static int get_name_to_buffer(const struct path *path, int flags, char *buffer,
- int size, char **name, const char **info)
-{
- int adjust = (flags & PATH_IS_DIR) ? 1 : 0;
- int error = d_namespace_path(path, buffer, size - adjust, name, flags);
-
- if (!error && (flags & PATH_IS_DIR) && (*name)[1] != '\0')
- /*
- * Append "/" to the pathname. The root directory is a special
- * case; it already ends in slash.
- */
- strcpy(&buffer[size - 2], "/");
-
- if (info && error) {
- if (error == -ENOENT)
- *info = "Failed name lookup - deleted entry";
- else if (error == -EACCES)
- *info = "Failed name lookup - disconnected path";
- else if (error == -ENAMETOOLONG)
- *info = "Failed name lookup - name too long";
- else
- *info = "Failed name lookup";
- }
+ /*
+ * Append "/" to the pathname. The root directory is a special
+ * case; it already ends in slash.
+ */
+ if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/'))
+ strcpy(&buf[aa_g_path_max - 2], "/");
return error;
}
/**
- * aa_path_name - compute the pathname of a file
+ * aa_path_name - get the pathname to a buffer ensure dir / is appended
* @path: path the file (NOT NULL)
* @flags: flags controlling path name generation
- * @buffer: buffer that aa_get_name() allocated (NOT NULL)
+ * @buffer: buffer to put name in (NOT NULL)
* @name: Returns - the generated path name if !error (NOT NULL)
* @info: Returns - information on why the path lookup failed (MAYBE NULL)
+ * @disconnected: string to prepend to disconnected paths
*
* @name is a pointer to the beginning of the pathname (which usually differs
* from the beginning of the buffer), or NULL. If there is an error @name
@@ -215,32 +198,23 @@ static int get_name_to_buffer(const struct path *path, int flags, char *buffer,
*
* Returns: %0 else error code if could retrieve name
*/
-int aa_path_name(const struct path *path, int flags, char **buffer,
- const char **name, const char **info)
+int aa_path_name(const struct path *path, int flags, char *buffer,
+ const char **name, const char **info, const char *disconnected)
{
- char *buf, *str = NULL;
- int size = 256;
- int error;
-
- *name = NULL;
- *buffer = NULL;
- for (;;) {
- /* freed by caller */
- buf = kmalloc(size, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- error = get_name_to_buffer(path, flags, buf, size, &str, info);
- if (error != -ENAMETOOLONG)
- break;
-
- kfree(buf);
- size <<= 1;
- if (size > aa_g_path_max)
- return -ENAMETOOLONG;
- *info = NULL;
+ char *str = NULL;
+ int error = d_namespace_path(path, buffer, &str, flags, disconnected);
+
+ if (info && error) {
+ if (error == -ENOENT)
+ *info = "Failed name lookup - deleted entry";
+ else if (error == -EACCES)
+ *info = "Failed name lookup - disconnected path";
+ else if (error == -ENAMETOOLONG)
+ *info = "Failed name lookup - name too long";
+ else
+ *info = "Failed name lookup";
}
- *buffer = buf;
+
*name = str;
return error;
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index cf9d670dca94..244ea4a4a8f0 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -101,20 +101,9 @@ const char *const aa_profile_mode_names[] = {
"unconfined",
};
-/* requires profile list write lock held */
-void __aa_update_proxy(struct aa_profile *orig, struct aa_profile *new)
-{
- struct aa_profile *tmp;
-
- tmp = rcu_dereference_protected(orig->proxy->profile,
- mutex_is_locked(&orig->ns->lock));
- rcu_assign_pointer(orig->proxy->profile, aa_get_profile(new));
- orig->flags |= PFLAG_STALE;
- aa_put_profile(tmp);
-}
/**
- * __list_add_profile - add a profile to a list
+ * __add_profile - add a profiles to list and label tree
* @list: list to add it to (NOT NULL)
* @profile: the profile to add (NOT NULL)
*
@@ -122,12 +111,21 @@ void __aa_update_proxy(struct aa_profile *orig, struct aa_profile *new)
*
* Requires: namespace lock be held, or list not be shared
*/
-static void __list_add_profile(struct list_head *list,
- struct aa_profile *profile)
+static void __add_profile(struct list_head *list, struct aa_profile *profile)
{
+ struct aa_label *l;
+
+ AA_BUG(!list);
+ AA_BUG(!profile);
+ AA_BUG(!profile->ns);
+ AA_BUG(!mutex_is_locked(&profile->ns->lock));
+
list_add_rcu(&profile->base.list, list);
/* get list reference */
aa_get_profile(profile);
+ l = aa_label_insert(&profile->ns->labels, &profile->label);
+ AA_BUG(l != &profile->label);
+ aa_put_label(l);
}
/**
@@ -144,6 +142,10 @@ static void __list_add_profile(struct list_head *list,
*/
static void __list_remove_profile(struct aa_profile *profile)
{
+ AA_BUG(!profile);
+ AA_BUG(!profile->ns);
+ AA_BUG(!mutex_is_locked(&profile->ns->lock));
+
list_del_rcu(&profile->base.list);
aa_put_profile(profile);
}
@@ -156,11 +158,15 @@ static void __list_remove_profile(struct aa_profile *profile)
*/
static void __remove_profile(struct aa_profile *profile)
{
+ AA_BUG(!profile);
+ AA_BUG(!profile->ns);
+ AA_BUG(!mutex_is_locked(&profile->ns->lock));
+
/* release any children lists first */
__aa_profile_list_release(&profile->base.profiles);
/* released by free_profile */
- __aa_update_proxy(profile, profile->ns->unconfined);
- __aa_fs_profile_rmdir(profile);
+ aa_label_remove(&profile->label);
+ __aafs_profile_rmdir(profile);
__list_remove_profile(profile);
}
@@ -177,24 +183,6 @@ void __aa_profile_list_release(struct list_head *head)
__remove_profile(profile);
}
-
-static void free_proxy(struct aa_proxy *p)
-{
- if (p) {
- /* r->profile will not be updated any more as r is dead */
- aa_put_profile(rcu_dereference_protected(p->profile, true));
- kzfree(p);
- }
-}
-
-
-void aa_free_proxy_kref(struct kref *kref)
-{
- struct aa_proxy *p = container_of(kref, struct aa_proxy, count);
-
- free_proxy(p);
-}
-
/**
* aa_free_data - free a data blob
* @ptr: data to free
@@ -242,7 +230,6 @@ void aa_free_profile(struct aa_profile *profile)
kzfree(profile->dirname);
aa_put_dfa(profile->xmatch);
aa_put_dfa(profile->policy.dfa);
- aa_put_proxy(profile->proxy);
if (profile->data) {
rht = profile->data;
@@ -253,30 +240,8 @@ void aa_free_profile(struct aa_profile *profile)
kzfree(profile->hash);
aa_put_loaddata(profile->rawdata);
- kzfree(profile);
-}
-
-/**
- * aa_free_profile_rcu - free aa_profile by rcu (called by aa_free_profile_kref)
- * @head: rcu_head callback for freeing of a profile (NOT NULL)
- */
-static void aa_free_profile_rcu(struct rcu_head *head)
-{
- struct aa_profile *p = container_of(head, struct aa_profile, rcu);
- if (p->flags & PFLAG_NS_COUNT)
- aa_free_ns(p->ns);
- else
- aa_free_profile(p);
-}
-/**
- * aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile)
- * @kr: kref callback for freeing of a profile (NOT NULL)
- */
-void aa_free_profile_kref(struct kref *kref)
-{
- struct aa_profile *p = container_of(kref, struct aa_profile, count);
- call_rcu(&p->rcu, aa_free_profile_rcu);
+ kzfree(profile);
}
/**
@@ -286,30 +251,40 @@ void aa_free_profile_kref(struct kref *kref)
*
* Returns: refcount profile or NULL on failure
*/
-struct aa_profile *aa_alloc_profile(const char *hname, gfp_t gfp)
+struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy,
+ gfp_t gfp)
{
struct aa_profile *profile;
/* freed by free_profile - usually through aa_put_profile */
- profile = kzalloc(sizeof(*profile), gfp);
+ profile = kzalloc(sizeof(*profile) + sizeof(struct aa_profile *) * 2,
+ gfp);
if (!profile)
return NULL;
- profile->proxy = kzalloc(sizeof(struct aa_proxy), gfp);
- if (!profile->proxy)
- goto fail;
- kref_init(&profile->proxy->count);
-
if (!aa_policy_init(&profile->base, NULL, hname, gfp))
goto fail;
- kref_init(&profile->count);
+ if (!aa_label_init(&profile->label, 1))
+ goto fail;
+
+ /* update being set needed by fs interface */
+ if (!proxy) {
+ proxy = aa_alloc_proxy(&profile->label, gfp);
+ if (!proxy)
+ goto fail;
+ } else
+ aa_get_proxy(proxy);
+ profile->label.proxy = proxy;
+
+ profile->label.hname = profile->base.hname;
+ profile->label.flags |= FLAG_PROFILE;
+ profile->label.vec[0] = profile;
/* refcount released by caller */
return profile;
fail:
- kzfree(profile->proxy);
- kzfree(profile);
+ aa_free_profile(profile);
return NULL;
}
@@ -362,14 +337,14 @@ name:
if (profile)
goto out;
- profile = aa_alloc_profile(name, gfp);
+ profile = aa_alloc_profile(name, NULL, gfp);
if (!profile)
goto fail;
profile->mode = APPARMOR_COMPLAIN;
- profile->flags |= PFLAG_NULL;
+ profile->label.flags |= FLAG_NULL;
if (hat)
- profile->flags |= PFLAG_HAT;
+ profile->label.flags |= FLAG_HAT;
profile->path_flags = parent->path_flags;
/* released on free_profile */
@@ -379,7 +354,7 @@ name:
profile->policy.dfa = aa_get_dfa(nulldfa);
mutex_lock(&profile->ns->lock);
- __list_add_profile(&parent->base.profiles, profile);
+ __add_profile(&parent->base.profiles, profile);
mutex_unlock(&profile->ns->lock);
/* refcount released by caller */
@@ -389,7 +364,6 @@ out:
return profile;
fail:
- kfree(name);
aa_free_profile(profile);
return NULL;
}
@@ -397,33 +371,33 @@ fail:
/* TODO: profile accounting - setup in remove */
/**
- * __find_child - find a profile on @head list with a name matching @name
+ * __strn_find_child - find a profile on @head list using substring of @name
* @head: list to search (NOT NULL)
* @name: name of profile (NOT NULL)
+ * @len: length of @name substring to match
*
* Requires: rcu_read_lock be held
*
* Returns: unrefcounted profile ptr, or NULL if not found
*/
-static struct aa_profile *__find_child(struct list_head *head, const char *name)
+static struct aa_profile *__strn_find_child(struct list_head *head,
+ const char *name, int len)
{
- return (struct aa_profile *)__policy_find(head, name);
+ return (struct aa_profile *)__policy_strn_find(head, name, len);
}
/**
- * __strn_find_child - find a profile on @head list using substring of @name
+ * __find_child - find a profile on @head list with a name matching @name
* @head: list to search (NOT NULL)
* @name: name of profile (NOT NULL)
- * @len: length of @name substring to match
*
* Requires: rcu_read_lock be held
*
* Returns: unrefcounted profile ptr, or NULL if not found
*/
-static struct aa_profile *__strn_find_child(struct list_head *head,
- const char *name, int len)
+static struct aa_profile *__find_child(struct list_head *head, const char *name)
{
- return (struct aa_profile *)__policy_strn_find(head, name, len);
+ return __strn_find_child(head, name, strlen(name));
}
/**
@@ -556,7 +530,7 @@ struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *hname)
return aa_lookupn_profile(ns, hname, strlen(hname));
}
-struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base,
+struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
const char *fqname, size_t n)
{
struct aa_profile *profile;
@@ -566,11 +540,11 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base,
name = aa_splitn_fqname(fqname, n, &ns_name, &ns_len);
if (ns_name) {
- ns = aa_findn_ns(base->ns, ns_name, ns_len);
+ ns = aa_lookupn_ns(labels_ns(base), ns_name, ns_len);
if (!ns)
return NULL;
} else
- ns = aa_get_ns(base->ns);
+ ns = aa_get_ns(labels_ns(base));
if (name)
profile = aa_lookupn_profile(ns, name, n - (name - fqname));
@@ -596,7 +570,7 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace,
const char **info)
{
if (profile) {
- if (profile->flags & PFLAG_IMMUTABLE) {
+ if (profile->label.flags & FLAG_IMMUTIBLE) {
*info = "cannot replace immutible profile";
return -EPERM;
} else if (noreplace) {
@@ -619,29 +593,31 @@ static void audit_cb(struct audit_buffer *ab, void *va)
}
/**
- * aa_audit_policy - Do auditing of policy changes
- * @profile: profile to check if it can manage policy
+ * audit_policy - Do auditing of policy changes
+ * @label: label to check if it can manage policy
* @op: policy operation being performed
- * @gfp: memory allocation flags
- * @nsname: name of the ns being manipulated (MAY BE NULL)
+ * @ns_name: name of namespace being manipulated
* @name: name of profile being manipulated (NOT NULL)
* @info: any extra information to be audited (MAYBE NULL)
* @error: error code
*
* Returns: the error to be returned after audit is done
*/
-static int audit_policy(struct aa_profile *profile, const char *op,
- const char *nsname, const char *name,
+static int audit_policy(struct aa_label *label, const char *op,
+ const char *ns_name, const char *name,
const char *info, int error)
{
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
- aad(&sa)->iface.ns = nsname;
+ aad(&sa)->iface.ns = ns_name;
aad(&sa)->name = name;
aad(&sa)->info = info;
aad(&sa)->error = error;
+ aad(&sa)->label = label;
+
+ aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, audit_cb);
- return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb);
+ return error;
}
/**
@@ -685,22 +661,30 @@ bool policy_admin_capable(struct aa_ns *ns)
/**
* aa_may_manage_policy - can the current task manage policy
- * @profile: profile to check if it can manage policy
+ * @label: label to check if it can manage policy
* @op: the policy manipulation operation being done
*
* Returns: 0 if the task is allowed to manipulate policy else error
*/
-int aa_may_manage_policy(struct aa_profile *profile, struct aa_ns *ns,
- const char *op)
+int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask)
{
+ const char *op;
+
+ if (mask & AA_MAY_REMOVE_POLICY)
+ op = OP_PROF_RM;
+ else if (mask & AA_MAY_REPLACE_POLICY)
+ op = OP_PROF_REPL;
+ else
+ op = OP_PROF_LOAD;
+
/* check if loading policy is locked out */
if (aa_g_lock_policy)
- return audit_policy(profile, op, NULL, NULL,
- "policy_locked", -EACCES);
+ return audit_policy(label, op, NULL, NULL, "policy_locked",
+ -EACCES);
if (!policy_admin_capable(ns))
- return audit_policy(profile, op, NULL, NULL,
- "not policy admin", -EACCES);
+ return audit_policy(label, op, NULL, NULL, "not policy admin",
+ -EACCES);
/* TODO: add fine grained mediation of policy loads */
return 0;
@@ -742,8 +726,7 @@ static struct aa_profile *__list_lookup_parent(struct list_head *lh,
*
* Requires: namespace list lock be held, or list not be shared
*/
-static void __replace_profile(struct aa_profile *old, struct aa_profile *new,
- bool share_proxy)
+static void __replace_profile(struct aa_profile *old, struct aa_profile *new)
{
struct aa_profile *child, *tmp;
@@ -758,7 +741,7 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new,
p = __find_child(&new->base.profiles, child->base.name);
if (p) {
/* @p replaces @child */
- __replace_profile(child, p, share_proxy);
+ __replace_profile(child, p);
continue;
}
@@ -776,15 +759,9 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new,
struct aa_profile *parent = aa_deref_parent(old);
rcu_assign_pointer(new->parent, aa_get_profile(parent));
}
- __aa_update_proxy(old, new);
- if (share_proxy) {
- aa_put_proxy(new->proxy);
- new->proxy = aa_get_proxy(old->proxy);
- } else if (!rcu_access_pointer(new->proxy->profile))
- /* aafs interface uses proxy */
- rcu_assign_pointer(new->proxy->profile,
- aa_get_profile(new));
- __aa_fs_profile_migrate_dents(old, new);
+ aa_label_replace(&old->label, &new->label);
+ /* migrate dents must come after label replacement b/c update */
+ __aafs_profile_migrate_dents(old, new);
if (list_empty(&new->base.list)) {
/* new is not on a list already */
@@ -821,11 +798,41 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname,
return 0;
}
+static void share_name(struct aa_profile *old, struct aa_profile *new)
+{
+ aa_put_str(new->base.hname);
+ aa_get_str(old->base.hname);
+ new->base.hname = old->base.hname;
+ new->base.name = old->base.name;
+ new->label.hname = old->label.hname;
+}
+
+/* Update to newest version of parent after previous replacements
+ * Returns: unrefcount newest version of parent
+ */
+static struct aa_profile *update_to_newest_parent(struct aa_profile *new)
+{
+ struct aa_profile *parent, *newest;
+
+ parent = rcu_dereference_protected(new->parent,
+ mutex_is_locked(&new->ns->lock));
+ newest = aa_get_newest_profile(parent);
+
+ /* parent replaced in this atomic set? */
+ if (newest != parent) {
+ aa_put_profile(parent);
+ rcu_assign_pointer(new->parent, newest);
+ } else
+ aa_put_profile(newest);
+
+ return newest;
+}
+
/**
* aa_replace_profiles - replace profile(s) on the profile list
- * @view: namespace load is viewed from
+ * @policy_ns: namespace load is occurring on
* @label: label that is attempting to load/replace policy
- * @noreplace: true if only doing addition, no replacement allowed
+ * @mask: permission mask
* @udata: serialized data stream (NOT NULL)
*
* unpack and replace a profile on the profile list and uses of that profile
@@ -834,16 +841,19 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname,
*
* Returns: size of data consumed else error code on failure.
*/
-ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
- bool noreplace, struct aa_loaddata *udata)
+ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
+ u32 mask, struct aa_loaddata *udata)
{
const char *ns_name, *info = NULL;
struct aa_ns *ns = NULL;
struct aa_load_ent *ent, *tmp;
- const char *op = OP_PROF_REPL;
+ struct aa_loaddata *rawdata_ent;
+ const char *op;
ssize_t count, error;
LIST_HEAD(lh);
+ op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD;
+ aa_get_loaddata(udata);
/* released below */
error = aa_unpack(udata, &lh, &ns_name);
if (error)
@@ -874,7 +884,8 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
count++;
}
if (ns_name) {
- ns = aa_prepare_ns(view, ns_name);
+ ns = aa_prepare_ns(policy_ns ? policy_ns : labels_ns(label),
+ ns_name);
if (IS_ERR(ns)) {
op = OP_PROF_LOAD;
info = "failed to prepare namespace";
@@ -884,22 +895,38 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
goto fail;
}
} else
- ns = aa_get_ns(view);
+ ns = aa_get_ns(policy_ns ? policy_ns : labels_ns(label));
mutex_lock(&ns->lock);
+ /* check for duplicate rawdata blobs: space and file dedup */
+ list_for_each_entry(rawdata_ent, &ns->rawdata_list, list) {
+ if (aa_rawdata_eq(rawdata_ent, udata)) {
+ struct aa_loaddata *tmp;
+
+ tmp = __aa_get_loaddata(rawdata_ent);
+ /* check we didn't fail the race */
+ if (tmp) {
+ aa_put_loaddata(udata);
+ udata = tmp;
+ break;
+ }
+ }
+ }
/* setup parent and ns info */
list_for_each_entry(ent, &lh, list) {
struct aa_policy *policy;
+
ent->new->rawdata = aa_get_loaddata(udata);
- error = __lookup_replace(ns, ent->new->base.hname, noreplace,
+ error = __lookup_replace(ns, ent->new->base.hname,
+ !(mask & AA_MAY_REPLACE_POLICY),
&ent->old, &info);
if (error)
goto fail_lock;
if (ent->new->rename) {
error = __lookup_replace(ns, ent->new->rename,
- noreplace, &ent->rename,
- &info);
+ !(mask & AA_MAY_REPLACE_POLICY),
+ &ent->rename, &info);
if (error)
goto fail_lock;
}
@@ -929,15 +956,16 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
}
/* create new fs entries for introspection if needed */
+ if (!udata->dents[AAFS_LOADDATA_DIR]) {
+ error = __aa_fs_create_rawdata(ns, udata);
+ if (error) {
+ info = "failed to create raw_data dir and files";
+ ent = NULL;
+ goto fail_lock;
+ }
+ }
list_for_each_entry(ent, &lh, list) {
- if (ent->old) {
- /* inherit old interface files */
-
- /* if (ent->rename)
- TODO: support rename */
- /* } else if (ent->rename) {
- TODO: support rename */
- } else {
+ if (!ent->old) {
struct dentry *parent;
if (rcu_access_pointer(ent->new->parent)) {
struct aa_profile *p;
@@ -945,65 +973,61 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
parent = prof_child_dir(p);
} else
parent = ns_subprofs_dir(ent->new->ns);
- error = __aa_fs_profile_mkdir(ent->new, parent);
+ error = __aafs_profile_mkdir(ent->new, parent);
}
if (error) {
- info = "failed to create ";
+ info = "failed to create";
goto fail_lock;
}
}
/* Done with checks that may fail - do actual replacement */
+ __aa_bump_ns_revision(ns);
+ __aa_loaddata_update(udata, ns->revision);
list_for_each_entry_safe(ent, tmp, &lh, list) {
list_del_init(&ent->list);
op = (!ent->old && !ent->rename) ? OP_PROF_LOAD : OP_PROF_REPL;
- audit_policy(profile, op, NULL, ent->new->base.hname,
- NULL, error);
+ if (ent->old && ent->old->rawdata == ent->new->rawdata) {
+ /* dedup actual profile replacement */
+ audit_policy(label, op, ns_name, ent->new->base.hname,
+ "same as current profile, skipping",
+ error);
+ goto skip;
+ }
+
+ /*
+ * TODO: finer dedup based on profile range in data. Load set
+ * can differ but profile may remain unchanged
+ */
+ audit_policy(label, op, ns_name, ent->new->base.hname, NULL,
+ error);
if (ent->old) {
- __replace_profile(ent->old, ent->new, 1);
- if (ent->rename) {
- /* aafs interface uses proxy */
- struct aa_proxy *r = ent->new->proxy;
- rcu_assign_pointer(r->profile,
- aa_get_profile(ent->new));
- __replace_profile(ent->rename, ent->new, 0);
- }
- } else if (ent->rename) {
- /* aafs interface uses proxy */
- rcu_assign_pointer(ent->new->proxy->profile,
- aa_get_profile(ent->new));
- __replace_profile(ent->rename, ent->new, 0);
- } else if (ent->new->parent) {
- struct aa_profile *parent, *newest;
- parent = aa_deref_parent(ent->new);
- newest = aa_get_newest_profile(parent);
-
- /* parent replaced in this atomic set? */
- if (newest != parent) {
- aa_get_profile(newest);
- rcu_assign_pointer(ent->new->parent, newest);
- aa_put_profile(parent);
- }
- /* aafs interface uses proxy */
- rcu_assign_pointer(ent->new->proxy->profile,
- aa_get_profile(ent->new));
- __list_add_profile(&newest->base.profiles, ent->new);
- aa_put_profile(newest);
+ share_name(ent->old, ent->new);
+ __replace_profile(ent->old, ent->new);
} else {
- /* aafs interface uses proxy */
- rcu_assign_pointer(ent->new->proxy->profile,
- aa_get_profile(ent->new));
- __list_add_profile(&ns->base.profiles, ent->new);
+ struct list_head *lh;
+
+ if (rcu_access_pointer(ent->new->parent)) {
+ struct aa_profile *parent;
+
+ parent = update_to_newest_parent(ent->new);
+ lh = &parent->base.profiles;
+ } else
+ lh = &ns->base.profiles;
+ __add_profile(lh, ent->new);
}
+ skip:
aa_load_ent_free(ent);
}
+ __aa_labelset_update_subtree(ns);
mutex_unlock(&ns->lock);
out:
aa_put_ns(ns);
+ aa_put_loaddata(udata);
if (error)
return error;
@@ -1013,10 +1037,10 @@ fail_lock:
mutex_unlock(&ns->lock);
/* audit cause of failure */
- op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL;
+ op = (ent && !ent->old) ? OP_PROF_LOAD : OP_PROF_REPL;
fail:
- audit_policy(profile, op, ns_name, ent ? ent->new->base.hname : NULL,
- info, error);
+ audit_policy(label, op, ns_name, ent ? ent->new->base.hname : NULL,
+ info, error);
/* audit status that rest of profiles in the atomic set failed too */
info = "valid profile in failed atomic policy load";
list_for_each_entry(tmp, &lh, list) {
@@ -1026,8 +1050,8 @@ fail:
continue;
}
op = (!tmp->old) ? OP_PROF_LOAD : OP_PROF_REPL;
- audit_policy(profile, op, ns_name,
- tmp->new->base.hname, info, error);
+ audit_policy(label, op, ns_name, tmp->new->base.hname, info,
+ error);
}
list_for_each_entry_safe(ent, tmp, &lh, list) {
list_del_init(&ent->list);
@@ -1039,8 +1063,8 @@ fail:
/**
* aa_remove_profiles - remove profile(s) from the system
- * @view: namespace the remove is being done from
- * @subj: profile attempting to remove policy
+ * @policy_ns: namespace the remove is being done from
+ * @subj: label attempting to remove policy
* @fqname: name of the profile or namespace to remove (NOT NULL)
* @size: size of the name
*
@@ -1051,13 +1075,13 @@ fail:
*
* Returns: size of data consume else error code if fails
*/
-ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj,
+ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_label *subj,
char *fqname, size_t size)
{
- struct aa_ns *root = NULL, *ns = NULL;
+ struct aa_ns *ns = NULL;
struct aa_profile *profile = NULL;
const char *name = fqname, *info = NULL;
- char *ns_name = NULL;
+ const char *ns_name = NULL;
ssize_t error = 0;
if (*fqname == 0) {
@@ -1066,12 +1090,13 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj,
goto fail;
}
- root = view;
-
if (fqname[0] == ':') {
- name = aa_split_fqname(fqname, &ns_name);
+ size_t ns_len;
+
+ name = aa_splitn_fqname(fqname, size, &ns_name, &ns_len);
/* released below */
- ns = aa_find_ns(root, ns_name);
+ ns = aa_lookupn_ns(policy_ns ? policy_ns : labels_ns(subj),
+ ns_name, ns_len);
if (!ns) {
info = "namespace does not exist";
error = -ENOENT;
@@ -1079,12 +1104,13 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj,
}
} else
/* released below */
- ns = aa_get_ns(root);
+ ns = aa_get_ns(policy_ns ? policy_ns : labels_ns(subj));
if (!name) {
/* remove namespace - can only happen if fqname[0] == ':' */
mutex_lock(&ns->parent->lock);
__aa_remove_ns(ns);
+ __aa_bump_ns_revision(ns);
mutex_unlock(&ns->parent->lock);
} else {
/* remove profile */
@@ -1097,6 +1123,8 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj,
}
name = profile->base.hname;
__remove_profile(profile);
+ __aa_labelset_update_subtree(ns);
+ __aa_bump_ns_revision(ns);
mutex_unlock(&ns->lock);
}
diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c
index 93d1826c4b09..351d3bab3a3d 100644
--- a/security/apparmor/policy_ns.c
+++ b/security/apparmor/policy_ns.c
@@ -23,6 +23,7 @@
#include "include/apparmor.h"
#include "include/context.h"
#include "include/policy_ns.h"
+#include "include/label.h"
#include "include/policy.h"
/* root profile namespace */
@@ -99,15 +100,17 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name)
goto fail_ns;
INIT_LIST_HEAD(&ns->sub_ns);
+ INIT_LIST_HEAD(&ns->rawdata_list);
mutex_init(&ns->lock);
+ init_waitqueue_head(&ns->wait);
/* released by aa_free_ns() */
- ns->unconfined = aa_alloc_profile("unconfined", GFP_KERNEL);
+ ns->unconfined = aa_alloc_profile("unconfined", NULL, GFP_KERNEL);
if (!ns->unconfined)
goto fail_unconfined;
- ns->unconfined->flags = PFLAG_IX_ON_NAME_ERROR |
- PFLAG_IMMUTABLE | PFLAG_NS_COUNT;
+ ns->unconfined->label.flags |= FLAG_IX_ON_NAME_ERROR |
+ FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED;
ns->unconfined->mode = APPARMOR_UNCONFINED;
/* ns and ns->unconfined share ns->unconfined refcount */
@@ -115,6 +118,8 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name)
atomic_set(&ns->uniq_null, 0);
+ aa_labelset_init(&ns->labels);
+
return ns;
fail_unconfined:
@@ -137,6 +142,7 @@ void aa_free_ns(struct aa_ns *ns)
return;
aa_policy_destroy(&ns->base);
+ aa_labelset_destroy(&ns->labels);
aa_put_ns(ns->parent);
ns->unconfined->ns = NULL;
@@ -181,6 +187,60 @@ struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name)
return aa_findn_ns(root, name, strlen(name));
}
+/**
+ * __aa_lookupn_ns - lookup the namespace matching @hname
+ * @base: base list to start looking up profile name from (NOT NULL)
+ * @hname: hierarchical ns name (NOT NULL)
+ * @n: length of @hname
+ *
+ * Requires: rcu_read_lock be held
+ *
+ * Returns: unrefcounted ns pointer or NULL if not found
+ *
+ * Do a relative name lookup, recursing through profile tree.
+ */
+struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n)
+{
+ struct aa_ns *ns = view;
+ const char *split;
+
+ for (split = strnstr(hname, "//", n); split;
+ split = strnstr(hname, "//", n)) {
+ ns = __aa_findn_ns(&ns->sub_ns, hname, split - hname);
+ if (!ns)
+ return NULL;
+
+ n -= split + 2 - hname;
+ hname = split + 2;
+ }
+
+ if (n)
+ return __aa_findn_ns(&ns->sub_ns, hname, n);
+ return NULL;
+}
+
+/**
+ * aa_lookupn_ns - look up a policy namespace relative to @view
+ * @view: namespace to search in (NOT NULL)
+ * @name: name of namespace to find (NOT NULL)
+ * @n: length of @name
+ *
+ * Returns: a refcounted namespace on the list, or NULL if no namespace
+ * called @name exists.
+ *
+ * refcount released by caller
+ */
+struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n)
+{
+ struct aa_ns *ns = NULL;
+
+ rcu_read_lock();
+ ns = aa_get_ns(__aa_lookupn_ns(view, name, n));
+ rcu_read_unlock();
+
+ return ns;
+}
+
static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name,
struct dentry *dir)
{
@@ -195,7 +255,7 @@ static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name,
if (!ns)
return NULL;
mutex_lock(&ns->lock);
- error = __aa_fs_ns_mkdir(ns, ns_subns_dir(parent), name);
+ error = __aafs_ns_mkdir(ns, ns_subns_dir(parent), name, dir);
if (error) {
AA_ERROR("Failed to create interface for ns %s\n",
ns->base.name);
@@ -281,9 +341,15 @@ static void destroy_ns(struct aa_ns *ns)
/* release all sub namespaces */
__ns_list_release(&ns->sub_ns);
- if (ns->parent)
- __aa_update_proxy(ns->unconfined, ns->parent->unconfined);
- __aa_fs_ns_rmdir(ns);
+ if (ns->parent) {
+ unsigned long flags;
+
+ write_lock_irqsave(&ns->labels.lock, flags);
+ __aa_proxy_redirect(ns_unconfined(ns),
+ ns_unconfined(ns->parent));
+ write_unlock_irqrestore(&ns->labels.lock, flags);
+ }
+ __aafs_ns_rmdir(ns);
mutex_unlock(&ns->lock);
}
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index f3422a91353c..c600f4dd1783 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -13,7 +13,7 @@
* License.
*
* AppArmor uses a serialized binary format for loading policy. To find
- * policy format documentation look in Documentation/security/apparmor.txt
+ * policy format documentation see Documentation/admin-guide/LSM/apparmor.rst
* All policy is validated before it is used.
*/
@@ -26,6 +26,7 @@
#include "include/context.h"
#include "include/crypto.h"
#include "include/match.h"
+#include "include/path.h"
#include "include/policy.h"
#include "include/policy_unpack.h"
@@ -107,7 +108,7 @@ static int audit_iface(struct aa_profile *new, const char *ns_name,
const char *name, const char *info, struct aa_ext *e,
int error)
{
- struct aa_profile *profile = __aa_current_profile();
+ struct aa_profile *profile = labels_profile(aa_current_raw_label());
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL);
if (e)
aad(&sa)->iface.pos = e->pos - e->start;
@@ -122,16 +123,73 @@ static int audit_iface(struct aa_profile *new, const char *ns_name,
return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb);
}
+void __aa_loaddata_update(struct aa_loaddata *data, long revision)
+{
+ AA_BUG(!data);
+ AA_BUG(!data->ns);
+ AA_BUG(!data->dents[AAFS_LOADDATA_REVISION]);
+ AA_BUG(!mutex_is_locked(&data->ns->lock));
+ AA_BUG(data->revision > revision);
+
+ data->revision = revision;
+ d_inode(data->dents[AAFS_LOADDATA_DIR])->i_mtime =
+ current_time(d_inode(data->dents[AAFS_LOADDATA_DIR]));
+ d_inode(data->dents[AAFS_LOADDATA_REVISION])->i_mtime =
+ current_time(d_inode(data->dents[AAFS_LOADDATA_REVISION]));
+}
+
+bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r)
+{
+ if (l->size != r->size)
+ return false;
+ if (aa_g_hash_policy && memcmp(l->hash, r->hash, aa_hash_size()) != 0)
+ return false;
+ return memcmp(l->data, r->data, r->size) == 0;
+}
+
+/*
+ * need to take the ns mutex lock which is NOT safe most places that
+ * put_loaddata is called, so we have to delay freeing it
+ */
+static void do_loaddata_free(struct work_struct *work)
+{
+ struct aa_loaddata *d = container_of(work, struct aa_loaddata, work);
+ struct aa_ns *ns = aa_get_ns(d->ns);
+
+ if (ns) {
+ mutex_lock(&ns->lock);
+ __aa_fs_remove_rawdata(d);
+ mutex_unlock(&ns->lock);
+ aa_put_ns(ns);
+ }
+
+ kzfree(d->hash);
+ kfree(d->name);
+ kvfree(d);
+}
+
void aa_loaddata_kref(struct kref *kref)
{
struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count);
if (d) {
- kzfree(d->hash);
- kvfree(d);
+ INIT_WORK(&d->work, do_loaddata_free);
+ schedule_work(&d->work);
}
}
+struct aa_loaddata *aa_loaddata_alloc(size_t size)
+{
+ struct aa_loaddata *d = kvzalloc(sizeof(*d) + size, GFP_KERNEL);
+
+ if (d == NULL)
+ return ERR_PTR(-ENOMEM);
+ kref_init(&d->count);
+ INIT_LIST_HEAD(&d->list);
+
+ return d;
+}
+
/* test if read will be in packed data bounds */
static bool inbounds(struct aa_ext *e, size_t size)
{
@@ -408,7 +466,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
profile->file.trans.size = size;
for (i = 0; i < size; i++) {
char *str;
- int c, j, size2 = unpack_strdup(e, &str, NULL);
+ int c, j, pos, size2 = unpack_strdup(e, &str, NULL);
/* unpack_strdup verifies that the last character is
* null termination byte.
*/
@@ -420,19 +478,25 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
goto fail;
/* count internal # of internal \0 */
- for (c = j = 0; j < size2 - 2; j++) {
- if (!str[j])
+ for (c = j = 0; j < size2 - 1; j++) {
+ if (!str[j]) {
+ pos = j;
c++;
+ }
}
if (*str == ':') {
+ /* first character after : must be valid */
+ if (!str[1])
+ goto fail;
/* beginning with : requires an embedded \0,
* verify that exactly 1 internal \0 exists
* trailing \0 already verified by unpack_strdup
+ *
+ * convert \0 back to : for label_parse
*/
- if (c != 1)
- goto fail;
- /* first character after : must be valid */
- if (!str[1])
+ if (c == 1)
+ str[pos] = ':';
+ else if (c > 1)
goto fail;
} else if (c)
/* fail - all other cases with embedded \0 */
@@ -545,7 +609,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
name = tmpname;
}
- profile = aa_alloc_profile(name, GFP_KERNEL);
+ profile = aa_alloc_profile(name, NULL, GFP_KERNEL);
if (!profile)
return ERR_PTR(-ENOMEM);
@@ -569,13 +633,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
profile->xmatch_len = tmp;
}
+ /* disconnected attachment string is optional */
+ (void) unpack_str(e, &profile->disconnected, "disconnected");
+
/* per profile debug flags (complain, audit) */
if (!unpack_nameX(e, AA_STRUCT, "flags"))
goto fail;
if (!unpack_u32(e, &tmp, NULL))
goto fail;
if (tmp & PACKED_FLAG_HAT)
- profile->flags |= PFLAG_HAT;
+ profile->label.flags |= FLAG_HAT;
if (!unpack_u32(e, &tmp, NULL))
goto fail;
if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG))
@@ -594,10 +661,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
/* path_flags is optional */
if (unpack_u32(e, &profile->path_flags, "path_flags"))
- profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED;
+ profile->path_flags |= profile->label.flags &
+ PATH_MEDIATE_DELETED;
else
/* set a default value if path_flags field is not present */
- profile->path_flags = PFLAG_MEDIATE_DELETED;
+ profile->path_flags = PATH_MEDIATE_DELETED;
if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
goto fail;
diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c
index 3466a27bca09..d81617379d63 100644
--- a/security/apparmor/procattr.c
+++ b/security/apparmor/procattr.c
@@ -34,50 +34,41 @@
*
* Returns: size of string placed in @string else error code on failure
*/
-int aa_getprocattr(struct aa_profile *profile, char **string)
+int aa_getprocattr(struct aa_label *label, char **string)
{
- char *str;
- int len = 0, mode_len = 0, ns_len = 0, name_len;
- const char *mode_str = aa_profile_mode_names[profile->mode];
- const char *ns_name = NULL;
- struct aa_ns *ns = profile->ns;
- struct aa_ns *current_ns = __aa_current_profile()->ns;
- char *s;
-
- if (!aa_ns_visible(current_ns, ns, true))
- return -EACCES;
-
- ns_name = aa_ns_name(current_ns, ns, true);
- ns_len = strlen(ns_name);
+ struct aa_ns *ns = labels_ns(label);
+ struct aa_ns *current_ns = aa_get_current_ns();
+ int len;
- /* if the visible ns_name is > 0 increase size for : :// seperator */
- if (ns_len)
- ns_len += 4;
+ if (!aa_ns_visible(current_ns, ns, true)) {
+ aa_put_ns(current_ns);
+ return -EACCES;
+ }
- /* unconfined profiles don't have a mode string appended */
- if (!unconfined(profile))
- mode_len = strlen(mode_str) + 3; /* + 3 for _() */
+ len = aa_label_snxprint(NULL, 0, current_ns, label,
+ FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
+ FLAG_HIDDEN_UNCONFINED);
+ AA_BUG(len < 0);
- name_len = strlen(profile->base.hname);
- len = mode_len + ns_len + name_len + 1; /* + 1 for \n */
- s = str = kmalloc(len + 1, GFP_KERNEL); /* + 1 \0 */
- if (!str)
+ *string = kmalloc(len + 2, GFP_KERNEL);
+ if (!*string) {
+ aa_put_ns(current_ns);
return -ENOMEM;
+ }
- if (ns_len) {
- /* skip over prefix current_ns->base.hname and separating // */
- sprintf(s, ":%s://", ns_name);
- s += ns_len;
+ len = aa_label_snxprint(*string, len + 2, current_ns, label,
+ FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
+ FLAG_HIDDEN_UNCONFINED);
+ if (len < 0) {
+ aa_put_ns(current_ns);
+ return len;
}
- if (unconfined(profile))
- /* mode string not being appended */
- sprintf(s, "%s\n", profile->base.hname);
- else
- sprintf(s, "%s (%s)\n", profile->base.hname, mode_str);
- *string = str;
-
- /* NOTE: len does not include \0 of string, not saved as part of file */
- return len;
+
+ (*string)[len] = '\n';
+ (*string)[len + 1] = 0;
+
+ aa_put_ns(current_ns);
+ return len + 1;
}
/**
@@ -108,11 +99,11 @@ static char *split_token_from_name(const char *op, char *args, u64 *token)
* aa_setprocattr_chagnehat - handle procattr interface to change_hat
* @args: args received from writing to /proc/<pid>/attr/current (NOT NULL)
* @size: size of the args
- * @test: true if this is a test of change_hat permissions
+ * @flags: set of flags governing behavior
*
* Returns: %0 or error code if change_hat fails
*/
-int aa_setprocattr_changehat(char *args, size_t size, int test)
+int aa_setprocattr_changehat(char *args, size_t size, int flags)
{
char *hat;
u64 token;
@@ -147,5 +138,5 @@ int aa_setprocattr_changehat(char *args, size_t size, int test)
AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n",
__func__, current->pid, token, count, "<NULL>");
- return aa_change_hat(hats, count, token, test);
+ return aa_change_hat(hats, count, token, flags);
}
diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c
index 86a941afd956..d8bc842594ed 100644
--- a/security/apparmor/resource.c
+++ b/security/apparmor/resource.c
@@ -13,6 +13,7 @@
*/
#include <linux/audit.h>
+#include <linux/security.h>
#include "include/audit.h"
#include "include/context.h"
@@ -24,8 +25,8 @@
*/
#include "rlim_names.h"
-struct aa_fs_entry aa_fs_entry_rlimit[] = {
- AA_FS_FILE_STRING("mask", AA_FS_RLIMIT_MASK),
+struct aa_sfs_entry aa_sfs_entry_rlimit[] = {
+ AA_SFS_FILE_STRING("mask", AA_SFS_RLIMIT_MASK),
{ }
};
@@ -36,6 +37,11 @@ static void audit_cb(struct audit_buffer *ab, void *va)
audit_log_format(ab, " rlimit=%s value=%lu",
rlim_names[aad(sa)->rlim.rlim], aad(sa)->rlim.max);
+ if (aad(sa)->peer) {
+ audit_log_format(ab, " peer=");
+ aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
+ FLAGS_NONE, GFP_ATOMIC);
+ }
}
/**
@@ -48,13 +54,17 @@ static void audit_cb(struct audit_buffer *ab, void *va)
* Returns: 0 or sa->error else other error code on failure
*/
static int audit_resource(struct aa_profile *profile, unsigned int resource,
- unsigned long value, int error)
+ unsigned long value, struct aa_label *peer,
+ const char *info, int error)
{
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT);
aad(&sa)->rlim.rlim = resource;
aad(&sa)->rlim.max = value;
+ aad(&sa)->peer = peer;
+ aad(&sa)->info = info;
aad(&sa)->error = error;
+
return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb);
}
@@ -72,9 +82,21 @@ int aa_map_resource(int resource)
return rlim_map[resource];
}
+static int profile_setrlimit(struct aa_profile *profile, unsigned int resource,
+ struct rlimit *new_rlim)
+{
+ int e = 0;
+
+ if (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max >
+ profile->rlimits.limits[resource].rlim_max)
+ e = -EACCES;
+ return audit_resource(profile, resource, new_rlim->rlim_max, NULL, NULL,
+ e);
+}
+
/**
* aa_task_setrlimit - test permission to set an rlimit
- * @profile - profile confining the task (NOT NULL)
+ * @label - label confining the task (NOT NULL)
* @task - task the resource is being set on
* @resource - the resource being set
* @new_rlim - the new resource limit (NOT NULL)
@@ -83,14 +105,15 @@ int aa_map_resource(int resource)
*
* Returns: 0 or error code if setting resource failed
*/
-int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task,
+int aa_task_setrlimit(struct aa_label *label, struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim)
{
- struct aa_profile *task_profile;
+ struct aa_profile *profile;
+ struct aa_label *peer;
int error = 0;
rcu_read_lock();
- task_profile = aa_get_profile(aa_cred_profile(__task_cred(task)));
+ peer = aa_get_newest_cred_label(__task_cred(task));
rcu_read_unlock();
/* TODO: extend resource control to handle other (non current)
@@ -99,53 +122,70 @@ int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task,
* the same profile or that the task setting the resource of another
* task has CAP_SYS_RESOURCE.
*/
- if ((profile != task_profile &&
- aa_capable(profile, CAP_SYS_RESOURCE, 1)) ||
- (profile->rlimits.mask & (1 << resource) &&
- new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max))
- error = -EACCES;
- aa_put_profile(task_profile);
-
- return audit_resource(profile, resource, new_rlim->rlim_max, error);
+ if (label != peer &&
+ !aa_capable(label, CAP_SYS_RESOURCE, SECURITY_CAP_NOAUDIT))
+ error = fn_for_each(label, profile,
+ audit_resource(profile, resource,
+ new_rlim->rlim_max, peer,
+ "cap_sys_resoure", -EACCES));
+ else
+ error = fn_for_each_confined(label, profile,
+ profile_setrlimit(profile, resource, new_rlim));
+ aa_put_label(peer);
+
+ return error;
}
/**
* __aa_transition_rlimits - apply new profile rlimits
- * @old: old profile on task (NOT NULL)
- * @new: new profile with rlimits to apply (NOT NULL)
+ * @old_l: old label on task (NOT NULL)
+ * @new_l: new label with rlimits to apply (NOT NULL)
*/
-void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new)
+void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l)
{
unsigned int mask = 0;
struct rlimit *rlim, *initrlim;
- int i;
+ struct aa_profile *old, *new;
+ struct label_it i;
+
+ old = labels_profile(old_l);
+ new = labels_profile(new_l);
- /* for any rlimits the profile controlled reset the soft limit
- * to the less of the tasks hard limit and the init tasks soft limit
+ /* for any rlimits the profile controlled, reset the soft limit
+ * to the lesser of the tasks hard limit and the init tasks soft limit
*/
- if (old->rlimits.mask) {
- for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
- if (old->rlimits.mask & mask) {
- rlim = current->signal->rlim + i;
- initrlim = init_task.signal->rlim + i;
- rlim->rlim_cur = min(rlim->rlim_max,
- initrlim->rlim_cur);
+ label_for_each_confined(i, old_l, old) {
+ if (old->rlimits.mask) {
+ int j;
+
+ for (j = 0, mask = 1; j < RLIM_NLIMITS; j++,
+ mask <<= 1) {
+ if (old->rlimits.mask & mask) {
+ rlim = current->signal->rlim + j;
+ initrlim = init_task.signal->rlim + j;
+ rlim->rlim_cur = min(rlim->rlim_max,
+ initrlim->rlim_cur);
+ }
}
}
}
/* set any new hard limits as dictated by the new profile */
- if (!new->rlimits.mask)
- return;
- for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
- if (!(new->rlimits.mask & mask))
- continue;
+ label_for_each_confined(i, new_l, new) {
+ int j;
- rlim = current->signal->rlim + i;
- rlim->rlim_max = min(rlim->rlim_max,
- new->rlimits.limits[i].rlim_max);
- /* soft limit should not exceed hard limit */
- rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
+ if (!new->rlimits.mask)
+ continue;
+ for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) {
+ if (!(new->rlimits.mask & mask))
+ continue;
+
+ rlim = current->signal->rlim + j;
+ rlim->rlim_max = min(rlim->rlim_max,
+ new->rlimits.limits[j].rlim_max);
+ /* soft limit should not exceed hard limit */
+ rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
+ }
}
}
diff --git a/security/inode.c b/security/inode.c
index eccd58ef2ae8..8dd9ca8848e4 100644
--- a/security/inode.c
+++ b/security/inode.c
@@ -26,11 +26,31 @@
static struct vfsmount *mount;
static int mount_count;
+static void securityfs_evict_inode(struct inode *inode)
+{
+ truncate_inode_pages_final(&inode->i_data);
+ clear_inode(inode);
+ if (S_ISLNK(inode->i_mode))
+ kfree(inode->i_link);
+}
+
+static const struct super_operations securityfs_super_operations = {
+ .statfs = simple_statfs,
+ .evict_inode = securityfs_evict_inode,
+};
+
static int fill_super(struct super_block *sb, void *data, int silent)
{
static const struct tree_descr files[] = {{""}};
+ int error;
+
+ error = simple_fill_super(sb, SECURITYFS_MAGIC, files);
+ if (error)
+ return error;
+
+ sb->s_op = &securityfs_super_operations;
- return simple_fill_super(sb, SECURITYFS_MAGIC, files);
+ return 0;
}
static struct dentry *get_sb(struct file_system_type *fs_type,
@@ -48,7 +68,7 @@ static struct file_system_type fs_type = {
};
/**
- * securityfs_create_file - create a file in the securityfs filesystem
+ * securityfs_create_dentry - create a dentry in the securityfs filesystem
*
* @name: a pointer to a string containing the name of the file to create.
* @mode: the permission that the file should have
@@ -60,34 +80,35 @@ static struct file_system_type fs_type = {
* the open() call.
* @fops: a pointer to a struct file_operations that should be used for
* this file.
+ * @iops: a point to a struct of inode_operations that should be used for
+ * this file/dir
*
- * This is the basic "create a file" function for securityfs. It allows for a
- * wide range of flexibility in creating a file, or a directory (if you
- * want to create a directory, the securityfs_create_dir() function is
- * recommended to be used instead).
+ * This is the basic "create a file/dir/symlink" function for
+ * securityfs. It allows for a wide range of flexibility in creating
+ * a file, or a directory (if you want to create a directory, the
+ * securityfs_create_dir() function is recommended to be used
+ * instead).
*
* This function returns a pointer to a dentry if it succeeds. This
- * pointer must be passed to the securityfs_remove() function when the file is
- * to be removed (no automatic cleanup happens if your module is unloaded,
- * you are responsible here). If an error occurs, the function will return
- * the error value (via ERR_PTR).
+ * pointer must be passed to the securityfs_remove() function when the
+ * file is to be removed (no automatic cleanup happens if your module
+ * is unloaded, you are responsible here). If an error occurs, the
+ * function will return the error value (via ERR_PTR).
*
* If securityfs is not enabled in the kernel, the value %-ENODEV is
* returned.
*/
-struct dentry *securityfs_create_file(const char *name, umode_t mode,
- struct dentry *parent, void *data,
- const struct file_operations *fops)
+static struct dentry *securityfs_create_dentry(const char *name, umode_t mode,
+ struct dentry *parent, void *data,
+ const struct file_operations *fops,
+ const struct inode_operations *iops)
{
struct dentry *dentry;
- int is_dir = S_ISDIR(mode);
struct inode *dir, *inode;
int error;
- if (!is_dir) {
- BUG_ON(!fops);
+ if (!(mode & S_IFMT))
mode = (mode & S_IALLUGO) | S_IFREG;
- }
pr_debug("securityfs: creating file '%s'\n",name);
@@ -120,11 +141,14 @@ struct dentry *securityfs_create_file(const char *name, umode_t mode,
inode->i_mode = mode;
inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
inode->i_private = data;
- if (is_dir) {
+ if (S_ISDIR(mode)) {
inode->i_op = &simple_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
inc_nlink(inode);
inc_nlink(dir);
+ } else if (S_ISLNK(mode)) {
+ inode->i_op = iops ? iops : &simple_symlink_inode_operations;
+ inode->i_link = data;
} else {
inode->i_fop = fops;
}
@@ -141,6 +165,38 @@ out:
simple_release_fs(&mount, &mount_count);
return dentry;
}
+
+/**
+ * securityfs_create_file - create a file in the securityfs filesystem
+ *
+ * @name: a pointer to a string containing the name of the file to create.
+ * @mode: the permission that the file should have
+ * @parent: a pointer to the parent dentry for this file. This should be a
+ * directory dentry if set. If this parameter is %NULL, then the
+ * file will be created in the root of the securityfs filesystem.
+ * @data: a pointer to something that the caller will want to get to later
+ * on. The inode.i_private pointer will point to this value on
+ * the open() call.
+ * @fops: a pointer to a struct file_operations that should be used for
+ * this file.
+ *
+ * This function creates a file in securityfs with the given @name.
+ *
+ * This function returns a pointer to a dentry if it succeeds. This
+ * pointer must be passed to the securityfs_remove() function when the file is
+ * to be removed (no automatic cleanup happens if your module is unloaded,
+ * you are responsible here). If an error occurs, the function will return
+ * the error value (via ERR_PTR).
+ *
+ * If securityfs is not enabled in the kernel, the value %-ENODEV is
+ * returned.
+ */
+struct dentry *securityfs_create_file(const char *name, umode_t mode,
+ struct dentry *parent, void *data,
+ const struct file_operations *fops)
+{
+ return securityfs_create_dentry(name, mode, parent, data, fops, NULL);
+}
EXPORT_SYMBOL_GPL(securityfs_create_file);
/**
@@ -165,13 +221,59 @@ EXPORT_SYMBOL_GPL(securityfs_create_file);
*/
struct dentry *securityfs_create_dir(const char *name, struct dentry *parent)
{
- return securityfs_create_file(name,
- S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO,
- parent, NULL, NULL);
+ return securityfs_create_file(name, S_IFDIR | 0755, parent, NULL, NULL);
}
EXPORT_SYMBOL_GPL(securityfs_create_dir);
/**
+ * securityfs_create_symlink - create a symlink in the securityfs filesystem
+ *
+ * @name: a pointer to a string containing the name of the symlink to
+ * create.
+ * @parent: a pointer to the parent dentry for the symlink. This should be a
+ * directory dentry if set. If this parameter is %NULL, then the
+ * directory will be created in the root of the securityfs filesystem.
+ * @target: a pointer to a string containing the name of the symlink's target.
+ * If this parameter is %NULL, then the @iops parameter needs to be
+ * setup to handle .readlink and .get_link inode_operations.
+ * @iops: a pointer to the struct inode_operations to use for the symlink. If
+ * this parameter is %NULL, then the default simple_symlink_inode
+ * operations will be used.
+ *
+ * This function creates a symlink in securityfs with the given @name.
+ *
+ * This function returns a pointer to a dentry if it succeeds. This
+ * pointer must be passed to the securityfs_remove() function when the file is
+ * to be removed (no automatic cleanup happens if your module is unloaded,
+ * you are responsible here). If an error occurs, the function will return
+ * the error value (via ERR_PTR).
+ *
+ * If securityfs is not enabled in the kernel, the value %-ENODEV is
+ * returned.
+ */
+struct dentry *securityfs_create_symlink(const char *name,
+ struct dentry *parent,
+ const char *target,
+ const struct inode_operations *iops)
+{
+ struct dentry *dent;
+ char *link = NULL;
+
+ if (target) {
+ link = kstrdup(target, GFP_KERNEL);
+ if (!link)
+ return ERR_PTR(-ENOMEM);
+ }
+ dent = securityfs_create_dentry(name, S_IFLNK | 0444, parent,
+ link, NULL, iops);
+ if (IS_ERR(dent))
+ kfree(link);
+
+ return dent;
+}
+EXPORT_SYMBOL_GPL(securityfs_create_symlink);
+
+/**
* securityfs_remove - removes a file or directory from the securityfs filesystem
*
* @dentry: a pointer to a the dentry of the file or directory to be removed.
diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c
index 80052ed8d467..ab6a029062a1 100644
--- a/security/integrity/digsig_asymmetric.c
+++ b/security/integrity/digsig_asymmetric.c
@@ -92,13 +92,13 @@ int asymmetric_verify(struct key *keyring, const char *sig,
siglen -= sizeof(*hdr);
- if (siglen != __be16_to_cpu(hdr->sig_size))
+ if (siglen != be16_to_cpu(hdr->sig_size))
return -EBADMSG;
if (hdr->hash_algo >= HASH_ALGO__LAST)
return -ENOPKG;
- key = request_asymmetric_key(keyring, __be32_to_cpu(hdr->keyid));
+ key = request_asymmetric_key(keyring, be32_to_cpu(hdr->keyid));
if (IS_ERR(key))
return PTR_ERR(key);
diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c
index d7f282d75cc1..1d32cd20009a 100644
--- a/security/integrity/evm/evm_crypto.c
+++ b/security/integrity/evm/evm_crypto.c
@@ -164,7 +164,7 @@ static void hmac_add_misc(struct shash_desc *desc, struct inode *inode,
hmac_misc.mode = inode->i_mode;
crypto_shash_update(desc, (const u8 *)&hmac_misc, sizeof(hmac_misc));
if (evm_hmac_attrs & EVM_ATTR_FSUUID)
- crypto_shash_update(desc, inode->i_sb->s_uuid,
+ crypto_shash_update(desc, &inode->i_sb->s_uuid.b[0],
sizeof(inode->i_sb->s_uuid));
crypto_shash_final(desc, digest);
}
diff --git a/security/integrity/iint.c b/security/integrity/iint.c
index c710d22042f9..6fc888ca468e 100644
--- a/security/integrity/iint.c
+++ b/security/integrity/iint.c
@@ -182,7 +182,7 @@ security_initcall(integrity_iintcache_init);
*
*/
int integrity_kernel_read(struct file *file, loff_t offset,
- char *addr, unsigned long count)
+ void *addr, unsigned long count)
{
mm_segment_t old_fs;
char __user *buf = (char __user *)addr;
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index 370eb2f4dd37..35ef69312811 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -96,19 +96,19 @@ choice
config IMA_DEFAULT_HASH_SHA1
bool "SHA1 (default)"
- depends on CRYPTO_SHA1
+ depends on CRYPTO_SHA1=y
config IMA_DEFAULT_HASH_SHA256
bool "SHA256"
- depends on CRYPTO_SHA256 && !IMA_TEMPLATE
+ depends on CRYPTO_SHA256=y && !IMA_TEMPLATE
config IMA_DEFAULT_HASH_SHA512
bool "SHA512"
- depends on CRYPTO_SHA512 && !IMA_TEMPLATE
+ depends on CRYPTO_SHA512=y && !IMA_TEMPLATE
config IMA_DEFAULT_HASH_WP512
bool "WP512"
- depends on CRYPTO_WP512 && !IMA_TEMPLATE
+ depends on CRYPTO_WP512=y && !IMA_TEMPLATE
endchoice
config IMA_DEFAULT_HASH
@@ -155,6 +155,14 @@ config IMA_APPRAISE
<http://linux-ima.sourceforge.net>
If unsure, say N.
+config IMA_APPRAISE_BOOTPARAM
+ bool "ima_appraise boot parameter"
+ depends on IMA_APPRAISE
+ default y
+ help
+ This option enables the different "ima_appraise=" modes
+ (eg. fix, log) from the boot command line.
+
config IMA_TRUSTED_KEYRING
bool "Require all keys on the .ima keyring be signed (deprecated)"
depends on IMA_APPRAISE && SYSTEM_TRUSTED_KEYRING
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index b563fbd4d122..d52b487ad259 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -172,17 +172,22 @@ static inline unsigned long ima_hash_key(u8 *digest)
return hash_long(*digest, IMA_HASH_BITS);
}
+#define __ima_hooks(hook) \
+ hook(NONE) \
+ hook(FILE_CHECK) \
+ hook(MMAP_CHECK) \
+ hook(BPRM_CHECK) \
+ hook(POST_SETATTR) \
+ hook(MODULE_CHECK) \
+ hook(FIRMWARE_CHECK) \
+ hook(KEXEC_KERNEL_CHECK) \
+ hook(KEXEC_INITRAMFS_CHECK) \
+ hook(POLICY_CHECK) \
+ hook(MAX_CHECK)
+#define __ima_hook_enumify(ENUM) ENUM,
+
enum ima_hooks {
- FILE_CHECK = 1,
- MMAP_CHECK,
- BPRM_CHECK,
- POST_SETATTR,
- MODULE_CHECK,
- FIRMWARE_CHECK,
- KEXEC_KERNEL_CHECK,
- KEXEC_INITRAMFS_CHECK,
- POLICY_CHECK,
- MAX_CHECK
+ __ima_hooks(__ima_hook_enumify)
};
/* LIM API function definitions */
@@ -284,7 +289,7 @@ static inline int ima_read_xattr(struct dentry *dentry,
return 0;
}
-#endif
+#endif /* CONFIG_IMA_APPRAISE */
/* LSM based policy rules require audit */
#ifdef CONFIG_IMA_LSM_RULES
@@ -306,12 +311,12 @@ static inline int security_filter_rule_match(u32 secid, u32 field, u32 op,
{
return -EINVAL;
}
-#endif /* CONFIG_IMA_TRUSTED_KEYRING */
+#endif /* CONFIG_IMA_LSM_RULES */
#ifdef CONFIG_IMA_READ_POLICY
#define POLICY_FILE_FLAGS (S_IWUSR | S_IRUSR)
#else
#define POLICY_FILE_FLAGS S_IWUSR
-#endif /* CONFIG_IMA_WRITE_POLICY */
+#endif /* CONFIG_IMA_READ_POLICY */
#endif /* __LINUX_IMA_H */
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index 5d0785cfe063..809ba70fbbbf 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -20,18 +20,30 @@
static int __init default_appraise_setup(char *str)
{
+#ifdef CONFIG_IMA_APPRAISE_BOOTPARAM
if (strncmp(str, "off", 3) == 0)
ima_appraise = 0;
else if (strncmp(str, "log", 3) == 0)
ima_appraise = IMA_APPRAISE_LOG;
else if (strncmp(str, "fix", 3) == 0)
ima_appraise = IMA_APPRAISE_FIX;
+#endif
return 1;
}
__setup("ima_appraise=", default_appraise_setup);
/*
+ * is_ima_appraise_enabled - return appraise status
+ *
+ * Only return enabled, if not in ima_appraise="fix" or "log" modes.
+ */
+bool is_ima_appraise_enabled(void)
+{
+ return (ima_appraise & IMA_APPRAISE_ENFORCE) ? 1 : 0;
+}
+
+/*
* ima_must_appraise - set appraise flag
*
* Return 1 to appraise
@@ -205,7 +217,8 @@ int ima_appraise_measurement(enum ima_hooks func,
if (rc && rc != -ENODATA)
goto out;
- cause = "missing-hash";
+ cause = iint->flags & IMA_DIGSIG_REQUIRED ?
+ "IMA-signature-required" : "missing-hash";
status = INTEGRITY_NOLABEL;
if (opened & FILE_CREATED)
iint->flags |= IMA_NEW_FILE;
@@ -228,6 +241,7 @@ int ima_appraise_measurement(enum ima_hooks func,
case IMA_XATTR_DIGEST_NG:
/* first byte contains algorithm id */
hash_start = 1;
+ /* fall through */
case IMA_XATTR_DIGEST:
if (iint->flags & IMA_DIGSIG_REQUIRED) {
cause = "IMA-signature-required";
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index ca303e5d2b94..ad491c51e833 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -323,16 +323,11 @@ static ssize_t ima_write_policy(struct file *file, const char __user *buf,
if (*ppos != 0)
goto out;
- result = -ENOMEM;
- data = kmalloc(datalen + 1, GFP_KERNEL);
- if (!data)
+ data = memdup_user_nul(buf, datalen);
+ if (IS_ERR(data)) {
+ result = PTR_ERR(data);
goto out;
-
- *(data + datalen) = '\0';
-
- result = -EFAULT;
- if (copy_from_user(data, buf, datalen))
- goto out_free;
+ }
result = mutex_lock_interruptible(&ima_write_mutex);
if (result < 0)
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 3ab1067db624..95209a5f8595 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -61,7 +61,7 @@ struct ima_rule_entry {
enum ima_hooks func;
int mask;
unsigned long fsmagic;
- u8 fsuuid[16];
+ uuid_t fsuuid;
kuid_t uid;
kuid_t fowner;
bool (*uid_op)(kuid_t, kuid_t); /* Handlers for operators */
@@ -96,6 +96,8 @@ static struct ima_rule_entry dont_measure_rules[] __ro_after_init = {
{.action = DONT_MEASURE, .fsmagic = SELINUX_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_MEASURE, .fsmagic = CGROUP_SUPER_MAGIC,
.flags = IMA_FSMAGIC},
+ {.action = DONT_MEASURE, .fsmagic = CGROUP2_SUPER_MAGIC,
+ .flags = IMA_FSMAGIC},
{.action = DONT_MEASURE, .fsmagic = NSFS_MAGIC, .flags = IMA_FSMAGIC}
};
@@ -139,6 +141,7 @@ static struct ima_rule_entry default_appraise_rules[] __ro_after_init = {
{.action = DONT_APPRAISE, .fsmagic = SELINUX_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = NSFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = CGROUP_SUPER_MAGIC, .flags = IMA_FSMAGIC},
+ {.action = DONT_APPRAISE, .fsmagic = CGROUP2_SUPER_MAGIC, .flags = IMA_FSMAGIC},
#ifdef CONFIG_IMA_WRITE_POLICY
{.action = APPRAISE, .func = POLICY_CHECK,
.flags = IMA_FUNC | IMA_DIGSIG_REQUIRED},
@@ -153,6 +156,17 @@ static struct ima_rule_entry default_appraise_rules[] __ro_after_init = {
#endif
};
+static struct ima_rule_entry secure_boot_rules[] __ro_after_init = {
+ {.action = APPRAISE, .func = MODULE_CHECK,
+ .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED},
+ {.action = APPRAISE, .func = FIRMWARE_CHECK,
+ .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED},
+ {.action = APPRAISE, .func = KEXEC_KERNEL_CHECK,
+ .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED},
+ {.action = APPRAISE, .func = POLICY_CHECK,
+ .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED},
+};
+
static LIST_HEAD(ima_default_rules);
static LIST_HEAD(ima_policy_rules);
static LIST_HEAD(ima_temp_rules);
@@ -170,19 +184,27 @@ static int __init default_measure_policy_setup(char *str)
}
__setup("ima_tcb", default_measure_policy_setup);
+static bool ima_use_appraise_tcb __initdata;
+static bool ima_use_secure_boot __initdata;
static int __init policy_setup(char *str)
{
- if (ima_policy)
- return 1;
+ char *p;
- if (strcmp(str, "tcb") == 0)
- ima_policy = DEFAULT_TCB;
+ while ((p = strsep(&str, " |\n")) != NULL) {
+ if (*p == ' ')
+ continue;
+ if ((strcmp(p, "tcb") == 0) && !ima_policy)
+ ima_policy = DEFAULT_TCB;
+ else if (strcmp(p, "appraise_tcb") == 0)
+ ima_use_appraise_tcb = 1;
+ else if (strcmp(p, "secure_boot") == 0)
+ ima_use_secure_boot = 1;
+ }
return 1;
}
__setup("ima_policy=", policy_setup);
-static bool ima_use_appraise_tcb __initdata;
static int __init default_appraise_policy_setup(char *str)
{
ima_use_appraise_tcb = 1;
@@ -244,7 +266,7 @@ static bool ima_match_rules(struct ima_rule_entry *rule, struct inode *inode,
&& rule->fsmagic != inode->i_sb->s_magic)
return false;
if ((rule->flags & IMA_FSUUID) &&
- memcmp(rule->fsuuid, inode->i_sb->s_uuid, sizeof(rule->fsuuid)))
+ !uuid_equal(&rule->fsuuid, &inode->i_sb->s_uuid))
return false;
if ((rule->flags & IMA_UID) && !rule->uid_op(cred->uid, rule->uid))
return false;
@@ -405,12 +427,14 @@ void ima_update_policy_flag(void)
*/
void __init ima_init_policy(void)
{
- int i, measure_entries, appraise_entries;
+ int i, measure_entries, appraise_entries, secure_boot_entries;
/* if !ima_policy set entries = 0 so we load NO default rules */
measure_entries = ima_policy ? ARRAY_SIZE(dont_measure_rules) : 0;
appraise_entries = ima_use_appraise_tcb ?
ARRAY_SIZE(default_appraise_rules) : 0;
+ secure_boot_entries = ima_use_secure_boot ?
+ ARRAY_SIZE(secure_boot_rules) : 0;
for (i = 0; i < measure_entries; i++)
list_add_tail(&dont_measure_rules[i].list, &ima_default_rules);
@@ -429,6 +453,14 @@ void __init ima_init_policy(void)
break;
}
+ /*
+ * Insert the appraise rules requiring file signatures, prior to
+ * any other appraise rules.
+ */
+ for (i = 0; i < secure_boot_entries; i++)
+ list_add_tail(&secure_boot_rules[i].list,
+ &ima_default_rules);
+
for (i = 0; i < appraise_entries; i++) {
list_add_tail(&default_appraise_rules[i].list,
&ima_default_rules);
@@ -711,14 +743,12 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
case Opt_fsuuid:
ima_log_string(ab, "fsuuid", args[0].from);
- if (memchr_inv(entry->fsuuid, 0x00,
- sizeof(entry->fsuuid))) {
+ if (uuid_is_null(&entry->fsuuid)) {
result = -EINVAL;
break;
}
- result = blk_part_pack_uuid(args[0].from,
- entry->fsuuid);
+ result = uuid_parse(args[0].from, &entry->fsuuid);
if (!result)
entry->flags |= IMA_FSUUID;
break;
@@ -933,30 +963,17 @@ enum {
mask_exec = 0, mask_write, mask_read, mask_append
};
-static char *mask_tokens[] = {
+static const char *const mask_tokens[] = {
"MAY_EXEC",
"MAY_WRITE",
"MAY_READ",
"MAY_APPEND"
};
-enum {
- func_file = 0, func_mmap, func_bprm,
- func_module, func_firmware, func_post,
- func_kexec_kernel, func_kexec_initramfs,
- func_policy
-};
+#define __ima_hook_stringify(str) (#str),
-static char *func_tokens[] = {
- "FILE_CHECK",
- "MMAP_CHECK",
- "BPRM_CHECK",
- "MODULE_CHECK",
- "FIRMWARE_CHECK",
- "POST_SETATTR",
- "KEXEC_KERNEL_CHECK",
- "KEXEC_INITRAMFS_CHECK",
- "POLICY_CHECK"
+static const char *const func_tokens[] = {
+ __ima_hooks(__ima_hook_stringify)
};
void *ima_policy_start(struct seq_file *m, loff_t *pos)
@@ -993,49 +1010,16 @@ void ima_policy_stop(struct seq_file *m, void *v)
#define pt(token) policy_tokens[token + Opt_err].pattern
#define mt(token) mask_tokens[token]
-#define ft(token) func_tokens[token]
/*
* policy_func_show - display the ima_hooks policy rule
*/
static void policy_func_show(struct seq_file *m, enum ima_hooks func)
{
- char tbuf[64] = {0,};
-
- switch (func) {
- case FILE_CHECK:
- seq_printf(m, pt(Opt_func), ft(func_file));
- break;
- case MMAP_CHECK:
- seq_printf(m, pt(Opt_func), ft(func_mmap));
- break;
- case BPRM_CHECK:
- seq_printf(m, pt(Opt_func), ft(func_bprm));
- break;
- case MODULE_CHECK:
- seq_printf(m, pt(Opt_func), ft(func_module));
- break;
- case FIRMWARE_CHECK:
- seq_printf(m, pt(Opt_func), ft(func_firmware));
- break;
- case POST_SETATTR:
- seq_printf(m, pt(Opt_func), ft(func_post));
- break;
- case KEXEC_KERNEL_CHECK:
- seq_printf(m, pt(Opt_func), ft(func_kexec_kernel));
- break;
- case KEXEC_INITRAMFS_CHECK:
- seq_printf(m, pt(Opt_func), ft(func_kexec_initramfs));
- break;
- case POLICY_CHECK:
- seq_printf(m, pt(Opt_func), ft(func_policy));
- break;
- default:
- snprintf(tbuf, sizeof(tbuf), "%d", func);
- seq_printf(m, pt(Opt_func), tbuf);
- break;
- }
- seq_puts(m, " ");
+ if (func > 0 && func < MAX_CHECK)
+ seq_printf(m, "func=%s ", func_tokens[func]);
+ else
+ seq_printf(m, "func=%d ", func);
}
int ima_policy_show(struct seq_file *m, void *v)
@@ -1087,7 +1071,7 @@ int ima_policy_show(struct seq_file *m, void *v)
}
if (entry->flags & IMA_FSUUID) {
- seq_printf(m, "fsuuid=%pU", entry->fsuuid);
+ seq_printf(m, "fsuuid=%pU", &entry->fsuuid);
seq_puts(m, " ");
}
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index d9aa5ab71204..a02a86d51102 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -81,7 +81,7 @@ static int get_binary_runtime_size(struct ima_template_entry *entry)
size += sizeof(u32); /* pcr */
size += sizeof(entry->digest);
size += sizeof(int); /* template name size field */
- size += strlen(entry->template_desc->name) + 1;
+ size += strlen(entry->template_desc->name);
size += sizeof(entry->template_data_len);
size += entry->template_data_len;
return size;
diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c
index cebb37c63629..7412d0291ab9 100644
--- a/security/integrity/ima/ima_template.c
+++ b/security/integrity/ima/ima_template.c
@@ -19,6 +19,9 @@
#include "ima.h"
#include "ima_template_lib.h"
+enum header_fields { HDR_PCR, HDR_DIGEST, HDR_TEMPLATE_NAME,
+ HDR_TEMPLATE_DATA, HDR__LAST };
+
static struct ima_template_desc builtin_templates[] = {
{.name = IMA_TEMPLATE_IMA_NAME, .fmt = IMA_TEMPLATE_IMA_FMT},
{.name = "ima-ng", .fmt = "d-ng|n-ng"},
@@ -274,13 +277,6 @@ static int ima_restore_template_data(struct ima_template_desc *template_desc,
int template_data_size,
struct ima_template_entry **entry)
{
- struct binary_field_data {
- u32 len;
- u8 data[0];
- } __packed;
-
- struct binary_field_data *field_data;
- int offset = 0;
int ret = 0;
int i;
@@ -290,30 +286,19 @@ static int ima_restore_template_data(struct ima_template_desc *template_desc,
if (!*entry)
return -ENOMEM;
+ ret = ima_parse_buf(template_data, template_data + template_data_size,
+ NULL, template_desc->num_fields,
+ (*entry)->template_data, NULL, NULL,
+ ENFORCE_FIELDS | ENFORCE_BUFEND, "template data");
+ if (ret < 0) {
+ kfree(*entry);
+ return ret;
+ }
+
(*entry)->template_desc = template_desc;
for (i = 0; i < template_desc->num_fields; i++) {
- field_data = template_data + offset;
-
- /* Each field of the template data is prefixed with a length. */
- if (offset > (template_data_size - sizeof(*field_data))) {
- pr_err("Restoring the template field failed\n");
- ret = -EINVAL;
- break;
- }
- offset += sizeof(*field_data);
-
- if (ima_canonical_fmt)
- field_data->len = le32_to_cpu(field_data->len);
-
- if (offset > (template_data_size - field_data->len)) {
- pr_err("Restoring the template field data failed\n");
- ret = -EINVAL;
- break;
- }
- offset += field_data->len;
-
- (*entry)->template_data[i].len = field_data->len;
- (*entry)->template_data_len += sizeof(field_data->len);
+ struct ima_field_data *field_data = &(*entry)->template_data[i];
+ u8 *data = field_data->data;
(*entry)->template_data[i].data =
kzalloc(field_data->len + 1, GFP_KERNEL);
@@ -321,8 +306,8 @@ static int ima_restore_template_data(struct ima_template_desc *template_desc,
ret = -ENOMEM;
break;
}
- memcpy((*entry)->template_data[i].data, field_data->data,
- field_data->len);
+ memcpy((*entry)->template_data[i].data, data, field_data->len);
+ (*entry)->template_data_len += sizeof(field_data->len);
(*entry)->template_data_len += field_data->len;
}
@@ -337,27 +322,19 @@ static int ima_restore_template_data(struct ima_template_desc *template_desc,
/* Restore the serialized binary measurement list without extending PCRs. */
int ima_restore_measurement_list(loff_t size, void *buf)
{
- struct binary_hdr_v1 {
- u32 pcr;
- u8 digest[TPM_DIGEST_SIZE];
- u32 template_name_len;
- char template_name[0];
- } __packed;
char template_name[MAX_TEMPLATE_NAME_LEN];
- struct binary_data_v1 {
- u32 template_data_size;
- char template_data[0];
- } __packed;
-
struct ima_kexec_hdr *khdr = buf;
- struct binary_hdr_v1 *hdr_v1;
- struct binary_data_v1 *data_v1;
+ struct ima_field_data hdr[HDR__LAST] = {
+ [HDR_PCR] = {.len = sizeof(u32)},
+ [HDR_DIGEST] = {.len = TPM_DIGEST_SIZE},
+ };
void *bufp = buf + sizeof(*khdr);
void *bufendp;
struct ima_template_entry *entry;
struct ima_template_desc *template_desc;
+ DECLARE_BITMAP(hdr_mask, HDR__LAST);
unsigned long count = 0;
int ret = 0;
@@ -380,6 +357,10 @@ int ima_restore_measurement_list(loff_t size, void *buf)
return -EINVAL;
}
+ bitmap_zero(hdr_mask, HDR__LAST);
+ bitmap_set(hdr_mask, HDR_PCR, 1);
+ bitmap_set(hdr_mask, HDR_DIGEST, 1);
+
/*
* ima kexec buffer prefix: version, buffer size, count
* v1 format: pcr, digest, template-name-len, template-name,
@@ -387,31 +368,25 @@ int ima_restore_measurement_list(loff_t size, void *buf)
*/
bufendp = buf + khdr->buffer_size;
while ((bufp < bufendp) && (count++ < khdr->count)) {
- hdr_v1 = bufp;
- if (bufp > (bufendp - sizeof(*hdr_v1))) {
- pr_err("attempting to restore partial measurement\n");
- ret = -EINVAL;
- break;
- }
- bufp += sizeof(*hdr_v1);
+ int enforce_mask = ENFORCE_FIELDS;
- if (ima_canonical_fmt)
- hdr_v1->template_name_len =
- le32_to_cpu(hdr_v1->template_name_len);
+ enforce_mask |= (count == khdr->count) ? ENFORCE_BUFEND : 0;
+ ret = ima_parse_buf(bufp, bufendp, &bufp, HDR__LAST, hdr, NULL,
+ hdr_mask, enforce_mask, "entry header");
+ if (ret < 0)
+ break;
- if ((hdr_v1->template_name_len >= MAX_TEMPLATE_NAME_LEN) ||
- (bufp > (bufendp - hdr_v1->template_name_len))) {
+ if (hdr[HDR_TEMPLATE_NAME].len >= MAX_TEMPLATE_NAME_LEN) {
pr_err("attempting to restore a template name \
that is too long\n");
ret = -EINVAL;
break;
}
- data_v1 = bufp += (u_int8_t)hdr_v1->template_name_len;
/* template name is not null terminated */
- memcpy(template_name, hdr_v1->template_name,
- hdr_v1->template_name_len);
- template_name[hdr_v1->template_name_len] = 0;
+ memcpy(template_name, hdr[HDR_TEMPLATE_NAME].data,
+ hdr[HDR_TEMPLATE_NAME].len);
+ template_name[hdr[HDR_TEMPLATE_NAME].len] = 0;
if (strcmp(template_name, "ima") == 0) {
pr_err("attempting to restore an unsupported \
@@ -441,34 +416,17 @@ int ima_restore_measurement_list(loff_t size, void *buf)
break;
}
- if (bufp > (bufendp - sizeof(data_v1->template_data_size))) {
- pr_err("restoring the template data size failed\n");
- ret = -EINVAL;
- break;
- }
- bufp += (u_int8_t) sizeof(data_v1->template_data_size);
-
- if (ima_canonical_fmt)
- data_v1->template_data_size =
- le32_to_cpu(data_v1->template_data_size);
-
- if (bufp > (bufendp - data_v1->template_data_size)) {
- pr_err("restoring the template data failed\n");
- ret = -EINVAL;
- break;
- }
- bufp += data_v1->template_data_size;
-
ret = ima_restore_template_data(template_desc,
- data_v1->template_data,
- data_v1->template_data_size,
+ hdr[HDR_TEMPLATE_DATA].data,
+ hdr[HDR_TEMPLATE_DATA].len,
&entry);
if (ret < 0)
break;
- memcpy(entry->digest, hdr_v1->digest, TPM_DIGEST_SIZE);
- entry->pcr =
- !ima_canonical_fmt ? hdr_v1->pcr : le32_to_cpu(hdr_v1->pcr);
+ memcpy(entry->digest, hdr[HDR_DIGEST].data,
+ hdr[HDR_DIGEST].len);
+ entry->pcr = !ima_canonical_fmt ? *(hdr[HDR_PCR].data) :
+ le32_to_cpu(*(hdr[HDR_PCR].data));
ret = ima_restore_measurement_entry(entry);
if (ret < 0)
break;
diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c
index f9ba37b3928d..28af43f63572 100644
--- a/security/integrity/ima/ima_template_lib.c
+++ b/security/integrity/ima/ima_template_lib.c
@@ -159,6 +159,67 @@ void ima_show_template_sig(struct seq_file *m, enum ima_show_type show,
ima_show_template_field_data(m, show, DATA_FMT_HEX, field_data);
}
+/**
+ * ima_parse_buf() - Parses lengths and data from an input buffer
+ * @bufstartp: Buffer start address.
+ * @bufendp: Buffer end address.
+ * @bufcurp: Pointer to remaining (non-parsed) data.
+ * @maxfields: Length of fields array.
+ * @fields: Array containing lengths and pointers of parsed data.
+ * @curfields: Number of array items containing parsed data.
+ * @len_mask: Bitmap (if bit is set, data length should not be parsed).
+ * @enforce_mask: Check if curfields == maxfields and/or bufcurp == bufendp.
+ * @bufname: String identifier of the input buffer.
+ *
+ * Return: 0 on success, -EINVAL on error.
+ */
+int ima_parse_buf(void *bufstartp, void *bufendp, void **bufcurp,
+ int maxfields, struct ima_field_data *fields, int *curfields,
+ unsigned long *len_mask, int enforce_mask, char *bufname)
+{
+ void *bufp = bufstartp;
+ int i;
+
+ for (i = 0; i < maxfields; i++) {
+ if (len_mask == NULL || !test_bit(i, len_mask)) {
+ if (bufp > (bufendp - sizeof(u32)))
+ break;
+
+ fields[i].len = *(u32 *)bufp;
+ if (ima_canonical_fmt)
+ fields[i].len = le32_to_cpu(fields[i].len);
+
+ bufp += sizeof(u32);
+ }
+
+ if (bufp > (bufendp - fields[i].len))
+ break;
+
+ fields[i].data = bufp;
+ bufp += fields[i].len;
+ }
+
+ if ((enforce_mask & ENFORCE_FIELDS) && i != maxfields) {
+ pr_err("%s: nr of fields mismatch: expected: %d, current: %d\n",
+ bufname, maxfields, i);
+ return -EINVAL;
+ }
+
+ if ((enforce_mask & ENFORCE_BUFEND) && bufp != bufendp) {
+ pr_err("%s: buf end mismatch: expected: %p, current: %p\n",
+ bufname, bufendp, bufp);
+ return -EINVAL;
+ }
+
+ if (curfields)
+ *curfields = i;
+
+ if (bufcurp)
+ *bufcurp = bufp;
+
+ return 0;
+}
+
static int ima_eventdigest_init_common(u8 *digest, u32 digestsize, u8 hash_algo,
struct ima_field_data *field_data)
{
diff --git a/security/integrity/ima/ima_template_lib.h b/security/integrity/ima/ima_template_lib.h
index c344530c1d69..6a3d8b831deb 100644
--- a/security/integrity/ima/ima_template_lib.h
+++ b/security/integrity/ima/ima_template_lib.h
@@ -18,6 +18,9 @@
#include <linux/seq_file.h>
#include "ima.h"
+#define ENFORCE_FIELDS 0x00000001
+#define ENFORCE_BUFEND 0x00000002
+
void ima_show_template_digest(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data);
void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show,
@@ -26,6 +29,9 @@ void ima_show_template_string(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data);
void ima_show_template_sig(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data);
+int ima_parse_buf(void *bufstartp, void *bufendp, void **bufcurp,
+ int maxfields, struct ima_field_data *fields, int *curfields,
+ unsigned long *len_mask, int enforce_mask, char *bufname);
int ima_eventdigest_init(struct ima_event_data *event_data,
struct ima_field_data *field_data);
int ima_eventname_init(struct ima_event_data *event_data,
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index 24520b4ef3b0..a53e7e4ab06c 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -92,8 +92,8 @@ struct signature_v2_hdr {
uint8_t type; /* xattr type */
uint8_t version; /* signature format version */
uint8_t hash_algo; /* Digest algorithm [enum hash_algo] */
- uint32_t keyid; /* IMA key identifier - not X509/PGP specific */
- uint16_t sig_size; /* signature size */
+ __be32 keyid; /* IMA key identifier - not X509/PGP specific */
+ __be16 sig_size; /* signature size */
uint8_t sig[0]; /* signature payload */
} __packed;
@@ -118,7 +118,8 @@ struct integrity_iint_cache {
struct integrity_iint_cache *integrity_iint_find(struct inode *inode);
int integrity_kernel_read(struct file *file, loff_t offset,
- char *addr, unsigned long count);
+ void *addr, unsigned long count);
+
int __init integrity_read_file(const char *path, char **data);
#define INTEGRITY_KEYRING_EVM 0
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index 6fd95f76bfae..a7a23b5541f8 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -20,6 +20,10 @@ config KEYS
If you are unsure as to whether this is required, answer N.
+config KEYS_COMPAT
+ def_bool y
+ depends on COMPAT && KEYS
+
config PERSISTENT_KEYRINGS
bool "Enable register of persistent per-UID keyrings"
depends on KEYS
@@ -89,9 +93,9 @@ config ENCRYPTED_KEYS
config KEY_DH_OPERATIONS
bool "Diffie-Hellman operations on retained keys"
depends on KEYS
- select MPILIB
select CRYPTO
select CRYPTO_HASH
+ select CRYPTO_DH
help
This option provides support for calculating Diffie-Hellman
public keys and shared secrets using values stored as keys
diff --git a/security/keys/dh.c b/security/keys/dh.c
index e603bd912e4c..4755d4b4f945 100644
--- a/security/keys/dh.c
+++ b/security/keys/dh.c
@@ -8,34 +8,17 @@
* 2 of the License, or (at your option) any later version.
*/
-#include <linux/mpi.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
+#include <linux/scatterlist.h>
#include <linux/crypto.h>
#include <crypto/hash.h>
+#include <crypto/kpp.h>
+#include <crypto/dh.h>
#include <keys/user-type.h>
#include "internal.h"
-/*
- * Public key or shared secret generation function [RFC2631 sec 2.1.1]
- *
- * ya = g^xa mod p;
- * or
- * ZZ = yb^xa mod p;
- *
- * where xa is the local private key, ya is the local public key, g is
- * the generator, p is the prime, yb is the remote public key, and ZZ
- * is the shared secret.
- *
- * Both are the same calculation, so g or yb are the "base" and ya or
- * ZZ are the "result".
- */
-static int do_dh(MPI result, MPI base, MPI xa, MPI p)
-{
- return mpi_powm(result, base, xa, p);
-}
-
-static ssize_t mpi_from_key(key_serial_t keyid, size_t maxlen, MPI *mpi)
+static ssize_t dh_data_from_key(key_serial_t keyid, void **data)
{
struct key *key;
key_ref_t key_ref;
@@ -56,19 +39,17 @@ static ssize_t mpi_from_key(key_serial_t keyid, size_t maxlen, MPI *mpi)
status = key_validate(key);
if (status == 0) {
const struct user_key_payload *payload;
+ uint8_t *duplicate;
payload = user_key_payload_locked(key);
- if (maxlen == 0) {
- *mpi = NULL;
+ duplicate = kmemdup(payload->data, payload->datalen,
+ GFP_KERNEL);
+ if (duplicate) {
+ *data = duplicate;
ret = payload->datalen;
- } else if (payload->datalen <= maxlen) {
- *mpi = mpi_read_raw_data(payload->data,
- payload->datalen);
- if (*mpi)
- ret = payload->datalen;
} else {
- ret = -EINVAL;
+ ret = -ENOMEM;
}
}
up_read(&key->sem);
@@ -79,6 +60,29 @@ error:
return ret;
}
+static void dh_free_data(struct dh *dh)
+{
+ kzfree(dh->key);
+ kzfree(dh->p);
+ kzfree(dh->g);
+}
+
+struct dh_completion {
+ struct completion completion;
+ int err;
+};
+
+static void dh_crypto_done(struct crypto_async_request *req, int err)
+{
+ struct dh_completion *compl = req->data;
+
+ if (err == -EINPROGRESS)
+ return;
+
+ compl->err = err;
+ complete(&compl->completion);
+}
+
struct kdf_sdesc {
struct shash_desc shash;
char ctx[];
@@ -89,6 +93,7 @@ static int kdf_alloc(struct kdf_sdesc **sdesc_ret, char *hashname)
struct crypto_shash *tfm;
struct kdf_sdesc *sdesc;
int size;
+ int err;
/* allocate synchronous hash */
tfm = crypto_alloc_shash(hashname, 0, 0);
@@ -97,16 +102,25 @@ static int kdf_alloc(struct kdf_sdesc **sdesc_ret, char *hashname)
return PTR_ERR(tfm);
}
+ err = -EINVAL;
+ if (crypto_shash_digestsize(tfm) == 0)
+ goto out_free_tfm;
+
+ err = -ENOMEM;
size = sizeof(struct shash_desc) + crypto_shash_descsize(tfm);
sdesc = kmalloc(size, GFP_KERNEL);
if (!sdesc)
- return -ENOMEM;
+ goto out_free_tfm;
sdesc->shash.tfm = tfm;
sdesc->shash.flags = 0x0;
*sdesc_ret = sdesc;
return 0;
+
+out_free_tfm:
+ crypto_free_shash(tfm);
+ return err;
}
static void kdf_dealloc(struct kdf_sdesc *sdesc)
@@ -120,14 +134,6 @@ static void kdf_dealloc(struct kdf_sdesc *sdesc)
kzfree(sdesc);
}
-/* convert 32 bit integer into its string representation */
-static inline void crypto_kw_cpu_to_be32(u32 val, u8 *buf)
-{
- __be32 *a = (__be32 *)buf;
-
- *a = cpu_to_be32(val);
-}
-
/*
* Implementation of the KDF in counter mode according to SP800-108 section 5.1
* as well as SP800-56A section 5.8.1 (Single-step KDF).
@@ -138,25 +144,39 @@ static inline void crypto_kw_cpu_to_be32(u32 val, u8 *buf)
* 5.8.1.2).
*/
static int kdf_ctr(struct kdf_sdesc *sdesc, const u8 *src, unsigned int slen,
- u8 *dst, unsigned int dlen)
+ u8 *dst, unsigned int dlen, unsigned int zlen)
{
struct shash_desc *desc = &sdesc->shash;
unsigned int h = crypto_shash_digestsize(desc->tfm);
int err = 0;
u8 *dst_orig = dst;
- u32 i = 1;
- u8 iteration[sizeof(u32)];
+ __be32 counter = cpu_to_be32(1);
while (dlen) {
err = crypto_shash_init(desc);
if (err)
goto err;
- crypto_kw_cpu_to_be32(i, iteration);
- err = crypto_shash_update(desc, iteration, sizeof(u32));
+ err = crypto_shash_update(desc, (u8 *)&counter, sizeof(__be32));
if (err)
goto err;
+ if (zlen && h) {
+ u8 tmpbuffer[h];
+ size_t chunk = min_t(size_t, zlen, h);
+ memset(tmpbuffer, 0, chunk);
+
+ do {
+ err = crypto_shash_update(desc, tmpbuffer,
+ chunk);
+ if (err)
+ goto err;
+
+ zlen -= chunk;
+ chunk = min_t(size_t, zlen, h);
+ } while (zlen);
+ }
+
if (src && slen) {
err = crypto_shash_update(desc, src, slen);
if (err)
@@ -179,7 +199,7 @@ static int kdf_ctr(struct kdf_sdesc *sdesc, const u8 *src, unsigned int slen,
dlen -= h;
dst += h;
- i++;
+ counter = cpu_to_be32(be32_to_cpu(counter) + 1);
}
}
@@ -192,7 +212,7 @@ err:
static int keyctl_dh_compute_kdf(struct kdf_sdesc *sdesc,
char __user *buffer, size_t buflen,
- uint8_t *kbuf, size_t kbuflen)
+ uint8_t *kbuf, size_t kbuflen, size_t lzero)
{
uint8_t *outbuf = NULL;
int ret;
@@ -203,7 +223,7 @@ static int keyctl_dh_compute_kdf(struct kdf_sdesc *sdesc,
goto err;
}
- ret = kdf_ctr(sdesc, kbuf, kbuflen, outbuf, buflen);
+ ret = kdf_ctr(sdesc, kbuf, kbuflen, outbuf, buflen, lzero);
if (ret)
goto err;
@@ -221,21 +241,26 @@ long __keyctl_dh_compute(struct keyctl_dh_params __user *params,
struct keyctl_kdf_params *kdfcopy)
{
long ret;
- MPI base, private, prime, result;
- unsigned nbytes;
+ ssize_t dlen;
+ int secretlen;
+ int outlen;
struct keyctl_dh_params pcopy;
- uint8_t *kbuf;
- ssize_t keylen;
- size_t resultlen;
+ struct dh dh_inputs;
+ struct scatterlist outsg;
+ struct dh_completion compl;
+ struct crypto_kpp *tfm;
+ struct kpp_request *req;
+ uint8_t *secret;
+ uint8_t *outbuf;
struct kdf_sdesc *sdesc = NULL;
if (!params || (!buffer && buflen)) {
ret = -EINVAL;
- goto out;
+ goto out1;
}
if (copy_from_user(&pcopy, params, sizeof(pcopy)) != 0) {
ret = -EFAULT;
- goto out;
+ goto out1;
}
if (kdfcopy) {
@@ -244,104 +269,147 @@ long __keyctl_dh_compute(struct keyctl_dh_params __user *params,
if (buflen > KEYCTL_KDF_MAX_OUTPUT_LEN ||
kdfcopy->otherinfolen > KEYCTL_KDF_MAX_OI_LEN) {
ret = -EMSGSIZE;
- goto out;
+ goto out1;
}
/* get KDF name string */
hashname = strndup_user(kdfcopy->hashname, CRYPTO_MAX_ALG_NAME);
if (IS_ERR(hashname)) {
ret = PTR_ERR(hashname);
- goto out;
+ goto out1;
}
/* allocate KDF from the kernel crypto API */
ret = kdf_alloc(&sdesc, hashname);
kfree(hashname);
if (ret)
- goto out;
+ goto out1;
}
- /*
- * If the caller requests postprocessing with a KDF, allow an
- * arbitrary output buffer size since the KDF ensures proper truncation.
- */
- keylen = mpi_from_key(pcopy.prime, kdfcopy ? SIZE_MAX : buflen, &prime);
- if (keylen < 0 || !prime) {
- /* buflen == 0 may be used to query the required buffer size,
- * which is the prime key length.
- */
- ret = keylen;
- goto out;
+ memset(&dh_inputs, 0, sizeof(dh_inputs));
+
+ dlen = dh_data_from_key(pcopy.prime, &dh_inputs.p);
+ if (dlen < 0) {
+ ret = dlen;
+ goto out1;
+ }
+ dh_inputs.p_size = dlen;
+
+ dlen = dh_data_from_key(pcopy.base, &dh_inputs.g);
+ if (dlen < 0) {
+ ret = dlen;
+ goto out2;
}
+ dh_inputs.g_size = dlen;
- /* The result is never longer than the prime */
- resultlen = keylen;
+ dlen = dh_data_from_key(pcopy.private, &dh_inputs.key);
+ if (dlen < 0) {
+ ret = dlen;
+ goto out2;
+ }
+ dh_inputs.key_size = dlen;
- keylen = mpi_from_key(pcopy.base, SIZE_MAX, &base);
- if (keylen < 0 || !base) {
- ret = keylen;
- goto error1;
+ secretlen = crypto_dh_key_len(&dh_inputs);
+ secret = kmalloc(secretlen, GFP_KERNEL);
+ if (!secret) {
+ ret = -ENOMEM;
+ goto out2;
}
+ ret = crypto_dh_encode_key(secret, secretlen, &dh_inputs);
+ if (ret)
+ goto out3;
- keylen = mpi_from_key(pcopy.private, SIZE_MAX, &private);
- if (keylen < 0 || !private) {
- ret = keylen;
- goto error2;
+ tfm = crypto_alloc_kpp("dh", CRYPTO_ALG_TYPE_KPP, 0);
+ if (IS_ERR(tfm)) {
+ ret = PTR_ERR(tfm);
+ goto out3;
+ }
+
+ ret = crypto_kpp_set_secret(tfm, secret, secretlen);
+ if (ret)
+ goto out4;
+
+ outlen = crypto_kpp_maxsize(tfm);
+
+ if (!kdfcopy) {
+ /*
+ * When not using a KDF, buflen 0 is used to read the
+ * required buffer length
+ */
+ if (buflen == 0) {
+ ret = outlen;
+ goto out4;
+ } else if (outlen > buflen) {
+ ret = -EOVERFLOW;
+ goto out4;
+ }
}
- result = mpi_alloc(0);
- if (!result) {
+ outbuf = kzalloc(kdfcopy ? (outlen + kdfcopy->otherinfolen) : outlen,
+ GFP_KERNEL);
+ if (!outbuf) {
ret = -ENOMEM;
- goto error3;
+ goto out4;
}
- /* allocate space for DH shared secret and SP800-56A otherinfo */
- kbuf = kmalloc(kdfcopy ? (resultlen + kdfcopy->otherinfolen) : resultlen,
- GFP_KERNEL);
- if (!kbuf) {
+ sg_init_one(&outsg, outbuf, outlen);
+
+ req = kpp_request_alloc(tfm, GFP_KERNEL);
+ if (!req) {
ret = -ENOMEM;
- goto error4;
+ goto out5;
}
+ kpp_request_set_input(req, NULL, 0);
+ kpp_request_set_output(req, &outsg, outlen);
+ init_completion(&compl.completion);
+ kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
+ CRYPTO_TFM_REQ_MAY_SLEEP,
+ dh_crypto_done, &compl);
+
/*
- * Concatenate SP800-56A otherinfo past DH shared secret -- the
- * input to the KDF is (DH shared secret || otherinfo)
+ * For DH, generate_public_key and generate_shared_secret are
+ * the same calculation
*/
- if (kdfcopy && kdfcopy->otherinfo &&
- copy_from_user(kbuf + resultlen, kdfcopy->otherinfo,
- kdfcopy->otherinfolen) != 0) {
- ret = -EFAULT;
- goto error5;
+ ret = crypto_kpp_generate_public_key(req);
+ if (ret == -EINPROGRESS) {
+ wait_for_completion(&compl.completion);
+ ret = compl.err;
+ if (ret)
+ goto out6;
}
- ret = do_dh(result, base, private, prime);
- if (ret)
- goto error5;
-
- ret = mpi_read_buffer(result, kbuf, resultlen, &nbytes, NULL);
- if (ret != 0)
- goto error5;
-
if (kdfcopy) {
- ret = keyctl_dh_compute_kdf(sdesc, buffer, buflen, kbuf,
- resultlen + kdfcopy->otherinfolen);
- } else {
- ret = nbytes;
- if (copy_to_user(buffer, kbuf, nbytes) != 0)
+ /*
+ * Concatenate SP800-56A otherinfo past DH shared secret -- the
+ * input to the KDF is (DH shared secret || otherinfo)
+ */
+ if (copy_from_user(outbuf + req->dst_len, kdfcopy->otherinfo,
+ kdfcopy->otherinfolen) != 0) {
ret = -EFAULT;
+ goto out6;
+ }
+
+ ret = keyctl_dh_compute_kdf(sdesc, buffer, buflen, outbuf,
+ req->dst_len + kdfcopy->otherinfolen,
+ outlen - req->dst_len);
+ } else if (copy_to_user(buffer, outbuf, req->dst_len) == 0) {
+ ret = req->dst_len;
+ } else {
+ ret = -EFAULT;
}
-error5:
- kzfree(kbuf);
-error4:
- mpi_free(result);
-error3:
- mpi_free(private);
-error2:
- mpi_free(base);
-error1:
- mpi_free(prime);
-out:
+out6:
+ kpp_request_free(req);
+out5:
+ kzfree(outbuf);
+out4:
+ crypto_free_kpp(tfm);
+out3:
+ kzfree(secret);
+out2:
+ dh_free_data(&dh_inputs);
+out1:
kdf_dealloc(sdesc);
return ret;
}
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 0010955d7876..69855ba0d3b3 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -11,7 +11,7 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 2 of the License.
*
- * See Documentation/security/keys-trusted-encrypted.txt
+ * See Documentation/security/keys/trusted-encrypted.rst
*/
#include <linux/uaccess.h>
@@ -30,6 +30,7 @@
#include <linux/scatterlist.h>
#include <linux/ctype.h>
#include <crypto/aes.h>
+#include <crypto/algapi.h>
#include <crypto/hash.h>
#include <crypto/sha.h>
#include <crypto/skcipher.h>
@@ -54,13 +55,7 @@ static int blksize;
#define MAX_DATA_SIZE 4096
#define MIN_DATA_SIZE 20
-struct sdesc {
- struct shash_desc shash;
- char ctx[];
-};
-
-static struct crypto_shash *hashalg;
-static struct crypto_shash *hmacalg;
+static struct crypto_shash *hash_tfm;
enum {
Opt_err = -1, Opt_new, Opt_load, Opt_update
@@ -141,23 +136,22 @@ static int valid_ecryptfs_desc(const char *ecryptfs_desc)
*/
static int valid_master_desc(const char *new_desc, const char *orig_desc)
{
- if (!memcmp(new_desc, KEY_TRUSTED_PREFIX, KEY_TRUSTED_PREFIX_LEN)) {
- if (strlen(new_desc) == KEY_TRUSTED_PREFIX_LEN)
- goto out;
- if (orig_desc)
- if (memcmp(new_desc, orig_desc, KEY_TRUSTED_PREFIX_LEN))
- goto out;
- } else if (!memcmp(new_desc, KEY_USER_PREFIX, KEY_USER_PREFIX_LEN)) {
- if (strlen(new_desc) == KEY_USER_PREFIX_LEN)
- goto out;
- if (orig_desc)
- if (memcmp(new_desc, orig_desc, KEY_USER_PREFIX_LEN))
- goto out;
- } else
- goto out;
+ int prefix_len;
+
+ if (!strncmp(new_desc, KEY_TRUSTED_PREFIX, KEY_TRUSTED_PREFIX_LEN))
+ prefix_len = KEY_TRUSTED_PREFIX_LEN;
+ else if (!strncmp(new_desc, KEY_USER_PREFIX, KEY_USER_PREFIX_LEN))
+ prefix_len = KEY_USER_PREFIX_LEN;
+ else
+ return -EINVAL;
+
+ if (!new_desc[prefix_len])
+ return -EINVAL;
+
+ if (orig_desc && strncmp(new_desc, orig_desc, prefix_len))
+ return -EINVAL;
+
return 0;
-out:
- return -EINVAL;
}
/*
@@ -321,53 +315,38 @@ error:
return ukey;
}
-static struct sdesc *alloc_sdesc(struct crypto_shash *alg)
-{
- struct sdesc *sdesc;
- int size;
-
- size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
- sdesc = kmalloc(size, GFP_KERNEL);
- if (!sdesc)
- return ERR_PTR(-ENOMEM);
- sdesc->shash.tfm = alg;
- sdesc->shash.flags = 0x0;
- return sdesc;
-}
-
-static int calc_hmac(u8 *digest, const u8 *key, unsigned int keylen,
+static int calc_hash(struct crypto_shash *tfm, u8 *digest,
const u8 *buf, unsigned int buflen)
{
- struct sdesc *sdesc;
- int ret;
+ SHASH_DESC_ON_STACK(desc, tfm);
+ int err;
- sdesc = alloc_sdesc(hmacalg);
- if (IS_ERR(sdesc)) {
- pr_info("encrypted_key: can't alloc %s\n", hmac_alg);
- return PTR_ERR(sdesc);
- }
+ desc->tfm = tfm;
+ desc->flags = 0;
- ret = crypto_shash_setkey(hmacalg, key, keylen);
- if (!ret)
- ret = crypto_shash_digest(&sdesc->shash, buf, buflen, digest);
- kfree(sdesc);
- return ret;
+ err = crypto_shash_digest(desc, buf, buflen, digest);
+ shash_desc_zero(desc);
+ return err;
}
-static int calc_hash(u8 *digest, const u8 *buf, unsigned int buflen)
+static int calc_hmac(u8 *digest, const u8 *key, unsigned int keylen,
+ const u8 *buf, unsigned int buflen)
{
- struct sdesc *sdesc;
- int ret;
+ struct crypto_shash *tfm;
+ int err;
- sdesc = alloc_sdesc(hashalg);
- if (IS_ERR(sdesc)) {
- pr_info("encrypted_key: can't alloc %s\n", hash_alg);
- return PTR_ERR(sdesc);
+ tfm = crypto_alloc_shash(hmac_alg, 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(tfm)) {
+ pr_err("encrypted_key: can't alloc %s transform: %ld\n",
+ hmac_alg, PTR_ERR(tfm));
+ return PTR_ERR(tfm);
}
- ret = crypto_shash_digest(&sdesc->shash, buf, buflen, digest);
- kfree(sdesc);
- return ret;
+ err = crypto_shash_setkey(tfm, key, keylen);
+ if (!err)
+ err = calc_hash(tfm, digest, buf, buflen);
+ crypto_free_shash(tfm);
+ return err;
}
enum derived_key_type { ENC_KEY, AUTH_KEY };
@@ -385,10 +364,9 @@ static int get_derived_key(u8 *derived_key, enum derived_key_type key_type,
derived_buf_len = HASH_SIZE;
derived_buf = kzalloc(derived_buf_len, GFP_KERNEL);
- if (!derived_buf) {
- pr_err("encrypted_key: out of memory\n");
+ if (!derived_buf)
return -ENOMEM;
- }
+
if (key_type)
strcpy(derived_buf, "AUTH_KEY");
else
@@ -396,8 +374,8 @@ static int get_derived_key(u8 *derived_key, enum derived_key_type key_type,
memcpy(derived_buf + strlen(derived_buf) + 1, master_key,
master_keylen);
- ret = calc_hash(derived_key, derived_buf, derived_buf_len);
- kfree(derived_buf);
+ ret = calc_hash(hash_tfm, derived_key, derived_buf, derived_buf_len);
+ kzfree(derived_buf);
return ret;
}
@@ -480,12 +458,9 @@ static int derived_key_encrypt(struct encrypted_key_payload *epayload,
struct skcipher_request *req;
unsigned int encrypted_datalen;
u8 iv[AES_BLOCK_SIZE];
- unsigned int padlen;
- char pad[16];
int ret;
encrypted_datalen = roundup(epayload->decrypted_datalen, blksize);
- padlen = encrypted_datalen - epayload->decrypted_datalen;
req = init_skcipher_req(derived_key, derived_keylen);
ret = PTR_ERR(req);
@@ -493,11 +468,10 @@ static int derived_key_encrypt(struct encrypted_key_payload *epayload,
goto out;
dump_decrypted_data(epayload);
- memset(pad, 0, sizeof pad);
sg_init_table(sg_in, 2);
sg_set_buf(&sg_in[0], epayload->decrypted_data,
epayload->decrypted_datalen);
- sg_set_buf(&sg_in[1], pad, padlen);
+ sg_set_page(&sg_in[1], ZERO_PAGE(0), AES_BLOCK_SIZE, 0);
sg_init_table(sg_out, 1);
sg_set_buf(sg_out, epayload->encrypted_data, encrypted_datalen);
@@ -533,6 +507,7 @@ static int datablob_hmac_append(struct encrypted_key_payload *epayload,
if (!ret)
dump_hmac(NULL, digest, HASH_SIZE);
out:
+ memzero_explicit(derived_key, sizeof(derived_key));
return ret;
}
@@ -561,8 +536,8 @@ static int datablob_hmac_verify(struct encrypted_key_payload *epayload,
ret = calc_hmac(digest, derived_key, sizeof derived_key, p, len);
if (ret < 0)
goto out;
- ret = memcmp(digest, epayload->format + epayload->datablob_len,
- sizeof digest);
+ ret = crypto_memneq(digest, epayload->format + epayload->datablob_len,
+ sizeof(digest));
if (ret) {
ret = -EINVAL;
dump_hmac("datablob",
@@ -571,6 +546,7 @@ static int datablob_hmac_verify(struct encrypted_key_payload *epayload,
dump_hmac("calc", digest, HASH_SIZE);
}
out:
+ memzero_explicit(derived_key, sizeof(derived_key));
return ret;
}
@@ -584,9 +560,14 @@ static int derived_key_decrypt(struct encrypted_key_payload *epayload,
struct skcipher_request *req;
unsigned int encrypted_datalen;
u8 iv[AES_BLOCK_SIZE];
- char pad[16];
+ u8 *pad;
int ret;
+ /* Throwaway buffer to hold the unused zero padding at the end */
+ pad = kmalloc(AES_BLOCK_SIZE, GFP_KERNEL);
+ if (!pad)
+ return -ENOMEM;
+
encrypted_datalen = roundup(epayload->decrypted_datalen, blksize);
req = init_skcipher_req(derived_key, derived_keylen);
ret = PTR_ERR(req);
@@ -594,13 +575,12 @@ static int derived_key_decrypt(struct encrypted_key_payload *epayload,
goto out;
dump_encrypted_data(epayload, encrypted_datalen);
- memset(pad, 0, sizeof pad);
sg_init_table(sg_in, 1);
sg_init_table(sg_out, 2);
sg_set_buf(sg_in, epayload->encrypted_data, encrypted_datalen);
sg_set_buf(&sg_out[0], epayload->decrypted_data,
epayload->decrypted_datalen);
- sg_set_buf(&sg_out[1], pad, sizeof pad);
+ sg_set_buf(&sg_out[1], pad, AES_BLOCK_SIZE);
memcpy(iv, epayload->iv, sizeof(iv));
skcipher_request_set_crypt(req, sg_in, sg_out, encrypted_datalen, iv);
@@ -612,6 +592,7 @@ static int derived_key_decrypt(struct encrypted_key_payload *epayload,
goto out;
dump_decrypted_data(epayload);
out:
+ kfree(pad);
return ret;
}
@@ -722,6 +703,7 @@ static int encrypted_key_decrypt(struct encrypted_key_payload *epayload,
out:
up_read(&mkey->sem);
key_put(mkey);
+ memzero_explicit(derived_key, sizeof(derived_key));
return ret;
}
@@ -828,13 +810,13 @@ static int encrypted_instantiate(struct key *key,
ret = encrypted_init(epayload, key->description, format, master_desc,
decrypted_datalen, hex_encoded_iv);
if (ret < 0) {
- kfree(epayload);
+ kzfree(epayload);
goto out;
}
rcu_assign_keypointer(key, epayload);
out:
- kfree(datablob);
+ kzfree(datablob);
return ret;
}
@@ -843,8 +825,7 @@ static void encrypted_rcu_free(struct rcu_head *rcu)
struct encrypted_key_payload *epayload;
epayload = container_of(rcu, struct encrypted_key_payload, rcu);
- memset(epayload->decrypted_data, 0, epayload->decrypted_datalen);
- kfree(epayload);
+ kzfree(epayload);
}
/*
@@ -902,7 +883,7 @@ static int encrypted_update(struct key *key, struct key_preparsed_payload *prep)
rcu_assign_keypointer(key, new_epayload);
call_rcu(&epayload->rcu, encrypted_rcu_free);
out:
- kfree(buf);
+ kzfree(buf);
return ret;
}
@@ -960,33 +941,26 @@ static long encrypted_read(const struct key *key, char __user *buffer,
up_read(&mkey->sem);
key_put(mkey);
+ memzero_explicit(derived_key, sizeof(derived_key));
if (copy_to_user(buffer, ascii_buf, asciiblob_len) != 0)
ret = -EFAULT;
- kfree(ascii_buf);
+ kzfree(ascii_buf);
return asciiblob_len;
out:
up_read(&mkey->sem);
key_put(mkey);
+ memzero_explicit(derived_key, sizeof(derived_key));
return ret;
}
/*
- * encrypted_destroy - before freeing the key, clear the decrypted data
- *
- * Before freeing the key, clear the memory containing the decrypted
- * key data.
+ * encrypted_destroy - clear and free the key's payload
*/
static void encrypted_destroy(struct key *key)
{
- struct encrypted_key_payload *epayload = key->payload.data[0];
-
- if (!epayload)
- return;
-
- memzero_explicit(epayload->decrypted_data, epayload->decrypted_datalen);
- kfree(key->payload.data[0]);
+ kzfree(key->payload.data[0]);
}
struct key_type key_type_encrypted = {
@@ -999,47 +973,17 @@ struct key_type key_type_encrypted = {
};
EXPORT_SYMBOL_GPL(key_type_encrypted);
-static void encrypted_shash_release(void)
-{
- if (hashalg)
- crypto_free_shash(hashalg);
- if (hmacalg)
- crypto_free_shash(hmacalg);
-}
-
-static int __init encrypted_shash_alloc(void)
+static int __init init_encrypted(void)
{
int ret;
- hmacalg = crypto_alloc_shash(hmac_alg, 0, CRYPTO_ALG_ASYNC);
- if (IS_ERR(hmacalg)) {
- pr_info("encrypted_key: could not allocate crypto %s\n",
- hmac_alg);
- return PTR_ERR(hmacalg);
- }
-
- hashalg = crypto_alloc_shash(hash_alg, 0, CRYPTO_ALG_ASYNC);
- if (IS_ERR(hashalg)) {
- pr_info("encrypted_key: could not allocate crypto %s\n",
- hash_alg);
- ret = PTR_ERR(hashalg);
- goto hashalg_fail;
+ hash_tfm = crypto_alloc_shash(hash_alg, 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(hash_tfm)) {
+ pr_err("encrypted_key: can't allocate %s transform: %ld\n",
+ hash_alg, PTR_ERR(hash_tfm));
+ return PTR_ERR(hash_tfm);
}
- return 0;
-
-hashalg_fail:
- crypto_free_shash(hmacalg);
- return ret;
-}
-
-static int __init init_encrypted(void)
-{
- int ret;
-
- ret = encrypted_shash_alloc();
- if (ret < 0)
- return ret;
ret = aes_get_sizes();
if (ret < 0)
goto out;
@@ -1048,14 +992,14 @@ static int __init init_encrypted(void)
goto out;
return 0;
out:
- encrypted_shash_release();
+ crypto_free_shash(hash_tfm);
return ret;
}
static void __exit cleanup_encrypted(void)
{
- encrypted_shash_release();
+ crypto_free_shash(hash_tfm);
unregister_key_type(&key_type_encrypted);
}
diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c
index b5b4812dbc87..cbf0bc127a73 100644
--- a/security/keys/encrypted-keys/masterkey_trusted.c
+++ b/security/keys/encrypted-keys/masterkey_trusted.c
@@ -11,7 +11,7 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 2 of the License.
*
- * See Documentation/security/keys-trusted-encrypted.txt
+ * See Documentation/security/keys/trusted-encrypted.rst
*/
#include <linux/uaccess.h>
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 595becc6d0d2..87cb260e4890 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -158,9 +158,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
kfree(key->description);
-#ifdef KEY_DEBUGGING
- key->magic = KEY_DEBUG_MAGIC_X;
-#endif
+ memzero_explicit(key, sizeof(*key));
kmem_cache_free(key_jar, key);
}
}
diff --git a/security/keys/internal.h b/security/keys/internal.h
index c0f8682eba69..91bc6214ae57 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -13,6 +13,7 @@
#define _INTERNAL_H
#include <linux/sched.h>
+#include <linux/wait_bit.h>
#include <linux/cred.h>
#include <linux/key-type.h>
#include <linux/task_work.h>
diff --git a/security/keys/key.c b/security/keys/key.c
index 455c04d80bbb..83da68d98b40 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -660,14 +660,11 @@ not_found:
goto error;
found:
- /* pretend it doesn't exist if it is awaiting deletion */
- if (refcount_read(&key->usage) == 0)
- goto not_found;
-
- /* this races with key_put(), but that doesn't matter since key_put()
- * doesn't actually change the key
+ /* A key is allowed to be looked up only if someone still owns a
+ * reference to it - otherwise it's awaiting the gc.
*/
- __key_get(key);
+ if (!refcount_inc_not_zero(&key->usage))
+ goto not_found;
error:
spin_unlock(&key_serial_lock);
@@ -966,12 +963,11 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
/* the key must be writable */
ret = key_permission(key_ref, KEY_NEED_WRITE);
if (ret < 0)
- goto error;
+ return ret;
/* attempt to update it if supported */
- ret = -EOPNOTSUPP;
if (!key->type->update)
- goto error;
+ return -EOPNOTSUPP;
memset(&prep, 0, sizeof(prep));
prep.data = payload;
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 447a7d5cee0f..ab0b337c84b4 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -99,7 +99,7 @@ SYSCALL_DEFINE5(add_key, const char __user *, _type,
/* pull the payload in if one was supplied */
payload = NULL;
- if (_payload) {
+ if (plen) {
ret = -ENOMEM;
payload = kvmalloc(plen, GFP_KERNEL);
if (!payload)
@@ -132,7 +132,10 @@ SYSCALL_DEFINE5(add_key, const char __user *, _type,
key_ref_put(keyring_ref);
error3:
- kvfree(payload);
+ if (payload) {
+ memzero_explicit(payload, plen);
+ kvfree(payload);
+ }
error2:
kfree(description);
error:
@@ -324,7 +327,7 @@ long keyctl_update_key(key_serial_t id,
/* pull the payload in if one was supplied */
payload = NULL;
- if (_payload) {
+ if (plen) {
ret = -ENOMEM;
payload = kmalloc(plen, GFP_KERNEL);
if (!payload)
@@ -347,7 +350,7 @@ long keyctl_update_key(key_serial_t id,
key_ref_put(key_ref);
error2:
- kfree(payload);
+ kzfree(payload);
error:
return ret;
}
@@ -1093,7 +1096,10 @@ long keyctl_instantiate_key_common(key_serial_t id,
keyctl_change_reqkey_auth(NULL);
error2:
- kvfree(payload);
+ if (payload) {
+ memzero_explicit(payload, plen);
+ kvfree(payload);
+ }
error:
return ret;
}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4d1678e4586f..de81793f9920 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -706,7 +706,7 @@ descend_to_keyring:
* Non-keyrings avoid the leftmost branch of the root entirely (root
* slots 1-15).
*/
- ptr = ACCESS_ONCE(keyring->keys.root);
+ ptr = READ_ONCE(keyring->keys.root);
if (!ptr)
goto not_this_keyring;
@@ -720,7 +720,7 @@ descend_to_keyring:
if ((shortcut->index_key[0] & ASSOC_ARRAY_FAN_MASK) != 0)
goto not_this_keyring;
- ptr = ACCESS_ONCE(shortcut->next_node);
+ ptr = READ_ONCE(shortcut->next_node);
node = assoc_array_ptr_to_node(ptr);
goto begin_node;
}
@@ -740,7 +740,7 @@ descend_to_node:
if (assoc_array_ptr_is_shortcut(ptr)) {
shortcut = assoc_array_ptr_to_shortcut(ptr);
smp_read_barrier_depends();
- ptr = ACCESS_ONCE(shortcut->next_node);
+ ptr = READ_ONCE(shortcut->next_node);
BUG_ON(!assoc_array_ptr_is_node(ptr));
}
node = assoc_array_ptr_to_node(ptr);
@@ -752,7 +752,7 @@ begin_node:
ascend_to_node:
/* Go through the slots in a node */
for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) {
- ptr = ACCESS_ONCE(node->slots[slot]);
+ ptr = READ_ONCE(node->slots[slot]);
if (assoc_array_ptr_is_meta(ptr) && node->back_pointer)
goto descend_to_node;
@@ -790,13 +790,13 @@ ascend_to_node:
/* We've dealt with all the slots in the current node, so now we need
* to ascend to the parent and continue processing there.
*/
- ptr = ACCESS_ONCE(node->back_pointer);
+ ptr = READ_ONCE(node->back_pointer);
slot = node->parent_slot;
if (ptr && assoc_array_ptr_is_shortcut(ptr)) {
shortcut = assoc_array_ptr_to_shortcut(ptr);
smp_read_barrier_depends();
- ptr = ACCESS_ONCE(shortcut->back_pointer);
+ ptr = READ_ONCE(shortcut->back_pointer);
slot = shortcut->parent_slot;
}
if (!ptr)
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 2217dfec7996..86bced9fdbdf 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -809,15 +809,14 @@ long join_session_keyring(const char *name)
ret = PTR_ERR(keyring);
goto error2;
} else if (keyring == new->session_keyring) {
- key_put(keyring);
ret = 0;
- goto error2;
+ goto error3;
}
/* we've got a keyring - now to install it */
ret = install_session_keyring_to_cred(new, keyring);
if (ret < 0)
- goto error2;
+ goto error3;
commit_creds(new);
mutex_unlock(&key_session_mutex);
@@ -827,6 +826,8 @@ long join_session_keyring(const char *name)
okay:
return ret;
+error3:
+ key_put(keyring);
error2:
mutex_unlock(&key_session_mutex);
error:
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 9822e500d50d..63e63a42db3c 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -8,7 +8,7 @@
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
- * See Documentation/security/keys-request-key.txt
+ * See Documentation/security/keys/request-key.rst
*/
#include <linux/module.h>
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index 0f062156dfb2..afe9d22ab361 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -8,7 +8,7 @@
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
- * See Documentation/security/keys-request-key.txt
+ * See Documentation/security/keys/request-key.rst
*/
#include <linux/module.h>
diff --git a/security/keys/trusted.c b/security/keys/trusted.c
index 2ae31c5a87de..ddfaebf60fc8 100644
--- a/security/keys/trusted.c
+++ b/security/keys/trusted.c
@@ -8,7 +8,7 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 2 of the License.
*
- * See Documentation/security/keys-trusted-encrypted.txt
+ * See Documentation/security/keys/trusted-encrypted.rst
*/
#include <crypto/hash_info.h>
@@ -70,7 +70,7 @@ static int TSS_sha1(const unsigned char *data, unsigned int datalen,
}
ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest);
- kfree(sdesc);
+ kzfree(sdesc);
return ret;
}
@@ -114,7 +114,7 @@ static int TSS_rawhmac(unsigned char *digest, const unsigned char *key,
if (!ret)
ret = crypto_shash_final(&sdesc->shash, digest);
out:
- kfree(sdesc);
+ kzfree(sdesc);
return ret;
}
@@ -165,7 +165,7 @@ static int TSS_authhmac(unsigned char *digest, const unsigned char *key,
paramdigest, TPM_NONCE_SIZE, h1,
TPM_NONCE_SIZE, h2, 1, &c, 0, 0);
out:
- kfree(sdesc);
+ kzfree(sdesc);
return ret;
}
@@ -246,7 +246,7 @@ static int TSS_checkhmac1(unsigned char *buffer,
if (memcmp(testhmac, authdata, SHA1_DIGEST_SIZE))
ret = -EINVAL;
out:
- kfree(sdesc);
+ kzfree(sdesc);
return ret;
}
@@ -347,7 +347,7 @@ static int TSS_checkhmac2(unsigned char *buffer,
if (memcmp(testhmac2, authdata2, SHA1_DIGEST_SIZE))
ret = -EINVAL;
out:
- kfree(sdesc);
+ kzfree(sdesc);
return ret;
}
@@ -564,7 +564,7 @@ static int tpm_seal(struct tpm_buf *tb, uint16_t keytype,
*bloblen = storedsize;
}
out:
- kfree(td);
+ kzfree(td);
return ret;
}
@@ -678,7 +678,7 @@ static int key_seal(struct trusted_key_payload *p,
if (ret < 0)
pr_info("trusted_key: srkseal failed (%d)\n", ret);
- kfree(tb);
+ kzfree(tb);
return ret;
}
@@ -703,7 +703,7 @@ static int key_unseal(struct trusted_key_payload *p,
/* pull migratable flag out of sealed key */
p->migratable = p->key[--p->key_len];
- kfree(tb);
+ kzfree(tb);
return ret;
}
@@ -1037,12 +1037,12 @@ static int trusted_instantiate(struct key *key,
if (!ret && options->pcrlock)
ret = pcrlock(options->pcrlock);
out:
- kfree(datablob);
- kfree(options);
+ kzfree(datablob);
+ kzfree(options);
if (!ret)
rcu_assign_keypointer(key, payload);
else
- kfree(payload);
+ kzfree(payload);
return ret;
}
@@ -1051,8 +1051,7 @@ static void trusted_rcu_free(struct rcu_head *rcu)
struct trusted_key_payload *p;
p = container_of(rcu, struct trusted_key_payload, rcu);
- memset(p->key, 0, p->key_len);
- kfree(p);
+ kzfree(p);
}
/*
@@ -1094,13 +1093,13 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep)
ret = datablob_parse(datablob, new_p, new_o);
if (ret != Opt_update) {
ret = -EINVAL;
- kfree(new_p);
+ kzfree(new_p);
goto out;
}
if (!new_o->keyhandle) {
ret = -EINVAL;
- kfree(new_p);
+ kzfree(new_p);
goto out;
}
@@ -1114,22 +1113,22 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep)
ret = key_seal(new_p, new_o);
if (ret < 0) {
pr_info("trusted_key: key_seal failed (%d)\n", ret);
- kfree(new_p);
+ kzfree(new_p);
goto out;
}
if (new_o->pcrlock) {
ret = pcrlock(new_o->pcrlock);
if (ret < 0) {
pr_info("trusted_key: pcrlock failed (%d)\n", ret);
- kfree(new_p);
+ kzfree(new_p);
goto out;
}
}
rcu_assign_keypointer(key, new_p);
call_rcu(&p->rcu, trusted_rcu_free);
out:
- kfree(datablob);
- kfree(new_o);
+ kzfree(datablob);
+ kzfree(new_o);
return ret;
}
@@ -1158,24 +1157,19 @@ static long trusted_read(const struct key *key, char __user *buffer,
for (i = 0; i < p->blob_len; i++)
bufp = hex_byte_pack(bufp, p->blob[i]);
if ((copy_to_user(buffer, ascii_buf, 2 * p->blob_len)) != 0) {
- kfree(ascii_buf);
+ kzfree(ascii_buf);
return -EFAULT;
}
- kfree(ascii_buf);
+ kzfree(ascii_buf);
return 2 * p->blob_len;
}
/*
- * trusted_destroy - before freeing the key, clear the decrypted data
+ * trusted_destroy - clear and free the key's payload
*/
static void trusted_destroy(struct key *key)
{
- struct trusted_key_payload *p = key->payload.data[0];
-
- if (!p)
- return;
- memset(p->key, 0, p->key_len);
- kfree(key->payload.data[0]);
+ kzfree(key->payload.data[0]);
}
struct key_type key_type_trusted = {
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 26605134f17a..3d8c68eba516 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -86,10 +86,18 @@ EXPORT_SYMBOL_GPL(user_preparse);
*/
void user_free_preparse(struct key_preparsed_payload *prep)
{
- kfree(prep->payload.data[0]);
+ kzfree(prep->payload.data[0]);
}
EXPORT_SYMBOL_GPL(user_free_preparse);
+static void user_free_payload_rcu(struct rcu_head *head)
+{
+ struct user_key_payload *payload;
+
+ payload = container_of(head, struct user_key_payload, rcu);
+ kzfree(payload);
+}
+
/*
* update a user defined key
* - the key's semaphore is write-locked
@@ -112,7 +120,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
prep->payload.data[0] = NULL;
if (zap)
- kfree_rcu(zap, rcu);
+ call_rcu(&zap->rcu, user_free_payload_rcu);
return ret;
}
EXPORT_SYMBOL_GPL(user_update);
@@ -130,7 +138,7 @@ void user_revoke(struct key *key)
if (upayload) {
rcu_assign_keypointer(key, NULL);
- kfree_rcu(upayload, rcu);
+ call_rcu(&upayload->rcu, user_free_payload_rcu);
}
}
@@ -143,7 +151,7 @@ void user_destroy(struct key *key)
{
struct user_key_payload *upayload = key->payload.data[0];
- kfree(upayload);
+ kzfree(upayload);
}
EXPORT_SYMBOL_GPL(user_destroy);
diff --git a/security/lsm_audit.c b/security/lsm_audit.c
index 37f04dadc8d6..28d4c3a528ab 100644
--- a/security/lsm_audit.c
+++ b/security/lsm_audit.c
@@ -410,6 +410,22 @@ static void dump_common_audit_data(struct audit_buffer *ab,
audit_log_format(ab, " kmod=");
audit_log_untrustedstring(ab, a->u.kmod_name);
break;
+ case LSM_AUDIT_DATA_IBPKEY: {
+ struct in6_addr sbn_pfx;
+
+ memset(&sbn_pfx.s6_addr, 0,
+ sizeof(sbn_pfx.s6_addr));
+ memcpy(&sbn_pfx.s6_addr, &a->u.ibpkey->subnet_prefix,
+ sizeof(a->u.ibpkey->subnet_prefix));
+ audit_log_format(ab, " pkey=0x%x subnet_prefix=%pI6c",
+ a->u.ibpkey->pkey, &sbn_pfx);
+ break;
+ }
+ case LSM_AUDIT_DATA_IBENDPORT:
+ audit_log_format(ab, " device=%s port_num=%u",
+ a->u.ibendport->dev_name,
+ a->u.ibendport->port);
+ break;
} /* switch (a->type) */
}
diff --git a/security/security.c b/security/security.c
index b9fea3999cf8..30132378d103 100644
--- a/security/security.c
+++ b/security/security.c
@@ -4,6 +4,7 @@
* Copyright (C) 2001 WireX Communications, Inc <chris@wirex.com>
* Copyright (C) 2001-2002 Greg Kroah-Hartman <greg@kroah.com>
* Copyright (C) 2001 Networks Associates Technology, Inc <ssmalley@nai.com>
+ * Copyright (C) 2016 Mellanox Technologies
*
* 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
@@ -25,6 +26,7 @@
#include <linux/mount.h>
#include <linux/personality.h>
#include <linux/backing-dev.h>
+#include <linux/string.h>
#include <net/flow.h>
#define MAX_LSM_EVM_XATTR 2
@@ -33,6 +35,8 @@
#define SECURITY_NAME_MAX 10
struct security_hook_heads security_hook_heads __lsm_ro_after_init;
+static ATOMIC_NOTIFIER_HEAD(lsm_notifier_chain);
+
char *lsm_names;
/* Boot-time LSM user choice */
static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] =
@@ -86,6 +90,21 @@ static int __init choose_lsm(char *str)
}
__setup("security=", choose_lsm);
+static bool match_last_lsm(const char *list, const char *lsm)
+{
+ const char *last;
+
+ if (WARN_ON(!list || !lsm))
+ return false;
+ last = strrchr(list, ',');
+ if (last)
+ /* Pass the comma, strcmp() will check for '\0' */
+ last++;
+ else
+ last = list;
+ return !strcmp(last, lsm);
+}
+
static int lsm_append(char *new, char **result)
{
char *cp;
@@ -93,6 +112,9 @@ static int lsm_append(char *new, char **result)
if (*result == NULL) {
*result = kstrdup(new, GFP_KERNEL);
} else {
+ /* Check if it is the last registered name */
+ if (match_last_lsm(*result, new))
+ return 0;
cp = kasprintf(GFP_KERNEL, "%s,%s", *result, new);
if (cp == NULL)
return -ENOMEM;
@@ -146,6 +168,24 @@ void __init security_add_hooks(struct security_hook_list *hooks, int count,
panic("%s - Cannot get early memory.\n", __func__);
}
+int call_lsm_notifier(enum lsm_event event, void *data)
+{
+ return atomic_notifier_call_chain(&lsm_notifier_chain, event, data);
+}
+EXPORT_SYMBOL(call_lsm_notifier);
+
+int register_lsm_notifier(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_register(&lsm_notifier_chain, nb);
+}
+EXPORT_SYMBOL(register_lsm_notifier);
+
+int unregister_lsm_notifier(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_unregister(&lsm_notifier_chain, nb);
+}
+EXPORT_SYMBOL(unregister_lsm_notifier);
+
/*
* Hook list operation macros.
*
@@ -380,9 +420,12 @@ int security_sb_set_mnt_opts(struct super_block *sb,
EXPORT_SYMBOL(security_sb_set_mnt_opts);
int security_sb_clone_mnt_opts(const struct super_block *oldsb,
- struct super_block *newsb)
+ struct super_block *newsb,
+ unsigned long kern_flags,
+ unsigned long *set_kern_flags)
{
- return call_int_hook(sb_clone_mnt_opts, 0, oldsb, newsb);
+ return call_int_hook(sb_clone_mnt_opts, 0, oldsb, newsb,
+ kern_flags, set_kern_flags);
}
EXPORT_SYMBOL(security_sb_clone_mnt_opts);
@@ -1496,6 +1539,33 @@ EXPORT_SYMBOL(security_tun_dev_open);
#endif /* CONFIG_SECURITY_NETWORK */
+#ifdef CONFIG_SECURITY_INFINIBAND
+
+int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey)
+{
+ return call_int_hook(ib_pkey_access, 0, sec, subnet_prefix, pkey);
+}
+EXPORT_SYMBOL(security_ib_pkey_access);
+
+int security_ib_endport_manage_subnet(void *sec, const char *dev_name, u8 port_num)
+{
+ return call_int_hook(ib_endport_manage_subnet, 0, sec, dev_name, port_num);
+}
+EXPORT_SYMBOL(security_ib_endport_manage_subnet);
+
+int security_ib_alloc_security(void **sec)
+{
+ return call_int_hook(ib_alloc_security, 0, sec);
+}
+EXPORT_SYMBOL(security_ib_alloc_security);
+
+void security_ib_free_security(void *sec)
+{
+ call_void_hook(ib_free_security, sec);
+}
+EXPORT_SYMBOL(security_ib_free_security);
+#endif /* CONFIG_SECURITY_INFINIBAND */
+
#ifdef CONFIG_SECURITY_NETWORK_XFRM
int security_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp,
diff --git a/security/selinux/Makefile b/security/selinux/Makefile
index 3411c33e2a44..ff5895ede96f 100644
--- a/security/selinux/Makefile
+++ b/security/selinux/Makefile
@@ -5,7 +5,7 @@
obj-$(CONFIG_SECURITY_SELINUX) := selinux.o
selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o \
- netnode.o netport.o exports.o \
+ netnode.o netport.o ibpkey.o exports.o \
ss/ebitmap.o ss/hashtab.o ss/symtab.o ss/sidtab.o ss/avtab.o \
ss/policydb.o ss/services.o ss/conditional.o ss/mls.o ss/status.o
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index e67a526d1f30..33fd061305c4 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -17,6 +17,7 @@
* Paul Moore <paul@paul-moore.com>
* Copyright (C) 2007 Hitachi Software Engineering Co., Ltd.
* Yuichi Nakamura <ynakam@hitachisoft.jp>
+ * Copyright (C) 2016 Mellanox Technologies
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -90,6 +91,7 @@
#include "netif.h"
#include "netnode.h"
#include "netport.h"
+#include "ibpkey.h"
#include "xfrm.h"
#include "netlabel.h"
#include "audit.h"
@@ -171,6 +173,16 @@ static int selinux_netcache_avc_callback(u32 event)
return 0;
}
+static int selinux_lsm_notifier_avc_callback(u32 event)
+{
+ if (event == AVC_CALLBACK_RESET) {
+ sel_ib_pkey_flush();
+ call_lsm_notifier(LSM_POLICY_CHANGE, NULL);
+ }
+
+ return 0;
+}
+
/*
* initialise the security for the init task
*/
@@ -398,18 +410,6 @@ static void superblock_free_security(struct super_block *sb)
kfree(sbsec);
}
-/* The file system's label must be initialized prior to use. */
-
-static const char *labeling_behaviors[7] = {
- "uses xattr",
- "uses transition SIDs",
- "uses task SIDs",
- "uses genfs_contexts",
- "not configured for labeling",
- "uses mountpoint labeling",
- "uses native labeling",
-};
-
static inline int inode_doinit(struct inode *inode)
{
return inode_doinit_with_dentry(inode, NULL);
@@ -524,13 +524,17 @@ static int sb_finish_set_opts(struct super_block *sb)
}
}
- if (sbsec->behavior > ARRAY_SIZE(labeling_behaviors))
- printk(KERN_ERR "SELinux: initialized (dev %s, type %s), unknown behavior\n",
- sb->s_id, sb->s_type->name);
-
sbsec->flags |= SE_SBINITIALIZED;
+
+ /*
+ * Explicitly set or clear SBLABEL_MNT. It's not sufficient to simply
+ * leave the flag untouched because sb_clone_mnt_opts might be handing
+ * us a superblock that needs the flag to be cleared.
+ */
if (selinux_is_sblabel_mnt(sb))
sbsec->flags |= SBLABEL_MNT;
+ else
+ sbsec->flags &= ~SBLABEL_MNT;
/* Initialize the root inode. */
rc = inode_doinit_with_dentry(root_inode, root);
@@ -809,6 +813,7 @@ static int selinux_set_mnt_opts(struct super_block *sb,
sbsec->flags |= SE_SBPROC | SE_SBGENFS;
if (!strcmp(sb->s_type->name, "debugfs") ||
+ !strcmp(sb->s_type->name, "tracefs") ||
!strcmp(sb->s_type->name, "sysfs") ||
!strcmp(sb->s_type->name, "pstore"))
sbsec->flags |= SE_SBGENFS;
@@ -963,8 +968,11 @@ mismatch:
}
static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb,
- struct super_block *newsb)
+ struct super_block *newsb,
+ unsigned long kern_flags,
+ unsigned long *set_kern_flags)
{
+ int rc = 0;
const struct superblock_security_struct *oldsbsec = oldsb->s_security;
struct superblock_security_struct *newsbsec = newsb->s_security;
@@ -979,6 +987,13 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb,
if (!ss_initialized)
return 0;
+ /*
+ * Specifying internal flags without providing a place to
+ * place the results is not allowed.
+ */
+ if (kern_flags && !set_kern_flags)
+ return -EINVAL;
+
/* how can we clone if the old one wasn't set up?? */
BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED));
@@ -994,6 +1009,18 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb,
newsbsec->def_sid = oldsbsec->def_sid;
newsbsec->behavior = oldsbsec->behavior;
+ if (newsbsec->behavior == SECURITY_FS_USE_NATIVE &&
+ !(kern_flags & SECURITY_LSM_NATIVE_LABELS) && !set_context) {
+ rc = security_fs_use(newsb);
+ if (rc)
+ goto out;
+ }
+
+ if (kern_flags & SECURITY_LSM_NATIVE_LABELS && !set_context) {
+ newsbsec->behavior = SECURITY_FS_USE_NATIVE;
+ *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS;
+ }
+
if (set_context) {
u32 sid = oldsbsec->mntpoint_sid;
@@ -1013,8 +1040,9 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb,
}
sb_finish_set_opts(newsb);
+out:
mutex_unlock(&newsbsec->lock);
- return 0;
+ return rc;
}
static int selinux_parse_opts_str(char *options,
@@ -1106,10 +1134,8 @@ static int selinux_parse_opts_str(char *options,
opts->mnt_opts_flags = kcalloc(NUM_SEL_MNT_OPTS, sizeof(int),
GFP_KERNEL);
- if (!opts->mnt_opts_flags) {
- kfree(opts->mnt_opts);
+ if (!opts->mnt_opts_flags)
goto out_err;
- }
if (fscontext) {
opts->mnt_opts[num_mnt_opts] = fscontext;
@@ -1132,6 +1158,7 @@ static int selinux_parse_opts_str(char *options,
return 0;
out_err:
+ security_free_mnt_opts(opts);
kfree(context);
kfree(defcontext);
kfree(fscontext);
@@ -2063,8 +2090,9 @@ static inline u32 file_to_av(struct file *file)
static inline u32 open_file_to_av(struct file *file)
{
u32 av = file_to_av(file);
+ struct inode *inode = file_inode(file);
- if (selinux_policycap_openperm)
+ if (selinux_policycap_openperm && inode->i_sb->s_magic != SOCKFS_MAGIC)
av |= FILE__OPEN;
return av;
@@ -3059,6 +3087,7 @@ static int selinux_inode_permission(struct inode *inode, int mask)
static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr)
{
const struct cred *cred = current_cred();
+ struct inode *inode = d_backing_inode(dentry);
unsigned int ia_valid = iattr->ia_valid;
__u32 av = FILE__WRITE;
@@ -3074,8 +3103,10 @@ static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr)
ATTR_ATIME_SET | ATTR_MTIME_SET | ATTR_TIMES_SET))
return dentry_has_perm(cred, dentry, FILE__SETATTR);
- if (selinux_policycap_openperm && (ia_valid & ATTR_SIZE)
- && !(ia_valid & ATTR_FILE))
+ if (selinux_policycap_openperm &&
+ inode->i_sb->s_magic != SOCKFS_MAGIC &&
+ (ia_valid & ATTR_SIZE) &&
+ !(ia_valid & ATTR_FILE))
av |= FILE__OPEN;
return dentry_has_perm(cred, dentry, av);
@@ -3107,6 +3138,18 @@ static int selinux_inode_setotherxattr(struct dentry *dentry, const char *name)
return dentry_has_perm(cred, dentry, FILE__SETATTR);
}
+static bool has_cap_mac_admin(bool audit)
+{
+ const struct cred *cred = current_cred();
+ int cap_audit = audit ? SECURITY_CAP_AUDIT : SECURITY_CAP_NOAUDIT;
+
+ if (cap_capable(cred, &init_user_ns, CAP_MAC_ADMIN, cap_audit))
+ return false;
+ if (cred_has_capability(cred, CAP_MAC_ADMIN, cap_audit, true))
+ return false;
+ return true;
+}
+
static int selinux_inode_setxattr(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
@@ -3138,7 +3181,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name,
rc = security_context_to_sid(value, size, &newsid, GFP_KERNEL);
if (rc == -EINVAL) {
- if (!capable(CAP_MAC_ADMIN)) {
+ if (!has_cap_mac_admin(true)) {
struct audit_buffer *ab;
size_t audit_size;
const char *str;
@@ -3264,13 +3307,8 @@ static int selinux_inode_getsecurity(struct inode *inode, const char *name, void
* and lack of permission just means that we fall back to the
* in-core context value, not a denial.
*/
- error = cap_capable(current_cred(), &init_user_ns, CAP_MAC_ADMIN,
- SECURITY_CAP_NOAUDIT);
- if (!error)
- error = cred_has_capability(current_cred(), CAP_MAC_ADMIN,
- SECURITY_CAP_NOAUDIT, true);
isec = inode_security(inode);
- if (!error)
+ if (has_cap_mac_admin(false))
error = security_sid_to_context_force(isec->sid, &context,
&size);
else
@@ -3550,6 +3588,18 @@ static int selinux_mmap_addr(unsigned long addr)
static int selinux_mmap_file(struct file *file, unsigned long reqprot,
unsigned long prot, unsigned long flags)
{
+ struct common_audit_data ad;
+ int rc;
+
+ if (file) {
+ ad.type = LSM_AUDIT_DATA_FILE;
+ ad.u.file = file;
+ rc = inode_has_perm(current_cred(), file_inode(file),
+ FILE__MAP, &ad);
+ if (rc)
+ return rc;
+ }
+
if (selinux_checkreqprot)
prot = reqprot;
@@ -3710,7 +3760,8 @@ static int selinux_file_open(struct file *file, const struct cred *cred)
/* task security operations */
-static int selinux_task_create(unsigned long clone_flags)
+static int selinux_task_alloc(struct task_struct *task,
+ unsigned long clone_flags)
{
u32 sid = current_sid();
@@ -5918,7 +5969,7 @@ static int selinux_setprocattr(const char *name, void *value, size_t size)
}
error = security_context_to_sid(value, size, &sid, GFP_KERNEL);
if (error == -EINVAL && !strcmp(name, "fscreate")) {
- if (!capable(CAP_MAC_ADMIN)) {
+ if (!has_cap_mac_admin(true)) {
struct audit_buffer *ab;
size_t audit_size;
@@ -6128,7 +6179,70 @@ static int selinux_key_getsecurity(struct key *key, char **_buffer)
*_buffer = context;
return rc;
}
+#endif
+
+#ifdef CONFIG_SECURITY_INFINIBAND
+static int selinux_ib_pkey_access(void *ib_sec, u64 subnet_prefix, u16 pkey_val)
+{
+ struct common_audit_data ad;
+ int err;
+ u32 sid = 0;
+ struct ib_security_struct *sec = ib_sec;
+ struct lsm_ibpkey_audit ibpkey;
+
+ err = sel_ib_pkey_sid(subnet_prefix, pkey_val, &sid);
+ if (err)
+ return err;
+ ad.type = LSM_AUDIT_DATA_IBPKEY;
+ ibpkey.subnet_prefix = subnet_prefix;
+ ibpkey.pkey = pkey_val;
+ ad.u.ibpkey = &ibpkey;
+ return avc_has_perm(sec->sid, sid,
+ SECCLASS_INFINIBAND_PKEY,
+ INFINIBAND_PKEY__ACCESS, &ad);
+}
+
+static int selinux_ib_endport_manage_subnet(void *ib_sec, const char *dev_name,
+ u8 port_num)
+{
+ struct common_audit_data ad;
+ int err;
+ u32 sid = 0;
+ struct ib_security_struct *sec = ib_sec;
+ struct lsm_ibendport_audit ibendport;
+
+ err = security_ib_endport_sid(dev_name, port_num, &sid);
+
+ if (err)
+ return err;
+
+ ad.type = LSM_AUDIT_DATA_IBENDPORT;
+ strncpy(ibendport.dev_name, dev_name, sizeof(ibendport.dev_name));
+ ibendport.port = port_num;
+ ad.u.ibendport = &ibendport;
+ return avc_has_perm(sec->sid, sid,
+ SECCLASS_INFINIBAND_ENDPORT,
+ INFINIBAND_ENDPORT__MANAGE_SUBNET, &ad);
+}
+
+static int selinux_ib_alloc_security(void **ib_sec)
+{
+ struct ib_security_struct *sec;
+
+ sec = kzalloc(sizeof(*sec), GFP_KERNEL);
+ if (!sec)
+ return -ENOMEM;
+ sec->sid = current_sid();
+
+ *ib_sec = sec;
+ return 0;
+}
+
+static void selinux_ib_free_security(void *ib_sec)
+{
+ kfree(ib_sec);
+}
#endif
static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
@@ -6213,7 +6327,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(file_open, selinux_file_open),
- LSM_HOOK_INIT(task_create, selinux_task_create),
+ LSM_HOOK_INIT(task_alloc, selinux_task_alloc),
LSM_HOOK_INIT(cred_alloc_blank, selinux_cred_alloc_blank),
LSM_HOOK_INIT(cred_free, selinux_cred_free),
LSM_HOOK_INIT(cred_prepare, selinux_cred_prepare),
@@ -6315,7 +6429,13 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(tun_dev_attach_queue, selinux_tun_dev_attach_queue),
LSM_HOOK_INIT(tun_dev_attach, selinux_tun_dev_attach),
LSM_HOOK_INIT(tun_dev_open, selinux_tun_dev_open),
-
+#ifdef CONFIG_SECURITY_INFINIBAND
+ LSM_HOOK_INIT(ib_pkey_access, selinux_ib_pkey_access),
+ LSM_HOOK_INIT(ib_endport_manage_subnet,
+ selinux_ib_endport_manage_subnet),
+ LSM_HOOK_INIT(ib_alloc_security, selinux_ib_alloc_security),
+ LSM_HOOK_INIT(ib_free_security, selinux_ib_free_security),
+#endif
#ifdef CONFIG_SECURITY_NETWORK_XFRM
LSM_HOOK_INIT(xfrm_policy_alloc_security, selinux_xfrm_policy_alloc),
LSM_HOOK_INIT(xfrm_policy_clone_security, selinux_xfrm_policy_clone),
@@ -6379,6 +6499,9 @@ static __init int selinux_init(void)
if (avc_add_callback(selinux_netcache_avc_callback, AVC_CALLBACK_RESET))
panic("SELinux: Unable to register AVC netcache callback\n");
+ if (avc_add_callback(selinux_lsm_notifier_avc_callback, AVC_CALLBACK_RESET))
+ panic("SELinux: Unable to register AVC LSM notifier callback\n");
+
if (selinux_enforcing)
printk(KERN_DEBUG "SELinux: Starting in enforcing mode\n");
else
@@ -6448,6 +6571,23 @@ static struct nf_hook_ops selinux_nf_ops[] = {
#endif /* IPV6 */
};
+static int __net_init selinux_nf_register(struct net *net)
+{
+ return nf_register_net_hooks(net, selinux_nf_ops,
+ ARRAY_SIZE(selinux_nf_ops));
+}
+
+static void __net_exit selinux_nf_unregister(struct net *net)
+{
+ nf_unregister_net_hooks(net, selinux_nf_ops,
+ ARRAY_SIZE(selinux_nf_ops));
+}
+
+static struct pernet_operations selinux_net_ops = {
+ .init = selinux_nf_register,
+ .exit = selinux_nf_unregister,
+};
+
static int __init selinux_nf_ip_init(void)
{
int err;
@@ -6457,13 +6597,12 @@ static int __init selinux_nf_ip_init(void)
printk(KERN_DEBUG "SELinux: Registering netfilter hooks\n");
- err = nf_register_hooks(selinux_nf_ops, ARRAY_SIZE(selinux_nf_ops));
+ err = register_pernet_subsys(&selinux_net_ops);
if (err)
- panic("SELinux: nf_register_hooks: error %d\n", err);
+ panic("SELinux: register_pernet_subsys: error %d\n", err);
return 0;
}
-
__initcall(selinux_nf_ip_init);
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
@@ -6471,7 +6610,7 @@ static void selinux_nf_ip_exit(void)
{
printk(KERN_DEBUG "SELinux: Unregistering netfilter hooks\n");
- nf_unregister_hooks(selinux_nf_ops, ARRAY_SIZE(selinux_nf_ops));
+ unregister_pernet_subsys(&selinux_net_ops);
}
#endif
diff --git a/security/selinux/ibpkey.c b/security/selinux/ibpkey.c
new file mode 100644
index 000000000000..e3614ee5f1c0
--- /dev/null
+++ b/security/selinux/ibpkey.c
@@ -0,0 +1,245 @@
+/*
+ * Pkey table
+ *
+ * SELinux must keep a mapping of Infinband PKEYs to labels/SIDs. This
+ * mapping is maintained as part of the normal policy but a fast cache is
+ * needed to reduce the lookup overhead.
+ *
+ * This code is heavily based on the "netif" and "netport" concept originally
+ * developed by
+ * James Morris <jmorris@redhat.com> and
+ * Paul Moore <paul@paul-moore.com>
+ * (see security/selinux/netif.c and security/selinux/netport.c for more
+ * information)
+ *
+ */
+
+/*
+ * (c) Mellanox Technologies, 2016
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/rcupdate.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+
+#include "ibpkey.h"
+#include "objsec.h"
+
+#define SEL_PKEY_HASH_SIZE 256
+#define SEL_PKEY_HASH_BKT_LIMIT 16
+
+struct sel_ib_pkey_bkt {
+ int size;
+ struct list_head list;
+};
+
+struct sel_ib_pkey {
+ struct pkey_security_struct psec;
+ struct list_head list;
+ struct rcu_head rcu;
+};
+
+static LIST_HEAD(sel_ib_pkey_list);
+static DEFINE_SPINLOCK(sel_ib_pkey_lock);
+static struct sel_ib_pkey_bkt sel_ib_pkey_hash[SEL_PKEY_HASH_SIZE];
+
+/**
+ * sel_ib_pkey_hashfn - Hashing function for the pkey table
+ * @pkey: pkey number
+ *
+ * Description:
+ * This is the hashing function for the pkey table, it returns the bucket
+ * number for the given pkey.
+ *
+ */
+static unsigned int sel_ib_pkey_hashfn(u16 pkey)
+{
+ return (pkey & (SEL_PKEY_HASH_SIZE - 1));
+}
+
+/**
+ * sel_ib_pkey_find - Search for a pkey record
+ * @subnet_prefix: subnet_prefix
+ * @pkey_num: pkey_num
+ *
+ * Description:
+ * Search the pkey table and return the matching record. If an entry
+ * can not be found in the table return NULL.
+ *
+ */
+static struct sel_ib_pkey *sel_ib_pkey_find(u64 subnet_prefix, u16 pkey_num)
+{
+ unsigned int idx;
+ struct sel_ib_pkey *pkey;
+
+ idx = sel_ib_pkey_hashfn(pkey_num);
+ list_for_each_entry_rcu(pkey, &sel_ib_pkey_hash[idx].list, list) {
+ if (pkey->psec.pkey == pkey_num &&
+ pkey->psec.subnet_prefix == subnet_prefix)
+ return pkey;
+ }
+
+ return NULL;
+}
+
+/**
+ * sel_ib_pkey_insert - Insert a new pkey into the table
+ * @pkey: the new pkey record
+ *
+ * Description:
+ * Add a new pkey record to the hash table.
+ *
+ */
+static void sel_ib_pkey_insert(struct sel_ib_pkey *pkey)
+{
+ unsigned int idx;
+
+ /* 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
+ */
+ idx = sel_ib_pkey_hashfn(pkey->psec.pkey);
+ list_add_rcu(&pkey->list, &sel_ib_pkey_hash[idx].list);
+ if (sel_ib_pkey_hash[idx].size == SEL_PKEY_HASH_BKT_LIMIT) {
+ struct sel_ib_pkey *tail;
+
+ tail = list_entry(
+ rcu_dereference_protected(
+ sel_ib_pkey_hash[idx].list.prev,
+ lockdep_is_held(&sel_ib_pkey_lock)),
+ struct sel_ib_pkey, list);
+ list_del_rcu(&tail->list);
+ kfree_rcu(tail, rcu);
+ } else {
+ sel_ib_pkey_hash[idx].size++;
+ }
+}
+
+/**
+ * sel_ib_pkey_sid_slow - Lookup the SID of a pkey using the policy
+ * @subnet_prefix: subnet prefix
+ * @pkey_num: pkey number
+ * @sid: pkey SID
+ *
+ * Description:
+ * This function determines the SID of a pkey by querying the security
+ * policy. The result is added to the pkey table to speedup future
+ * queries. Returns zero on success, negative values on failure.
+ *
+ */
+static int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid)
+{
+ int ret;
+ struct sel_ib_pkey *pkey;
+ struct sel_ib_pkey *new = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sel_ib_pkey_lock, flags);
+ pkey = sel_ib_pkey_find(subnet_prefix, pkey_num);
+ if (pkey) {
+ *sid = pkey->psec.sid;
+ spin_unlock_irqrestore(&sel_ib_pkey_lock, flags);
+ return 0;
+ }
+
+ ret = security_ib_pkey_sid(subnet_prefix, pkey_num, sid);
+ if (ret)
+ goto out;
+
+ /* If this memory allocation fails still return 0. The SID
+ * is valid, it just won't be added to the cache.
+ */
+ new = kzalloc(sizeof(*new), GFP_ATOMIC);
+ if (!new)
+ goto out;
+
+ new->psec.subnet_prefix = subnet_prefix;
+ new->psec.pkey = pkey_num;
+ new->psec.sid = *sid;
+ sel_ib_pkey_insert(new);
+
+out:
+ spin_unlock_irqrestore(&sel_ib_pkey_lock, flags);
+ return ret;
+}
+
+/**
+ * sel_ib_pkey_sid - Lookup the SID of a PKEY
+ * @subnet_prefix: subnet_prefix
+ * @pkey_num: pkey number
+ * @sid: pkey SID
+ *
+ * Description:
+ * This function determines the SID of a PKEY using the fastest method
+ * possible. First the pkey table is queried, but if an entry can't be found
+ * then the policy is queried and the result is added to the table to speedup
+ * future queries. Returns zero on success, negative values on failure.
+ *
+ */
+int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *sid)
+{
+ struct sel_ib_pkey *pkey;
+
+ rcu_read_lock();
+ pkey = sel_ib_pkey_find(subnet_prefix, pkey_num);
+ if (pkey) {
+ *sid = pkey->psec.sid;
+ rcu_read_unlock();
+ return 0;
+ }
+ rcu_read_unlock();
+
+ return sel_ib_pkey_sid_slow(subnet_prefix, pkey_num, sid);
+}
+
+/**
+ * sel_ib_pkey_flush - Flush the entire pkey table
+ *
+ * Description:
+ * Remove all entries from the pkey table
+ *
+ */
+void sel_ib_pkey_flush(void)
+{
+ unsigned int idx;
+ struct sel_ib_pkey *pkey, *pkey_tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sel_ib_pkey_lock, flags);
+ for (idx = 0; idx < SEL_PKEY_HASH_SIZE; idx++) {
+ list_for_each_entry_safe(pkey, pkey_tmp,
+ &sel_ib_pkey_hash[idx].list, list) {
+ list_del_rcu(&pkey->list);
+ kfree_rcu(pkey, rcu);
+ }
+ sel_ib_pkey_hash[idx].size = 0;
+ }
+ spin_unlock_irqrestore(&sel_ib_pkey_lock, flags);
+}
+
+static __init int sel_ib_pkey_init(void)
+{
+ int iter;
+
+ if (!selinux_enabled)
+ return 0;
+
+ for (iter = 0; iter < SEL_PKEY_HASH_SIZE; iter++) {
+ INIT_LIST_HEAD(&sel_ib_pkey_hash[iter].list);
+ sel_ib_pkey_hash[iter].size = 0;
+ }
+
+ return 0;
+}
+
+subsys_initcall(sel_ib_pkey_init);
diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h
index 1e0cc9b5de20..b9fe3434b036 100644
--- a/security/selinux/include/classmap.h
+++ b/security/selinux/include/classmap.h
@@ -1,7 +1,7 @@
#include <linux/capability.h>
#define COMMON_FILE_SOCK_PERMS "ioctl", "read", "write", "create", \
- "getattr", "setattr", "lock", "relabelfrom", "relabelto", "append"
+ "getattr", "setattr", "lock", "relabelfrom", "relabelto", "append", "map"
#define COMMON_FILE_PERMS COMMON_FILE_SOCK_PERMS, "unlink", "link", \
"rename", "execute", "quotaon", "mounton", "audit_access", \
@@ -231,6 +231,10 @@ struct security_class_mapping secclass_map[] = {
{ COMMON_SOCK_PERMS, NULL } },
{ "smc_socket",
{ COMMON_SOCK_PERMS, NULL } },
+ { "infiniband_pkey",
+ { "access", NULL } },
+ { "infiniband_endport",
+ { "manage_subnet", NULL } },
{ NULL }
};
diff --git a/security/selinux/include/ibpkey.h b/security/selinux/include/ibpkey.h
new file mode 100644
index 000000000000..b17a19e348e6
--- /dev/null
+++ b/security/selinux/include/ibpkey.h
@@ -0,0 +1,31 @@
+/*
+ * pkey table
+ *
+ * SELinux must keep a mapping of pkeys to labels/SIDs. This
+ * mapping is maintained as part of the normal policy but a fast cache is
+ * needed to reduce the lookup overhead.
+ *
+ */
+
+/*
+ * (c) Mellanox Technologies, 2016
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _SELINUX_IB_PKEY_H
+#define _SELINUX_IB_PKEY_H
+
+void sel_ib_pkey_flush(void);
+
+int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey, u32 *sid);
+
+#endif
diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h
index c03cdcd12a3b..6ebc61e370ff 100644
--- a/security/selinux/include/objsec.h
+++ b/security/selinux/include/objsec.h
@@ -10,6 +10,7 @@
*
* Copyright (C) 2001,2002 Networks Associates Technology, Inc.
* Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com>
+ * Copyright (C) 2016 Mellanox Technologies
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -139,6 +140,16 @@ struct key_security_struct {
u32 sid; /* SID of key */
};
+struct ib_security_struct {
+ u32 sid; /* SID of the queue pair or MAD agent */
+};
+
+struct pkey_security_struct {
+ u64 subnet_prefix; /* Port subnet prefix */
+ u16 pkey; /* PKey number */
+ u32 sid; /* SID of pkey */
+};
+
extern unsigned int selinux_checkreqprot;
#endif /* _SELINUX_OBJSEC_H_ */
diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
index f979c35e037e..e91f08c16c0b 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -36,10 +36,11 @@
#define POLICYDB_VERSION_DEFAULT_TYPE 28
#define POLICYDB_VERSION_CONSTRAINT_NAMES 29
#define POLICYDB_VERSION_XPERMS_IOCTL 30
+#define POLICYDB_VERSION_INFINIBAND 31
/* Range of policy versions we understand*/
#define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE
-#define POLICYDB_VERSION_MAX POLICYDB_VERSION_XPERMS_IOCTL
+#define POLICYDB_VERSION_MAX POLICYDB_VERSION_INFINIBAND
/* Mask for just the mount related flags */
#define SE_MNTMASK 0x0f
@@ -76,6 +77,8 @@ enum {
};
#define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1)
+extern char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX];
+
extern int selinux_policycap_netpeer;
extern int selinux_policycap_openperm;
extern int selinux_policycap_extsockclass;
@@ -178,6 +181,10 @@ int security_get_user_sids(u32 callsid, char *username,
int security_port_sid(u8 protocol, u16 port, u32 *out_sid);
+int security_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *out_sid);
+
+int security_ib_endport_sid(const char *dev_name, u8 port_num, u32 *out_sid);
+
int security_netif_sid(char *name, u32 *if_sid);
int security_node_sid(u16 domain, void *addr, u32 addrlen,
diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c
index 5aeaf30b7a13..7b7433a1a34c 100644
--- a/security/selinux/nlmsgtab.c
+++ b/security/selinux/nlmsgtab.c
@@ -79,6 +79,7 @@ static const struct nlmsg_perm nlmsg_route_perms[] =
{ RTM_GETNSID, NETLINK_ROUTE_SOCKET__NLMSG_READ },
{ RTM_NEWSTATS, NETLINK_ROUTE_SOCKET__NLMSG_READ },
{ RTM_GETSTATS, NETLINK_ROUTE_SOCKET__NLMSG_READ },
+ { RTM_NEWCACHEREPORT, NETLINK_ROUTE_SOCKET__NLMSG_READ },
};
static const struct nlmsg_perm nlmsg_tcpdiag_perms[] =
@@ -158,7 +159,7 @@ int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm)
switch (sclass) {
case SECCLASS_NETLINK_ROUTE_SOCKET:
/* RTM_MAX always point to RTM_SETxxxx, ie RTM_NEWxxx + 3 */
- BUILD_BUG_ON(RTM_MAX != (RTM_NEWSTATS + 3));
+ BUILD_BUG_ON(RTM_MAX != (RTM_NEWCACHEREPORT + 3));
err = nlmsg_perm(nlmsg_type, perm, nlmsg_route_perms,
sizeof(nlmsg_route_perms));
break;
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index 50062e70140d..9010a3632d6f 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -41,15 +41,6 @@
#include "objsec.h"
#include "conditional.h"
-/* Policy capability filenames */
-static char *policycap_names[] = {
- "network_peer_controls",
- "open_perms",
- "extended_socket_class",
- "always_check_network",
- "cgroup_seclabel"
-};
-
unsigned int selinux_checkreqprot = CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE;
static int __init checkreqprot_setup(char *str)
@@ -163,6 +154,8 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf,
avc_ss_reset(0);
selnl_notify_setenforce(selinux_enforcing);
selinux_status_update_setenforce(selinux_enforcing);
+ if (!selinux_enforcing)
+ call_lsm_notifier(LSM_POLICY_CHANGE, NULL);
}
length = count;
out:
@@ -1750,9 +1743,9 @@ static int sel_make_policycap(void)
sel_remove_entries(policycap_dir);
for (iter = 0; iter <= POLICYDB_CAPABILITY_MAX; iter++) {
- if (iter < ARRAY_SIZE(policycap_names))
+ if (iter < ARRAY_SIZE(selinux_policycap_names))
dentry = d_alloc_name(policycap_dir,
- policycap_names[iter]);
+ selinux_policycap_names[iter]);
else
dentry = d_alloc_name(policycap_dir, "unknown");
diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c
index 9db4709a6877..ad38299164c3 100644
--- a/security/selinux/ss/ebitmap.c
+++ b/security/selinux/ss/ebitmap.c
@@ -24,6 +24,8 @@
#define BITS_PER_U64 (sizeof(u64) * 8)
+static struct kmem_cache *ebitmap_node_cachep;
+
int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2)
{
struct ebitmap_node *n1, *n2;
@@ -54,7 +56,7 @@ int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src)
n = src->node;
prev = NULL;
while (n) {
- new = kzalloc(sizeof(*new), GFP_ATOMIC);
+ new = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC);
if (!new) {
ebitmap_destroy(dst);
return -ENOMEM;
@@ -162,7 +164,7 @@ int ebitmap_netlbl_import(struct ebitmap *ebmap,
if (e_iter == NULL ||
offset >= e_iter->startbit + EBITMAP_SIZE) {
e_prev = e_iter;
- e_iter = kzalloc(sizeof(*e_iter), GFP_ATOMIC);
+ e_iter = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC);
if (e_iter == NULL)
goto netlbl_import_failure;
e_iter->startbit = offset - (offset % EBITMAP_SIZE);
@@ -288,7 +290,7 @@ int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value)
prev->next = n->next;
else
e->node = n->next;
- kfree(n);
+ kmem_cache_free(ebitmap_node_cachep, n);
}
return 0;
}
@@ -299,7 +301,7 @@ int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value)
if (!value)
return 0;
- new = kzalloc(sizeof(*new), GFP_ATOMIC);
+ new = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC);
if (!new)
return -ENOMEM;
@@ -332,7 +334,7 @@ void ebitmap_destroy(struct ebitmap *e)
while (n) {
temp = n;
n = n->next;
- kfree(temp);
+ kmem_cache_free(ebitmap_node_cachep, temp);
}
e->highbit = 0;
@@ -400,7 +402,7 @@ int ebitmap_read(struct ebitmap *e, void *fp)
if (!n || startbit >= n->startbit + EBITMAP_SIZE) {
struct ebitmap_node *tmp;
- tmp = kzalloc(sizeof(*tmp), GFP_KERNEL);
+ tmp = kmem_cache_zalloc(ebitmap_node_cachep, GFP_KERNEL);
if (!tmp) {
printk(KERN_ERR
"SELinux: ebitmap: out of memory\n");
@@ -519,3 +521,15 @@ int ebitmap_write(struct ebitmap *e, void *fp)
}
return 0;
}
+
+void ebitmap_cache_init(void)
+{
+ ebitmap_node_cachep = kmem_cache_create("ebitmap_node",
+ sizeof(struct ebitmap_node),
+ 0, SLAB_PANIC, NULL);
+}
+
+void ebitmap_cache_destroy(void)
+{
+ kmem_cache_destroy(ebitmap_node_cachep);
+}
diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h
index 9637b8c71085..6d5a9ac4251f 100644
--- a/security/selinux/ss/ebitmap.h
+++ b/security/selinux/ss/ebitmap.h
@@ -130,6 +130,9 @@ void ebitmap_destroy(struct ebitmap *e);
int ebitmap_read(struct ebitmap *e, void *fp);
int ebitmap_write(struct ebitmap *e, void *fp);
+void ebitmap_cache_init(void);
+void ebitmap_cache_destroy(void);
+
#ifdef CONFIG_NETLABEL
int ebitmap_netlbl_export(struct ebitmap *ebmap,
struct netlbl_lsm_catmap **catmap);
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 0080122760ad..aa6500abb178 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -17,6 +17,11 @@
*
* Added support for the policy capability bitmap
*
+ * Update: Mellanox Techonologies
+ *
+ * Added Infiniband support
+ *
+ * Copyright (C) 2016 Mellanox Techonologies
* Copyright (C) 2007 Hewlett-Packard Development Company, L.P.
* Copyright (C) 2004-2005 Trusted Computer Solutions, Inc.
* Copyright (C) 2003 - 2004 Tresys Technology, LLC
@@ -76,81 +81,86 @@ static struct policydb_compat_info policydb_compat[] = {
{
.version = POLICYDB_VERSION_BASE,
.sym_num = SYM_NUM - 3,
- .ocon_num = OCON_NUM - 1,
+ .ocon_num = OCON_NUM - 3,
},
{
.version = POLICYDB_VERSION_BOOL,
.sym_num = SYM_NUM - 2,
- .ocon_num = OCON_NUM - 1,
+ .ocon_num = OCON_NUM - 3,
},
{
.version = POLICYDB_VERSION_IPV6,
.sym_num = SYM_NUM - 2,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_NLCLASS,
.sym_num = SYM_NUM - 2,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_MLS,
.sym_num = SYM_NUM,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_AVTAB,
.sym_num = SYM_NUM,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_RANGETRANS,
.sym_num = SYM_NUM,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_POLCAP,
.sym_num = SYM_NUM,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_PERMISSIVE,
.sym_num = SYM_NUM,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_BOUNDARY,
.sym_num = SYM_NUM,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_FILENAME_TRANS,
.sym_num = SYM_NUM,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_ROLETRANS,
.sym_num = SYM_NUM,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_NEW_OBJECT_DEFAULTS,
.sym_num = SYM_NUM,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_DEFAULT_TYPE,
.sym_num = SYM_NUM,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_CONSTRAINT_NAMES,
.sym_num = SYM_NUM,
- .ocon_num = OCON_NUM,
+ .ocon_num = OCON_NUM - 2,
},
{
.version = POLICYDB_VERSION_XPERMS_IOCTL,
.sym_num = SYM_NUM,
+ .ocon_num = OCON_NUM - 2,
+ },
+ {
+ .version = POLICYDB_VERSION_INFINIBAND,
+ .sym_num = SYM_NUM,
.ocon_num = OCON_NUM,
},
};
@@ -538,34 +548,30 @@ static int policydb_index(struct policydb *p)
symtab_hash_eval(p->symtab);
#endif
- rc = -ENOMEM;
p->class_val_to_struct = kcalloc(p->p_classes.nprim,
sizeof(*p->class_val_to_struct),
GFP_KERNEL);
if (!p->class_val_to_struct)
- goto out;
+ return -ENOMEM;
- rc = -ENOMEM;
p->role_val_to_struct = kcalloc(p->p_roles.nprim,
sizeof(*p->role_val_to_struct),
GFP_KERNEL);
if (!p->role_val_to_struct)
- goto out;
+ return -ENOMEM;
- rc = -ENOMEM;
p->user_val_to_struct = kcalloc(p->p_users.nprim,
sizeof(*p->user_val_to_struct),
GFP_KERNEL);
if (!p->user_val_to_struct)
- goto out;
+ return -ENOMEM;
/* Yes, I want the sizeof the pointer, not the structure */
- rc = -ENOMEM;
p->type_val_to_struct_array = flex_array_alloc(sizeof(struct type_datum *),
p->p_types.nprim,
GFP_KERNEL | __GFP_ZERO);
if (!p->type_val_to_struct_array)
- goto out;
+ return -ENOMEM;
rc = flex_array_prealloc(p->type_val_to_struct_array, 0,
p->p_types.nprim, GFP_KERNEL | __GFP_ZERO);
@@ -577,12 +583,11 @@ static int policydb_index(struct policydb *p)
goto out;
for (i = 0; i < SYM_NUM; i++) {
- rc = -ENOMEM;
p->sym_val_to_name[i] = flex_array_alloc(sizeof(char *),
p->symtab[i].nprim,
GFP_KERNEL | __GFP_ZERO);
if (!p->sym_val_to_name[i])
- goto out;
+ return -ENOMEM;
rc = flex_array_prealloc(p->sym_val_to_name[i],
0, p->symtab[i].nprim,
@@ -2211,6 +2216,51 @@ static int ocontext_read(struct policydb *p, struct policydb_compat_info *info,
goto out;
break;
}
+ case OCON_IBPKEY:
+ rc = next_entry(nodebuf, fp, sizeof(u32) * 4);
+ if (rc)
+ goto out;
+
+ c->u.ibpkey.subnet_prefix = be64_to_cpu(*((__be64 *)nodebuf));
+
+ if (nodebuf[2] > 0xffff ||
+ nodebuf[3] > 0xffff) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ c->u.ibpkey.low_pkey = le32_to_cpu(nodebuf[2]);
+ c->u.ibpkey.high_pkey = le32_to_cpu(nodebuf[3]);
+
+ rc = context_read_and_validate(&c->context[0],
+ p,
+ fp);
+ if (rc)
+ goto out;
+ break;
+ case OCON_IBENDPORT:
+ rc = next_entry(buf, fp, sizeof(u32) * 2);
+ if (rc)
+ goto out;
+ len = le32_to_cpu(buf[0]);
+
+ rc = str_read(&c->u.ibendport.dev_name, GFP_KERNEL, fp, len);
+ if (rc)
+ goto out;
+
+ if (buf[1] > 0xff || buf[1] == 0) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ c->u.ibendport.port = le32_to_cpu(buf[1]);
+
+ rc = context_read_and_validate(&c->context[0],
+ p,
+ fp);
+ if (rc)
+ goto out;
+ break;
}
}
}
@@ -3140,6 +3190,33 @@ static int ocontext_write(struct policydb *p, struct policydb_compat_info *info,
if (rc)
return rc;
break;
+ case OCON_IBPKEY:
+ *((__be64 *)nodebuf) = cpu_to_be64(c->u.ibpkey.subnet_prefix);
+
+ nodebuf[2] = cpu_to_le32(c->u.ibpkey.low_pkey);
+ nodebuf[3] = cpu_to_le32(c->u.ibpkey.high_pkey);
+
+ rc = put_entry(nodebuf, sizeof(u32), 4, fp);
+ if (rc)
+ return rc;
+ rc = context_write(p, &c->context[0], fp);
+ if (rc)
+ return rc;
+ break;
+ case OCON_IBENDPORT:
+ len = strlen(c->u.ibendport.dev_name);
+ buf[0] = cpu_to_le32(len);
+ buf[1] = cpu_to_le32(c->u.ibendport.port);
+ rc = put_entry(buf, sizeof(u32), 2, fp);
+ if (rc)
+ return rc;
+ rc = put_entry(c->u.ibendport.dev_name, 1, len, fp);
+ if (rc)
+ return rc;
+ rc = context_write(p, &c->context[0], fp);
+ if (rc)
+ return rc;
+ break;
}
}
}
diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
index 725d5945a97e..5d23eed35fa7 100644
--- a/security/selinux/ss/policydb.h
+++ b/security/selinux/ss/policydb.h
@@ -187,6 +187,15 @@ struct ocontext {
u32 addr[4];
u32 mask[4];
} node6; /* IPv6 node information */
+ struct {
+ u64 subnet_prefix;
+ u16 low_pkey;
+ u16 high_pkey;
+ } ibpkey;
+ struct {
+ char *dev_name;
+ u8 port;
+ } ibendport;
} u;
union {
u32 sclass; /* security class for genfs */
@@ -215,14 +224,16 @@ struct genfs {
#define SYM_NUM 8
/* object context array indices */
-#define OCON_ISID 0 /* initial SIDs */
-#define OCON_FS 1 /* unlabeled file systems */
-#define OCON_PORT 2 /* TCP and UDP port numbers */
-#define OCON_NETIF 3 /* network interfaces */
-#define OCON_NODE 4 /* nodes */
-#define OCON_FSUSE 5 /* fs_use */
-#define OCON_NODE6 6 /* IPv6 nodes */
-#define OCON_NUM 7
+#define OCON_ISID 0 /* initial SIDs */
+#define OCON_FS 1 /* unlabeled file systems */
+#define OCON_PORT 2 /* TCP and UDP port numbers */
+#define OCON_NETIF 3 /* network interfaces */
+#define OCON_NODE 4 /* nodes */
+#define OCON_FSUSE 5 /* fs_use */
+#define OCON_NODE6 6 /* IPv6 nodes */
+#define OCON_IBPKEY 7 /* Infiniband PKeys */
+#define OCON_IBENDPORT 8 /* Infiniband end ports */
+#define OCON_NUM 9
/* The policy database */
struct policydb {
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 60d9b0252321..2f02fa67ec2e 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -70,6 +70,15 @@
#include "ebitmap.h"
#include "audit.h"
+/* Policy capability names */
+char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = {
+ "network_peer_controls",
+ "open_perms",
+ "extended_socket_class",
+ "always_check_network",
+ "cgroup_seclabel"
+};
+
int selinux_policycap_netpeer;
int selinux_policycap_openperm;
int selinux_policycap_extsockclass;
@@ -1986,6 +1995,9 @@ bad:
static void security_load_policycaps(void)
{
+ unsigned int i;
+ struct ebitmap_node *node;
+
selinux_policycap_netpeer = ebitmap_get_bit(&policydb.policycaps,
POLICYDB_CAPABILITY_NETPEER);
selinux_policycap_openperm = ebitmap_get_bit(&policydb.policycaps,
@@ -1997,6 +2009,17 @@ static void security_load_policycaps(void)
selinux_policycap_cgroupseclabel =
ebitmap_get_bit(&policydb.policycaps,
POLICYDB_CAPABILITY_CGROUPSECLABEL);
+
+ for (i = 0; i < ARRAY_SIZE(selinux_policycap_names); i++)
+ pr_info("SELinux: policy capability %s=%d\n",
+ selinux_policycap_names[i],
+ ebitmap_get_bit(&policydb.policycaps, i));
+
+ ebitmap_for_each_positive_bit(&policydb.policycaps, node, i) {
+ if (i >= ARRAY_SIZE(selinux_policycap_names))
+ pr_info("SELinux: unknown policy capability %u\n",
+ i);
+ }
}
static int security_preserve_bools(struct policydb *p);
@@ -2031,9 +2054,11 @@ int security_load_policy(void *data, size_t len)
if (!ss_initialized) {
avtab_cache_init();
+ ebitmap_cache_init();
rc = policydb_read(&policydb, fp);
if (rc) {
avtab_cache_destroy();
+ ebitmap_cache_destroy();
goto out;
}
@@ -2044,6 +2069,7 @@ int security_load_policy(void *data, size_t len)
if (rc) {
policydb_destroy(&policydb);
avtab_cache_destroy();
+ ebitmap_cache_destroy();
goto out;
}
@@ -2051,6 +2077,7 @@ int security_load_policy(void *data, size_t len)
if (rc) {
policydb_destroy(&policydb);
avtab_cache_destroy();
+ ebitmap_cache_destroy();
goto out;
}
@@ -2210,6 +2237,87 @@ out:
}
/**
+ * security_pkey_sid - Obtain the SID for a pkey.
+ * @subnet_prefix: Subnet Prefix
+ * @pkey_num: pkey number
+ * @out_sid: security identifier
+ */
+int security_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *out_sid)
+{
+ struct ocontext *c;
+ int rc = 0;
+
+ read_lock(&policy_rwlock);
+
+ c = policydb.ocontexts[OCON_IBPKEY];
+ while (c) {
+ if (c->u.ibpkey.low_pkey <= pkey_num &&
+ c->u.ibpkey.high_pkey >= pkey_num &&
+ c->u.ibpkey.subnet_prefix == subnet_prefix)
+ break;
+
+ c = c->next;
+ }
+
+ if (c) {
+ if (!c->sid[0]) {
+ rc = sidtab_context_to_sid(&sidtab,
+ &c->context[0],
+ &c->sid[0]);
+ if (rc)
+ goto out;
+ }
+ *out_sid = c->sid[0];
+ } else
+ *out_sid = SECINITSID_UNLABELED;
+
+out:
+ read_unlock(&policy_rwlock);
+ return rc;
+}
+
+/**
+ * security_ib_endport_sid - Obtain the SID for a subnet management interface.
+ * @dev_name: device name
+ * @port: port number
+ * @out_sid: security identifier
+ */
+int security_ib_endport_sid(const char *dev_name, u8 port_num, u32 *out_sid)
+{
+ struct ocontext *c;
+ int rc = 0;
+
+ read_lock(&policy_rwlock);
+
+ c = policydb.ocontexts[OCON_IBENDPORT];
+ while (c) {
+ if (c->u.ibendport.port == port_num &&
+ !strncmp(c->u.ibendport.dev_name,
+ dev_name,
+ IB_DEVICE_NAME_MAX))
+ break;
+
+ c = c->next;
+ }
+
+ if (c) {
+ if (!c->sid[0]) {
+ rc = sidtab_context_to_sid(&sidtab,
+ &c->context[0],
+ &c->sid[0]);
+ if (rc)
+ goto out;
+ }
+ *out_sid = c->sid[0];
+ } else
+ *out_sid = SECINITSID_UNLABELED;
+
+out:
+ read_unlock(&policy_rwlock);
+ return rc;
+}
+
+/**
* security_netif_sid - Obtain the SID for a network interface.
* @name: interface name
* @if_sid: interface SID
diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c
index f6915f257486..c5f436b15d19 100644
--- a/security/selinux/ss/sidtab.c
+++ b/security/selinux/ss/sidtab.c
@@ -32,13 +32,11 @@ int sidtab_init(struct sidtab *s)
int sidtab_insert(struct sidtab *s, u32 sid, struct context *context)
{
- int hvalue, rc = 0;
+ int hvalue;
struct sidtab_node *prev, *cur, *newnode;
- if (!s) {
- rc = -ENOMEM;
- goto out;
- }
+ if (!s)
+ return -ENOMEM;
hvalue = SIDTAB_HASH(sid);
prev = NULL;
@@ -48,21 +46,17 @@ int sidtab_insert(struct sidtab *s, u32 sid, struct context *context)
cur = cur->next;
}
- if (cur && sid == cur->sid) {
- rc = -EEXIST;
- goto out;
- }
+ if (cur && sid == cur->sid)
+ return -EEXIST;
newnode = kmalloc(sizeof(*newnode), GFP_ATOMIC);
- if (!newnode) {
- rc = -ENOMEM;
- goto out;
- }
+ if (!newnode)
+ return -ENOMEM;
+
newnode->sid = sid;
if (context_cpy(&newnode->context, context)) {
kfree(newnode);
- rc = -ENOMEM;
- goto out;
+ return -ENOMEM;
}
if (prev) {
@@ -78,8 +72,7 @@ int sidtab_insert(struct sidtab *s, u32 sid, struct context *context)
s->nel++;
if (sid >= s->next_sid)
s->next_sid = sid + 1;
-out:
- return rc;
+ return 0;
}
static struct context *sidtab_search_core(struct sidtab *s, u32 sid, int force)
diff --git a/security/smack/smack.h b/security/smack/smack.h
index 612b810fbbc6..6a71fc7831ab 100644
--- a/security/smack/smack.h
+++ b/security/smack/smack.h
@@ -320,7 +320,7 @@ int smk_netlbl_mls(int, char *, struct netlbl_lsm_secattr *, int);
struct smack_known *smk_import_entry(const char *, int);
void smk_insert_entry(struct smack_known *skp);
struct smack_known *smk_find_entry(const char *);
-int smack_privileged(int cap);
+bool smack_privileged(int cap);
void smk_destroy_label_list(struct list_head *list);
/*
diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c
index a4b2e6b94abd..1a3004189447 100644
--- a/security/smack/smack_access.c
+++ b/security/smack/smack_access.c
@@ -627,35 +627,38 @@ DEFINE_MUTEX(smack_onlycap_lock);
* Is the task privileged and allowed to be privileged
* by the onlycap rule.
*
- * Returns 1 if the task is allowed to be privileged, 0 if it's not.
+ * Returns true if the task is allowed to be privileged, false if it's not.
*/
-int smack_privileged(int cap)
+bool smack_privileged(int cap)
{
struct smack_known *skp = smk_of_current();
struct smack_known_list_elem *sklep;
+ int rc;
/*
* All kernel tasks are privileged
*/
if (unlikely(current->flags & PF_KTHREAD))
- return 1;
+ return true;
- if (!capable(cap))
- return 0;
+ rc = cap_capable(current_cred(), &init_user_ns, cap,
+ SECURITY_CAP_AUDIT);
+ if (rc)
+ return false;
rcu_read_lock();
if (list_empty(&smack_onlycap_list)) {
rcu_read_unlock();
- return 1;
+ return true;
}
list_for_each_entry_rcu(sklep, &smack_onlycap_list, list) {
if (sklep->smk_label == skp) {
rcu_read_unlock();
- return 1;
+ return true;
}
}
rcu_read_unlock();
- return 0;
+ return false;
}
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 658f5d8c7e76..463af86812c7 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1915,7 +1915,7 @@ static int smack_file_receive(struct file *file)
smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH);
smk_ad_setfield_u_fs_path(&ad, file->f_path);
- if (S_ISSOCK(inode->i_mode)) {
+ if (inode->i_sb->s_magic == SOCKFS_MAGIC) {
sock = SOCKET_I(inode);
ssp = sock->sk->sk_security;
tsp = current_security();
diff --git a/security/smack/smack_netfilter.c b/security/smack/smack_netfilter.c
index 205b785fb400..cdeb0f3243dd 100644
--- a/security/smack/smack_netfilter.c
+++ b/security/smack/smack_netfilter.c
@@ -18,6 +18,7 @@
#include <linux/netfilter_ipv6.h>
#include <linux/netdevice.h>
#include <net/inet_sock.h>
+#include <net/net_namespace.h>
#include "smack.h"
#if IS_ENABLED(CONFIG_IPV6)
@@ -74,20 +75,29 @@ static struct nf_hook_ops smack_nf_ops[] = {
#endif /* IPV6 */
};
-static int __init smack_nf_ip_init(void)
+static int __net_init smack_nf_register(struct net *net)
+{
+ return nf_register_net_hooks(net, smack_nf_ops,
+ ARRAY_SIZE(smack_nf_ops));
+}
+
+static void __net_exit smack_nf_unregister(struct net *net)
{
- int err;
+ nf_unregister_net_hooks(net, smack_nf_ops, ARRAY_SIZE(smack_nf_ops));
+}
+static struct pernet_operations smack_net_ops = {
+ .init = smack_nf_register,
+ .exit = smack_nf_unregister,
+};
+
+static int __init smack_nf_ip_init(void)
+{
if (smack_enabled == 0)
return 0;
printk(KERN_DEBUG "Smack: Registering netfilter hooks\n");
-
- err = nf_register_hooks(smack_nf_ops, ARRAY_SIZE(smack_nf_ops));
- if (err)
- pr_info("Smack: nf_register_hooks: error %d\n", err);
-
- return 0;
+ return register_pernet_subsys(&smack_net_ops);
}
__initcall(smack_nf_ip_init);
diff --git a/security/yama/Kconfig b/security/yama/Kconfig
index 90c605eea892..96b27405558a 100644
--- a/security/yama/Kconfig
+++ b/security/yama/Kconfig
@@ -7,6 +7,7 @@ config SECURITY_YAMA
system-wide security settings beyond regular Linux discretionary
access controls. Currently available is ptrace scope restriction.
Like capabilities, this security module stacks with other LSMs.
- Further information can be found in Documentation/security/Yama.txt.
+ Further information can be found in
+ Documentation/admin-guide/LSM/Yama.rst.
If you are unsure how to answer this question, answer N.