|
@@ -19,6 +19,7 @@
|
|
#include <linux/syscalls.h>
|
|
#include <linux/syscalls.h>
|
|
#include <linux/tracehook.h>
|
|
#include <linux/tracehook.h>
|
|
#include <linux/personality.h>
|
|
#include <linux/personality.h>
|
|
|
|
+#include <linux/xattr.h>
|
|
|
|
|
|
#include "include/audit.h"
|
|
#include "include/audit.h"
|
|
#include "include/apparmorfs.h"
|
|
#include "include/apparmorfs.h"
|
|
@@ -301,8 +302,57 @@ static int change_profile_perms(struct aa_profile *profile,
|
|
return label_match(profile, target, stack, start, true, request, perms);
|
|
return label_match(profile, target, stack, start, true, request, perms);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/**
|
|
|
|
+ * 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)
|
|
|
|
+ *
|
|
|
|
+ * 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)
|
|
|
|
+{
|
|
|
|
+ int i;
|
|
|
|
+ size_t size;
|
|
|
|
+ struct dentry *d;
|
|
|
|
+ char *value = NULL;
|
|
|
|
+ int value_size = 0, ret = profile->xattr_count;
|
|
|
|
+
|
|
|
|
+ if (!bprm || !profile->xattr_count)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ 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) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Check the xattr value, not just presence */
|
|
|
|
+ if (profile->xattr_lens[i]) {
|
|
|
|
+ if (profile->xattr_lens[i] != size) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (memcmp(value, profile->xattr_values[i], size)) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ kfree(value);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* __attach_match_ - find an attachment match
|
|
* __attach_match_ - find an attachment match
|
|
|
|
+ * @bprm - binprm structure of transitioning task
|
|
* @name - to match against (NOT NULL)
|
|
* @name - to match against (NOT NULL)
|
|
* @head - profile list to walk (NOT NULL)
|
|
* @head - profile list to walk (NOT NULL)
|
|
* @info - info message if there was an error (NOT NULL)
|
|
* @info - info message if there was an error (NOT NULL)
|
|
@@ -316,11 +366,12 @@ static int change_profile_perms(struct aa_profile *profile,
|
|
*
|
|
*
|
|
* Returns: profile or NULL if no match found
|
|
* 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,
|
|
struct list_head *head,
|
|
const char **info)
|
|
const char **info)
|
|
{
|
|
{
|
|
- int len = 0;
|
|
|
|
|
|
+ int len = 0, xattrs = 0;
|
|
bool conflict = false;
|
|
bool conflict = false;
|
|
struct aa_profile *profile, *candidate = NULL;
|
|
struct aa_profile *profile, *candidate = NULL;
|
|
|
|
|
|
@@ -329,26 +380,56 @@ static struct aa_profile *__attach_match(const char *name,
|
|
&profile->label == ns_unconfined(profile->ns))
|
|
&profile->label == ns_unconfined(profile->ns))
|
|
continue;
|
|
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) {
|
|
- 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;
|
|
|
|
+ u32 perm;
|
|
|
|
+
|
|
|
|
+ if (profile->xmatch_len < len)
|
|
|
|
+ continue;
|
|
|
|
+
|
|
|
|
+ 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) {
|
|
|
|
+ int ret = aa_xattrs_match(bprm, profile);
|
|
|
|
+
|
|
|
|
+ /* Fail matching if the xattrs don't match */
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ continue;
|
|
|
|
+
|
|
|
|
+ /* The new match isn't more specific
|
|
|
|
+ * than the current best match
|
|
|
|
+ */
|
|
|
|
+ if (profile->xmatch_len == len &&
|
|
|
|
+ ret <= xattrs) {
|
|
|
|
+ /* Match is equivalent, so conflict */
|
|
|
|
+ if (ret == xattrs)
|
|
conflict = true;
|
|
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;
|
|
|
|
+ len = profile->xmatch_len;
|
|
|
|
+ xattrs = ret;
|
|
|
|
+ conflict = false;
|
|
}
|
|
}
|
|
- } else if (!strcmp(profile->base.name, name))
|
|
|
|
|
|
+ } else if (!strcmp(profile->base.name, name) &&
|
|
|
|
+ aa_xattrs_match(bprm, profile) >= 0)
|
|
/* exact non-re match, no more searching required */
|
|
/* exact non-re match, no more searching required */
|
|
return profile;
|
|
return profile;
|
|
}
|
|
}
|
|
@@ -363,6 +444,7 @@ static struct aa_profile *__attach_match(const char *name,
|
|
|
|
|
|
/**
|
|
/**
|
|
* find_attach - do attachment search for unconfined processes
|
|
* find_attach - do attachment search for unconfined processes
|
|
|
|
+ * @bprm - binprm structure of transitioning task
|
|
* @ns: the current namespace (NOT NULL)
|
|
* @ns: the current namespace (NOT NULL)
|
|
* @list: list to search (NOT NULL)
|
|
* @list: list to search (NOT NULL)
|
|
* @name: the executable name to match against (NOT NULL)
|
|
* @name: the executable name to match against (NOT NULL)
|
|
@@ -370,13 +452,14 @@ static struct aa_profile *__attach_match(const char *name,
|
|
*
|
|
*
|
|
* Returns: label or NULL if no match found
|
|
* 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)
|
|
const char *name, const char **info)
|
|
{
|
|
{
|
|
struct aa_profile *profile;
|
|
struct aa_profile *profile;
|
|
|
|
|
|
rcu_read_lock();
|
|
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();
|
|
rcu_read_unlock();
|
|
|
|
|
|
return profile ? &profile->label : NULL;
|
|
return profile ? &profile->label : NULL;
|
|
@@ -432,6 +515,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
|
|
/**
|
|
/**
|
|
* x_to_label - get target label for a given xindex
|
|
* x_to_label - get target label for a given xindex
|
|
* @profile: current profile (NOT NULL)
|
|
* @profile: current profile (NOT NULL)
|
|
|
|
+ * @bprm: binprm structure of transitioning task
|
|
* @name: name to lookup (NOT NULL)
|
|
* @name: name to lookup (NOT NULL)
|
|
* @xindex: index into x transition table
|
|
* @xindex: index into x transition table
|
|
* @lookupname: returns: name used in lookup if one was specified (NOT NULL)
|
|
* @lookupname: returns: name used in lookup if one was specified (NOT NULL)
|
|
@@ -441,6 +525,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
|
|
* Returns: refcounted label or NULL if not found available
|
|
* Returns: refcounted label or NULL if not found available
|
|
*/
|
|
*/
|
|
static struct aa_label *x_to_label(struct aa_profile *profile,
|
|
static struct aa_label *x_to_label(struct aa_profile *profile,
|
|
|
|
+ const struct linux_binprm *bprm,
|
|
const char *name, u32 xindex,
|
|
const char *name, u32 xindex,
|
|
const char **lookupname,
|
|
const char **lookupname,
|
|
const char **info)
|
|
const char **info)
|
|
@@ -468,11 +553,11 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
|
|
case AA_X_NAME:
|
|
case AA_X_NAME:
|
|
if (xindex & AA_X_CHILD)
|
|
if (xindex & AA_X_CHILD)
|
|
/* released by caller */
|
|
/* released by caller */
|
|
- new = find_attach(ns, &profile->base.profiles,
|
|
|
|
|
|
+ new = find_attach(bprm, ns, &profile->base.profiles,
|
|
name, info);
|
|
name, info);
|
|
else
|
|
else
|
|
/* released by caller */
|
|
/* released by caller */
|
|
- new = find_attach(ns, &ns->base.profiles,
|
|
|
|
|
|
+ new = find_attach(bprm, ns, &ns->base.profiles,
|
|
name, info);
|
|
name, info);
|
|
*lookupname = name;
|
|
*lookupname = name;
|
|
break;
|
|
break;
|
|
@@ -512,6 +597,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
|
|
bool *secure_exec)
|
|
bool *secure_exec)
|
|
{
|
|
{
|
|
struct aa_label *new = NULL;
|
|
struct aa_label *new = NULL;
|
|
|
|
+ struct aa_profile *component;
|
|
|
|
+ struct label_it i;
|
|
const char *info = NULL, *name = NULL, *target = NULL;
|
|
const char *info = NULL, *name = NULL, *target = NULL;
|
|
unsigned int state = profile->file.start;
|
|
unsigned int state = profile->file.start;
|
|
struct aa_perms perms = {};
|
|
struct aa_perms perms = {};
|
|
@@ -536,8 +623,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
|
|
}
|
|
}
|
|
|
|
|
|
if (profile_unconfined(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) {
|
|
if (new) {
|
|
AA_DEBUG("unconfined attached to new label");
|
|
AA_DEBUG("unconfined attached to new label");
|
|
return new;
|
|
return new;
|
|
@@ -550,7 +637,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
|
|
state = aa_str_perms(profile->file.dfa, state, name, cond, &perms);
|
|
state = aa_str_perms(profile->file.dfa, state, name, cond, &perms);
|
|
if (perms.allow & MAY_EXEC) {
|
|
if (perms.allow & MAY_EXEC) {
|
|
/* exec permission determine how to transition */
|
|
/* 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) {
|
|
if (new && new->proxy == profile->label.proxy && info) {
|
|
/* hack ix fallback - improve how this is detected */
|
|
/* hack ix fallback - improve how this is detected */
|
|
goto audit;
|
|
goto audit;
|
|
@@ -559,6 +647,20 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
|
|
info = "profile transition not found";
|
|
info = "profile transition not found";
|
|
/* remove MAY_EXEC to audit as failure */
|
|
/* remove MAY_EXEC to audit as failure */
|
|
perms.allow &= ~MAY_EXEC;
|
|
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) < 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)) {
|
|
} else if (COMPLAIN_MODE(profile)) {
|
|
/* no exec permission - learning mode */
|
|
/* no exec permission - learning mode */
|