if (error)
*info = "ptrace prevents transition"; return error;
}
/**** TODO: dedup to aa_label_match - needs perm and dfa, merging *specificallythisisanexactcopyofaa_label_matchexcept *aa_compute_permsisreplacedwithaa_compute_fperms *andpolicy->dfawithfile->dfa
****/ /* match a profile and its associated ns component if needed *Assumesvisibilitytesthasalreadybeendone. *Ifasubnsprofileisnottobematchedshouldbeprescreenedwith *visibilitytest.
*/ staticinline aa_state_t match_component(struct aa_profile *profile, struct aa_profile *tp, bool stack, aa_state_t state)
{ struct aa_ruleset *rules = profile->label.rules[0]; constchar *ns_name;
if (stack)
state = aa_dfa_match(rules->file->dfa, state, "&"); if (profile->ns == tp->ns) return aa_dfa_match(rules->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(rules->file->dfa, state, ":", 1);
state = aa_dfa_match(rules->file->dfa, state, ns_name);
state = aa_dfa_match_len(rules->file->dfa, state, ":", 1); return aa_dfa_match(rules->file->dfa, state, tp->base.hname);
}
/* 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; return0;
next:
label_for_each_cont(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue;
state = aa_dfa_match(rules->file->dfa, state, "//&");
state = match_component(profile, tp, false, state); if (!state) goto fail;
}
*perms = *(aa_lookup_condperms(current_fsuid(), rules->file, state,
&cond));
aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES;
return0;
fail:
*perms = nullperms; return -EACCES;
}
/** *label_components_match-findpermsforallsubcomponentsofalabel *@profile:profiletofindpermsfor *@label:labeltocheckaccesspermissionsfor *@stack:whetherthisisastackingrequest *@start:statetostartmatchin *@subns:whethertodopermissionchecksoncomponentsinasubns *@request:permissionstorequest *@perms:aninitializedpermsstructtoaddaccumulationto * *Returns:0onsuccesselseERROR * *ForthelabelA//&B//&C this does the perm match for each of A and B and C *@permsshouldbepreinitializedwithallpermsORapreviouspermission *checktobestacked.
*/ staticint label_components_match(struct aa_profile *profile, struct aa_label *label, bool stack,
aa_state_t start, bool subns, u32 request, struct aa_perms *perms)
{ struct aa_ruleset *rules = profile->label.rules[0]; struct aa_profile *tp; struct label_it i; struct aa_perms tmp; struct path_cond cond = { };
aa_state_t 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 */ return0;
if (profile->label.flags & FLAG_NULL &&
&profile->label == ns_unconfined(profile->ns)) continue;
/* Find the "best" matching profile. Profiles must *matchthepathandextendedattributes(ifany) *associatedwiththefile.Amorespecificpath *matchwillbepreferredoveralessspecificone, *andamatchwithmorematchingextendedattributes *willbepreferredoveronewithfewer.Ifthebest *matchhasboththesamelevelofpathspecificity *andthesamenumberofmatchingextendedattributes *asanotherprofile,signalaconflictandrefuseto *match.
*/ if (attach->xmatch->dfa) { unsignedint count;
aa_state_t state; struct aa_perms *perms;
state = aa_dfa_leftmatch(attach->xmatch->dfa,
attach->xmatch->start[AA_CLASS_XMATCH],
name, &count);
perms = aa_lookup_perms(attach->xmatch, state); /* any accepting state means a valid match. */ if (perms->allow & MAY_EXEC) { int ret = 0;
if (count < candidate_len) continue;
if (bprm && attach->xattr_count) { long rev = READ_ONCE(ns->revision);
if (!aa_get_profile_not0(profile)) goto restart;
rcu_read_unlock();
ret = aa_xattrs_match(bprm, profile,
state);
rcu_read_lock();
aa_put_profile(profile); if (rev !=
READ_ONCE(ns->revision)) /* policy changed */ goto restart; /* *Failmatchingifthexattrsdon't *match
*/ if (ret < 0) continue;
} /* *TODO:allowformoreflexiblebestmatch * *Thenewmatchisn'tmorespecific *thanthecurrentbestmatch
*/ if (count == candidate_len &&
ret <= candidate_xattrs) { /* Match is equivalent, so conflict */ if (ret == candidate_xattrs)
conflict = true; continue;
}
/* Either the same length with more matching *xattrs,oralongermatch
*/
candidate = profile;
candidate_len = max(count, attach->xmatch_len);
candidate_xattrs = ret;
conflict = false;
}
} elseif (!strcmp(profile->base.name, name)) { /* *oldexactnon-rematch,withoutconditionalssuch *asxattrs.nomoresearchingrequired
*/
candidate = profile; goto out;
}
}
if (!candidate || conflict) { if (conflict)
*info = CONFLICTING_ATTACH_STR;
rcu_read_unlock(); return NULL;
}
/* index is guaranteed to be in range, validated at load time */ /* TODO: move lookup parsing to unpack time so this is a straight *indexintotheresultantlabel
*/ for (next = rules->file->trans.table[index]; next;
next = next_name(xtype, next)) { constchar *lookup = (*next == '&') ? next + 1 : next;
*name = next; if (xindex & AA_X_CHILD) { /* 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, lookup, GFP_KERNEL, true, false); if (!IS_ERR_OR_NULL(label)) /* release by caller */ return label;
}
switch (xtype) { case AA_X_NONE: /* fail exec unless ix || ux fallback - handled by caller */
*lookupname = NULL; break; case AA_X_TABLE: /* TODO: fix when perm mapping done at unload */ /* released by caller *ifnullforbothstackanddirectwanttotryfallback
*/ 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) /* released by caller */ new = find_attach(bprm, ns, &profile->base.profiles,
name, info); else /* released by caller */ new = find_attach(bprm, ns, &ns->base.profiles,
name, info);
*lookupname = name; break;
}
/* fallback transition check */ if (!new) { if (xindex & AA_X_INHERIT) { /* (p|c|n)ix - don't change profile but do *usethenewestversion
*/ 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);
} elseif (xindex & AA_X_UNCONFINED) { new = aa_get_newest_label(ns_unconfined(profile->ns)); 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 *couldhavehappened,sonowcheckifinfowassetby *find_attachaswell(i.e.whetherweactuallyoverwrote) *andwarnaccordingly.
*/ 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);
}
}
if (new && stack) { /* base the stack on post domain transition */ struct aa_label *base = new;
new = aa_label_merge(base, stack, GFP_KERNEL); /* null on error */
aa_put_label(base);
}
aa_put_label(stack); /* released by caller */ returnnew;
}
error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
&name, &info, profile->disconnected); if (error) { if (profile_unconfined(profile) ||
(profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error");
error = 0; new = aa_get_newest_label(&profile->label);
}
name = bprm->filename; goto audit;
}
if (profile_unconfined(profile)) { new = find_attach(bprm, profile->ns,
&profile->ns->base.profiles, name, &info); /* info set -> something unusual that we should report *Currentlythisisonlyconflictingattachments,butother *infosaddedinthefutureshouldalsobeloggedbydefault *andonlyexcludedonacase-by-casebasis
*/ if (info) { /* Because perms is never used again after this audit *wedon'tneedtocareaboutclobberingit
*/
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(DEBUG_DOMAIN, "unconfined attached to new label"); returnnew;
}
AA_DEBUG(DEBUG_DOMAIN, "unconfined exec no attachment"); return aa_get_newest_label(&profile->label);
}
/* find exec permissions for name */
state = aa_str_perms(rules->file, state, name, cond, &perms); if (perms.allow & MAY_EXEC) { /* exec permission determine how to transition */ 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 *Becausepermsisneverusedagainafterthisaudit *wedon'tneedtocareaboutclobberingit
*/ 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;
} elseif (!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; if (COMPLAIN_MODE(profile)) { /* create null profile instead of failing */ goto create_learning_profile;
}
error = -EACCES;
}
} elseif (COMPLAIN_MODE(profile)) {
create_learning_profile: /* no exec permission - learning mode */
new_profile = aa_new_learning_profile(profile, false, name,
GFP_KERNEL); if (!new_profile) {
error = -ENOMEM;
info = "could not create null profile";
} else {
error = -EACCES; new = &new_profile->label;
}
perms.xindex |= AA_X_UNSAFE;
} else /* fail exec */
error = -EACCES;
if (!new) goto audit;
if (!(perms.xindex & AA_X_UNSAFE)) { if (DEBUG_ON) {
dbg_printk("apparmor: setting AT_SECURE for %s profile=",
name);
aa_label_printk(new, GFP_KERNEL);
dbg_printk("\n");
}
*secure_exec = true;
}
if (profile_unconfined(profile)) { /* change_profile on exec already granted */ /* *NOTE:Domaintransitionsfromunconfinedareallowed *evenwhenno_new_privsissetbecausethisalwaysresults *inafurtherreductionofpermissions.
*/ return0;
}
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(DEBUG_DOMAIN, "name lookup ix on error");
error = 0;
}
xname = bprm->filename; goto audit;
}
/* find exec permissions for name */
state = aa_str_perms(rules->file, 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. *onexecpermissionislinkedtoexecwithastandardpairing *exec\0change_profile
*/
state = aa_dfa_null_transition(rules->file->dfa, state);
error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC,
state, &perms); if (error) {
perms.allow &= ~AA_MAY_ONEXEC; goto audit;
}
if (!(perms.xindex & AA_X_UNSAFE)) { if (DEBUG_ON) {
dbg_printk("apparmor: setting AT_SECURE for %s label=",
xname);
aa_label_printk(onexec, GFP_KERNEL);
dbg_printk("\n");
}
*secure_exec = true;
}
/* TODO: determine how much we want to loosen this */
error = fn_for_each_in_ns(label, profile,
profile_onexec(subj_cred, profile, onexec, stack,
bprm, buffer, cond, unsafe)); if (error) return ERR_PTR(error);
/* buffer freed below, name is pointer into buffer */
buffer = aa_get_buffer(false); if (!buffer) {
error = -ENOMEM; goto done;
}
/* Test for onexec first as onexec override other x transitions. */ if (ctx->onexec) new = handle_onexec(subj_cred, label, ctx->onexec, ctx->token,
bprm, buffer, &cond, &unsafe); else new = fn_label_build(label, profile, GFP_KERNEL,
profile_transition(subj_cred, profile, bprm,
buffer,
&cond, &unsafe));
/* Policy has specified a domain transitions. If no_new_privs and *confinedensurethetransitionistoconfinementthatissubset *oftheconfinementwhenthetaskenterednonewprivs. * *NOTE:Domaintransitionsfromunconfinedandtostacked *subsetsareallowedevenwhenno_new_privsissetbecausethis *alwaysresultsinafurtherreductionofpermissions.
*/ if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) &&
!unconfined(label) &&
!aa_label_is_unconfined_subset(new, ctx->nnp)) {
error = -EPERM;
info = "no new privs"; goto audit;
}
if (bprm->unsafe & LSM_UNSAFE_SHARE) { /* FIXME: currently don't mediate shared state */
;
}
if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) { /* TODO: test needs to be profile of label to new */
error = may_change_ptraced_domain(bprm->cred, new, &info); if (error) goto audit;
}
if (unsafe) { if (DEBUG_ON) {
dbg_printk("setting AT_SECURE for %s label=",
bprm->filename);
aa_label_printk(new, GFP_KERNEL);
dbg_printk("\n");
}
bprm->secureexec = 1;
}
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_KERNEL);
dbg_printk("\n");
}
bprm->per_clear |= PER_CLEAR_ON_SETID;
}
aa_put_label(cred_label(bprm->cred)); /* transfer reference, released when cred is freed */
set_cred_label(bprm->cred, new);
/* return -EPERM when unconfined doesn't have children to avoid *changingthetraditionalerrorcodeforunconfined.
*/ if (unconfined(label)) { struct label_it i; bool empty = true;
if (empty) {
info = "unconfined can not change_hat";
error = -EPERM; goto fail;
}
}
if (count) { new = change_hat(subj_cred, label, hats, count, flags);
AA_BUG(!new); if (IS_ERR(new)) {
error = PTR_ERR(new); new = NULL; /* already audited */ goto out;
}
/* target cred is the same as current except new label */
error = may_change_ptraced_domain(subj_cred, new, &info); if (error) goto fail;
/* *nonewprivspreventsdomaintransitionsthatwould *reducerestrictions.
*/ 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(DEBUG_DOMAIN, "no_new_privs - change_hat denied");
error = -EPERM; goto out;
}
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;
} elseif (previous && !(flags & AA_CHANGE_TEST)) { /* *nonewprivspreventsdomaintransitionsthatwould *reducerestrictions.
*/ 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(DEBUG_DOMAIN, "no_new_privs - change_hat denied");
error = -EPERM; goto out;
}
/* Return to saved label. Kill task if restore fails *toavoidbruteforceattacks
*/
target = previous;
error = aa_restore_previous_label(token); if (error) { if (error == -EACCES) goto kill; goto fail;
}
} /* else ignore @flags && restores when there is no saved profile */
if (!fqname || !*fqname) {
aa_put_label(label);
AA_DEBUG(DEBUG_DOMAIN, "no profile name"); return -EINVAL;
}
if (flags & AA_CHANGE_ONEXEC) {
request = AA_MAY_ONEXEC; if (stack)
op = OP_STACK_ONEXEC; else
op = OP_CHANGE_ONEXEC;
} else {
request = AA_MAY_CHANGE_PROFILE; if (stack)
op = OP_STACK; else
op = OP_CHANGE_PROFILE;
}
/* This should move to a per profile test. Requires pushing build *intocallback
*/ if (!stack && unconfined(label) &&
label == &labels_ns(label)->unconfined->label &&
aa_unprivileged_unconfined_restricted && /* TODO: refactor so this check is a fn */
cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE,
CAP_OPT_NOAUDIT)) { /* regardless of the request in this case apparmor *stacksagainstunconfinedsoadminsetpolicycan'tbe *by-passed
*/
stack = true;
perms.audit = request;
(void) fn_for_each_in_ns(label, profile,
aa_audit_file(subj_cred, profile, &perms, op,
request, auditname, NULL, target,
GLOBAL_ROOT_UID, stack_msg, 0));
perms.audit = 0;
}
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;
info = "label not found";
error = PTR_ERR(target);
target = NULL; /* *TODO:fixmeusinglabels_profileisnotright-doprofile *percomplainprofile
*/ if ((flags & AA_CHANGE_TEST) ||
!COMPLAIN_MODE(labels_profile(label))) goto audit; /* released below */
tprofile = aa_new_learning_profile(labels_profile(label), false,
fqname, GFP_KERNEL); if (!tprofile) {
info = "failed null profile create";
error = -ENOMEM; goto audit;
}
target = &tprofile->label; goto check;
}
check: /* check if tracing task is allowed to trace target domain */
error = may_change_ptraced_domain(subj_cred, target, &info); if (error && !fn_for_each_in_ns(label, profile,
COMPLAIN_MODE(profile))) goto audit;
/* TODO: add permission check to allow this *if((flags&AA_CHANGE_ONEXEC)&&!current_is_single_threaded()){ *info="notasinglethreadedtask"; *error=-EACCES; *gotoaudit; *}
*/ if (flags & AA_CHANGE_TEST) goto out;
/* stacking is always a subset, so only check the nonstack case */ if (!stack) { new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
aa_get_label(target),
aa_get_label(&profile->label)); /* *nonewprivspreventsdomaintransitionsthatwould *reducerestrictions.
*/ 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(DEBUG_DOMAIN, "no_new_privs - change_hat denied");
error = -EPERM; goto out;
}
}
if (!(flags & AA_CHANGE_ONEXEC)) { /* only transition profiles in the current ns */ if (stack) new = aa_label_merge(label, target, GFP_KERNEL); if (IS_ERR_OR_NULL(new)) {
info = "failed to build target label"; if (!new)
error = -ENOMEM; else
error = PTR_ERR(new); new = NULL;
perms.allow = 0; goto audit;
}
error = aa_replace_current_label(new);
} else { if (new) {
aa_put_label(new); new = NULL;
}
/* full transition will be built in exec path */
aa_set_current_onexec(target, stack);
}
audit:
error = fn_for_each_in_ns(label, profile,
aa_audit_file(subj_cred,
profile, &perms, op, request, auditname,
NULL, new ? new : target,
GLOBAL_ROOT_UID, info, error));
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.