1b886d83cSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2898127c3SJohn Johansen /*
3898127c3SJohn Johansen * AppArmor security module
4898127c3SJohn Johansen *
5898127c3SJohn Johansen * This file contains AppArmor policy attachment and domain transitions
6898127c3SJohn Johansen *
7898127c3SJohn Johansen * Copyright (C) 2002-2008 Novell/SUSE
8898127c3SJohn Johansen * Copyright 2009-2010 Canonical Ltd.
9898127c3SJohn Johansen */
10898127c3SJohn Johansen
11898127c3SJohn Johansen #include <linux/errno.h>
12898127c3SJohn Johansen #include <linux/fdtable.h>
133cee6079SChristian Brauner #include <linux/fs.h>
14898127c3SJohn Johansen #include <linux/file.h>
15898127c3SJohn Johansen #include <linux/mount.h>
16898127c3SJohn Johansen #include <linux/syscalls.h>
17898127c3SJohn Johansen #include <linux/personality.h>
188e51f908SMatthew Garrett #include <linux/xattr.h>
193cee6079SChristian Brauner #include <linux/user_namespace.h>
20898127c3SJohn Johansen
21898127c3SJohn Johansen #include "include/audit.h"
22898127c3SJohn Johansen #include "include/apparmorfs.h"
23d8889d49SJohn Johansen #include "include/cred.h"
24898127c3SJohn Johansen #include "include/domain.h"
25898127c3SJohn Johansen #include "include/file.h"
26898127c3SJohn Johansen #include "include/ipc.h"
27898127c3SJohn Johansen #include "include/match.h"
28898127c3SJohn Johansen #include "include/path.h"
29898127c3SJohn Johansen #include "include/policy.h"
30cff281f6SJohn Johansen #include "include/policy_ns.h"
31898127c3SJohn Johansen
32898127c3SJohn Johansen /**
33898127c3SJohn Johansen * may_change_ptraced_domain - check if can change profile on ptraced task
34*690f33e1SJohn Johansen * @cred: cred of task changing domain
35b2d09ae4SJohn Johansen * @to_label: profile to change to (NOT NULL)
36b2d09ae4SJohn Johansen * @info: message if there is an error
37898127c3SJohn Johansen *
3851775fe7SOleg Nesterov * Check if current is ptraced and if so if the tracing task is allowed
39898127c3SJohn Johansen * to trace the new domain
40898127c3SJohn Johansen *
41898127c3SJohn Johansen * Returns: %0 or error if change not allowed
42898127c3SJohn Johansen */
may_change_ptraced_domain(const struct cred * to_cred,struct aa_label * to_label,const char ** info)43*690f33e1SJohn Johansen static int may_change_ptraced_domain(const struct cred *to_cred,
44*690f33e1SJohn Johansen struct aa_label *to_label,
45b2d09ae4SJohn Johansen const char **info)
46898127c3SJohn Johansen {
47898127c3SJohn Johansen struct task_struct *tracer;
48637f688dSJohn Johansen struct aa_label *tracerl = NULL;
49*690f33e1SJohn Johansen const struct cred *tracer_cred = NULL;
50*690f33e1SJohn Johansen
51898127c3SJohn Johansen int error = 0;
52898127c3SJohn Johansen
53898127c3SJohn Johansen rcu_read_lock();
5451775fe7SOleg Nesterov tracer = ptrace_parent(current);
55*690f33e1SJohn Johansen if (tracer) {
56898127c3SJohn Johansen /* released below */
57637f688dSJohn Johansen tracerl = aa_get_task_label(tracer);
58*690f33e1SJohn Johansen tracer_cred = get_task_cred(tracer);
59*690f33e1SJohn Johansen }
60898127c3SJohn Johansen /* not ptraced */
61637f688dSJohn Johansen if (!tracer || unconfined(tracerl))
62898127c3SJohn Johansen goto out;
63898127c3SJohn Johansen
64*690f33e1SJohn Johansen error = aa_may_ptrace(tracer_cred, tracerl, to_cred, to_label,
65*690f33e1SJohn Johansen PTRACE_MODE_ATTACH);
66898127c3SJohn Johansen
67898127c3SJohn Johansen out:
6804fdc099SJohn Johansen rcu_read_unlock();
69637f688dSJohn Johansen aa_put_label(tracerl);
70*690f33e1SJohn Johansen put_cred(tracer_cred);
71898127c3SJohn Johansen
72b2d09ae4SJohn Johansen if (error)
73b2d09ae4SJohn Johansen *info = "ptrace prevents transition";
74898127c3SJohn Johansen return error;
75898127c3SJohn Johansen }
76898127c3SJohn Johansen
7793c98a48SJohn Johansen /**** TODO: dedup to aa_label_match - needs perm and dfa, merging
7893c98a48SJohn Johansen * specifically this is an exact copy of aa_label_match except
7993c98a48SJohn Johansen * aa_compute_perms is replaced with aa_compute_fperms
8093c98a48SJohn Johansen * and policy.dfa with file.dfa
8193c98a48SJohn Johansen ****/
8293c98a48SJohn Johansen /* match a profile and its associated ns component if needed
8393c98a48SJohn Johansen * Assumes visibility test has already been done.
8493c98a48SJohn Johansen * If a subns profile is not to be matched should be prescreened with
8593c98a48SJohn Johansen * visibility test.
8693c98a48SJohn Johansen */
match_component(struct aa_profile * profile,struct aa_profile * tp,bool stack,aa_state_t state)8733fc95d8SJohn Johansen static inline aa_state_t match_component(struct aa_profile *profile,
8893c98a48SJohn Johansen struct aa_profile *tp,
8933fc95d8SJohn Johansen bool stack, aa_state_t state)
9093c98a48SJohn Johansen {
911ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
921ad22fccSJohn Johansen typeof(*rules), list);
9393c98a48SJohn Johansen const char *ns_name;
9493c98a48SJohn Johansen
9593c98a48SJohn Johansen if (stack)
96217af7e2SJohn Johansen state = aa_dfa_match(rules->file.dfa, state, "&");
9793c98a48SJohn Johansen if (profile->ns == tp->ns)
98217af7e2SJohn Johansen return aa_dfa_match(rules->file.dfa, state, tp->base.hname);
9993c98a48SJohn Johansen
10093c98a48SJohn Johansen /* try matching with namespace name and then profile */
10193c98a48SJohn Johansen ns_name = aa_ns_name(profile->ns, tp->ns, true);
102217af7e2SJohn Johansen state = aa_dfa_match_len(rules->file.dfa, state, ":", 1);
103217af7e2SJohn Johansen state = aa_dfa_match(rules->file.dfa, state, ns_name);
104217af7e2SJohn Johansen state = aa_dfa_match_len(rules->file.dfa, state, ":", 1);
105217af7e2SJohn Johansen return aa_dfa_match(rules->file.dfa, state, tp->base.hname);
10693c98a48SJohn Johansen }
10793c98a48SJohn Johansen
10893c98a48SJohn Johansen /**
10993c98a48SJohn Johansen * label_compound_match - find perms for full compound label
11093c98a48SJohn Johansen * @profile: profile to find perms for
11193c98a48SJohn Johansen * @label: label to check access permissions for
11293c98a48SJohn Johansen * @stack: whether this is a stacking request
113bab1f77fSYang Li * @state: state to start match in
11493c98a48SJohn Johansen * @subns: whether to do permission checks on components in a subns
11593c98a48SJohn Johansen * @request: permissions to request
11693c98a48SJohn Johansen * @perms: perms struct to set
11793c98a48SJohn Johansen *
11893c98a48SJohn Johansen * Returns: 0 on success else ERROR
11993c98a48SJohn Johansen *
12093c98a48SJohn Johansen * For the label A//&B//&C this does the perm match for A//&B//&C
12193c98a48SJohn Johansen * @perms should be preinitialized with allperms OR a previous permission
12293c98a48SJohn Johansen * check to be stacked.
12393c98a48SJohn Johansen */
label_compound_match(struct aa_profile * profile,struct aa_label * label,bool stack,aa_state_t state,bool subns,u32 request,struct aa_perms * perms)12493c98a48SJohn Johansen static int label_compound_match(struct aa_profile *profile,
12593c98a48SJohn Johansen struct aa_label *label, bool stack,
12633fc95d8SJohn Johansen aa_state_t state, bool subns, u32 request,
12793c98a48SJohn Johansen struct aa_perms *perms)
12893c98a48SJohn Johansen {
1291ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
1301ad22fccSJohn Johansen typeof(*rules), list);
13193c98a48SJohn Johansen struct aa_profile *tp;
13293c98a48SJohn Johansen struct label_it i;
13393c98a48SJohn Johansen struct path_cond cond = { };
13493c98a48SJohn Johansen
13593c98a48SJohn Johansen /* find first subcomponent that is visible */
13693c98a48SJohn Johansen label_for_each(i, label, tp) {
13793c98a48SJohn Johansen if (!aa_ns_visible(profile->ns, tp->ns, subns))
13893c98a48SJohn Johansen continue;
13993c98a48SJohn Johansen state = match_component(profile, tp, stack, state);
14093c98a48SJohn Johansen if (!state)
14193c98a48SJohn Johansen goto fail;
14293c98a48SJohn Johansen goto next;
14393c98a48SJohn Johansen }
14493c98a48SJohn Johansen
14593c98a48SJohn Johansen /* no component visible */
14693c98a48SJohn Johansen *perms = allperms;
14793c98a48SJohn Johansen return 0;
14893c98a48SJohn Johansen
14993c98a48SJohn Johansen next:
15093c98a48SJohn Johansen label_for_each_cont(i, label, tp) {
15193c98a48SJohn Johansen if (!aa_ns_visible(profile->ns, tp->ns, subns))
15293c98a48SJohn Johansen continue;
153217af7e2SJohn Johansen state = aa_dfa_match(rules->file.dfa, state, "//&");
15493c98a48SJohn Johansen state = match_component(profile, tp, false, state);
15593c98a48SJohn Johansen if (!state)
15693c98a48SJohn Johansen goto fail;
15793c98a48SJohn Johansen }
158217af7e2SJohn Johansen *perms = *(aa_lookup_fperms(&(rules->file), state, &cond));
15993c98a48SJohn Johansen aa_apply_modes_to_perms(profile, perms);
16093c98a48SJohn Johansen if ((perms->allow & request) != request)
16193c98a48SJohn Johansen return -EACCES;
16293c98a48SJohn Johansen
16393c98a48SJohn Johansen return 0;
16493c98a48SJohn Johansen
16593c98a48SJohn Johansen fail:
16693c98a48SJohn Johansen *perms = nullperms;
16793c98a48SJohn Johansen return -EACCES;
16893c98a48SJohn Johansen }
16993c98a48SJohn Johansen
17093c98a48SJohn Johansen /**
17193c98a48SJohn Johansen * label_components_match - find perms for all subcomponents of a label
17293c98a48SJohn Johansen * @profile: profile to find perms for
17393c98a48SJohn Johansen * @label: label to check access permissions for
17493c98a48SJohn Johansen * @stack: whether this is a stacking request
17593c98a48SJohn Johansen * @start: state to start match in
17693c98a48SJohn Johansen * @subns: whether to do permission checks on components in a subns
17793c98a48SJohn Johansen * @request: permissions to request
17893c98a48SJohn Johansen * @perms: an initialized perms struct to add accumulation to
17993c98a48SJohn Johansen *
18093c98a48SJohn Johansen * Returns: 0 on success else ERROR
18193c98a48SJohn Johansen *
18293c98a48SJohn Johansen * For the label A//&B//&C this does the perm match for each of A and B and C
18393c98a48SJohn Johansen * @perms should be preinitialized with allperms OR a previous permission
18493c98a48SJohn Johansen * check to be stacked.
18593c98a48SJohn Johansen */
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)18693c98a48SJohn Johansen static int label_components_match(struct aa_profile *profile,
18793c98a48SJohn Johansen struct aa_label *label, bool stack,
18833fc95d8SJohn Johansen aa_state_t start, bool subns, u32 request,
18993c98a48SJohn Johansen struct aa_perms *perms)
19093c98a48SJohn Johansen {
1911ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
1921ad22fccSJohn Johansen typeof(*rules), list);
19393c98a48SJohn Johansen struct aa_profile *tp;
19493c98a48SJohn Johansen struct label_it i;
19593c98a48SJohn Johansen struct aa_perms tmp;
19693c98a48SJohn Johansen struct path_cond cond = { };
19733fc95d8SJohn Johansen aa_state_t state = 0;
19893c98a48SJohn Johansen
19993c98a48SJohn Johansen /* find first subcomponent to test */
20093c98a48SJohn Johansen label_for_each(i, label, tp) {
20193c98a48SJohn Johansen if (!aa_ns_visible(profile->ns, tp->ns, subns))
20293c98a48SJohn Johansen continue;
20393c98a48SJohn Johansen state = match_component(profile, tp, stack, start);
20493c98a48SJohn Johansen if (!state)
20593c98a48SJohn Johansen goto fail;
20693c98a48SJohn Johansen goto next;
20793c98a48SJohn Johansen }
20893c98a48SJohn Johansen
20993c98a48SJohn Johansen /* no subcomponents visible - no change in perms */
21093c98a48SJohn Johansen return 0;
21193c98a48SJohn Johansen
21293c98a48SJohn Johansen next:
213217af7e2SJohn Johansen tmp = *(aa_lookup_fperms(&(rules->file), state, &cond));
21493c98a48SJohn Johansen aa_apply_modes_to_perms(profile, &tmp);
21593c98a48SJohn Johansen aa_perms_accum(perms, &tmp);
21693c98a48SJohn Johansen label_for_each_cont(i, label, tp) {
21793c98a48SJohn Johansen if (!aa_ns_visible(profile->ns, tp->ns, subns))
21893c98a48SJohn Johansen continue;
21993c98a48SJohn Johansen state = match_component(profile, tp, stack, start);
22093c98a48SJohn Johansen if (!state)
22193c98a48SJohn Johansen goto fail;
222217af7e2SJohn Johansen tmp = *(aa_lookup_fperms(&(rules->file), state, &cond));
22393c98a48SJohn Johansen aa_apply_modes_to_perms(profile, &tmp);
22493c98a48SJohn Johansen aa_perms_accum(perms, &tmp);
22593c98a48SJohn Johansen }
22693c98a48SJohn Johansen
22793c98a48SJohn Johansen if ((perms->allow & request) != request)
22893c98a48SJohn Johansen return -EACCES;
22993c98a48SJohn Johansen
23093c98a48SJohn Johansen return 0;
23193c98a48SJohn Johansen
23293c98a48SJohn Johansen fail:
23393c98a48SJohn Johansen *perms = nullperms;
23493c98a48SJohn Johansen return -EACCES;
23593c98a48SJohn Johansen }
23693c98a48SJohn Johansen
23793c98a48SJohn Johansen /**
23893c98a48SJohn Johansen * label_match - do a multi-component label match
23993c98a48SJohn Johansen * @profile: profile to match against (NOT NULL)
24093c98a48SJohn Johansen * @label: label to match (NOT NULL)
24193c98a48SJohn Johansen * @stack: whether this is a stacking request
24293c98a48SJohn Johansen * @state: state to start in
24393c98a48SJohn Johansen * @subns: whether to match subns components
24493c98a48SJohn Johansen * @request: permission request
24593c98a48SJohn Johansen * @perms: Returns computed perms (NOT NULL)
24693c98a48SJohn Johansen *
24793c98a48SJohn Johansen * Returns: the state the match finished in, may be the none matching state
24893c98a48SJohn Johansen */
label_match(struct aa_profile * profile,struct aa_label * label,bool stack,aa_state_t state,bool subns,u32 request,struct aa_perms * perms)24993c98a48SJohn Johansen static int label_match(struct aa_profile *profile, struct aa_label *label,
25033fc95d8SJohn Johansen bool stack, aa_state_t state, bool subns, u32 request,
25193c98a48SJohn Johansen struct aa_perms *perms)
25293c98a48SJohn Johansen {
25393c98a48SJohn Johansen int error;
25493c98a48SJohn Johansen
25593c98a48SJohn Johansen *perms = nullperms;
25693c98a48SJohn Johansen error = label_compound_match(profile, label, stack, state, subns,
25793c98a48SJohn Johansen request, perms);
25893c98a48SJohn Johansen if (!error)
25993c98a48SJohn Johansen return error;
26093c98a48SJohn Johansen
26193c98a48SJohn Johansen *perms = allperms;
26293c98a48SJohn Johansen return label_components_match(profile, label, stack, state, subns,
26393c98a48SJohn Johansen request, perms);
26493c98a48SJohn Johansen }
26593c98a48SJohn Johansen
26693c98a48SJohn Johansen /******* end TODO: dedup *****/
26793c98a48SJohn Johansen
268898127c3SJohn Johansen /**
269898127c3SJohn Johansen * change_profile_perms - find permissions for change_profile
270898127c3SJohn Johansen * @profile: the current profile (NOT NULL)
27193c98a48SJohn Johansen * @target: label to transition to (NOT NULL)
27293c98a48SJohn Johansen * @stack: whether this is a stacking request
273898127c3SJohn Johansen * @request: requested perms
274898127c3SJohn Johansen * @start: state to start matching in
275898127c3SJohn Johansen *
27693c98a48SJohn Johansen *
277898127c3SJohn Johansen * Returns: permission set
27893c98a48SJohn Johansen *
27993c98a48SJohn Johansen * currently only matches full label A//&B//&C or individual components A, B, C
28093c98a48SJohn Johansen * not arbitrary combinations. Eg. A//&B, C
281898127c3SJohn Johansen */
change_profile_perms(struct aa_profile * profile,struct aa_label * target,bool stack,u32 request,aa_state_t start,struct aa_perms * perms)28293c98a48SJohn Johansen static int change_profile_perms(struct aa_profile *profile,
28393c98a48SJohn Johansen struct aa_label *target, bool stack,
28433fc95d8SJohn Johansen u32 request, aa_state_t start,
28593c98a48SJohn Johansen struct aa_perms *perms)
28693c98a48SJohn Johansen {
28793c98a48SJohn Johansen if (profile_unconfined(profile)) {
28893c98a48SJohn Johansen perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
28993c98a48SJohn Johansen perms->audit = perms->quiet = perms->kill = 0;
29093c98a48SJohn Johansen return 0;
29193c98a48SJohn Johansen }
29293c98a48SJohn Johansen
29393c98a48SJohn Johansen /* TODO: add profile in ns screening */
29493c98a48SJohn Johansen return label_match(profile, target, stack, start, true, request, perms);
29593c98a48SJohn Johansen }
29693c98a48SJohn Johansen
297898127c3SJohn Johansen /**
2988e51f908SMatthew Garrett * aa_xattrs_match - check whether a file matches the xattrs defined in profile
2998e51f908SMatthew Garrett * @bprm: binprm struct for the process to validate
3008e51f908SMatthew Garrett * @profile: profile to match against (NOT NULL)
30173f488cdSJohn Johansen * @state: state to start match in
3028e51f908SMatthew Garrett *
3038e51f908SMatthew Garrett * Returns: number of extended attributes that matched, or < 0 on error
3048e51f908SMatthew Garrett */
aa_xattrs_match(const struct linux_binprm * bprm,struct aa_profile * profile,aa_state_t state)3058e51f908SMatthew Garrett static int aa_xattrs_match(const struct linux_binprm *bprm,
30633fc95d8SJohn Johansen struct aa_profile *profile, aa_state_t state)
3078e51f908SMatthew Garrett {
3088e51f908SMatthew Garrett int i;
3098e51f908SMatthew Garrett struct dentry *d;
3108e51f908SMatthew Garrett char *value = NULL;
311217af7e2SJohn Johansen struct aa_attachment *attach = &profile->attach;
31293761c93SLinus Torvalds int size, value_size = 0, ret = attach->xattr_count;
3138e51f908SMatthew Garrett
314217af7e2SJohn Johansen if (!bprm || !attach->xattr_count)
3158e51f908SMatthew Garrett return 0;
3168c62ed27SJohn Johansen might_sleep();
3178e51f908SMatthew Garrett
31873f488cdSJohn Johansen /* transition from exec match to xattr set */
319217af7e2SJohn Johansen state = aa_dfa_outofband_transition(attach->xmatch.dfa, state);
3208e51f908SMatthew Garrett d = bprm->file->f_path.dentry;
3218e51f908SMatthew Garrett
322217af7e2SJohn Johansen for (i = 0; i < attach->xattr_count; i++) {
3234609e1f1SChristian Brauner size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i],
324c7c7a1a1STycho Andersen &value, value_size, GFP_KERNEL);
32573f488cdSJohn Johansen if (size >= 0) {
3262d63dd43SJohn Johansen u32 index, perm;
3278e51f908SMatthew Garrett
3280df34a64SJohn Johansen /*
3290df34a64SJohn Johansen * Check the xattr presence before value. This ensure
3300df34a64SJohn Johansen * that not present xattr can be distinguished from a 0
3310df34a64SJohn Johansen * length value or rule that matches any value
3320df34a64SJohn Johansen */
333217af7e2SJohn Johansen state = aa_dfa_null_transition(attach->xmatch.dfa,
334048d4954SJohn Johansen state);
3350df34a64SJohn Johansen /* Check xattr value */
336217af7e2SJohn Johansen state = aa_dfa_match_len(attach->xmatch.dfa, state,
337048d4954SJohn Johansen value, size);
338217af7e2SJohn Johansen index = ACCEPT_TABLE(attach->xmatch.dfa)[state];
339217af7e2SJohn Johansen perm = attach->xmatch.perms[index].allow;
34073f488cdSJohn Johansen if (!(perm & MAY_EXEC)) {
3418e51f908SMatthew Garrett ret = -EINVAL;
3428e51f908SMatthew Garrett goto out;
3438e51f908SMatthew Garrett }
34473f488cdSJohn Johansen }
34573f488cdSJohn Johansen /* transition to next element */
346217af7e2SJohn Johansen state = aa_dfa_outofband_transition(attach->xmatch.dfa, state);
34773f488cdSJohn Johansen if (size < 0) {
34873f488cdSJohn Johansen /*
34973f488cdSJohn Johansen * No xattr match, so verify if transition to
35073f488cdSJohn Johansen * next element was valid. IFF so the xattr
35173f488cdSJohn Johansen * was optional.
35273f488cdSJohn Johansen */
35373f488cdSJohn Johansen if (!state) {
3548e51f908SMatthew Garrett ret = -EINVAL;
3558e51f908SMatthew Garrett goto out;
3568e51f908SMatthew Garrett }
35773f488cdSJohn Johansen /* don't count missing optional xattr as matched */
35873f488cdSJohn Johansen ret--;
3598e51f908SMatthew Garrett }
3608e51f908SMatthew Garrett }
3618e51f908SMatthew Garrett
3628e51f908SMatthew Garrett out:
3638e51f908SMatthew Garrett kfree(value);
3648e51f908SMatthew Garrett return ret;
3658e51f908SMatthew Garrett }
3668e51f908SMatthew Garrett
3678e51f908SMatthew Garrett /**
3688c62ed27SJohn Johansen * find_attach - do attachment search for unconfined processes
3698e51f908SMatthew Garrett * @bprm - binprm structure of transitioning task
3708c62ed27SJohn Johansen * @ns: the current namespace (NOT NULL)
371898127c3SJohn Johansen * @head - profile list to walk (NOT NULL)
3728c62ed27SJohn Johansen * @name - to match against (NOT NULL)
373844b8292SJohn Johansen * @info - info message if there was an error (NOT NULL)
374898127c3SJohn Johansen *
375898127c3SJohn Johansen * Do a linear search on the profiles in the list. There is a matching
376898127c3SJohn Johansen * preference where an exact match is preferred over a name which uses
377898127c3SJohn Johansen * expressions to match, and matching expressions with the greatest
378898127c3SJohn Johansen * xmatch_len are preferred.
379898127c3SJohn Johansen *
380898127c3SJohn Johansen * Requires: @head not be shared or have appropriate locks held
381898127c3SJohn Johansen *
3828c62ed27SJohn Johansen * Returns: label or NULL if no match found
383898127c3SJohn Johansen */
find_attach(const struct linux_binprm * bprm,struct aa_ns * ns,struct list_head * head,const char * name,const char ** info)3848c62ed27SJohn Johansen static struct aa_label *find_attach(const struct linux_binprm *bprm,
3858c62ed27SJohn Johansen struct aa_ns *ns, struct list_head *head,
3868c62ed27SJohn Johansen const char *name, const char **info)
387898127c3SJohn Johansen {
38821f60661SJohn Johansen int candidate_len = 0, candidate_xattrs = 0;
389844b8292SJohn Johansen bool conflict = false;
390898127c3SJohn Johansen struct aa_profile *profile, *candidate = NULL;
391898127c3SJohn Johansen
39221f60661SJohn Johansen AA_BUG(!name);
39321f60661SJohn Johansen AA_BUG(!head);
39421f60661SJohn Johansen
3958c62ed27SJohn Johansen rcu_read_lock();
3968c62ed27SJohn Johansen restart:
39701e2b670SJohn Johansen list_for_each_entry_rcu(profile, head, base.list) {
398217af7e2SJohn Johansen struct aa_attachment *attach = &profile->attach;
399217af7e2SJohn Johansen
40006d426d1SJohn Johansen if (profile->label.flags & FLAG_NULL &&
40106d426d1SJohn Johansen &profile->label == ns_unconfined(profile->ns))
402898127c3SJohn Johansen continue;
40306d426d1SJohn Johansen
4048e51f908SMatthew Garrett /* Find the "best" matching profile. Profiles must
4058e51f908SMatthew Garrett * match the path and extended attributes (if any)
4068e51f908SMatthew Garrett * associated with the file. A more specific path
4078e51f908SMatthew Garrett * match will be preferred over a less specific one,
4088e51f908SMatthew Garrett * and a match with more matching extended attributes
4098e51f908SMatthew Garrett * will be preferred over one with fewer. If the best
4108e51f908SMatthew Garrett * match has both the same level of path specificity
4118e51f908SMatthew Garrett * and the same number of matching extended attributes
4128e51f908SMatthew Garrett * as another profile, signal a conflict and refuse to
4138e51f908SMatthew Garrett * match.
4148e51f908SMatthew Garrett */
415217af7e2SJohn Johansen if (attach->xmatch.dfa) {
41633fc95d8SJohn Johansen unsigned int count;
41733fc95d8SJohn Johansen aa_state_t state;
4182d63dd43SJohn Johansen u32 index, perm;
419844b8292SJohn Johansen
420217af7e2SJohn Johansen state = aa_dfa_leftmatch(attach->xmatch.dfa,
421217af7e2SJohn Johansen attach->xmatch.start[AA_CLASS_XMATCH],
42221f60661SJohn Johansen name, &count);
423217af7e2SJohn Johansen index = ACCEPT_TABLE(attach->xmatch.dfa)[state];
424217af7e2SJohn Johansen perm = attach->xmatch.perms[index].allow;
425898127c3SJohn Johansen /* any accepting state means a valid match. */
426898127c3SJohn Johansen if (perm & MAY_EXEC) {
4278c62ed27SJohn Johansen int ret = 0;
4288e51f908SMatthew Garrett
42921f60661SJohn Johansen if (count < candidate_len)
43021f60661SJohn Johansen continue;
43121f60661SJohn Johansen
432217af7e2SJohn Johansen if (bprm && attach->xattr_count) {
4338c62ed27SJohn Johansen long rev = READ_ONCE(ns->revision);
4348c62ed27SJohn Johansen
4358c62ed27SJohn Johansen if (!aa_get_profile_not0(profile))
4368c62ed27SJohn Johansen goto restart;
4378c62ed27SJohn Johansen rcu_read_unlock();
4388c62ed27SJohn Johansen ret = aa_xattrs_match(bprm, profile,
4398c62ed27SJohn Johansen state);
4408c62ed27SJohn Johansen rcu_read_lock();
4418c62ed27SJohn Johansen aa_put_profile(profile);
4428c62ed27SJohn Johansen if (rev !=
4438c62ed27SJohn Johansen READ_ONCE(ns->revision))
4448c62ed27SJohn Johansen /* policy changed */
4458c62ed27SJohn Johansen goto restart;
4468c62ed27SJohn Johansen /*
4478c62ed27SJohn Johansen * Fail matching if the xattrs don't
4488c62ed27SJohn Johansen * match
4498c62ed27SJohn Johansen */
4508e51f908SMatthew Garrett if (ret < 0)
4518e51f908SMatthew Garrett continue;
4528c62ed27SJohn Johansen }
45373f488cdSJohn Johansen /*
45473f488cdSJohn Johansen * TODO: allow for more flexible best match
45573f488cdSJohn Johansen *
45673f488cdSJohn Johansen * The new match isn't more specific
4578e51f908SMatthew Garrett * than the current best match
4588e51f908SMatthew Garrett */
45921f60661SJohn Johansen if (count == candidate_len &&
46021f60661SJohn Johansen ret <= candidate_xattrs) {
4618e51f908SMatthew Garrett /* Match is equivalent, so conflict */
46221f60661SJohn Johansen if (ret == candidate_xattrs)
4631a3881d3SMatthew Garrett conflict = true;
4641a3881d3SMatthew Garrett continue;
4651a3881d3SMatthew Garrett }
4668e51f908SMatthew Garrett
4678e51f908SMatthew Garrett /* Either the same length with more matching
4688e51f908SMatthew Garrett * xattrs, or a longer match
4698e51f908SMatthew Garrett */
470898127c3SJohn Johansen candidate = profile;
471217af7e2SJohn Johansen candidate_len = max(count, attach->xmatch_len);
47221f60661SJohn Johansen candidate_xattrs = ret;
473844b8292SJohn Johansen conflict = false;
474844b8292SJohn Johansen }
4758c62ed27SJohn Johansen } else if (!strcmp(profile->base.name, name)) {
47673f488cdSJohn Johansen /*
47773f488cdSJohn Johansen * old exact non-re match, without conditionals such
47873f488cdSJohn Johansen * as xattrs. no more searching required
47973f488cdSJohn Johansen */
4808c62ed27SJohn Johansen candidate = profile;
4818c62ed27SJohn Johansen goto out;
4828c62ed27SJohn Johansen }
483898127c3SJohn Johansen }
484898127c3SJohn Johansen
4858c62ed27SJohn Johansen if (!candidate || conflict) {
4868c62ed27SJohn Johansen if (conflict)
487844b8292SJohn Johansen *info = "conflicting profile attachments";
4888c62ed27SJohn Johansen rcu_read_unlock();
489844b8292SJohn Johansen return NULL;
490844b8292SJohn Johansen }
491844b8292SJohn Johansen
4928c62ed27SJohn Johansen out:
4938c62ed27SJohn Johansen candidate = aa_get_newest_profile(candidate);
49401e2b670SJohn Johansen rcu_read_unlock();
495898127c3SJohn Johansen
4968c62ed27SJohn Johansen return &candidate->label;
497898127c3SJohn Johansen }
498898127c3SJohn Johansen
next_name(int xtype,const char * name)499898127c3SJohn Johansen static const char *next_name(int xtype, const char *name)
500898127c3SJohn Johansen {
501898127c3SJohn Johansen return NULL;
502898127c3SJohn Johansen }
503898127c3SJohn Johansen
504898127c3SJohn Johansen /**
505898127c3SJohn Johansen * x_table_lookup - lookup an x transition name via transition table
506898127c3SJohn Johansen * @profile: current profile (NOT NULL)
507898127c3SJohn Johansen * @xindex: index into x transition table
50893c98a48SJohn Johansen * @name: returns: name tested to find label (NOT NULL)
509898127c3SJohn Johansen *
51093c98a48SJohn Johansen * Returns: refcounted label, or NULL on failure (MAYBE NULL)
511898127c3SJohn Johansen */
x_table_lookup(struct aa_profile * profile,u32 xindex,const char ** name)5122ea3ffb7SJohn Johansen struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
51393c98a48SJohn Johansen const char **name)
514898127c3SJohn Johansen {
5151ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
5161ad22fccSJohn Johansen typeof(*rules), list);
51793c98a48SJohn Johansen struct aa_label *label = NULL;
518898127c3SJohn Johansen u32 xtype = xindex & AA_X_TYPE_MASK;
519898127c3SJohn Johansen int index = xindex & AA_X_INDEX_MASK;
52093c98a48SJohn Johansen
52193c98a48SJohn Johansen AA_BUG(!name);
522898127c3SJohn Johansen
523898127c3SJohn Johansen /* index is guaranteed to be in range, validated at load time */
52493c98a48SJohn Johansen /* TODO: move lookup parsing to unpack time so this is a straight
52593c98a48SJohn Johansen * index into the resultant label
52693c98a48SJohn Johansen */
527217af7e2SJohn Johansen for (*name = rules->file.trans.table[index]; !label && *name;
52893c98a48SJohn Johansen *name = next_name(xtype, *name)) {
529898127c3SJohn Johansen if (xindex & AA_X_CHILD) {
53093c98a48SJohn Johansen struct aa_profile *new_profile;
531898127c3SJohn Johansen /* release by caller */
53293c98a48SJohn Johansen new_profile = aa_find_child(profile, *name);
53393c98a48SJohn Johansen if (new_profile)
53493c98a48SJohn Johansen label = &new_profile->label;
535898127c3SJohn Johansen continue;
536898127c3SJohn Johansen }
5378ac2ca32SSebastian Andrzej Siewior label = aa_label_parse(&profile->label, *name, GFP_KERNEL,
53893c98a48SJohn Johansen true, false);
53993c98a48SJohn Johansen if (IS_ERR(label))
54093c98a48SJohn Johansen label = NULL;
541898127c3SJohn Johansen }
542898127c3SJohn Johansen
543898127c3SJohn Johansen /* released by caller */
544898127c3SJohn Johansen
54593c98a48SJohn Johansen return label;
546898127c3SJohn Johansen }
547898127c3SJohn Johansen
548898127c3SJohn Johansen /**
54993c98a48SJohn Johansen * x_to_label - get target label for a given xindex
550898127c3SJohn Johansen * @profile: current profile (NOT NULL)
5518e51f908SMatthew Garrett * @bprm: binprm structure of transitioning task
552898127c3SJohn Johansen * @name: name to lookup (NOT NULL)
553898127c3SJohn Johansen * @xindex: index into x transition table
55493c98a48SJohn Johansen * @lookupname: returns: name used in lookup if one was specified (NOT NULL)
555898127c3SJohn Johansen *
55693c98a48SJohn Johansen * find label for a transition index
557898127c3SJohn Johansen *
55893c98a48SJohn Johansen * Returns: refcounted label or NULL if not found available
559898127c3SJohn Johansen */
x_to_label(struct aa_profile * profile,const struct linux_binprm * bprm,const char * name,u32 xindex,const char ** lookupname,const char ** info)56093c98a48SJohn Johansen static struct aa_label *x_to_label(struct aa_profile *profile,
5618e51f908SMatthew Garrett const struct linux_binprm *bprm,
56293c98a48SJohn Johansen const char *name, u32 xindex,
56393c98a48SJohn Johansen const char **lookupname,
56493c98a48SJohn Johansen const char **info)
565898127c3SJohn Johansen {
5661ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
5671ad22fccSJohn Johansen typeof(*rules), list);
56893c98a48SJohn Johansen struct aa_label *new = NULL;
56998849dffSJohn Johansen struct aa_ns *ns = profile->ns;
570898127c3SJohn Johansen u32 xtype = xindex & AA_X_TYPE_MASK;
57193c98a48SJohn Johansen const char *stack = NULL;
572898127c3SJohn Johansen
573898127c3SJohn Johansen switch (xtype) {
574898127c3SJohn Johansen case AA_X_NONE:
575898127c3SJohn Johansen /* fail exec unless ix || ux fallback - handled by caller */
57693c98a48SJohn Johansen *lookupname = NULL;
57793c98a48SJohn Johansen break;
57893c98a48SJohn Johansen case AA_X_TABLE:
57993c98a48SJohn Johansen /* TODO: fix when perm mapping done at unload */
580217af7e2SJohn Johansen stack = rules->file.trans.table[xindex & AA_X_INDEX_MASK];
58193c98a48SJohn Johansen if (*stack != '&') {
58293c98a48SJohn Johansen /* released by caller */
58393c98a48SJohn Johansen new = x_table_lookup(profile, xindex, lookupname);
58493c98a48SJohn Johansen stack = NULL;
58593c98a48SJohn Johansen break;
58693c98a48SJohn Johansen }
587df561f66SGustavo A. R. Silva fallthrough; /* to X_NAME */
588898127c3SJohn Johansen case AA_X_NAME:
589898127c3SJohn Johansen if (xindex & AA_X_CHILD)
590898127c3SJohn Johansen /* released by caller */
5918e51f908SMatthew Garrett new = find_attach(bprm, ns, &profile->base.profiles,
592844b8292SJohn Johansen name, info);
593898127c3SJohn Johansen else
594898127c3SJohn Johansen /* released by caller */
5958e51f908SMatthew Garrett new = find_attach(bprm, ns, &ns->base.profiles,
596844b8292SJohn Johansen name, info);
59793c98a48SJohn Johansen *lookupname = name;
598898127c3SJohn Johansen break;
599898127c3SJohn Johansen }
600898127c3SJohn Johansen
60193c98a48SJohn Johansen if (!new) {
60293c98a48SJohn Johansen if (xindex & AA_X_INHERIT) {
60393c98a48SJohn Johansen /* (p|c|n)ix - don't change profile but do
60493c98a48SJohn Johansen * use the newest version
60593c98a48SJohn Johansen */
60693c98a48SJohn Johansen *info = "ix fallback";
60793c98a48SJohn Johansen /* no profile && no error */
60893c98a48SJohn Johansen new = aa_get_newest_label(&profile->label);
60993c98a48SJohn Johansen } else if (xindex & AA_X_UNCONFINED) {
61093c98a48SJohn Johansen new = aa_get_newest_label(ns_unconfined(profile->ns));
61193c98a48SJohn Johansen *info = "ux fallback";
61293c98a48SJohn Johansen }
61393c98a48SJohn Johansen }
61493c98a48SJohn Johansen
61593c98a48SJohn Johansen if (new && stack) {
61693c98a48SJohn Johansen /* base the stack on post domain transition */
61793c98a48SJohn Johansen struct aa_label *base = new;
61893c98a48SJohn Johansen
6198ac2ca32SSebastian Andrzej Siewior new = aa_label_parse(base, stack, GFP_KERNEL, true, false);
62093c98a48SJohn Johansen if (IS_ERR(new))
62193c98a48SJohn Johansen new = NULL;
62293c98a48SJohn Johansen aa_put_label(base);
62393c98a48SJohn Johansen }
62493c98a48SJohn Johansen
625898127c3SJohn Johansen /* released by caller */
62693c98a48SJohn Johansen return new;
62793c98a48SJohn Johansen }
62893c98a48SJohn Johansen
profile_transition(const struct cred * subj_cred,struct aa_profile * profile,const struct linux_binprm * bprm,char * buffer,struct path_cond * cond,bool * secure_exec)629*690f33e1SJohn Johansen static struct aa_label *profile_transition(const struct cred *subj_cred,
630*690f33e1SJohn Johansen struct aa_profile *profile,
63193c98a48SJohn Johansen const struct linux_binprm *bprm,
63293c98a48SJohn Johansen char *buffer, struct path_cond *cond,
63393c98a48SJohn Johansen bool *secure_exec)
63493c98a48SJohn Johansen {
6351ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
6361ad22fccSJohn Johansen typeof(*rules), list);
63793c98a48SJohn Johansen struct aa_label *new = NULL;
63893c98a48SJohn Johansen const char *info = NULL, *name = NULL, *target = NULL;
639217af7e2SJohn Johansen aa_state_t state = rules->file.start[AA_CLASS_FILE];
64093c98a48SJohn Johansen struct aa_perms perms = {};
64193c98a48SJohn Johansen bool nonewprivs = false;
64293c98a48SJohn Johansen int error = 0;
64393c98a48SJohn Johansen
64493c98a48SJohn Johansen AA_BUG(!profile);
64593c98a48SJohn Johansen AA_BUG(!bprm);
64693c98a48SJohn Johansen AA_BUG(!buffer);
64793c98a48SJohn Johansen
64893c98a48SJohn Johansen error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
64993c98a48SJohn Johansen &name, &info, profile->disconnected);
65093c98a48SJohn Johansen if (error) {
65193c98a48SJohn Johansen if (profile_unconfined(profile) ||
65293c98a48SJohn Johansen (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
65393c98a48SJohn Johansen AA_DEBUG("name lookup ix on error");
65493c98a48SJohn Johansen error = 0;
65593c98a48SJohn Johansen new = aa_get_newest_label(&profile->label);
65693c98a48SJohn Johansen }
65793c98a48SJohn Johansen name = bprm->filename;
65893c98a48SJohn Johansen goto audit;
65993c98a48SJohn Johansen }
66093c98a48SJohn Johansen
66193c98a48SJohn Johansen if (profile_unconfined(profile)) {
6628e51f908SMatthew Garrett new = find_attach(bprm, profile->ns,
6638e51f908SMatthew Garrett &profile->ns->base.profiles, name, &info);
66493c98a48SJohn Johansen if (new) {
66593c98a48SJohn Johansen AA_DEBUG("unconfined attached to new label");
66693c98a48SJohn Johansen return new;
66793c98a48SJohn Johansen }
66893c98a48SJohn Johansen AA_DEBUG("unconfined exec no attachment");
66993c98a48SJohn Johansen return aa_get_newest_label(&profile->label);
67093c98a48SJohn Johansen }
67193c98a48SJohn Johansen
67293c98a48SJohn Johansen /* find exec permissions for name */
673217af7e2SJohn Johansen state = aa_str_perms(&(rules->file), state, name, cond, &perms);
67493c98a48SJohn Johansen if (perms.allow & MAY_EXEC) {
67593c98a48SJohn Johansen /* exec permission determine how to transition */
6768e51f908SMatthew Garrett new = x_to_label(profile, bprm, name, perms.xindex, &target,
6778e51f908SMatthew Garrett &info);
67893c98a48SJohn Johansen if (new && new->proxy == profile->label.proxy && info) {
67993c98a48SJohn Johansen /* hack ix fallback - improve how this is detected */
68093c98a48SJohn Johansen goto audit;
68193c98a48SJohn Johansen } else if (!new) {
68293c98a48SJohn Johansen error = -EACCES;
68393c98a48SJohn Johansen info = "profile transition not found";
68493c98a48SJohn Johansen /* remove MAY_EXEC to audit as failure */
68593c98a48SJohn Johansen perms.allow &= ~MAY_EXEC;
68693c98a48SJohn Johansen }
68793c98a48SJohn Johansen } else if (COMPLAIN_MODE(profile)) {
68893c98a48SJohn Johansen /* no exec permission - learning mode */
6895d7c44efSJohn Johansen struct aa_profile *new_profile = NULL;
6905d7c44efSJohn Johansen
69158f89ce5SJohn Johansen new_profile = aa_new_learning_profile(profile, false, name,
6925d7c44efSJohn Johansen GFP_KERNEL);
69393c98a48SJohn Johansen if (!new_profile) {
69493c98a48SJohn Johansen error = -ENOMEM;
69593c98a48SJohn Johansen info = "could not create null profile";
69693c98a48SJohn Johansen } else {
69793c98a48SJohn Johansen error = -EACCES;
69893c98a48SJohn Johansen new = &new_profile->label;
69993c98a48SJohn Johansen }
70093c98a48SJohn Johansen perms.xindex |= AA_X_UNSAFE;
70193c98a48SJohn Johansen } else
70293c98a48SJohn Johansen /* fail exec */
70393c98a48SJohn Johansen error = -EACCES;
70493c98a48SJohn Johansen
70593c98a48SJohn Johansen if (!new)
70693c98a48SJohn Johansen goto audit;
70793c98a48SJohn Johansen
70893c98a48SJohn Johansen
70993c98a48SJohn Johansen if (!(perms.xindex & AA_X_UNSAFE)) {
71093c98a48SJohn Johansen if (DEBUG_ON) {
71193c98a48SJohn Johansen dbg_printk("apparmor: scrubbing environment variables"
71293c98a48SJohn Johansen " for %s profile=", name);
7138ac2ca32SSebastian Andrzej Siewior aa_label_printk(new, GFP_KERNEL);
71493c98a48SJohn Johansen dbg_printk("\n");
71593c98a48SJohn Johansen }
71693c98a48SJohn Johansen *secure_exec = true;
71793c98a48SJohn Johansen }
71893c98a48SJohn Johansen
71993c98a48SJohn Johansen audit:
720*690f33e1SJohn Johansen aa_audit_file(subj_cred, profile, &perms, OP_EXEC, MAY_EXEC, name,
721*690f33e1SJohn Johansen target, new,
72293c98a48SJohn Johansen cond->uid, info, error);
72393c98a48SJohn Johansen if (!new || nonewprivs) {
72493c98a48SJohn Johansen aa_put_label(new);
72593c98a48SJohn Johansen return ERR_PTR(error);
72693c98a48SJohn Johansen }
72793c98a48SJohn Johansen
72893c98a48SJohn Johansen return new;
72993c98a48SJohn Johansen }
73093c98a48SJohn Johansen
profile_onexec(const struct cred * subj_cred,struct aa_profile * profile,struct aa_label * onexec,bool stack,const struct linux_binprm * bprm,char * buffer,struct path_cond * cond,bool * secure_exec)731*690f33e1SJohn Johansen static int profile_onexec(const struct cred *subj_cred,
732*690f33e1SJohn Johansen struct aa_profile *profile, struct aa_label *onexec,
73393c98a48SJohn Johansen bool stack, const struct linux_binprm *bprm,
73493c98a48SJohn Johansen char *buffer, struct path_cond *cond,
73593c98a48SJohn Johansen bool *secure_exec)
73693c98a48SJohn Johansen {
7371ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
7381ad22fccSJohn Johansen typeof(*rules), list);
739217af7e2SJohn Johansen aa_state_t state = rules->file.start[AA_CLASS_FILE];
74093c98a48SJohn Johansen struct aa_perms perms = {};
74193c98a48SJohn Johansen const char *xname = NULL, *info = "change_profile onexec";
74293c98a48SJohn Johansen int error = -EACCES;
74393c98a48SJohn Johansen
74493c98a48SJohn Johansen AA_BUG(!profile);
74593c98a48SJohn Johansen AA_BUG(!onexec);
74693c98a48SJohn Johansen AA_BUG(!bprm);
74793c98a48SJohn Johansen AA_BUG(!buffer);
74893c98a48SJohn Johansen
74993c98a48SJohn Johansen if (profile_unconfined(profile)) {
75093c98a48SJohn Johansen /* change_profile on exec already granted */
75193c98a48SJohn Johansen /*
75293c98a48SJohn Johansen * NOTE: Domain transitions from unconfined are allowed
75393c98a48SJohn Johansen * even when no_new_privs is set because this aways results
75493c98a48SJohn Johansen * in a further reduction of permissions.
75593c98a48SJohn Johansen */
75693c98a48SJohn Johansen return 0;
75793c98a48SJohn Johansen }
75893c98a48SJohn Johansen
75993c98a48SJohn Johansen error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
76093c98a48SJohn Johansen &xname, &info, profile->disconnected);
76193c98a48SJohn Johansen if (error) {
76293c98a48SJohn Johansen if (profile_unconfined(profile) ||
76393c98a48SJohn Johansen (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
76493c98a48SJohn Johansen AA_DEBUG("name lookup ix on error");
76593c98a48SJohn Johansen error = 0;
76693c98a48SJohn Johansen }
76793c98a48SJohn Johansen xname = bprm->filename;
76893c98a48SJohn Johansen goto audit;
76993c98a48SJohn Johansen }
77093c98a48SJohn Johansen
77193c98a48SJohn Johansen /* find exec permissions for name */
772217af7e2SJohn Johansen state = aa_str_perms(&(rules->file), state, xname, cond, &perms);
77393c98a48SJohn Johansen if (!(perms.allow & AA_MAY_ONEXEC)) {
77493c98a48SJohn Johansen info = "no change_onexec valid for executable";
77593c98a48SJohn Johansen goto audit;
77693c98a48SJohn Johansen }
77793c98a48SJohn Johansen /* test if this exec can be paired with change_profile onexec.
77893c98a48SJohn Johansen * onexec permission is linked to exec with a standard pairing
77993c98a48SJohn Johansen * exec\0change_profile
78093c98a48SJohn Johansen */
781217af7e2SJohn Johansen state = aa_dfa_null_transition(rules->file.dfa, state);
78293c98a48SJohn Johansen error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC,
78393c98a48SJohn Johansen state, &perms);
78493c98a48SJohn Johansen if (error) {
78593c98a48SJohn Johansen perms.allow &= ~AA_MAY_ONEXEC;
78693c98a48SJohn Johansen goto audit;
78793c98a48SJohn Johansen }
78893c98a48SJohn Johansen
78993c98a48SJohn Johansen if (!(perms.xindex & AA_X_UNSAFE)) {
79093c98a48SJohn Johansen if (DEBUG_ON) {
79193c98a48SJohn Johansen dbg_printk("apparmor: scrubbing environment "
79293c98a48SJohn Johansen "variables for %s label=", xname);
7938ac2ca32SSebastian Andrzej Siewior aa_label_printk(onexec, GFP_KERNEL);
79493c98a48SJohn Johansen dbg_printk("\n");
79593c98a48SJohn Johansen }
79693c98a48SJohn Johansen *secure_exec = true;
79793c98a48SJohn Johansen }
79893c98a48SJohn Johansen
79993c98a48SJohn Johansen audit:
800*690f33e1SJohn Johansen return aa_audit_file(subj_cred, profile, &perms, OP_EXEC,
801*690f33e1SJohn Johansen AA_MAY_ONEXEC, xname,
80293c98a48SJohn Johansen NULL, onexec, cond->uid, info, error);
80393c98a48SJohn Johansen }
80493c98a48SJohn Johansen
80593c98a48SJohn Johansen /* ensure none ns domain transitions are correctly applied with onexec */
80693c98a48SJohn Johansen
handle_onexec(const struct cred * subj_cred,struct aa_label * label,struct aa_label * onexec,bool stack,const struct linux_binprm * bprm,char * buffer,struct path_cond * cond,bool * unsafe)807*690f33e1SJohn Johansen static struct aa_label *handle_onexec(const struct cred *subj_cred,
808*690f33e1SJohn Johansen struct aa_label *label,
80993c98a48SJohn Johansen struct aa_label *onexec, bool stack,
81093c98a48SJohn Johansen const struct linux_binprm *bprm,
81193c98a48SJohn Johansen char *buffer, struct path_cond *cond,
81293c98a48SJohn Johansen bool *unsafe)
81393c98a48SJohn Johansen {
81493c98a48SJohn Johansen struct aa_profile *profile;
81593c98a48SJohn Johansen struct aa_label *new;
81693c98a48SJohn Johansen int error;
81793c98a48SJohn Johansen
81893c98a48SJohn Johansen AA_BUG(!label);
81993c98a48SJohn Johansen AA_BUG(!onexec);
82093c98a48SJohn Johansen AA_BUG(!bprm);
82193c98a48SJohn Johansen AA_BUG(!buffer);
82293c98a48SJohn Johansen
82393c98a48SJohn Johansen if (!stack) {
82493c98a48SJohn Johansen error = fn_for_each_in_ns(label, profile,
825*690f33e1SJohn Johansen profile_onexec(subj_cred, profile, onexec, stack,
82693c98a48SJohn Johansen bprm, buffer, cond, unsafe));
82793c98a48SJohn Johansen if (error)
82893c98a48SJohn Johansen return ERR_PTR(error);
8298ac2ca32SSebastian Andrzej Siewior new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
83093c98a48SJohn Johansen aa_get_newest_label(onexec),
831*690f33e1SJohn Johansen profile_transition(subj_cred, profile, bprm,
832*690f33e1SJohn Johansen buffer,
83393c98a48SJohn Johansen cond, unsafe));
83493c98a48SJohn Johansen
83593c98a48SJohn Johansen } else {
836b2c2086cSZygmunt Krynicki /* TODO: determine how much we want to loosen this */
83793c98a48SJohn Johansen error = fn_for_each_in_ns(label, profile,
838*690f33e1SJohn Johansen profile_onexec(subj_cred, profile, onexec, stack, bprm,
83993c98a48SJohn Johansen buffer, cond, unsafe));
84093c98a48SJohn Johansen if (error)
84193c98a48SJohn Johansen return ERR_PTR(error);
8428ac2ca32SSebastian Andrzej Siewior new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
84393c98a48SJohn Johansen aa_label_merge(&profile->label, onexec,
8448ac2ca32SSebastian Andrzej Siewior GFP_KERNEL),
845*690f33e1SJohn Johansen profile_transition(subj_cred, profile, bprm,
846*690f33e1SJohn Johansen buffer,
84793c98a48SJohn Johansen cond, unsafe));
84893c98a48SJohn Johansen }
84993c98a48SJohn Johansen
85093c98a48SJohn Johansen if (new)
85193c98a48SJohn Johansen return new;
85293c98a48SJohn Johansen
85393c98a48SJohn Johansen /* TODO: get rid of GLOBAL_ROOT_UID */
85493c98a48SJohn Johansen error = fn_for_each_in_ns(label, profile,
855*690f33e1SJohn Johansen aa_audit_file(subj_cred, profile, &nullperms,
856*690f33e1SJohn Johansen OP_CHANGE_ONEXEC,
85793c98a48SJohn Johansen AA_MAY_ONEXEC, bprm->filename, NULL,
85893c98a48SJohn Johansen onexec, GLOBAL_ROOT_UID,
85993c98a48SJohn Johansen "failed to build target label", -ENOMEM));
86093c98a48SJohn Johansen return ERR_PTR(error);
861898127c3SJohn Johansen }
862898127c3SJohn Johansen
863898127c3SJohn Johansen /**
864b8bff599SEric W. Biederman * apparmor_bprm_creds_for_exec - Update the new creds on the bprm struct
865898127c3SJohn Johansen * @bprm: binprm for the exec (NOT NULL)
866898127c3SJohn Johansen *
867898127c3SJohn Johansen * Returns: %0 or error on failure
86893c98a48SJohn Johansen *
86993c98a48SJohn Johansen * TODO: once the other paths are done see if we can't refactor into a fn
870898127c3SJohn Johansen */
apparmor_bprm_creds_for_exec(struct linux_binprm * bprm)871b8bff599SEric W. Biederman int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
872898127c3SJohn Johansen {
873f175221aSJohn Johansen struct aa_task_ctx *ctx;
87493c98a48SJohn Johansen struct aa_label *label, *new = NULL;
875*690f33e1SJohn Johansen const struct cred *subj_cred;
87693c98a48SJohn Johansen struct aa_profile *profile;
877898127c3SJohn Johansen char *buffer = NULL;
87893c98a48SJohn Johansen const char *info = NULL;
87993c98a48SJohn Johansen int error = 0;
88093c98a48SJohn Johansen bool unsafe = false;
881e67fe633SChristian Brauner vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(bprm->file),
8823cee6079SChristian Brauner file_inode(bprm->file));
883898127c3SJohn Johansen struct path_cond cond = {
8845e26a01eSChristian Brauner vfsuid_into_kuid(vfsuid),
885496ad9aaSAl Viro file_inode(bprm->file)->i_mode
886898127c3SJohn Johansen };
887898127c3SJohn Johansen
888*690f33e1SJohn Johansen subj_cred = current_cred();
889de62de59SJohn Johansen ctx = task_ctx(current);
890d9087c49SJohn Johansen AA_BUG(!cred_label(bprm->cred));
891f175221aSJohn Johansen AA_BUG(!ctx);
892898127c3SJohn Johansen
893d9087c49SJohn Johansen label = aa_get_newest_label(cred_label(bprm->cred));
8944227c333SJohn Johansen
8959fcf78ccSJohn Johansen /*
8969fcf78ccSJohn Johansen * Detect no new privs being set, and store the label it
8979fcf78ccSJohn Johansen * occurred under. Ideally this would happen when nnp
8989fcf78ccSJohn Johansen * is set but there isn't a good way to do that yet.
8999fcf78ccSJohn Johansen *
9009fcf78ccSJohn Johansen * Testing for unconfined must be done before the subset test
9019fcf78ccSJohn Johansen */
9029fcf78ccSJohn Johansen if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && !unconfined(label) &&
9039fcf78ccSJohn Johansen !ctx->nnp)
9049fcf78ccSJohn Johansen ctx->nnp = aa_get_label(label);
9059fcf78ccSJohn Johansen
9064227c333SJohn Johansen /* buffer freed below, name is pointer into buffer */
907341c1fdaSJohn Johansen buffer = aa_get_buffer(false);
908df323337SSebastian Andrzej Siewior if (!buffer) {
909df323337SSebastian Andrzej Siewior error = -ENOMEM;
910df323337SSebastian Andrzej Siewior goto done;
911df323337SSebastian Andrzej Siewior }
912df323337SSebastian Andrzej Siewior
91393c98a48SJohn Johansen /* Test for onexec first as onexec override other x transitions. */
914f175221aSJohn Johansen if (ctx->onexec)
915*690f33e1SJohn Johansen new = handle_onexec(subj_cred, label, ctx->onexec, ctx->token,
91693c98a48SJohn Johansen bprm, buffer, &cond, &unsafe);
917898127c3SJohn Johansen else
9188ac2ca32SSebastian Andrzej Siewior new = fn_label_build(label, profile, GFP_KERNEL,
919*690f33e1SJohn Johansen profile_transition(subj_cred, profile, bprm,
920*690f33e1SJohn Johansen buffer,
92193c98a48SJohn Johansen &cond, &unsafe));
922898127c3SJohn Johansen
92393c98a48SJohn Johansen AA_BUG(!new);
92493c98a48SJohn Johansen if (IS_ERR(new)) {
92593c98a48SJohn Johansen error = PTR_ERR(new);
92693c98a48SJohn Johansen goto done;
92793c98a48SJohn Johansen } else if (!new) {
928898127c3SJohn Johansen error = -ENOMEM;
92993c98a48SJohn Johansen goto done;
930c29bceb3SJohn Johansen }
931c29bceb3SJohn Johansen
9329fcf78ccSJohn Johansen /* Policy has specified a domain transitions. If no_new_privs and
9339fcf78ccSJohn Johansen * confined ensure the transition is to confinement that is subset
9349fcf78ccSJohn Johansen * of the confinement when the task entered no new privs.
9359fcf78ccSJohn Johansen *
9369fcf78ccSJohn Johansen * NOTE: Domain transitions from unconfined and to stacked
9379fcf78ccSJohn Johansen * subsets are allowed even when no_new_privs is set because this
9389fcf78ccSJohn Johansen * aways results in a further reduction of permissions.
9399fcf78ccSJohn Johansen */
9409fcf78ccSJohn Johansen if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) &&
9413ed4aaa9SJohn Johansen !unconfined(label) &&
9423ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(new, ctx->nnp)) {
9439fcf78ccSJohn Johansen error = -EPERM;
9449fcf78ccSJohn Johansen info = "no new privs";
9459fcf78ccSJohn Johansen goto audit;
9469fcf78ccSJohn Johansen }
947898127c3SJohn Johansen
948898127c3SJohn Johansen if (bprm->unsafe & LSM_UNSAFE_SHARE) {
949898127c3SJohn Johansen /* FIXME: currently don't mediate shared state */
950898127c3SJohn Johansen ;
951898127c3SJohn Johansen }
952898127c3SJohn Johansen
95393c98a48SJohn Johansen if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) {
95493c98a48SJohn Johansen /* TODO: test needs to be profile of label to new */
955*690f33e1SJohn Johansen error = may_change_ptraced_domain(bprm->cred, new, &info);
956f7da2de0SJohn Johansen if (error)
957898127c3SJohn Johansen goto audit;
958898127c3SJohn Johansen }
959898127c3SJohn Johansen
96093c98a48SJohn Johansen if (unsafe) {
96193c98a48SJohn Johansen if (DEBUG_ON) {
96293c98a48SJohn Johansen dbg_printk("scrubbing environment variables for %s "
96393c98a48SJohn Johansen "label=", bprm->filename);
9648ac2ca32SSebastian Andrzej Siewior aa_label_printk(new, GFP_KERNEL);
96593c98a48SJohn Johansen dbg_printk("\n");
96693c98a48SJohn Johansen }
967993b3ab0SKees Cook bprm->secureexec = 1;
968898127c3SJohn Johansen }
96993c98a48SJohn Johansen
97093c98a48SJohn Johansen if (label->proxy != new->proxy) {
97193c98a48SJohn Johansen /* when transitioning clear unsafe personality bits */
97293c98a48SJohn Johansen if (DEBUG_ON) {
97393c98a48SJohn Johansen dbg_printk("apparmor: clearing unsafe personality "
97493c98a48SJohn Johansen "bits. %s label=", bprm->filename);
9758ac2ca32SSebastian Andrzej Siewior aa_label_printk(new, GFP_KERNEL);
97693c98a48SJohn Johansen dbg_printk("\n");
97793c98a48SJohn Johansen }
978898127c3SJohn Johansen bprm->per_clear |= PER_CLEAR_ON_SETID;
97993c98a48SJohn Johansen }
980d9087c49SJohn Johansen aa_put_label(cred_label(bprm->cred));
981d9087c49SJohn Johansen /* transfer reference, released when cred is freed */
98269b5a44aSCasey Schaufler set_cred_label(bprm->cred, new);
983898127c3SJohn Johansen
98493c98a48SJohn Johansen done:
985637f688dSJohn Johansen aa_put_label(label);
986df323337SSebastian Andrzej Siewior aa_put_buffer(buffer);
987898127c3SJohn Johansen
988898127c3SJohn Johansen return error;
98993c98a48SJohn Johansen
99093c98a48SJohn Johansen audit:
99193c98a48SJohn Johansen error = fn_for_each(label, profile,
992*690f33e1SJohn Johansen aa_audit_file(current_cred(), profile, &nullperms,
993*690f33e1SJohn Johansen OP_EXEC, MAY_EXEC,
99493c98a48SJohn Johansen bprm->filename, NULL, new,
9955e26a01eSChristian Brauner vfsuid_into_kuid(vfsuid), info, error));
99693c98a48SJohn Johansen aa_put_label(new);
99793c98a48SJohn Johansen goto done;
998898127c3SJohn Johansen }
999898127c3SJohn Johansen
1000898127c3SJohn Johansen /*
1001898127c3SJohn Johansen * Functions for self directed profile change
1002898127c3SJohn Johansen */
1003898127c3SJohn Johansen
100489dbf196SJohn Johansen
100589dbf196SJohn Johansen /* helper fn for change_hat
1006898127c3SJohn Johansen *
100789dbf196SJohn Johansen * Returns: label for hat transition OR ERR_PTR. Does NOT return NULL
1008898127c3SJohn Johansen */
build_change_hat(const struct cred * subj_cred,struct aa_profile * profile,const char * name,bool sibling)1009*690f33e1SJohn Johansen static struct aa_label *build_change_hat(const struct cred *subj_cred,
1010*690f33e1SJohn Johansen struct aa_profile *profile,
101189dbf196SJohn Johansen const char *name, bool sibling)
1012898127c3SJohn Johansen {
101389dbf196SJohn Johansen struct aa_profile *root, *hat = NULL;
101489dbf196SJohn Johansen const char *info = NULL;
101589dbf196SJohn Johansen int error = 0;
101689dbf196SJohn Johansen
101789dbf196SJohn Johansen if (sibling && PROFILE_IS_HAT(profile)) {
101889dbf196SJohn Johansen root = aa_get_profile_rcu(&profile->parent);
101989dbf196SJohn Johansen } else if (!sibling && !PROFILE_IS_HAT(profile)) {
102089dbf196SJohn Johansen root = aa_get_profile(profile);
102189dbf196SJohn Johansen } else {
102289dbf196SJohn Johansen info = "conflicting target types";
102389dbf196SJohn Johansen error = -EPERM;
102489dbf196SJohn Johansen goto audit;
102589dbf196SJohn Johansen }
102689dbf196SJohn Johansen
102789dbf196SJohn Johansen hat = aa_find_child(root, name);
102889dbf196SJohn Johansen if (!hat) {
102989dbf196SJohn Johansen error = -ENOENT;
103089dbf196SJohn Johansen if (COMPLAIN_MODE(profile)) {
103158f89ce5SJohn Johansen hat = aa_new_learning_profile(profile, true, name,
103289dbf196SJohn Johansen GFP_KERNEL);
103389dbf196SJohn Johansen if (!hat) {
103489dbf196SJohn Johansen info = "failed null profile create";
103589dbf196SJohn Johansen error = -ENOMEM;
103689dbf196SJohn Johansen }
103789dbf196SJohn Johansen }
103889dbf196SJohn Johansen }
103989dbf196SJohn Johansen aa_put_profile(root);
104089dbf196SJohn Johansen
104189dbf196SJohn Johansen audit:
1042*690f33e1SJohn Johansen aa_audit_file(subj_cred, profile, &nullperms, OP_CHANGE_HAT,
1043*690f33e1SJohn Johansen AA_MAY_CHANGEHAT,
104489dbf196SJohn Johansen name, hat ? hat->base.hname : NULL,
104524b87a16SJohn Johansen hat ? &hat->label : NULL, GLOBAL_ROOT_UID, info,
104689dbf196SJohn Johansen error);
104789dbf196SJohn Johansen if (!hat || (error && error != -ENOENT))
104889dbf196SJohn Johansen return ERR_PTR(error);
104989dbf196SJohn Johansen /* if hat && error - complain mode, already audited and we adjust for
105089dbf196SJohn Johansen * complain mode allow by returning hat->label
105189dbf196SJohn Johansen */
105289dbf196SJohn Johansen return &hat->label;
105389dbf196SJohn Johansen }
105489dbf196SJohn Johansen
105589dbf196SJohn Johansen /* helper fn for changing into a hat
105689dbf196SJohn Johansen *
105789dbf196SJohn Johansen * Returns: label for hat transition or ERR_PTR. Does not return NULL
105889dbf196SJohn Johansen */
change_hat(const struct cred * subj_cred,struct aa_label * label,const char * hats[],int count,int flags)1059*690f33e1SJohn Johansen static struct aa_label *change_hat(const struct cred *subj_cred,
1060*690f33e1SJohn Johansen struct aa_label *label, const char *hats[],
106189dbf196SJohn Johansen int count, int flags)
106289dbf196SJohn Johansen {
106389dbf196SJohn Johansen struct aa_profile *profile, *root, *hat = NULL;
106489dbf196SJohn Johansen struct aa_label *new;
106589dbf196SJohn Johansen struct label_it it;
106689dbf196SJohn Johansen bool sibling = false;
106789dbf196SJohn Johansen const char *name, *info = NULL;
106889dbf196SJohn Johansen int i, error;
106989dbf196SJohn Johansen
107089dbf196SJohn Johansen AA_BUG(!label);
107189dbf196SJohn Johansen AA_BUG(!hats);
107289dbf196SJohn Johansen AA_BUG(count < 1);
107389dbf196SJohn Johansen
107489dbf196SJohn Johansen if (PROFILE_IS_HAT(labels_profile(label)))
107589dbf196SJohn Johansen sibling = true;
107689dbf196SJohn Johansen
107789dbf196SJohn Johansen /*find first matching hat */
107889dbf196SJohn Johansen for (i = 0; i < count && !hat; i++) {
107989dbf196SJohn Johansen name = hats[i];
108089dbf196SJohn Johansen label_for_each_in_ns(it, labels_ns(label), label, profile) {
108189dbf196SJohn Johansen if (sibling && PROFILE_IS_HAT(profile)) {
108289dbf196SJohn Johansen root = aa_get_profile_rcu(&profile->parent);
108389dbf196SJohn Johansen } else if (!sibling && !PROFILE_IS_HAT(profile)) {
108489dbf196SJohn Johansen root = aa_get_profile(profile);
108589dbf196SJohn Johansen } else { /* conflicting change type */
108689dbf196SJohn Johansen info = "conflicting targets types";
108789dbf196SJohn Johansen error = -EPERM;
108889dbf196SJohn Johansen goto fail;
108989dbf196SJohn Johansen }
109089dbf196SJohn Johansen hat = aa_find_child(root, name);
109189dbf196SJohn Johansen aa_put_profile(root);
109289dbf196SJohn Johansen if (!hat) {
109389dbf196SJohn Johansen if (!COMPLAIN_MODE(profile))
109489dbf196SJohn Johansen goto outer_continue;
109589dbf196SJohn Johansen /* complain mode succeed as if hat */
109689dbf196SJohn Johansen } else if (!PROFILE_IS_HAT(hat)) {
109789dbf196SJohn Johansen info = "target not hat";
109889dbf196SJohn Johansen error = -EPERM;
109989dbf196SJohn Johansen aa_put_profile(hat);
110089dbf196SJohn Johansen goto fail;
110189dbf196SJohn Johansen }
110289dbf196SJohn Johansen aa_put_profile(hat);
110389dbf196SJohn Johansen }
110489dbf196SJohn Johansen /* found a hat for all profiles in ns */
110589dbf196SJohn Johansen goto build;
110689dbf196SJohn Johansen outer_continue:
110789dbf196SJohn Johansen ;
110889dbf196SJohn Johansen }
110989dbf196SJohn Johansen /* no hats that match, find appropriate error
111089dbf196SJohn Johansen *
111189dbf196SJohn Johansen * In complain mode audit of the failure is based off of the first
111289dbf196SJohn Johansen * hat supplied. This is done due how userspace interacts with
111389dbf196SJohn Johansen * change_hat.
111489dbf196SJohn Johansen */
111589dbf196SJohn Johansen name = NULL;
111689dbf196SJohn Johansen label_for_each_in_ns(it, labels_ns(label), label, profile) {
111789dbf196SJohn Johansen if (!list_empty(&profile->base.profiles)) {
111889dbf196SJohn Johansen info = "hat not found";
111989dbf196SJohn Johansen error = -ENOENT;
112089dbf196SJohn Johansen goto fail;
112189dbf196SJohn Johansen }
112289dbf196SJohn Johansen }
112389dbf196SJohn Johansen info = "no hats defined";
112489dbf196SJohn Johansen error = -ECHILD;
112589dbf196SJohn Johansen
112689dbf196SJohn Johansen fail:
112789dbf196SJohn Johansen label_for_each_in_ns(it, labels_ns(label), label, profile) {
112889dbf196SJohn Johansen /*
112989dbf196SJohn Johansen * no target as it has failed to be found or built
113089dbf196SJohn Johansen *
113189dbf196SJohn Johansen * change_hat uses probing and should not log failures
113289dbf196SJohn Johansen * related to missing hats
113389dbf196SJohn Johansen */
113489dbf196SJohn Johansen /* TODO: get rid of GLOBAL_ROOT_UID */
113589dbf196SJohn Johansen if (count > 1 || COMPLAIN_MODE(profile)) {
1136*690f33e1SJohn Johansen aa_audit_file(subj_cred, profile, &nullperms,
1137*690f33e1SJohn Johansen OP_CHANGE_HAT,
113889dbf196SJohn Johansen AA_MAY_CHANGEHAT, name, NULL, NULL,
113989dbf196SJohn Johansen GLOBAL_ROOT_UID, info, error);
114089dbf196SJohn Johansen }
114189dbf196SJohn Johansen }
114289dbf196SJohn Johansen return ERR_PTR(error);
114389dbf196SJohn Johansen
114489dbf196SJohn Johansen build:
114589dbf196SJohn Johansen new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
1146*690f33e1SJohn Johansen build_change_hat(subj_cred, profile, name,
1147*690f33e1SJohn Johansen sibling),
114889dbf196SJohn Johansen aa_get_label(&profile->label));
114989dbf196SJohn Johansen if (!new) {
115089dbf196SJohn Johansen info = "label build failed";
115189dbf196SJohn Johansen error = -ENOMEM;
115289dbf196SJohn Johansen goto fail;
115389dbf196SJohn Johansen } /* else if (IS_ERR) build_change_hat has logged error so return new */
115489dbf196SJohn Johansen
115589dbf196SJohn Johansen return new;
1156898127c3SJohn Johansen }
1157898127c3SJohn Johansen
1158898127c3SJohn Johansen /**
1159898127c3SJohn Johansen * aa_change_hat - change hat to/from subprofile
1160898127c3SJohn Johansen * @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0)
1161898127c3SJohn Johansen * @count: number of hat names in @hats
1162898127c3SJohn Johansen * @token: magic value to validate the hat change
1163df8073c6SJohn Johansen * @flags: flags affecting behavior of the change
1164898127c3SJohn Johansen *
116589dbf196SJohn Johansen * Returns %0 on success, error otherwise.
116689dbf196SJohn Johansen *
1167898127c3SJohn Johansen * Change to the first profile specified in @hats that exists, and store
1168898127c3SJohn Johansen * the @hat_magic in the current task context. If the count == 0 and the
1169898127c3SJohn Johansen * @token matches that stored in the current task context, return to the
1170898127c3SJohn Johansen * top level profile.
1171898127c3SJohn Johansen *
117289dbf196SJohn Johansen * change_hat only applies to profiles in the current ns, and each profile
117389dbf196SJohn Johansen * in the ns must make the same transition otherwise change_hat will fail.
1174898127c3SJohn Johansen */
aa_change_hat(const char * hats[],int count,u64 token,int flags)1175df8073c6SJohn Johansen int aa_change_hat(const char *hats[], int count, u64 token, int flags)
1176898127c3SJohn Johansen {
1177*690f33e1SJohn Johansen const struct cred *subj_cred;
11789fcf78ccSJohn Johansen struct aa_task_ctx *ctx = task_ctx(current);
117989dbf196SJohn Johansen struct aa_label *label, *previous, *new = NULL, *target = NULL;
118089dbf196SJohn Johansen struct aa_profile *profile;
11812d679f3cSJohn Johansen struct aa_perms perms = {};
118289dbf196SJohn Johansen const char *info = NULL;
1183898127c3SJohn Johansen int error = 0;
1184898127c3SJohn Johansen
1185898127c3SJohn Johansen /* released below */
1186*690f33e1SJohn Johansen subj_cred = get_current_cred();
1187*690f33e1SJohn Johansen label = aa_get_newest_cred_label(subj_cred);
1188f175221aSJohn Johansen previous = aa_get_newest_label(ctx->previous);
1189898127c3SJohn Johansen
11909fcf78ccSJohn Johansen /*
11919fcf78ccSJohn Johansen * Detect no new privs being set, and store the label it
11929fcf78ccSJohn Johansen * occurred under. Ideally this would happen when nnp
11939fcf78ccSJohn Johansen * is set but there isn't a good way to do that yet.
11949fcf78ccSJohn Johansen *
11959fcf78ccSJohn Johansen * Testing for unconfined must be done before the subset test
11969fcf78ccSJohn Johansen */
11979fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp)
11989fcf78ccSJohn Johansen ctx->nnp = aa_get_label(label);
11999fcf78ccSJohn Johansen
1200637f688dSJohn Johansen if (unconfined(label)) {
120189dbf196SJohn Johansen info = "unconfined can not change_hat";
1202898127c3SJohn Johansen error = -EPERM;
120389dbf196SJohn Johansen goto fail;
1204898127c3SJohn Johansen }
1205898127c3SJohn Johansen
1206898127c3SJohn Johansen if (count) {
1207*690f33e1SJohn Johansen new = change_hat(subj_cred, label, hats, count, flags);
120889dbf196SJohn Johansen AA_BUG(!new);
120989dbf196SJohn Johansen if (IS_ERR(new)) {
121089dbf196SJohn Johansen error = PTR_ERR(new);
121189dbf196SJohn Johansen new = NULL;
121289dbf196SJohn Johansen /* already audited */
1213898127c3SJohn Johansen goto out;
1214898127c3SJohn Johansen }
1215898127c3SJohn Johansen
1216*690f33e1SJohn Johansen /* target cred is the same as current except new label */
1217*690f33e1SJohn Johansen error = may_change_ptraced_domain(subj_cred, new, &info);
121889dbf196SJohn Johansen if (error)
121989dbf196SJohn Johansen goto fail;
1220898127c3SJohn Johansen
12219fcf78ccSJohn Johansen /*
12229fcf78ccSJohn Johansen * no new privs prevents domain transitions that would
12239fcf78ccSJohn Johansen * reduce restrictions.
12249fcf78ccSJohn Johansen */
12259fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) &&
12263ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(new, ctx->nnp)) {
12279fcf78ccSJohn Johansen /* not an apparmor denial per se, so don't log it */
12289fcf78ccSJohn Johansen AA_DEBUG("no_new_privs - change_hat denied");
12299fcf78ccSJohn Johansen error = -EPERM;
12309fcf78ccSJohn Johansen goto out;
12319fcf78ccSJohn Johansen }
12329fcf78ccSJohn Johansen
123389dbf196SJohn Johansen if (flags & AA_CHANGE_TEST)
123489dbf196SJohn Johansen goto out;
1235898127c3SJohn Johansen
123689dbf196SJohn Johansen target = new;
123789dbf196SJohn Johansen error = aa_set_current_hat(new, token);
1238898127c3SJohn Johansen if (error == -EACCES)
1239898127c3SJohn Johansen /* kill task in case of brute force attacks */
124089dbf196SJohn Johansen goto kill;
124189dbf196SJohn Johansen } else if (previous && !(flags & AA_CHANGE_TEST)) {
12429fcf78ccSJohn Johansen /*
12439fcf78ccSJohn Johansen * no new privs prevents domain transitions that would
12449fcf78ccSJohn Johansen * reduce restrictions.
12459fcf78ccSJohn Johansen */
12469fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) &&
12473ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(previous, ctx->nnp)) {
12489fcf78ccSJohn Johansen /* not an apparmor denial per se, so don't log it */
12499fcf78ccSJohn Johansen AA_DEBUG("no_new_privs - change_hat denied");
12509fcf78ccSJohn Johansen error = -EPERM;
12519fcf78ccSJohn Johansen goto out;
12529fcf78ccSJohn Johansen }
12539fcf78ccSJohn Johansen
125489dbf196SJohn Johansen /* Return to saved label. Kill task if restore fails
1255898127c3SJohn Johansen * to avoid brute force attacks
1256898127c3SJohn Johansen */
125789dbf196SJohn Johansen target = previous;
1258637f688dSJohn Johansen error = aa_restore_previous_label(token);
125989dbf196SJohn Johansen if (error) {
126089dbf196SJohn Johansen if (error == -EACCES)
126189dbf196SJohn Johansen goto kill;
126289dbf196SJohn Johansen goto fail;
126389dbf196SJohn Johansen }
126489dbf196SJohn Johansen } /* else ignore @flags && restores when there is no saved profile */
1265898127c3SJohn Johansen
1266898127c3SJohn Johansen out:
126789dbf196SJohn Johansen aa_put_label(new);
126889dbf196SJohn Johansen aa_put_label(previous);
1269637f688dSJohn Johansen aa_put_label(label);
1270*690f33e1SJohn Johansen put_cred(subj_cred);
1271898127c3SJohn Johansen
1272898127c3SJohn Johansen return error;
127389dbf196SJohn Johansen
127489dbf196SJohn Johansen kill:
127589dbf196SJohn Johansen info = "failed token match";
127689dbf196SJohn Johansen perms.kill = AA_MAY_CHANGEHAT;
127789dbf196SJohn Johansen
127889dbf196SJohn Johansen fail:
127989dbf196SJohn Johansen fn_for_each_in_ns(label, profile,
1280*690f33e1SJohn Johansen aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT,
128189dbf196SJohn Johansen AA_MAY_CHANGEHAT, NULL, NULL, target,
128289dbf196SJohn Johansen GLOBAL_ROOT_UID, info, error));
128389dbf196SJohn Johansen
128489dbf196SJohn Johansen goto out;
1285898127c3SJohn Johansen }
1286898127c3SJohn Johansen
128789dbf196SJohn Johansen
change_profile_perms_wrapper(const char * op,const char * name,const struct cred * subj_cred,struct aa_profile * profile,struct aa_label * target,bool stack,u32 request,struct aa_perms * perms)1288e00b02bbSJohn Johansen static int change_profile_perms_wrapper(const char *op, const char *name,
1289*690f33e1SJohn Johansen const struct cred *subj_cred,
1290e00b02bbSJohn Johansen struct aa_profile *profile,
1291e00b02bbSJohn Johansen struct aa_label *target, bool stack,
1292e00b02bbSJohn Johansen u32 request, struct aa_perms *perms)
1293e00b02bbSJohn Johansen {
12941ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
12951ad22fccSJohn Johansen typeof(*rules), list);
1296e00b02bbSJohn Johansen const char *info = NULL;
1297e00b02bbSJohn Johansen int error = 0;
1298e00b02bbSJohn Johansen
1299e00b02bbSJohn Johansen if (!error)
1300e00b02bbSJohn Johansen error = change_profile_perms(profile, target, stack, request,
1301217af7e2SJohn Johansen rules->file.start[AA_CLASS_FILE],
130253bdc46fSJohn Johansen perms);
1303e00b02bbSJohn Johansen if (error)
1304*690f33e1SJohn Johansen error = aa_audit_file(subj_cred, profile, perms, op, request,
1305*690f33e1SJohn Johansen name,
1306e00b02bbSJohn Johansen NULL, target, GLOBAL_ROOT_UID, info,
1307e00b02bbSJohn Johansen error);
1308e00b02bbSJohn Johansen
1309e00b02bbSJohn Johansen return error;
1310e00b02bbSJohn Johansen }
131189dbf196SJohn Johansen
1312898127c3SJohn Johansen /**
1313898127c3SJohn Johansen * aa_change_profile - perform a one-way profile transition
1314aa9a39adSJohn Johansen * @fqname: name of profile may include namespace (NOT NULL)
1315df8073c6SJohn Johansen * @flags: flags affecting change behavior
1316898127c3SJohn Johansen *
1317898127c3SJohn Johansen * Change to new profile @name. Unlike with hats, there is no way
1318898127c3SJohn Johansen * to change back. If @name isn't specified the current profile name is
1319898127c3SJohn Johansen * used.
1320898127c3SJohn Johansen * If @onexec then the transition is delayed until
1321898127c3SJohn Johansen * the next exec.
1322898127c3SJohn Johansen *
1323898127c3SJohn Johansen * Returns %0 on success, error otherwise.
1324898127c3SJohn Johansen */
aa_change_profile(const char * fqname,int flags)1325df8073c6SJohn Johansen int aa_change_profile(const char *fqname, int flags)
1326898127c3SJohn Johansen {
1327e00b02bbSJohn Johansen struct aa_label *label, *new = NULL, *target = NULL;
1328e00b02bbSJohn Johansen struct aa_profile *profile;
13292d679f3cSJohn Johansen struct aa_perms perms = {};
1330e00b02bbSJohn Johansen const char *info = NULL;
1331e00b02bbSJohn Johansen const char *auditname = fqname; /* retain leading & if stack */
1332e00b02bbSJohn Johansen bool stack = flags & AA_CHANGE_STACK;
13339fcf78ccSJohn Johansen struct aa_task_ctx *ctx = task_ctx(current);
1334*690f33e1SJohn Johansen const struct cred *subj_cred = get_current_cred();
133547f6e5ccSJohn Johansen int error = 0;
1336e00b02bbSJohn Johansen char *op;
1337898127c3SJohn Johansen u32 request;
1338898127c3SJohn Johansen
13399fcf78ccSJohn Johansen label = aa_get_current_label();
13409fcf78ccSJohn Johansen
13419fcf78ccSJohn Johansen /*
13429fcf78ccSJohn Johansen * Detect no new privs being set, and store the label it
13439fcf78ccSJohn Johansen * occurred under. Ideally this would happen when nnp
13449fcf78ccSJohn Johansen * is set but there isn't a good way to do that yet.
13459fcf78ccSJohn Johansen *
13469fcf78ccSJohn Johansen * Testing for unconfined must be done before the subset test
13479fcf78ccSJohn Johansen */
13489fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp)
13499fcf78ccSJohn Johansen ctx->nnp = aa_get_label(label);
13509fcf78ccSJohn Johansen
1351aa9a39adSJohn Johansen if (!fqname || !*fqname) {
1352a0b845ffSXiyu Yang aa_put_label(label);
1353aa9a39adSJohn Johansen AA_DEBUG("no profile name");
1354898127c3SJohn Johansen return -EINVAL;
1355aa9a39adSJohn Johansen }
1356898127c3SJohn Johansen
1357df8073c6SJohn Johansen if (flags & AA_CHANGE_ONEXEC) {
1358898127c3SJohn Johansen request = AA_MAY_ONEXEC;
1359e00b02bbSJohn Johansen if (stack)
1360e00b02bbSJohn Johansen op = OP_STACK_ONEXEC;
1361e00b02bbSJohn Johansen else
1362898127c3SJohn Johansen op = OP_CHANGE_ONEXEC;
1363898127c3SJohn Johansen } else {
1364898127c3SJohn Johansen request = AA_MAY_CHANGE_PROFILE;
1365e00b02bbSJohn Johansen if (stack)
1366e00b02bbSJohn Johansen op = OP_STACK;
1367e00b02bbSJohn Johansen else
1368898127c3SJohn Johansen op = OP_CHANGE_PROFILE;
1369898127c3SJohn Johansen }
1370898127c3SJohn Johansen
1371e00b02bbSJohn Johansen if (*fqname == '&') {
1372e00b02bbSJohn Johansen stack = true;
1373e00b02bbSJohn Johansen /* don't have label_parse() do stacking */
1374e00b02bbSJohn Johansen fqname++;
1375c29bceb3SJohn Johansen }
1376e00b02bbSJohn Johansen target = aa_label_parse(label, fqname, GFP_KERNEL, true, false);
1377e00b02bbSJohn Johansen if (IS_ERR(target)) {
1378e00b02bbSJohn Johansen struct aa_profile *tprofile;
1379c29bceb3SJohn Johansen
1380e00b02bbSJohn Johansen info = "label not found";
1381e00b02bbSJohn Johansen error = PTR_ERR(target);
1382e00b02bbSJohn Johansen target = NULL;
1383e00b02bbSJohn Johansen /*
1384e00b02bbSJohn Johansen * TODO: fixme using labels_profile is not right - do profile
1385e00b02bbSJohn Johansen * per complain profile
1386e00b02bbSJohn Johansen */
1387df8073c6SJohn Johansen if ((flags & AA_CHANGE_TEST) ||
1388e00b02bbSJohn Johansen !COMPLAIN_MODE(labels_profile(label)))
1389898127c3SJohn Johansen goto audit;
1390898127c3SJohn Johansen /* released below */
139158f89ce5SJohn Johansen tprofile = aa_new_learning_profile(labels_profile(label), false,
1392e00b02bbSJohn Johansen fqname, GFP_KERNEL);
1393e00b02bbSJohn Johansen if (!tprofile) {
1394898127c3SJohn Johansen info = "failed null profile create";
1395898127c3SJohn Johansen error = -ENOMEM;
1396898127c3SJohn Johansen goto audit;
1397898127c3SJohn Johansen }
1398e00b02bbSJohn Johansen target = &tprofile->label;
1399e00b02bbSJohn Johansen goto check;
1400898127c3SJohn Johansen }
1401898127c3SJohn Johansen
1402e00b02bbSJohn Johansen /*
1403e00b02bbSJohn Johansen * self directed transitions only apply to current policy ns
1404e00b02bbSJohn Johansen * TODO: currently requiring perms for stacking and straight change
1405e00b02bbSJohn Johansen * stacking doesn't strictly need this. Determine how much
1406e00b02bbSJohn Johansen * we want to loosen this restriction for stacking
1407e00b02bbSJohn Johansen *
1408e00b02bbSJohn Johansen * if (!stack) {
1409e00b02bbSJohn Johansen */
1410e00b02bbSJohn Johansen error = fn_for_each_in_ns(label, profile,
1411e00b02bbSJohn Johansen change_profile_perms_wrapper(op, auditname,
1412*690f33e1SJohn Johansen subj_cred,
1413e00b02bbSJohn Johansen profile, target, stack,
1414e00b02bbSJohn Johansen request, &perms));
1415e00b02bbSJohn Johansen if (error)
1416e00b02bbSJohn Johansen /* auditing done in change_profile_perms_wrapper */
1417e00b02bbSJohn Johansen goto out;
1418aa9a39adSJohn Johansen
1419e00b02bbSJohn Johansen /* } */
1420e00b02bbSJohn Johansen
1421e00b02bbSJohn Johansen check:
1422898127c3SJohn Johansen /* check if tracing task is allowed to trace target domain */
1423*690f33e1SJohn Johansen error = may_change_ptraced_domain(subj_cred, target, &info);
1424e00b02bbSJohn Johansen if (error && !fn_for_each_in_ns(label, profile,
1425e00b02bbSJohn Johansen COMPLAIN_MODE(profile)))
1426e00b02bbSJohn Johansen goto audit;
1427e00b02bbSJohn Johansen
1428e00b02bbSJohn Johansen /* TODO: add permission check to allow this
1429e00b02bbSJohn Johansen * if ((flags & AA_CHANGE_ONEXEC) && !current_is_single_threaded()) {
1430e00b02bbSJohn Johansen * info = "not a single threaded task";
1431e00b02bbSJohn Johansen * error = -EACCES;
1432e00b02bbSJohn Johansen * goto audit;
1433e00b02bbSJohn Johansen * }
1434e00b02bbSJohn Johansen */
1435e00b02bbSJohn Johansen if (flags & AA_CHANGE_TEST)
1436e00b02bbSJohn Johansen goto out;
1437e00b02bbSJohn Johansen
14389fcf78ccSJohn Johansen /* stacking is always a subset, so only check the nonstack case */
14399fcf78ccSJohn Johansen if (!stack) {
14409fcf78ccSJohn Johansen new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
14419fcf78ccSJohn Johansen aa_get_label(target),
14429fcf78ccSJohn Johansen aa_get_label(&profile->label));
14439fcf78ccSJohn Johansen /*
14449fcf78ccSJohn Johansen * no new privs prevents domain transitions that would
14459fcf78ccSJohn Johansen * reduce restrictions.
14469fcf78ccSJohn Johansen */
14479fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) &&
14483ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(new, ctx->nnp)) {
14499fcf78ccSJohn Johansen /* not an apparmor denial per se, so don't log it */
14509fcf78ccSJohn Johansen AA_DEBUG("no_new_privs - change_hat denied");
14519fcf78ccSJohn Johansen error = -EPERM;
14529fcf78ccSJohn Johansen goto out;
14539fcf78ccSJohn Johansen }
14549fcf78ccSJohn Johansen }
14559fcf78ccSJohn Johansen
1456e00b02bbSJohn Johansen if (!(flags & AA_CHANGE_ONEXEC)) {
1457e00b02bbSJohn Johansen /* only transition profiles in the current ns */
1458e00b02bbSJohn Johansen if (stack)
1459e00b02bbSJohn Johansen new = aa_label_merge(label, target, GFP_KERNEL);
1460e00b02bbSJohn Johansen if (IS_ERR_OR_NULL(new)) {
1461e00b02bbSJohn Johansen info = "failed to build target label";
1462d6d478aeSJohn Johansen if (!new)
1463d6d478aeSJohn Johansen error = -ENOMEM;
1464d6d478aeSJohn Johansen else
1465e00b02bbSJohn Johansen error = PTR_ERR(new);
1466e00b02bbSJohn Johansen new = NULL;
1467e00b02bbSJohn Johansen perms.allow = 0;
1468898127c3SJohn Johansen goto audit;
1469898127c3SJohn Johansen }
1470e00b02bbSJohn Johansen error = aa_replace_current_label(new);
14719fcf78ccSJohn Johansen } else {
14729fcf78ccSJohn Johansen if (new) {
14739fcf78ccSJohn Johansen aa_put_label(new);
14749fcf78ccSJohn Johansen new = NULL;
14759fcf78ccSJohn Johansen }
14769fcf78ccSJohn Johansen
1477e00b02bbSJohn Johansen /* full transition will be built in exec path */
1478e00b02bbSJohn Johansen error = aa_set_current_onexec(target, stack);
14799fcf78ccSJohn Johansen }
1480898127c3SJohn Johansen
1481898127c3SJohn Johansen audit:
1482e00b02bbSJohn Johansen error = fn_for_each_in_ns(label, profile,
1483*690f33e1SJohn Johansen aa_audit_file(subj_cred,
1484*690f33e1SJohn Johansen profile, &perms, op, request, auditname,
1485e00b02bbSJohn Johansen NULL, new ? new : target,
1486e00b02bbSJohn Johansen GLOBAL_ROOT_UID, info, error));
1487898127c3SJohn Johansen
1488e00b02bbSJohn Johansen out:
1489e00b02bbSJohn Johansen aa_put_label(new);
1490e00b02bbSJohn Johansen aa_put_label(target);
1491637f688dSJohn Johansen aa_put_label(label);
1492*690f33e1SJohn Johansen put_cred(subj_cred);
1493898127c3SJohn Johansen
1494898127c3SJohn Johansen return error;
1495898127c3SJohn Johansen }
1496