summaryrefslogtreecommitdiff
path: root/security/apparmor/domain.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/apparmor/domain.c')
-rw-r--r--security/apparmor/domain.c203
1 files changed, 137 insertions, 66 deletions
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 5939bd9a9b9b..267da82afb14 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -28,6 +28,12 @@
#include "include/policy.h"
#include "include/policy_ns.h"
+static const char * const CONFLICTING_ATTACH_STR = "conflicting profile attachments";
+static const char * const CONFLICTING_ATTACH_STR_IX =
+ "conflicting profile attachments - ix fallback";
+static const char * const CONFLICTING_ATTACH_STR_UX =
+ "conflicting profile attachments - ux fallback";
+
/**
* may_change_ptraced_domain - check if can change profile on ptraced task
* @to_cred: cred of task changing domain
@@ -87,8 +93,7 @@ static inline aa_state_t match_component(struct aa_profile *profile,
struct aa_profile *tp,
bool stack, aa_state_t state)
{
- struct aa_ruleset *rules = list_first_entry(&profile->rules,
- typeof(*rules), list);
+ struct aa_ruleset *rules = profile->label.rules[0];
const char *ns_name;
if (stack)
@@ -125,8 +130,7 @@ static int label_compound_match(struct aa_profile *profile,
aa_state_t state, bool subns, u32 request,
struct aa_perms *perms)
{
- struct aa_ruleset *rules = list_first_entry(&profile->rules,
- typeof(*rules), list);
+ struct aa_ruleset *rules = profile->label.rules[0];
struct aa_profile *tp;
struct label_it i;
struct path_cond cond = { };
@@ -154,7 +158,8 @@ next:
if (!state)
goto fail;
}
- *perms = *(aa_lookup_fperms(rules->file, state, &cond));
+ *perms = *(aa_lookup_condperms(current_fsuid(), rules->file, state,
+ &cond));
aa_apply_modes_to_perms(profile, perms);
if ((perms->allow & request) != request)
return -EACCES;
@@ -187,8 +192,7 @@ static int label_components_match(struct aa_profile *profile,
aa_state_t start, bool subns, u32 request,
struct aa_perms *perms)
{
- struct aa_ruleset *rules = list_first_entry(&profile->rules,
- typeof(*rules), list);
+ struct aa_ruleset *rules = profile->label.rules[0];
struct aa_profile *tp;
struct label_it i;
struct aa_perms tmp;
@@ -209,7 +213,8 @@ static int label_components_match(struct aa_profile *profile,
return 0;
next:
- tmp = *(aa_lookup_fperms(rules->file, state, &cond));
+ tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state,
+ &cond));
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
label_for_each_cont(i, label, tp) {
@@ -218,7 +223,8 @@ next:
state = match_component(profile, tp, stack, start);
if (!state)
goto fail;
- tmp = *(aa_lookup_fperms(rules->file, state, &cond));
+ tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state,
+ &cond));
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
}
@@ -323,7 +329,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm,
size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i],
&value, value_size, GFP_KERNEL);
if (size >= 0) {
- u32 index, perm;
+ struct aa_perms *perms;
/*
* Check the xattr presence before value. This ensure
@@ -335,9 +341,8 @@ static int aa_xattrs_match(const struct linux_binprm *bprm,
/* Check xattr value */
state = aa_dfa_match_len(attach->xmatch->dfa, state,
value, size);
- index = ACCEPT_TABLE(attach->xmatch->dfa)[state];
- perm = attach->xmatch->perms[index].allow;
- if (!(perm & MAY_EXEC)) {
+ perms = aa_lookup_perms(attach->xmatch, state);
+ if (!(perms->allow & MAY_EXEC)) {
ret = -EINVAL;
goto out;
}
@@ -415,15 +420,14 @@ restart:
if (attach->xmatch->dfa) {
unsigned int count;
aa_state_t state;
- u32 index, perm;
+ struct aa_perms *perms;
state = aa_dfa_leftmatch(attach->xmatch->dfa,
attach->xmatch->start[AA_CLASS_XMATCH],
name, &count);
- index = ACCEPT_TABLE(attach->xmatch->dfa)[state];
- perm = attach->xmatch->perms[index].allow;
+ perms = aa_lookup_perms(attach->xmatch, state);
/* any accepting state means a valid match. */
- if (perm & MAY_EXEC) {
+ if (perms->allow & MAY_EXEC) {
int ret = 0;
if (count < candidate_len)
@@ -484,7 +488,7 @@ restart:
if (!candidate || conflict) {
if (conflict)
- *info = "conflicting profile attachments";
+ *info = CONFLICTING_ATTACH_STR;
rcu_read_unlock();
return NULL;
}
@@ -508,15 +512,16 @@ static const char *next_name(int xtype, const char *name)
* @name: returns: name tested to find label (NOT NULL)
*
* Returns: refcounted label, or NULL on failure (MAYBE NULL)
+ * @name will always be set with the last name tried
*/
struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
const char **name)
{
- struct aa_ruleset *rules = list_first_entry(&profile->rules,
- typeof(*rules), list);
+ struct aa_ruleset *rules = profile->label.rules[0];
struct aa_label *label = NULL;
u32 xtype = xindex & AA_X_TYPE_MASK;
int index = xindex & AA_X_INDEX_MASK;
+ const char *next;
AA_BUG(!name);
@@ -524,25 +529,27 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
/* TODO: move lookup parsing to unpack time so this is a straight
* index into the resultant label
*/
- for (*name = rules->file->trans.table[index]; !label && *name;
- *name = next_name(xtype, *name)) {
+ for (next = rules->file->trans.table[index]; next;
+ next = next_name(xtype, next)) {
+ const char *lookup = (*next == '&') ? next + 1 : next;
+ *name = next;
if (xindex & AA_X_CHILD) {
- struct aa_profile *new_profile;
- /* release by caller */
- new_profile = aa_find_child(profile, *name);
- if (new_profile)
- label = &new_profile->label;
+ /* TODO: switich to parse to get stack of child */
+ struct aa_profile *new = aa_find_child(profile, lookup);
+
+ if (new)
+ /* release by caller */
+ return &new->label;
continue;
}
- label = aa_label_parse(&profile->label, *name, GFP_KERNEL,
+ label = aa_label_parse(&profile->label, lookup, GFP_KERNEL,
true, false);
- if (IS_ERR(label))
- label = NULL;
+ if (!IS_ERR_OR_NULL(label))
+ /* release by caller */
+ return label;
}
- /* released by caller */
-
- return label;
+ return NULL;
}
/**
@@ -564,12 +571,12 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
const char **lookupname,
const char **info)
{
- struct aa_ruleset *rules = list_first_entry(&profile->rules,
- typeof(*rules), list);
struct aa_label *new = NULL;
+ struct aa_label *stack = NULL;
struct aa_ns *ns = profile->ns;
u32 xtype = xindex & AA_X_TYPE_MASK;
- const char *stack = NULL;
+ /* Used for info checks during fallback handling */
+ const char *old_info = NULL;
switch (xtype) {
case AA_X_NONE:
@@ -578,13 +585,14 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
break;
case AA_X_TABLE:
/* TODO: fix when perm mapping done at unload */
- stack = rules->file->trans.table[xindex & AA_X_INDEX_MASK];
- if (*stack != '&') {
- /* released by caller */
- new = x_table_lookup(profile, xindex, lookupname);
- stack = NULL;
+ /* released by caller
+ * if null for both stack and direct want to try fallback
+ */
+ new = x_table_lookup(profile, xindex, lookupname);
+ if (!new || **lookupname != '&')
break;
- }
+ stack = new;
+ new = NULL;
fallthrough; /* to X_NAME */
case AA_X_NAME:
if (xindex & AA_X_CHILD)
@@ -599,17 +607,38 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
break;
}
+ /* fallback transition check */
if (!new) {
if (xindex & AA_X_INHERIT) {
/* (p|c|n)ix - don't change profile but do
* use the newest version
*/
- *info = "ix fallback";
+ if (*info == CONFLICTING_ATTACH_STR) {
+ *info = CONFLICTING_ATTACH_STR_IX;
+ } else {
+ old_info = *info;
+ *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 (*info == CONFLICTING_ATTACH_STR) {
+ *info = CONFLICTING_ATTACH_STR_UX;
+ } else {
+ old_info = *info;
+ *info = "ux fallback";
+ }
+ }
+ /* We set old_info on the code paths above where overwriting
+ * could have happened, so now check if info was set by
+ * find_attach as well (i.e. whether we actually overwrote)
+ * and warn accordingly.
+ */
+ if (old_info && old_info != CONFLICTING_ATTACH_STR) {
+ pr_warn_ratelimited(
+ "AppArmor: find_attach (from profile %s) audit info \"%s\" dropped",
+ profile->base.hname, old_info);
}
}
@@ -617,12 +646,12 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
/* base the stack on post domain transition */
struct aa_label *base = new;
- new = aa_label_parse(base, stack, GFP_KERNEL, true, false);
- if (IS_ERR(new))
- new = NULL;
+ new = aa_label_merge(base, stack, GFP_KERNEL);
+ /* null on error */
aa_put_label(base);
}
+ aa_put_label(stack);
/* released by caller */
return new;
}
@@ -633,8 +662,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,
char *buffer, struct path_cond *cond,
bool *secure_exec)
{
- struct aa_ruleset *rules = list_first_entry(&profile->rules,
- typeof(*rules), list);
+ struct aa_ruleset *rules = profile->label.rules[0];
struct aa_label *new = NULL;
struct aa_profile *new_profile = NULL;
const char *info = NULL, *name = NULL, *target = NULL;
@@ -652,7 +680,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,
if (error) {
if (profile_unconfined(profile) ||
(profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
- AA_DEBUG("name lookup ix on error");
+ AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error");
error = 0;
new = aa_get_newest_label(&profile->label);
}
@@ -663,11 +691,27 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,
if (profile_unconfined(profile)) {
new = find_attach(bprm, profile->ns,
&profile->ns->base.profiles, name, &info);
+ /* info set -> something unusual that we should report
+ * Currently this is only conflicting attachments, but other
+ * infos added in the future should also be logged by default
+ * and only excluded on a case-by-case basis
+ */
+ if (info) {
+ /* Because perms is never used again after this audit
+ * we don't need to care about clobbering it
+ */
+ perms.audit |= MAY_EXEC;
+ perms.allow |= MAY_EXEC;
+ /* Don't cause error if auditing fails */
+ (void) aa_audit_file(subj_cred, profile, &perms,
+ OP_EXEC, MAY_EXEC, name, target, new, cond->uid,
+ info, error);
+ }
if (new) {
- AA_DEBUG("unconfined attached to new label");
+ AA_DEBUG(DEBUG_DOMAIN, "unconfined attached to new label");
return new;
}
- AA_DEBUG("unconfined exec no attachment");
+ AA_DEBUG(DEBUG_DOMAIN, "unconfined exec no attachment");
return aa_get_newest_label(&profile->label);
}
@@ -678,9 +722,21 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,
new = x_to_label(profile, bprm, name, perms.xindex, &target,
&info);
if (new && new->proxy == profile->label.proxy && info) {
+ /* Force audit on conflicting attachment fallback
+ * Because perms is never used again after this audit
+ * we don't need to care about clobbering it
+ */
+ if (info == CONFLICTING_ATTACH_STR_IX
+ || info == CONFLICTING_ATTACH_STR_UX)
+ perms.audit |= MAY_EXEC;
/* hack ix fallback - improve how this is detected */
goto audit;
} else if (!new) {
+ if (info) {
+ pr_warn_ratelimited(
+ "AppArmor: %s (from profile %s) audit info \"%s\" dropped on missing transition",
+ __func__, profile->base.hname, info);
+ }
info = "profile transition not found";
/* remove MAY_EXEC to audit as failure or complaint */
perms.allow &= ~MAY_EXEC;
@@ -739,8 +795,7 @@ static int profile_onexec(const struct cred *subj_cred,
char *buffer, struct path_cond *cond,
bool *secure_exec)
{
- struct aa_ruleset *rules = list_first_entry(&profile->rules,
- typeof(*rules), list);
+ struct aa_ruleset *rules = profile->label.rules[0];
aa_state_t state = rules->file->start[AA_CLASS_FILE];
struct aa_perms perms = {};
const char *xname = NULL, *info = "change_profile onexec";
@@ -755,7 +810,7 @@ static int profile_onexec(const struct cred *subj_cred,
/* change_profile on exec already granted */
/*
* NOTE: Domain transitions from unconfined are allowed
- * even when no_new_privs is set because this aways results
+ * even when no_new_privs is set because this always results
* in a further reduction of permissions.
*/
return 0;
@@ -766,7 +821,7 @@ static int profile_onexec(const struct cred *subj_cred,
if (error) {
if (profile_unconfined(profile) ||
(profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
- AA_DEBUG("name lookup ix on error");
+ AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error");
error = 0;
}
xname = bprm->filename;
@@ -926,7 +981,7 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
*
* NOTE: Domain transitions from unconfined and to stacked
* subsets are allowed even when no_new_privs is set because this
- * aways results in a further reduction of permissions.
+ * always results in a further reduction of permissions.
*/
if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) &&
!unconfined(label) &&
@@ -1188,10 +1243,24 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags)
if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp)
ctx->nnp = aa_get_label(label);
+ /* return -EPERM when unconfined doesn't have children to avoid
+ * changing the traditional error code for unconfined.
+ */
if (unconfined(label)) {
- info = "unconfined can not change_hat";
- error = -EPERM;
- goto fail;
+ struct label_it i;
+ bool empty = true;
+
+ rcu_read_lock();
+ label_for_each_in_ns(i, labels_ns(label), label, profile) {
+ empty &= list_empty(&profile->base.profiles);
+ }
+ rcu_read_unlock();
+
+ if (empty) {
+ info = "unconfined can not change_hat";
+ error = -EPERM;
+ goto fail;
+ }
}
if (count) {
@@ -1216,7 +1285,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags)
if (task_no_new_privs(current) && !unconfined(label) &&
!aa_label_is_unconfined_subset(new, ctx->nnp)) {
/* not an apparmor denial per se, so don't log it */
- AA_DEBUG("no_new_privs - change_hat denied");
+ AA_DEBUG(DEBUG_DOMAIN,
+ "no_new_privs - change_hat denied");
error = -EPERM;
goto out;
}
@@ -1237,7 +1307,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags)
if (task_no_new_privs(current) && !unconfined(label) &&
!aa_label_is_unconfined_subset(previous, ctx->nnp)) {
/* not an apparmor denial per se, so don't log it */
- AA_DEBUG("no_new_privs - change_hat denied");
+ AA_DEBUG(DEBUG_DOMAIN,
+ "no_new_privs - change_hat denied");
error = -EPERM;
goto out;
}
@@ -1282,8 +1353,7 @@ static int change_profile_perms_wrapper(const char *op, const char *name,
struct aa_label *target, bool stack,
u32 request, struct aa_perms *perms)
{
- struct aa_ruleset *rules = list_first_entry(&profile->rules,
- typeof(*rules), list);
+ struct aa_ruleset *rules = profile->label.rules[0];
const char *info = NULL;
int error = 0;
@@ -1343,7 +1413,7 @@ int aa_change_profile(const char *fqname, int flags)
if (!fqname || !*fqname) {
aa_put_label(label);
- AA_DEBUG("no profile name");
+ AA_DEBUG(DEBUG_DOMAIN, "no profile name");
return -EINVAL;
}
@@ -1462,7 +1532,8 @@ check:
if (task_no_new_privs(current) && !unconfined(label) &&
!aa_label_is_unconfined_subset(new, ctx->nnp)) {
/* not an apparmor denial per se, so don't log it */
- AA_DEBUG("no_new_privs - change_hat denied");
+ AA_DEBUG(DEBUG_DOMAIN,
+ "no_new_privs - change_hat denied");
error = -EPERM;
goto out;
}