// SPDX-License-Identifier: GPL-2.0-only /* *AppArmorsecuritymodule * *ThisfilecontainsAppArmorpolicymanipulationfunctions * *Copyright(C)1998-2008Novell/SUSE *Copyright2009-2010CanonicalLtd. * *AppArmorpolicyisbasedaroundprofiles,whichcontaintherulesa *taskisconfinedby.Everytaskinthesystemhasaprofileattached *toitdeterminedeitherbymatching"unconfined"tasksagainstthe *visiblesetofprofilesorbyfollowingaprofilesattachmentrules. * *Eachprofileexistsinaprofilenamespacewhichisacontainerof *visibleprofiles.Eachnamespacecontainsaspecial"unconfined"profile, *whichdoesn'tenforceanyconfinementonataskbeyondDAC. * *Namespaceandprofilenamescanbewrittentogetherineither *oftwosyntaxes. *:namespace:profile-usedbykernelinterfacesforeasydetection *namespace://profile - used by policy * *Profilenamescannotstartwith:or@or^andmaynotcontain\0 * *Reservedprofilenames *unconfined-specialautomaticallygeneratedunconfinedprofile *inherit-specialnametoindicateprofileinheritance *null-XXXX-YYYY-specialautomaticallygeneratedlearningprofiles * *Namespacenamesmaynotstartwith/or@andmaynotcontain\0or: *Reservednamespacenames *user-XXXX-userdefinedprofiles * *a// in a profile or namespace name indicates a hierarchical name with the *namebeforethe// being the parent and the name after the child. * *Profileandnamespacehierarchiesservetwodifferentbutsimilarpurposes. *Thenamespacecontainsthesetofvisibleprofilesthatareconsidered *forattachment.Thehierarchyofnamespacesallowsforvirtualizing *thenamespacesothatforexampleachrootcanhaveitsownsetofprofiles *whichmaydefinesomelocalusernamespaces. *Theprofilehierarchyseverstwodistinctpurposes, *-itallowsforsubprofilesorhats,whichallowsanapplicationtorun *subprogramsunderitsownprofilewithdifferentrestrictionthanit *self,andnothaveitusethesystemprofile. *eg.ifamailprogramstartsaneditor,thepolicymightmakethe *restrictionstighterontheeditortighterthanthemailprogram, *anddefinitelydifferentthangeneraleditorrestrictions *-itallowsforbinaryhierarchyofprofiles,sothatexecutionhistory *ispreserved.Thisfeatureisn'texploitedbyAppArmorreferencepolicy *butisallowed.NOTE:thisiscurrentlysuboptimalbecauseprofile *aliasingisnotcurrentlyimplementedsothataprofileforeach *levelmustbedefined. *eg./bin/bash///bin/ls as a name would indicate /bin/ls was started *from/bin/bash * *Aprofileornamespacenamethatcancontainoneormore// separators *isreferredtoasanhname(hierarchical). *eg./bin/bash//bin/ls * *Anfqnameisanamethatmaycontainbothnamespaceandprofilehnames. *eg.:ns:/bin/bash//bin/ls * *NOTES: *-lockingofprofilelistsiscurrentlyfairlycoarse.Allprofile *listswithinanamespaceusethenamespacelock. *FIXME:moveprofileliststousingrcu_lists
*/
/* release any children lists first */
__aa_profile_list_release(&profile->base.profiles); /* released by free_profile */
aa_label_remove(&profile->label);
__aafs_profile_rmdir(profile);
__list_remove_profile(profile);
}
/* freed by free_profile - usually through aa_put_profile *thisaddsspaceforasinglerulesetintherulessectionofthe *label
*/
profile = kzalloc(struct_size(profile, label.rules, 1), gfp); if (!profile) return NULL;
if (!aa_policy_init(&profile->base, NULL, hname, gfp)) goto fail; if (!aa_label_init(&profile->label, 1, gfp)) goto fail;
/* allocate the first ruleset, but leave it empty */
profile->label.rules[0] = aa_alloc_ruleset(gfp); if (!profile->label.rules[0]) goto fail;
profile->n_rules = 1;
/* 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;
rcu_read_lock(); do {
profile = __lookupn_profile(&ns->base, hname, n);
} while (profile && !aa_get_profile_not0(profile));
rcu_read_unlock();
/* the unconfined profile is not in the regular profile list */ if (!profile && strncmp(hname, "unconfined", n) == 0)
profile = aa_get_newest_profile(ns->unconfined);
/* refcount released by caller */ return profile;
}
profile = aa_alloc_profile(name, NULL, gfp); if (!profile) return NULL;
/* TODO: ideally we should inherit abi from parent */
profile->label.flags |= FLAG_NULL;
profile->attach.xmatch = aa_get_pdb(nullpdb);
rules = profile->label.rules[0];
rules->file = aa_get_pdb(nullpdb);
rules->policy = aa_get_pdb(nullpdb);
aa_compute_profile_mediates(profile);
if (parent) {
profile->path_flags = parent->path_flags; /* override/inherit what is mediated from parent */
profile->label.mediates = parent->label.mediates; /* released on free_profile */
rcu_assign_pointer(profile->parent, aa_get_profile(parent));
profile->ns = aa_get_ns(parent->ns);
}
if (base) {
name = kmalloc(strlen(parent->base.hname) + 8 + strlen(base),
gfp); if (name) {
sprintf(name, "%s//null-%s", parent->base.hname, base); goto name;
} /* fall through to try shorter uniq */
}
name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, gfp); if (!name) return NULL;
sprintf(name, "%s//null-%x", parent->base.hname,
atomic_inc_return(&parent->ns->uniq_null));
name: /* lookup to see if this is a dup creation */
bname = basename(name);
profile = aa_find_child(parent, bname); if (profile) goto out;
profile = aa_alloc_null(parent, name, gfp); if (!profile) goto fail;
profile->mode = APPARMOR_COMPLAIN; if (hat)
profile->label.flags |= FLAG_HAT;
mutex_lock_nested(&profile->ns->lock, profile->ns->level);
p = __find_child(&parent->base.profiles, bname); if (p) {
aa_free_profile(profile);
profile = aa_get_profile(p);
} else {
__add_profile(&parent->base.profiles, profile);
}
mutex_unlock(&profile->ns->lock);
/* refcount released by caller */
out:
kfree(name);
/* don't call out to other LSMs in the stack for apparmor policy admin *permissions
*/ staticint policy_ns_capable(conststruct cred *subj_cred, struct aa_label *label, struct user_namespace *userns, int cap)
{ int err;
/* check for MAC_ADMIN cap in cred */
err = cap_capable(subj_cred, userns, cap, CAP_OPT_NONE); if (!err)
err = aa_capable(subj_cred, label, cap, CAP_OPT_NONE);
list_del_init(&child->base.list);
p = __find_child(&new->base.profiles, child->base.name); if (p) { /* @p replaces @child */
__replace_profile(child, p); continue;
}
/* inherit @child and its children */ /* TODO: update hname of inherited children */ /* list refcount transferred to @new */
p = aa_deref_parent(child);
rcu_assign_pointer(child->parent, aa_get_profile(new));
list_add_rcu(&child->base.list, &new->base.profiles);
aa_put_profile(p);
}
}
if (!rcu_access_pointer(new->parent)) { struct aa_profile *parent = aa_deref_parent(old);
rcu_assign_pointer(new->parent, aa_get_profile(parent));
}
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 */
list_replace_rcu(&old->base.list, &new->base.list);
aa_get_profile(new);
aa_put_profile(old);
} else
__list_remove_profile(old);
}
/** *__lookup_replace-lookupreplacementinformationforaprofile *@ns:namespacethelookupoccursin *@hname:nameofprofiletolookup *@noreplace:trueifnotreplacinganexistingprofile *@p:Returns-profiletobereplaced *@info:Returns-infostringonwhylookupfailed * *Returns:profiletoreplace(noref)onsuccesselseptrerror
*/ staticint __lookup_replace(struct aa_ns *ns, constchar *hname, bool noreplace, struct aa_profile **p, constchar **info)
{
*p = aa_get_profile(__lookup_profile(&ns->base, hname)); if (*p) { int error = replacement_allowed(*p, noreplace, info); if (error) {
*info = "profile can not be replaced"; return error;
}
}
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) goto out;
/* ensure that profiles are all for the same ns *TODO:updatelockingtoremovethisconstraint.Allprofilesin *theloadsetmustsucceedasasetortheloadwill *fail.Sortentlistandtakenslocksinhierarchyorder
*/
count = 0;
list_for_each_entry(ent, &lh, list) { if (ns_name) { if (ent->ns_name &&
strcmp(ent->ns_name, ns_name) != 0) {
info = "policy load has mixed namespaces";
error = -EACCES; goto fail;
}
} elseif (ent->ns_name) { if (count) {
info = "policy load has mixed namespaces";
error = -EACCES; goto fail;
}
ns_name = ent->ns_name;
} else
count++;
} if (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";
error = PTR_ERR(ns);
ns = NULL;
ent = NULL; goto fail;
}
} else
ns = aa_get_ns(policy_ns ? policy_ns : labels_ns(label));
mutex_lock_nested(&ns->lock, ns->level); /* check for duplicate rawdata blobs: space and file dedup */ if (!list_empty(&ns->rawdata_list)) {
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; struct aa_profile *p;
if (aa_g_export_binary)
ent->new->rawdata = aa_get_loaddata(udata);
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,
!(mask & AA_MAY_REPLACE_POLICY),
&ent->rename, &info); if (error) goto fail_lock;
}
/* released when @new is freed */
ent->new->ns = aa_get_ns(ns);
if (ent->old || ent->rename) continue;
/* no ref on policy only use inside lock */
p = NULL;
policy = __lookup_parent(ns, ent->new->base.hname); if (!policy) { /* first check for parent in the load set */
p = __list_lookup_parent(&lh, ent->new); if (!p) { /* *fillinmissingparentwithnull *profilethatdoesn'thave *permissions.Thisallowsfor *individualprofileloadingwhere *thechildisloadedbeforethe *parent,andoutsideofthecurrent *atomicset.Thisunfortunatelycan *happenwithsomeuserspaces.The *nullprofilewillbereplacedonce *theparentisloaded.
*/
policy = __create_missing_ancestors(ns,
ent->new->base.hname,
GFP_KERNEL); if (!policy) {
error = -ENOENT;
info = "parent does not exist"; goto fail_lock;
}
}
} if (!p && policy != &ns->base) /* released on profile replacement or free_profile */
p = (struct aa_profile *) policy;
rcu_assign_pointer(ent->new->parent, aa_get_profile(p));
}
/* create new fs entries for introspection if needed */ if (!udata->dents[AAFS_LOADDATA_DIR] && aa_g_export_binary) {
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) { struct dentry *parent; if (rcu_access_pointer(ent->new->parent)) { struct aa_profile *p;
p = aa_deref_parent(ent->new);
parent = prof_child_dir(p);
} else
parent = ns_subprofs_dir(ent->new->ns);
error = __aafs_profile_mkdir(ent->new, parent);
}
if (error) {
info = "failed to create"; goto fail_lock;
}
}
/* Done with checks that may fail - do actual replacement */
__aa_bump_ns_revision(ns); if (aa_g_export_binary)
__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;
if (ent->old && ent->old->rawdata == ent->new->rawdata &&
ent->new->rawdata) { /* dedup actual profile replacement */
audit_policy(label, op, ns_name, ent->new->base.hname, "same as current profile, skipping",
error); /* break refcount cycle with proxy. */
aa_put_proxy(ent->new->label.proxy);
ent->new->label.proxy = NULL; goto skip;
}
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.