summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2018-04-13 15:38:53 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2018-04-13 15:38:53 -0700
commit80a17a5f501ea048d86f81d629c94062b76610d4 (patch)
tree55dd0a1490d1e6631ca319a1205f974f041b494f
parentedda415314804c29fa07e538938fa07947012d8f (diff)
parent588558eb6d0e0b6edfa65a67e906c2ffeba63ff1 (diff)
Merge tag 'apparmor-pr-2018-04-10' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor
Pull apparmor updates from John Johansen: "Features: - add base infrastructure for socket mediation. ABI bump and additional checks to ensure only v8 compliant policy uses socket af mediation. - improve and cleanup dfa verification - improve profile attachment logic - improve overlapping expression handling - add the xattr matching to the attachment logic - improve signal mediation handling with stacked labels - improve handling of no_new_privs in a label stack Cleanups and changes: - use dfa to parse string split - bounded version of label_parse - proper line wrap nulldfa.in - split context out into task and cred naming to better match usage - simplify code in aafs Bug fixes: - fix display of .ns_name for containers - fix resource audit messages when auditing peer - fix logging of the existence test for signals - fix resource audit messages when auditing peer - fix display of .ns_name for containers - fix an error code in verify_table_headers() - fix memory leak on buffer on error exit path - fix error returns checks by making size a ssize_t" * tag 'apparmor-pr-2018-04-10' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor: (36 commits) apparmor: fix memory leak on buffer on error exit path apparmor: fix dangling symlinks to policy rawdata after replacement apparmor: Fix an error code in verify_table_headers() apparmor: fix error returns checks by making size a ssize_t apparmor: update MAINTAINERS file git and wiki locations apparmor: remove POLICY_MEDIATES_SAFE apparmor: add base infastructure for socket mediation apparmor: improve overlapping domain attachment resolution apparmor: convert attaching profiles via xattrs to use dfa matching apparmor: Add support for attaching profiles via xattr, presence and value apparmor: cleanup: simplify code to get ns symlink name apparmor: cleanup create_aafs() error path apparmor: dfa split verification of table headers apparmor: dfa add support for state differential encoding apparmor: dfa move character match into a macro apparmor: update domain transitions that are subsets of confinement at nnp apparmor: move context.h to cred.h apparmor: move task related defines and fns to task.X files apparmor: cleanup, drop unused fn __aa_task_is_confined() apparmor: cleanup fixup description of aa_replace_profiles ...
-rw-r--r--MAINTAINERS4
-rw-r--r--security/apparmor/.gitignore1
-rw-r--r--security/apparmor/Makefile45
-rw-r--r--security/apparmor/apparmorfs.c203
-rw-r--r--security/apparmor/capability.c2
-rw-r--r--security/apparmor/domain.c355
-rw-r--r--security/apparmor/file.c32
-rw-r--r--security/apparmor/include/apparmor.h3
-rw-r--r--security/apparmor/include/audit.h19
-rw-r--r--security/apparmor/include/cred.h (renamed from security/apparmor/include/context.h)63
-rw-r--r--security/apparmor/include/label.h28
-rw-r--r--security/apparmor/include/match.h28
-rw-r--r--security/apparmor/include/net.h106
-rw-r--r--security/apparmor/include/perms.h5
-rw-r--r--security/apparmor/include/policy.h23
-rw-r--r--security/apparmor/include/policy_unpack.h2
-rw-r--r--security/apparmor/include/sig_names.h5
-rw-r--r--security/apparmor/include/task.h94
-rw-r--r--security/apparmor/ipc.c52
-rw-r--r--security/apparmor/label.c42
-rw-r--r--security/apparmor/lib.c5
-rw-r--r--security/apparmor/lsm.c467
-rw-r--r--security/apparmor/match.c423
-rw-r--r--security/apparmor/mount.c2
-rw-r--r--security/apparmor/net.c187
-rw-r--r--security/apparmor/nulldfa.in108
-rw-r--r--security/apparmor/policy.c11
-rw-r--r--security/apparmor/policy_ns.c2
-rw-r--r--security/apparmor/policy_unpack.c70
-rw-r--r--security/apparmor/procattr.c2
-rw-r--r--security/apparmor/resource.c2
-rw-r--r--security/apparmor/stacksplitdfa.in114
-rw-r--r--security/apparmor/task.c (renamed from security/apparmor/context.c)139
33 files changed, 2119 insertions, 525 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index f79952e6f627..9b091ef6579b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -934,8 +934,8 @@ F: drivers/char/apm-emulation.c
APPARMOR SECURITY MODULE
M: John Johansen <john.johansen@canonical.com>
L: apparmor@lists.ubuntu.com (subscribers-only, general discussion)
-W: apparmor.wiki.kernel.org
-T: git git://git.kernel.org/pub/scm/linux/kernel/git/jj/apparmor-dev.git
+W: wiki.apparmor.net
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor
S: Supported
F: security/apparmor/
F: Documentation/admin-guide/LSM/apparmor.rst
diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore
index 9cdec70d72b8..d5b291e94264 100644
--- a/security/apparmor/.gitignore
+++ b/security/apparmor/.gitignore
@@ -1,5 +1,6 @@
#
# Generated include files
#
+net_names.h
capability_names.h
rlim_names.h
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 9a6b4033d52b..ff23fcfefe19 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -3,13 +3,46 @@
#
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
-apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
+apparmor-y := apparmorfs.o audit.o capability.o task.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 label.o mount.o
+ resource.o secid.o file.o policy_ns.o label.o mount.o net.o
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
-clean-files := capability_names.h rlim_names.h
+clean-files := capability_names.h rlim_names.h net_names.h
+# Build a lower case string table of address family names
+# Transform lines from
+# #define AF_LOCAL 1 /* POSIX name for AF_UNIX */
+# #define AF_INET 2 /* Internet IP Protocol */
+# to
+# [1] = "local",
+# [2] = "inet",
+#
+# and build the securityfs entries for the mapping.
+# Transforms lines from
+# #define AF_INET 2 /* Internet IP Protocol */
+# to
+# #define AA_SFS_AF_MASK "local inet"
+quiet_cmd_make-af = GEN $@
+cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\
+ sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "/AF_ROUTE/d" -e \
+ 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\
+ echo "};" >> $@ ;\
+ printf '%s' '\#define AA_SFS_AF_MASK "' >> $@ ;\
+ sed -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "/AF_ROUTE/d" -e \
+ 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\
+ $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
+
+# Build a lower case string table of sock type names
+# Transform lines from
+# SOCK_STREAM = 1,
+# to
+# [1] = "stream",
+quiet_cmd_make-sock = GEN $@
+cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\
+ sed $^ >>$@ -r -n \
+ -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\
+ echo "};" >> $@
# Build a lower case string table of capability names
# Transforms lines from
@@ -62,6 +95,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
$(obj)/capability.o : $(obj)/capability_names.h
+$(obj)/net.o : $(obj)/net_names.h
$(obj)/resource.o : $(obj)/rlim_names.h
$(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
$(src)/Makefile
@@ -69,3 +103,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
$(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \
$(src)/Makefile
$(call cmd,make-rlim)
+$(obj)/net_names.h : $(srctree)/include/linux/socket.h \
+ $(srctree)/include/linux/net.h \
+ $(src)/Makefile
+ $(call cmd,make-af)
+ $(call cmd,make-sock)
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index a9428daa69f3..949dd8a48164 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -30,10 +30,9 @@
#include "include/apparmor.h"
#include "include/apparmorfs.h"
#include "include/audit.h"
-#include "include/context.h"
+#include "include/cred.h"
#include "include/crypto.h"
#include "include/ipc.h"
-#include "include/policy_ns.h"
#include "include/label.h"
#include "include/policy.h"
#include "include/policy_ns.h"
@@ -120,9 +119,7 @@ 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);
+ seq_printf(seq, "%s:[%lu]", AAFS_NAME, d_inode(dentry)->i_ino);
return 0;
}
@@ -313,6 +310,7 @@ static struct dentry *aafs_create_dir(const char *name, struct dentry *parent)
* @name: name of dentry to create
* @parent: parent directory for this dentry
* @target: if symlink, symlink target string
+ * @private: private data
* @iops: struct of inode_operations that should be used
*
* If @target parameter is %NULL, then the @iops parameter needs to be
@@ -321,17 +319,17 @@ static struct dentry *aafs_create_dir(const char *name, struct dentry *parent)
static struct dentry *aafs_create_symlink(const char *name,
struct dentry *parent,
const char *target,
+ void *private,
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,
+ dent = aafs_create(name, S_IFLNK | 0444, parent, private, link, NULL,
iops);
if (IS_ERR(dent))
kfree(link);
@@ -622,7 +620,7 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,
tmp = aa_compute_fperms(dfa, state, &cond);
}
} else if (profile->policy.dfa) {
- if (!PROFILE_MEDIATES_SAFE(profile, *match_str))
+ if (!PROFILE_MEDIATES(profile, *match_str))
return; /* no change to current perms */
dfa = profile->policy.dfa;
state = aa_dfa_match_len(dfa, profile->policy.start[0],
@@ -1189,9 +1187,7 @@ static int seq_ns_level_show(struct seq_file *seq, void *v)
static int seq_ns_name_show(struct seq_file *seq, void *v)
{
struct aa_label *label = begin_current_label_crit_section();
-
- seq_printf(seq, "%s\n", aa_ns_name(labels_ns(label),
- labels_ns(label), true));
+ seq_printf(seq, "%s\n", labels_ns(label)->base.name);
end_current_label_crit_section(label);
return 0;
@@ -1484,26 +1480,97 @@ static int profile_depth(struct aa_profile *profile)
return depth;
}
-static int gen_symlink_name(char *buffer, size_t bsize, int depth,
- const char *dirname, const char *fname)
+static char *gen_symlink_name(int depth, const char *dirname, const char *fname)
{
+ char *buffer, *s;
int error;
+ int size = depth * 6 + strlen(dirname) + strlen(fname) + 11;
+
+ s = buffer = kmalloc(size, GFP_KERNEL);
+ if (!buffer)
+ return ERR_PTR(-ENOMEM);
for (; depth > 0; depth--) {
- if (bsize < 7)
- return -ENAMETOOLONG;
- strcpy(buffer, "../../");
- buffer += 6;
- bsize -= 6;
+ strcpy(s, "../../");
+ s += 6;
+ size -= 6;
}
- error = snprintf(buffer, bsize, "raw_data/%s/%s", dirname, fname);
- if (error >= bsize || error < 0)
- return -ENAMETOOLONG;
+ error = snprintf(s, size, "raw_data/%s/%s", dirname, fname);
+ if (error >= size || error < 0) {
+ kfree(buffer);
+ return ERR_PTR(-ENAMETOOLONG);
+ }
- return 0;
+ return buffer;
+}
+
+static void rawdata_link_cb(void *arg)
+{
+ kfree(arg);
+}
+
+static const char *rawdata_get_link_base(struct dentry *dentry,
+ struct inode *inode,
+ struct delayed_call *done,
+ const char *name)
+{
+ struct aa_proxy *proxy = inode->i_private;
+ struct aa_label *label;
+ struct aa_profile *profile;
+ char *target;
+ int depth;
+
+ if (!dentry)
+ return ERR_PTR(-ECHILD);
+
+ label = aa_get_label_rcu(&proxy->label);
+ profile = labels_profile(label);
+ depth = profile_depth(profile);
+ target = gen_symlink_name(depth, profile->rawdata->name, name);
+ aa_put_label(label);
+
+ if (IS_ERR(target))
+ return target;
+
+ set_delayed_call(done, rawdata_link_cb, target);
+
+ return target;
+}
+
+static const char *rawdata_get_link_sha1(struct dentry *dentry,
+ struct inode *inode,
+ struct delayed_call *done)
+{
+ return rawdata_get_link_base(dentry, inode, done, "sha1");
+}
+
+static const char *rawdata_get_link_abi(struct dentry *dentry,
+ struct inode *inode,
+ struct delayed_call *done)
+{
+ return rawdata_get_link_base(dentry, inode, done, "abi");
+}
+
+static const char *rawdata_get_link_data(struct dentry *dentry,
+ struct inode *inode,
+ struct delayed_call *done)
+{
+ return rawdata_get_link_base(dentry, inode, done, "raw_data");
}
+static const struct inode_operations rawdata_link_sha1_iops = {
+ .get_link = rawdata_get_link_sha1,
+};
+
+static const struct inode_operations rawdata_link_abi_iops = {
+ .get_link = rawdata_get_link_abi,
+};
+static const struct inode_operations rawdata_link_data_iops = {
+ .get_link = rawdata_get_link_data,
+};
+
+
/*
* Requires: @profile->ns->lock held
*/
@@ -1574,34 +1641,28 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
}
if (profile->rawdata) {
- 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);
+ dent = aafs_create_symlink("raw_sha1", dir, NULL,
+ profile->label.proxy,
+ &rawdata_link_sha1_iops);
if (IS_ERR(dent))
goto fail;
+ aa_get_proxy(profile->label.proxy);
profile->dents[AAFS_PROF_RAW_HASH] = dent;
- 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);
+ dent = aafs_create_symlink("raw_abi", dir, NULL,
+ profile->label.proxy,
+ &rawdata_link_abi_iops);
if (IS_ERR(dent))
goto fail;
+ aa_get_proxy(profile->label.proxy);
profile->dents[AAFS_PROF_RAW_ABI] = dent;
- 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);
+ dent = aafs_create_symlink("raw_data", dir, NULL,
+ profile->label.proxy,
+ &rawdata_link_data_iops);
if (IS_ERR(dent))
goto fail;
+ aa_get_proxy(profile->label.proxy);
profile->dents[AAFS_PROF_RAW_DATA] = dent;
}
@@ -2152,6 +2213,10 @@ static struct aa_sfs_entry aa_sfs_entry_signal[] = {
{ }
};
+static struct aa_sfs_entry aa_sfs_entry_attach[] = {
+ AA_SFS_FILE_BOOLEAN("xattr", 1),
+ { }
+};
static struct aa_sfs_entry aa_sfs_entry_domain[] = {
AA_SFS_FILE_BOOLEAN("change_hat", 1),
AA_SFS_FILE_BOOLEAN("change_hatv", 1),
@@ -2159,6 +2224,9 @@ static struct aa_sfs_entry aa_sfs_entry_domain[] = {
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_BOOLEAN("post_nnp_subset", 1),
+ AA_SFS_FILE_BOOLEAN("computed_longest_left", 1),
+ AA_SFS_DIR("attach_conditions", aa_sfs_entry_attach),
AA_SFS_FILE_STRING("version", "1.2"),
{ }
};
@@ -2167,6 +2235,7 @@ 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),
+ AA_SFS_FILE_BOOLEAN("v8", 1),
{ }
};
@@ -2202,6 +2271,7 @@ 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("network_v8", aa_sfs_entry_network),
AA_SFS_DIR("mount", aa_sfs_entry_mount),
AA_SFS_DIR("namespaces", aa_sfs_entry_ns),
AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK),
@@ -2394,29 +2464,18 @@ static const char *policy_get_link(struct dentry *dentry,
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 = snprintf(name, sizeof(name), "%s:[%lu]", AAFS_NAME,
+ d_inode(dentry)->i_ino);
+ if (res > 0 && res < sizeof(name))
res = readlink_copy(buffer, buflen, name);
- aa_put_ns(ns);
+ else
+ res = -ENOENT;
return res;
}
@@ -2460,34 +2519,26 @@ static int __init aa_create_aafs(void)
dent = securityfs_create_file(".load", 0666, aa_sfs_entry.dentry,
NULL, &aa_fs_profile_load);
- if (IS_ERR(dent)) {
- error = PTR_ERR(dent);
- goto error;
- }
+ if (IS_ERR(dent))
+ goto dent_error;
ns_subload(root_ns) = dent;
dent = securityfs_create_file(".replace", 0666, aa_sfs_entry.dentry,
NULL, &aa_fs_profile_replace);
- if (IS_ERR(dent)) {
- error = PTR_ERR(dent);
- goto error;
- }
+ if (IS_ERR(dent))
+ goto dent_error;
ns_subreplace(root_ns) = dent;
dent = securityfs_create_file(".remove", 0666, aa_sfs_entry.dentry,
NULL, &aa_fs_profile_remove);
- if (IS_ERR(dent)) {
- error = PTR_ERR(dent);
- goto error;
- }
+ if (IS_ERR(dent))
+ goto dent_error;
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;
- }
+ if (IS_ERR(dent))
+ goto dent_error;
ns_subrevision(root_ns) = dent;
/* policy tree referenced by magic policy symlink */
@@ -2501,10 +2552,8 @@ static int __init aa_create_aafs(void)
/* 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;
- }
+ if (IS_ERR(dent))
+ goto dent_error;
error = aa_mk_null_file(aa_sfs_entry.dentry);
if (error)
@@ -2516,6 +2565,8 @@ static int __init aa_create_aafs(void)
aa_info_message("AppArmor Filesystem Enabled");
return 0;
+dent_error:
+ error = PTR_ERR(dent);
error:
aa_destroy_aafs();
AA_ERROR("Error creating AppArmor securityfs\n");
diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c
index 67e347192a55..253ef6e9d445 100644
--- a/security/apparmor/capability.c
+++ b/security/apparmor/capability.c
@@ -19,7 +19,7 @@
#include "include/apparmor.h"
#include "include/capability.h"
-#include "include/context.h"
+#include "include/cred.h"
#include "include/policy.h"
#include "include/audit.h"
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 6a54d2ffa840..590b7e8cd21c 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -19,10 +19,11 @@
#include <linux/syscalls.h>
#include <linux/tracehook.h>
#include <linux/personality.h>
+#include <linux/xattr.h>
#include "include/audit.h"
#include "include/apparmorfs.h"
-#include "include/context.h"
+#include "include/cred.h"
#include "include/domain.h"
#include "include/file.h"
#include "include/ipc.h"
@@ -302,7 +303,70 @@ static int change_profile_perms(struct aa_profile *profile,
}
/**
+ * aa_xattrs_match - check whether a file matches the xattrs defined in profile
+ * @bprm: binprm struct for the process to validate
+ * @profile: profile to match against (NOT NULL)
+ * @state: state to start match in
+ *
+ * Returns: number of extended attributes that matched, or < 0 on error
+ */
+static int aa_xattrs_match(const struct linux_binprm *bprm,
+ struct aa_profile *profile, unsigned int state)
+{
+ int i;
+ ssize_t size;
+ struct dentry *d;
+ char *value = NULL;
+ int value_size = 0, ret = profile->xattr_count;
+
+ if (!bprm || !profile->xattr_count)
+ return 0;
+
+ /* transition from exec match to xattr set */
+ state = aa_dfa_null_transition(profile->xmatch, state);
+
+ d = bprm->file->f_path.dentry;
+
+ for (i = 0; i < profile->xattr_count; i++) {
+ size = vfs_getxattr_alloc(d, profile->xattrs[i], &value,
+ value_size, GFP_KERNEL);
+ if (size >= 0) {
+ u32 perm;
+
+ /* Check the xattr value, not just presence */
+ state = aa_dfa_match_len(profile->xmatch, state, value,
+ size);
+ perm = dfa_user_allow(profile->xmatch, state);
+ if (!(perm & MAY_EXEC)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+ /* transition to next element */
+ state = aa_dfa_null_transition(profile->xmatch, state);
+ if (size < 0) {
+ /*
+ * No xattr match, so verify if transition to
+ * next element was valid. IFF so the xattr
+ * was optional.
+ */
+ if (!state) {
+ ret = -EINVAL;
+ goto out;
+ }
+ /* don't count missing optional xattr as matched */
+ ret--;
+ }
+ }
+
+out:
+ kfree(value);
+ return ret;
+}
+
+/**
* __attach_match_ - find an attachment match
+ * @bprm - binprm structure of transitioning task
* @name - to match against (NOT NULL)
* @head - profile list to walk (NOT NULL)
* @info - info message if there was an error (NOT NULL)
@@ -316,40 +380,80 @@ static int change_profile_perms(struct aa_profile *profile,
*
* Returns: profile or NULL if no match found
*/
-static struct aa_profile *__attach_match(const char *name,
+static struct aa_profile *__attach_match(const struct linux_binprm *bprm,
+ const char *name,
struct list_head *head,
const char **info)
{
- int len = 0;
+ int candidate_len = 0, candidate_xattrs = 0;
bool conflict = false;
struct aa_profile *profile, *candidate = NULL;
+ AA_BUG(!name);
+ AA_BUG(!head);
+
list_for_each_entry_rcu(profile, head, base.list) {
if (profile->label.flags & FLAG_NULL &&
&profile->label == ns_unconfined(profile->ns))
continue;
+ /* Find the "best" matching profile. Profiles must
+ * match the path and extended attributes (if any)
+ * associated with the file. A more specific path
+ * match will be preferred over a less specific one,
+ * and a match with more matching extended attributes
+ * will be preferred over one with fewer. If the best
+ * match has both the same level of path specificity
+ * and the same number of matching extended attributes
+ * as another profile, signal a conflict and refuse to
+ * match.
+ */
if (profile->xmatch) {
- if (profile->xmatch_len >= len) {
- unsigned int state;
- u32 perm;
-
- state = aa_dfa_match(profile->xmatch,
- DFA_START, name);
- perm = dfa_user_allow(profile->xmatch, state);
- /* any accepting state means a valid match. */
- if (perm & MAY_EXEC) {
- if (profile->xmatch_len == len) {
+ unsigned int state, count;
+ u32 perm;
+
+ state = aa_dfa_leftmatch(profile->xmatch, DFA_START,
+ name, &count);
+ perm = dfa_user_allow(profile->xmatch, state);
+ /* any accepting state means a valid match. */
+ if (perm & MAY_EXEC) {
+ int ret;
+
+ if (count < candidate_len)
+ continue;
+
+ ret = aa_xattrs_match(bprm, profile, state);
+ /* Fail matching if the xattrs don't match */
+ if (ret < 0)
+ continue;
+
+ /*
+ * TODO: allow for more flexible best match
+ *
+ * The new match isn't more specific
+ * than the current best match
+ */
+ if (count == candidate_len &&
+ ret <= candidate_xattrs) {
+ /* Match is equivalent, so conflict */
+ if (ret == candidate_xattrs)
conflict = true;
- continue;
- }
- candidate = profile;
- len = profile->xmatch_len;
- conflict = false;
+ continue;
}
+
+ /* Either the same length with more matching
+ * xattrs, or a longer match
+ */
+ candidate = profile;
+ candidate_len = profile->xmatch_len;
+ candidate_xattrs = ret;
+ conflict = false;
}
} else if (!strcmp(profile->base.name, name))
- /* exact non-re match, no more searching required */
+ /*
+ * old exact non-re match, without conditionals such
+ * as xattrs. no more searching required
+ */
return profile;
}
@@ -363,6 +467,7 @@ static struct aa_profile *__attach_match(const char *name,
/**
* find_attach - do attachment search for unconfined processes
+ * @bprm - binprm structure of transitioning task
* @ns: the current namespace (NOT NULL)
* @list: list to search (NOT NULL)
* @name: the executable name to match against (NOT NULL)
@@ -370,13 +475,14 @@ static struct aa_profile *__attach_match(const char *name,
*
* Returns: label or NULL if no match found
*/
-static struct aa_label *find_attach(struct aa_ns *ns, struct list_head *list,
+static struct aa_label *find_attach(const struct linux_binprm *bprm,
+ struct aa_ns *ns, struct list_head *list,
const char *name, const char **info)
{
struct aa_profile *profile;
rcu_read_lock();
- profile = aa_get_profile(__attach_match(name, list, info));
+ profile = aa_get_profile(__attach_match(bprm, name, list, info));
rcu_read_unlock();
return profile ? &profile->label : NULL;
@@ -432,6 +538,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
/**
* x_to_label - get target label for a given xindex
* @profile: current profile (NOT NULL)
+ * @bprm: binprm structure of transitioning task
* @name: name to lookup (NOT NULL)
* @xindex: index into x transition table
* @lookupname: returns: name used in lookup if one was specified (NOT NULL)
@@ -441,6 +548,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
* Returns: refcounted label or NULL if not found available
*/
static struct aa_label *x_to_label(struct aa_profile *profile,
+ const struct linux_binprm *bprm,
const char *name, u32 xindex,
const char **lookupname,
const char **info)
@@ -468,11 +576,11 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
case AA_X_NAME:
if (xindex & AA_X_CHILD)
/* released by caller */
- new = find_attach(ns, &profile->base.profiles,
+ new = find_attach(bprm, ns, &profile->base.profiles,
name, info);
else
/* released by caller */
- new = find_attach(ns, &ns->base.profiles,
+ new = find_attach(bprm, ns, &ns->base.profiles,
name, info);
*lookupname = name;
break;
@@ -512,6 +620,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
bool *secure_exec)
{
struct aa_label *new = NULL;
+ struct aa_profile *component;
+ struct label_it i;
const char *info = NULL, *name = NULL, *target = NULL;
unsigned int state = profile->file.start;
struct aa_perms perms = {};
@@ -536,8 +646,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
}
if (profile_unconfined(profile)) {
- new = find_attach(profile->ns, &profile->ns->base.profiles,
- name, &info);
+ new = find_attach(bprm, profile->ns,
+ &profile->ns->base.profiles, name, &info);
if (new) {
AA_DEBUG("unconfined attached to new label");
return new;
@@ -550,7 +660,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
state = aa_str_perms(profile->file.dfa, state, name, cond, &perms);
if (perms.allow & MAY_EXEC) {
/* exec permission determine how to transition */
- new = x_to_label(profile, name, perms.xindex, &target, &info);
+ new = x_to_label(profile, bprm, name, perms.xindex, &target,
+ &info);
if (new && new->proxy == profile->label.proxy && info) {
/* hack ix fallback - improve how this is detected */
goto audit;
@@ -559,6 +670,21 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
info = "profile transition not found";
/* remove MAY_EXEC to audit as failure */
perms.allow &= ~MAY_EXEC;
+ } else {
+ /* verify that each component's xattr requirements are
+ * met, and fail execution otherwise
+ */
+ label_for_each(i, new, component) {
+ if (aa_xattrs_match(bprm, component, state) <
+ 0) {
+ error = -EACCES;
+ info = "required xattrs not present";
+ perms.allow &= ~MAY_EXEC;
+ aa_put_label(new);
+ new = NULL;
+ goto audit;
+ }
+ }
}
} else if (COMPLAIN_MODE(profile)) {
/* no exec permission - learning mode */
@@ -592,22 +718,6 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
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 result