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
34cd269ca9SYang Li * @to_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)4390c436a6SJohn Johansen static int may_change_ptraced_domain(const struct cred *to_cred,
4490c436a6SJohn 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;
4990c436a6SJohn Johansen const struct cred *tracer_cred = NULL;
5090c436a6SJohn Johansen
51898127c3SJohn Johansen int error = 0;
52898127c3SJohn Johansen
53898127c3SJohn Johansen rcu_read_lock();
5451775fe7SOleg Nesterov tracer = ptrace_parent(current);
5590c436a6SJohn Johansen if (tracer) {
56898127c3SJohn Johansen /* released below */
57637f688dSJohn Johansen tracerl = aa_get_task_label(tracer);
5890c436a6SJohn Johansen tracer_cred = get_task_cred(tracer);
5990c436a6SJohn Johansen }
60898127c3SJohn Johansen /* not ptraced */
61637f688dSJohn Johansen if (!tracer || unconfined(tracerl))
62898127c3SJohn Johansen goto out;
63898127c3SJohn Johansen
6490c436a6SJohn Johansen error = aa_may_ptrace(tracer_cred, tracerl, to_cred, to_label,
6590c436a6SJohn Johansen PTRACE_MODE_ATTACH);
66898127c3SJohn Johansen
67898127c3SJohn Johansen out:
6804fdc099SJohn Johansen rcu_read_unlock();
69637f688dSJohn Johansen aa_put_label(tracerl);
7090c436a6SJohn 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
8098b824ffSJohn 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)
9698b824ffSJohn Johansen state = aa_dfa_match(rules->file->dfa, state, "&");
9793c98a48SJohn Johansen if (profile->ns == tp->ns)
9898b824ffSJohn 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);
10298b824ffSJohn Johansen state = aa_dfa_match_len(rules->file->dfa, state, ":", 1);
10398b824ffSJohn Johansen state = aa_dfa_match(rules->file->dfa, state, ns_name);
10498b824ffSJohn Johansen state = aa_dfa_match_len(rules->file->dfa, state, ":", 1);
10598b824ffSJohn 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;
15398b824ffSJohn 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 }
15898b824ffSJohn 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:
21398b824ffSJohn 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;
22298b824ffSJohn 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
27576426c9dSGaosheng Cui * @perms: Returns computed perms (NOT NULL)
276898127c3SJohn Johansen *
27793c98a48SJohn Johansen *
278898127c3SJohn Johansen * Returns: permission set
27993c98a48SJohn Johansen *
28093c98a48SJohn Johansen * currently only matches full label A//&B//&C or individual components A, B, C
28193c98a48SJohn Johansen * not arbitrary combinations. Eg. A//&B, C
282898127c3SJohn Johansen */
change_profile_perms(struct aa_profile * profile,struct aa_label * target,bool stack,u32 request,aa_state_t start,struct aa_perms * perms)28393c98a48SJohn Johansen static int change_profile_perms(struct aa_profile *profile,
28493c98a48SJohn Johansen struct aa_label *target, bool stack,
28533fc95d8SJohn Johansen u32 request, aa_state_t start,
28693c98a48SJohn Johansen struct aa_perms *perms)
28793c98a48SJohn Johansen {
28893c98a48SJohn Johansen if (profile_unconfined(profile)) {
28993c98a48SJohn Johansen perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
29093c98a48SJohn Johansen perms->audit = perms->quiet = perms->kill = 0;
29193c98a48SJohn Johansen return 0;
29293c98a48SJohn Johansen }
29393c98a48SJohn Johansen
29493c98a48SJohn Johansen /* TODO: add profile in ns screening */
29593c98a48SJohn Johansen return label_match(profile, target, stack, start, true, request, perms);
29693c98a48SJohn Johansen }
29793c98a48SJohn Johansen
298898127c3SJohn Johansen /**
2998e51f908SMatthew Garrett * aa_xattrs_match - check whether a file matches the xattrs defined in profile
3008e51f908SMatthew Garrett * @bprm: binprm struct for the process to validate
3018e51f908SMatthew Garrett * @profile: profile to match against (NOT NULL)
30273f488cdSJohn Johansen * @state: state to start match in
3038e51f908SMatthew Garrett *
3048e51f908SMatthew Garrett * Returns: number of extended attributes that matched, or < 0 on error
3058e51f908SMatthew Garrett */
aa_xattrs_match(const struct linux_binprm * bprm,struct aa_profile * profile,aa_state_t state)3068e51f908SMatthew Garrett static int aa_xattrs_match(const struct linux_binprm *bprm,
30733fc95d8SJohn Johansen struct aa_profile *profile, aa_state_t state)
3088e51f908SMatthew Garrett {
3098e51f908SMatthew Garrett int i;
3108e51f908SMatthew Garrett struct dentry *d;
3118e51f908SMatthew Garrett char *value = NULL;
312217af7e2SJohn Johansen struct aa_attachment *attach = &profile->attach;
31393761c93SLinus Torvalds int size, value_size = 0, ret = attach->xattr_count;
3148e51f908SMatthew Garrett
315217af7e2SJohn Johansen if (!bprm || !attach->xattr_count)
3168e51f908SMatthew Garrett return 0;
3178c62ed27SJohn Johansen might_sleep();
3188e51f908SMatthew Garrett
31973f488cdSJohn Johansen /* transition from exec match to xattr set */
32098b824ffSJohn Johansen state = aa_dfa_outofband_transition(attach->xmatch->dfa, state);
3218e51f908SMatthew Garrett d = bprm->file->f_path.dentry;
3228e51f908SMatthew Garrett
323217af7e2SJohn Johansen for (i = 0; i < attach->xattr_count; i++) {
3244609e1f1SChristian Brauner size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i],
325c7c7a1a1STycho Andersen &value, value_size, GFP_KERNEL);
32673f488cdSJohn Johansen if (size >= 0) {
3272d63dd43SJohn Johansen u32 index, perm;
3288e51f908SMatthew Garrett
3290df34a64SJohn Johansen /*
3300df34a64SJohn Johansen * Check the xattr presence before value. This ensure
3310df34a64SJohn Johansen * that not present xattr can be distinguished from a 0
3320df34a64SJohn Johansen * length value or rule that matches any value
3330df34a64SJohn Johansen */
33498b824ffSJohn Johansen state = aa_dfa_null_transition(attach->xmatch->dfa,
335048d4954SJohn Johansen state);
3360df34a64SJohn Johansen /* Check xattr value */
33798b824ffSJohn Johansen state = aa_dfa_match_len(attach->xmatch->dfa, state,
338048d4954SJohn Johansen value, size);
33998b824ffSJohn Johansen index = ACCEPT_TABLE(attach->xmatch->dfa)[state];
34098b824ffSJohn Johansen perm = attach->xmatch->perms[index].allow;
34173f488cdSJohn Johansen if (!(perm & MAY_EXEC)) {
3428e51f908SMatthew Garrett ret = -EINVAL;
3438e51f908SMatthew Garrett goto out;
3448e51f908SMatthew Garrett }
34573f488cdSJohn Johansen }
34673f488cdSJohn Johansen /* transition to next element */
34798b824ffSJohn Johansen state = aa_dfa_outofband_transition(attach->xmatch->dfa, state);
34873f488cdSJohn Johansen if (size < 0) {
34973f488cdSJohn Johansen /*
35073f488cdSJohn Johansen * No xattr match, so verify if transition to
35173f488cdSJohn Johansen * next element was valid. IFF so the xattr
35273f488cdSJohn Johansen * was optional.
35373f488cdSJohn Johansen */
35473f488cdSJohn Johansen if (!state) {
3558e51f908SMatthew Garrett ret = -EINVAL;
3568e51f908SMatthew Garrett goto out;
3578e51f908SMatthew Garrett }
35873f488cdSJohn Johansen /* don't count missing optional xattr as matched */
35973f488cdSJohn Johansen ret--;
3608e51f908SMatthew Garrett }
3618e51f908SMatthew Garrett }
3628e51f908SMatthew Garrett
3638e51f908SMatthew Garrett out:
3648e51f908SMatthew Garrett kfree(value);
3658e51f908SMatthew Garrett return ret;
3668e51f908SMatthew Garrett }
3678e51f908SMatthew Garrett
3688e51f908SMatthew Garrett /**
3698c62ed27SJohn Johansen * find_attach - do attachment search for unconfined processes
37076426c9dSGaosheng Cui * @bprm: binprm structure of transitioning task
3718c62ed27SJohn Johansen * @ns: the current namespace (NOT NULL)
37276426c9dSGaosheng Cui * @head: profile list to walk (NOT NULL)
37376426c9dSGaosheng Cui * @name: to match against (NOT NULL)
37476426c9dSGaosheng Cui * @info: info message if there was an error (NOT NULL)
375898127c3SJohn Johansen *
376898127c3SJohn Johansen * Do a linear search on the profiles in the list. There is a matching
377898127c3SJohn Johansen * preference where an exact match is preferred over a name which uses
378898127c3SJohn Johansen * expressions to match, and matching expressions with the greatest
379898127c3SJohn Johansen * xmatch_len are preferred.
380898127c3SJohn Johansen *
381898127c3SJohn Johansen * Requires: @head not be shared or have appropriate locks held
382898127c3SJohn Johansen *
3838c62ed27SJohn Johansen * Returns: label or NULL if no match found
384898127c3SJohn Johansen */
find_attach(const struct linux_binprm * bprm,struct aa_ns * ns,struct list_head * head,const char * name,const char ** info)3858c62ed27SJohn Johansen static struct aa_label *find_attach(const struct linux_binprm *bprm,
3868c62ed27SJohn Johansen struct aa_ns *ns, struct list_head *head,
3878c62ed27SJohn Johansen const char *name, const char **info)
388898127c3SJohn Johansen {
38921f60661SJohn Johansen int candidate_len = 0, candidate_xattrs = 0;
390844b8292SJohn Johansen bool conflict = false;
391898127c3SJohn Johansen struct aa_profile *profile, *candidate = NULL;
392898127c3SJohn Johansen
39321f60661SJohn Johansen AA_BUG(!name);
39421f60661SJohn Johansen AA_BUG(!head);
39521f60661SJohn Johansen
3968c62ed27SJohn Johansen rcu_read_lock();
3978c62ed27SJohn Johansen restart:
39801e2b670SJohn Johansen list_for_each_entry_rcu(profile, head, base.list) {
399217af7e2SJohn Johansen struct aa_attachment *attach = &profile->attach;
400217af7e2SJohn Johansen
40106d426d1SJohn Johansen if (profile->label.flags & FLAG_NULL &&
40206d426d1SJohn Johansen &profile->label == ns_unconfined(profile->ns))
403898127c3SJohn Johansen continue;
40406d426d1SJohn Johansen
4058e51f908SMatthew Garrett /* Find the "best" matching profile. Profiles must
4068e51f908SMatthew Garrett * match the path and extended attributes (if any)
4078e51f908SMatthew Garrett * associated with the file. A more specific path
4088e51f908SMatthew Garrett * match will be preferred over a less specific one,
4098e51f908SMatthew Garrett * and a match with more matching extended attributes
4108e51f908SMatthew Garrett * will be preferred over one with fewer. If the best
4118e51f908SMatthew Garrett * match has both the same level of path specificity
4128e51f908SMatthew Garrett * and the same number of matching extended attributes
4138e51f908SMatthew Garrett * as another profile, signal a conflict and refuse to
4148e51f908SMatthew Garrett * match.
4158e51f908SMatthew Garrett */
41698b824ffSJohn Johansen if (attach->xmatch->dfa) {
41733fc95d8SJohn Johansen unsigned int count;
41833fc95d8SJohn Johansen aa_state_t state;
4192d63dd43SJohn Johansen u32 index, perm;
420844b8292SJohn Johansen
42198b824ffSJohn Johansen state = aa_dfa_leftmatch(attach->xmatch->dfa,
42298b824ffSJohn Johansen attach->xmatch->start[AA_CLASS_XMATCH],
42321f60661SJohn Johansen name, &count);
42498b824ffSJohn Johansen index = ACCEPT_TABLE(attach->xmatch->dfa)[state];
42598b824ffSJohn Johansen perm = attach->xmatch->perms[index].allow;
426898127c3SJohn Johansen /* any accepting state means a valid match. */
427898127c3SJohn Johansen if (perm & MAY_EXEC) {
4288c62ed27SJohn Johansen int ret = 0;
4298e51f908SMatthew Garrett
43021f60661SJohn Johansen if (count < candidate_len)
43121f60661SJohn Johansen continue;
43221f60661SJohn Johansen
433217af7e2SJohn Johansen if (bprm && attach->xattr_count) {
4348c62ed27SJohn Johansen long rev = READ_ONCE(ns->revision);
4358c62ed27SJohn Johansen
4368c62ed27SJohn Johansen if (!aa_get_profile_not0(profile))
4378c62ed27SJohn Johansen goto restart;
4388c62ed27SJohn Johansen rcu_read_unlock();
4398c62ed27SJohn Johansen ret = aa_xattrs_match(bprm, profile,
4408c62ed27SJohn Johansen state);
4418c62ed27SJohn Johansen rcu_read_lock();
4428c62ed27SJohn Johansen aa_put_profile(profile);
4438c62ed27SJohn Johansen if (rev !=
4448c62ed27SJohn Johansen READ_ONCE(ns->revision))
4458c62ed27SJohn Johansen /* policy changed */
4468c62ed27SJohn Johansen goto restart;
4478c62ed27SJohn Johansen /*
4488c62ed27SJohn Johansen * Fail matching if the xattrs don't
4498c62ed27SJohn Johansen * match
4508c62ed27SJohn Johansen */
4518e51f908SMatthew Garrett if (ret < 0)
4528e51f908SMatthew Garrett continue;
4538c62ed27SJohn Johansen }
45473f488cdSJohn Johansen /*
45573f488cdSJohn Johansen * TODO: allow for more flexible best match
45673f488cdSJohn Johansen *
45773f488cdSJohn Johansen * The new match isn't more specific
4588e51f908SMatthew Garrett * than the current best match
4598e51f908SMatthew Garrett */
46021f60661SJohn Johansen if (count == candidate_len &&
46121f60661SJohn Johansen ret <= candidate_xattrs) {
4628e51f908SMatthew Garrett /* Match is equivalent, so conflict */
46321f60661SJohn Johansen if (ret == candidate_xattrs)
4641a3881d3SMatthew Garrett conflict = true;
4651a3881d3SMatthew Garrett continue;
4661a3881d3SMatthew Garrett }
4678e51f908SMatthew Garrett
4688e51f908SMatthew Garrett /* Either the same length with more matching
4698e51f908SMatthew Garrett * xattrs, or a longer match
4708e51f908SMatthew Garrett */
471898127c3SJohn Johansen candidate = profile;
472217af7e2SJohn Johansen candidate_len = max(count, attach->xmatch_len);
47321f60661SJohn Johansen candidate_xattrs = ret;
474844b8292SJohn Johansen conflict = false;
475844b8292SJohn Johansen }
4768c62ed27SJohn Johansen } else if (!strcmp(profile->base.name, name)) {
47773f488cdSJohn Johansen /*
47873f488cdSJohn Johansen * old exact non-re match, without conditionals such
47973f488cdSJohn Johansen * as xattrs. no more searching required
48073f488cdSJohn Johansen */
4818c62ed27SJohn Johansen candidate = profile;
4828c62ed27SJohn Johansen goto out;
4838c62ed27SJohn Johansen }
484898127c3SJohn Johansen }
485898127c3SJohn Johansen
4868c62ed27SJohn Johansen if (!candidate || conflict) {
4878c62ed27SJohn Johansen if (conflict)
488844b8292SJohn Johansen *info = "conflicting profile attachments";
4898c62ed27SJohn Johansen rcu_read_unlock();
490844b8292SJohn Johansen return NULL;
491844b8292SJohn Johansen }
492844b8292SJohn Johansen
4938c62ed27SJohn Johansen out:
4948c62ed27SJohn Johansen candidate = aa_get_newest_profile(candidate);
49501e2b670SJohn Johansen rcu_read_unlock();
496898127c3SJohn Johansen
4978c62ed27SJohn Johansen return &candidate->label;
498898127c3SJohn Johansen }
499898127c3SJohn Johansen
next_name(int xtype,const char * name)500898127c3SJohn Johansen static const char *next_name(int xtype, const char *name)
501898127c3SJohn Johansen {
502898127c3SJohn Johansen return NULL;
503898127c3SJohn Johansen }
504898127c3SJohn Johansen
505898127c3SJohn Johansen /**
506898127c3SJohn Johansen * x_table_lookup - lookup an x transition name via transition table
507898127c3SJohn Johansen * @profile: current profile (NOT NULL)
508898127c3SJohn Johansen * @xindex: index into x transition table
50993c98a48SJohn Johansen * @name: returns: name tested to find label (NOT NULL)
510898127c3SJohn Johansen *
51193c98a48SJohn Johansen * Returns: refcounted label, or NULL on failure (MAYBE NULL)
512898127c3SJohn Johansen */
x_table_lookup(struct aa_profile * profile,u32 xindex,const char ** name)5132ea3ffb7SJohn Johansen struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
51493c98a48SJohn Johansen const char **name)
515898127c3SJohn Johansen {
5161ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
5171ad22fccSJohn Johansen typeof(*rules), list);
51893c98a48SJohn Johansen struct aa_label *label = NULL;
519898127c3SJohn Johansen u32 xtype = xindex & AA_X_TYPE_MASK;
520898127c3SJohn Johansen int index = xindex & AA_X_INDEX_MASK;
52193c98a48SJohn Johansen
52293c98a48SJohn Johansen AA_BUG(!name);
523898127c3SJohn Johansen
524898127c3SJohn Johansen /* index is guaranteed to be in range, validated at load time */
52593c98a48SJohn Johansen /* TODO: move lookup parsing to unpack time so this is a straight
52693c98a48SJohn Johansen * index into the resultant label
52793c98a48SJohn Johansen */
52898b824ffSJohn Johansen for (*name = rules->file->trans.table[index]; !label && *name;
52993c98a48SJohn Johansen *name = next_name(xtype, *name)) {
530898127c3SJohn Johansen if (xindex & AA_X_CHILD) {
53193c98a48SJohn Johansen struct aa_profile *new_profile;
532898127c3SJohn Johansen /* release by caller */
53393c98a48SJohn Johansen new_profile = aa_find_child(profile, *name);
53493c98a48SJohn Johansen if (new_profile)
53593c98a48SJohn Johansen label = &new_profile->label;
536898127c3SJohn Johansen continue;
537898127c3SJohn Johansen }
5388ac2ca32SSebastian Andrzej Siewior label = aa_label_parse(&profile->label, *name, GFP_KERNEL,
53993c98a48SJohn Johansen true, false);
54093c98a48SJohn Johansen if (IS_ERR(label))
54193c98a48SJohn Johansen label = NULL;
542898127c3SJohn Johansen }
543898127c3SJohn Johansen
544898127c3SJohn Johansen /* released by caller */
545898127c3SJohn Johansen
54693c98a48SJohn Johansen return label;
547898127c3SJohn Johansen }
548898127c3SJohn Johansen
549898127c3SJohn Johansen /**
55093c98a48SJohn Johansen * x_to_label - get target label for a given xindex
551898127c3SJohn Johansen * @profile: current profile (NOT NULL)
5528e51f908SMatthew Garrett * @bprm: binprm structure of transitioning task
553898127c3SJohn Johansen * @name: name to lookup (NOT NULL)
554898127c3SJohn Johansen * @xindex: index into x transition table
55593c98a48SJohn Johansen * @lookupname: returns: name used in lookup if one was specified (NOT NULL)
55676426c9dSGaosheng Cui * @info: info message if there was an error (NOT NULL)
557898127c3SJohn Johansen *
55893c98a48SJohn Johansen * find label for a transition index
559898127c3SJohn Johansen *
56093c98a48SJohn Johansen * Returns: refcounted label or NULL if not found available
561898127c3SJohn Johansen */
x_to_label(struct aa_profile * profile,const struct linux_binprm * bprm,const char * name,u32 xindex,const char ** lookupname,const char ** info)56293c98a48SJohn Johansen static struct aa_label *x_to_label(struct aa_profile *profile,
5638e51f908SMatthew Garrett const struct linux_binprm *bprm,
56493c98a48SJohn Johansen const char *name, u32 xindex,
56593c98a48SJohn Johansen const char **lookupname,
56693c98a48SJohn Johansen const char **info)
567898127c3SJohn Johansen {
5681ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
5691ad22fccSJohn Johansen typeof(*rules), list);
57093c98a48SJohn Johansen struct aa_label *new = NULL;
57198849dffSJohn Johansen struct aa_ns *ns = profile->ns;
572898127c3SJohn Johansen u32 xtype = xindex & AA_X_TYPE_MASK;
57393c98a48SJohn Johansen const char *stack = NULL;
574898127c3SJohn Johansen
575898127c3SJohn Johansen switch (xtype) {
576898127c3SJohn Johansen case AA_X_NONE:
577898127c3SJohn Johansen /* fail exec unless ix || ux fallback - handled by caller */
57893c98a48SJohn Johansen *lookupname = NULL;
57993c98a48SJohn Johansen break;
58093c98a48SJohn Johansen case AA_X_TABLE:
58193c98a48SJohn Johansen /* TODO: fix when perm mapping done at unload */
58298b824ffSJohn Johansen stack = rules->file->trans.table[xindex & AA_X_INDEX_MASK];
58393c98a48SJohn Johansen if (*stack != '&') {
58493c98a48SJohn Johansen /* released by caller */
58593c98a48SJohn Johansen new = x_table_lookup(profile, xindex, lookupname);
58693c98a48SJohn Johansen stack = NULL;
58793c98a48SJohn Johansen break;
58893c98a48SJohn Johansen }
589df561f66SGustavo A. R. Silva fallthrough; /* to X_NAME */
590898127c3SJohn Johansen case AA_X_NAME:
591898127c3SJohn Johansen if (xindex & AA_X_CHILD)
592898127c3SJohn Johansen /* released by caller */
5938e51f908SMatthew Garrett new = find_attach(bprm, ns, &profile->base.profiles,
594844b8292SJohn Johansen name, info);
595898127c3SJohn Johansen else
596898127c3SJohn Johansen /* released by caller */
5978e51f908SMatthew Garrett new = find_attach(bprm, ns, &ns->base.profiles,
598844b8292SJohn Johansen name, info);
59993c98a48SJohn Johansen *lookupname = name;
600898127c3SJohn Johansen break;
601898127c3SJohn Johansen }
602898127c3SJohn Johansen
60393c98a48SJohn Johansen if (!new) {
60493c98a48SJohn Johansen if (xindex & AA_X_INHERIT) {
60593c98a48SJohn Johansen /* (p|c|n)ix - don't change profile but do
60693c98a48SJohn Johansen * use the newest version
60793c98a48SJohn Johansen */
60893c98a48SJohn Johansen *info = "ix fallback";
60993c98a48SJohn Johansen /* no profile && no error */
61093c98a48SJohn Johansen new = aa_get_newest_label(&profile->label);
61193c98a48SJohn Johansen } else if (xindex & AA_X_UNCONFINED) {
61293c98a48SJohn Johansen new = aa_get_newest_label(ns_unconfined(profile->ns));
61393c98a48SJohn Johansen *info = "ux fallback";
61493c98a48SJohn Johansen }
61593c98a48SJohn Johansen }
61693c98a48SJohn Johansen
61793c98a48SJohn Johansen if (new && stack) {
61893c98a48SJohn Johansen /* base the stack on post domain transition */
61993c98a48SJohn Johansen struct aa_label *base = new;
62093c98a48SJohn Johansen
6218ac2ca32SSebastian Andrzej Siewior new = aa_label_parse(base, stack, GFP_KERNEL, true, false);
62293c98a48SJohn Johansen if (IS_ERR(new))
62393c98a48SJohn Johansen new = NULL;
62493c98a48SJohn Johansen aa_put_label(base);
62593c98a48SJohn Johansen }
62693c98a48SJohn Johansen
627898127c3SJohn Johansen /* released by caller */
62893c98a48SJohn Johansen return new;
62993c98a48SJohn Johansen }
63093c98a48SJohn 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)63190c436a6SJohn Johansen static struct aa_label *profile_transition(const struct cred *subj_cred,
63290c436a6SJohn Johansen struct aa_profile *profile,
63393c98a48SJohn Johansen const struct linux_binprm *bprm,
63493c98a48SJohn Johansen char *buffer, struct path_cond *cond,
63593c98a48SJohn Johansen bool *secure_exec)
63693c98a48SJohn Johansen {
6371ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
6381ad22fccSJohn Johansen typeof(*rules), list);
63993c98a48SJohn Johansen struct aa_label *new = NULL;
64093c98a48SJohn Johansen const char *info = NULL, *name = NULL, *target = NULL;
64198b824ffSJohn Johansen aa_state_t state = rules->file->start[AA_CLASS_FILE];
64293c98a48SJohn Johansen struct aa_perms perms = {};
64393c98a48SJohn Johansen bool nonewprivs = false;
64493c98a48SJohn Johansen int error = 0;
64593c98a48SJohn Johansen
64693c98a48SJohn Johansen AA_BUG(!profile);
64793c98a48SJohn Johansen AA_BUG(!bprm);
64893c98a48SJohn Johansen AA_BUG(!buffer);
64993c98a48SJohn Johansen
65093c98a48SJohn Johansen error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
65193c98a48SJohn Johansen &name, &info, profile->disconnected);
65293c98a48SJohn Johansen if (error) {
65393c98a48SJohn Johansen if (profile_unconfined(profile) ||
65493c98a48SJohn Johansen (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
65593c98a48SJohn Johansen AA_DEBUG("name lookup ix on error");
65693c98a48SJohn Johansen error = 0;
65793c98a48SJohn Johansen new = aa_get_newest_label(&profile->label);
65893c98a48SJohn Johansen }
65993c98a48SJohn Johansen name = bprm->filename;
66093c98a48SJohn Johansen goto audit;
66193c98a48SJohn Johansen }
66293c98a48SJohn Johansen
66393c98a48SJohn Johansen if (profile_unconfined(profile)) {
6648e51f908SMatthew Garrett new = find_attach(bprm, profile->ns,
6658e51f908SMatthew Garrett &profile->ns->base.profiles, name, &info);
66693c98a48SJohn Johansen if (new) {
66793c98a48SJohn Johansen AA_DEBUG("unconfined attached to new label");
66893c98a48SJohn Johansen return new;
66993c98a48SJohn Johansen }
67093c98a48SJohn Johansen AA_DEBUG("unconfined exec no attachment");
67193c98a48SJohn Johansen return aa_get_newest_label(&profile->label);
67293c98a48SJohn Johansen }
67393c98a48SJohn Johansen
67493c98a48SJohn Johansen /* find exec permissions for name */
67598b824ffSJohn Johansen state = aa_str_perms(rules->file, state, name, cond, &perms);
67693c98a48SJohn Johansen if (perms.allow & MAY_EXEC) {
67793c98a48SJohn Johansen /* exec permission determine how to transition */
6788e51f908SMatthew Garrett new = x_to_label(profile, bprm, name, perms.xindex, &target,
6798e51f908SMatthew Garrett &info);
68093c98a48SJohn Johansen if (new && new->proxy == profile->label.proxy && info) {
68193c98a48SJohn Johansen /* hack ix fallback - improve how this is detected */
68293c98a48SJohn Johansen goto audit;
68393c98a48SJohn Johansen } else if (!new) {
68493c98a48SJohn Johansen error = -EACCES;
68593c98a48SJohn Johansen info = "profile transition not found";
68693c98a48SJohn Johansen /* remove MAY_EXEC to audit as failure */
68793c98a48SJohn Johansen perms.allow &= ~MAY_EXEC;
68893c98a48SJohn Johansen }
68993c98a48SJohn Johansen } else if (COMPLAIN_MODE(profile)) {
69093c98a48SJohn Johansen /* no exec permission - learning mode */
6915d7c44efSJohn Johansen struct aa_profile *new_profile = NULL;
6925d7c44efSJohn Johansen
69358f89ce5SJohn Johansen new_profile = aa_new_learning_profile(profile, false, name,
6945d7c44efSJohn Johansen GFP_KERNEL);
69593c98a48SJohn Johansen if (!new_profile) {
69693c98a48SJohn Johansen error = -ENOMEM;
69793c98a48SJohn Johansen info = "could not create null profile";
69893c98a48SJohn Johansen } else {
69993c98a48SJohn Johansen error = -EACCES;
70093c98a48SJohn Johansen new = &new_profile->label;
70193c98a48SJohn Johansen }
70293c98a48SJohn Johansen perms.xindex |= AA_X_UNSAFE;
70393c98a48SJohn Johansen } else
70493c98a48SJohn Johansen /* fail exec */
70593c98a48SJohn Johansen error = -EACCES;
70693c98a48SJohn Johansen
70793c98a48SJohn Johansen if (!new)
70893c98a48SJohn Johansen goto audit;
70993c98a48SJohn Johansen
71093c98a48SJohn Johansen
71193c98a48SJohn Johansen if (!(perms.xindex & AA_X_UNSAFE)) {
71293c98a48SJohn Johansen if (DEBUG_ON) {
71393c98a48SJohn Johansen dbg_printk("apparmor: scrubbing environment variables"
71493c98a48SJohn Johansen " for %s profile=", name);
7158ac2ca32SSebastian Andrzej Siewior aa_label_printk(new, GFP_KERNEL);
71693c98a48SJohn Johansen dbg_printk("\n");
71793c98a48SJohn Johansen }
71893c98a48SJohn Johansen *secure_exec = true;
71993c98a48SJohn Johansen }
72093c98a48SJohn Johansen
72193c98a48SJohn Johansen audit:
72290c436a6SJohn Johansen aa_audit_file(subj_cred, profile, &perms, OP_EXEC, MAY_EXEC, name,
72390c436a6SJohn Johansen target, new,
72493c98a48SJohn Johansen cond->uid, info, error);
72593c98a48SJohn Johansen if (!new || nonewprivs) {
72693c98a48SJohn Johansen aa_put_label(new);
72793c98a48SJohn Johansen return ERR_PTR(error);
72893c98a48SJohn Johansen }
72993c98a48SJohn Johansen
73093c98a48SJohn Johansen return new;
73193c98a48SJohn Johansen }
73293c98a48SJohn 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)73390c436a6SJohn Johansen static int profile_onexec(const struct cred *subj_cred,
73490c436a6SJohn Johansen struct aa_profile *profile, struct aa_label *onexec,
73593c98a48SJohn Johansen bool stack, const struct linux_binprm *bprm,
73693c98a48SJohn Johansen char *buffer, struct path_cond *cond,
73793c98a48SJohn Johansen bool *secure_exec)
73893c98a48SJohn Johansen {
7391ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
7401ad22fccSJohn Johansen typeof(*rules), list);
74198b824ffSJohn Johansen aa_state_t state = rules->file->start[AA_CLASS_FILE];
74293c98a48SJohn Johansen struct aa_perms perms = {};
74393c98a48SJohn Johansen const char *xname = NULL, *info = "change_profile onexec";
74493c98a48SJohn Johansen int error = -EACCES;
74593c98a48SJohn Johansen
74693c98a48SJohn Johansen AA_BUG(!profile);
74793c98a48SJohn Johansen AA_BUG(!onexec);
74893c98a48SJohn Johansen AA_BUG(!bprm);
74993c98a48SJohn Johansen AA_BUG(!buffer);
75093c98a48SJohn Johansen
75193c98a48SJohn Johansen if (profile_unconfined(profile)) {
75293c98a48SJohn Johansen /* change_profile on exec already granted */
75393c98a48SJohn Johansen /*
75493c98a48SJohn Johansen * NOTE: Domain transitions from unconfined are allowed
75593c98a48SJohn Johansen * even when no_new_privs is set because this aways results
75693c98a48SJohn Johansen * in a further reduction of permissions.
75793c98a48SJohn Johansen */
75893c98a48SJohn Johansen return 0;
75993c98a48SJohn Johansen }
76093c98a48SJohn Johansen
76193c98a48SJohn Johansen error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
76293c98a48SJohn Johansen &xname, &info, profile->disconnected);
76393c98a48SJohn Johansen if (error) {
76493c98a48SJohn Johansen if (profile_unconfined(profile) ||
76593c98a48SJohn Johansen (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
76693c98a48SJohn Johansen AA_DEBUG("name lookup ix on error");
76793c98a48SJohn Johansen error = 0;
76893c98a48SJohn Johansen }
76993c98a48SJohn Johansen xname = bprm->filename;
77093c98a48SJohn Johansen goto audit;
77193c98a48SJohn Johansen }
77293c98a48SJohn Johansen
77393c98a48SJohn Johansen /* find exec permissions for name */
77498b824ffSJohn Johansen state = aa_str_perms(rules->file, state, xname, cond, &perms);
77593c98a48SJohn Johansen if (!(perms.allow & AA_MAY_ONEXEC)) {
77693c98a48SJohn Johansen info = "no change_onexec valid for executable";
77793c98a48SJohn Johansen goto audit;
77893c98a48SJohn Johansen }
77993c98a48SJohn Johansen /* test if this exec can be paired with change_profile onexec.
78093c98a48SJohn Johansen * onexec permission is linked to exec with a standard pairing
78193c98a48SJohn Johansen * exec\0change_profile
78293c98a48SJohn Johansen */
78398b824ffSJohn Johansen state = aa_dfa_null_transition(rules->file->dfa, state);
78493c98a48SJohn Johansen error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC,
78593c98a48SJohn Johansen state, &perms);
78693c98a48SJohn Johansen if (error) {
78793c98a48SJohn Johansen perms.allow &= ~AA_MAY_ONEXEC;
78893c98a48SJohn Johansen goto audit;
78993c98a48SJohn Johansen }
79093c98a48SJohn Johansen
79193c98a48SJohn Johansen if (!(perms.xindex & AA_X_UNSAFE)) {
79293c98a48SJohn Johansen if (DEBUG_ON) {
79393c98a48SJohn Johansen dbg_printk("apparmor: scrubbing environment "
79493c98a48SJohn Johansen "variables for %s label=", xname);
7958ac2ca32SSebastian Andrzej Siewior aa_label_printk(onexec, GFP_KERNEL);
79693c98a48SJohn Johansen dbg_printk("\n");
79793c98a48SJohn Johansen }
79893c98a48SJohn Johansen *secure_exec = true;
79993c98a48SJohn Johansen }
80093c98a48SJohn Johansen
80193c98a48SJohn Johansen audit:
80290c436a6SJohn Johansen return aa_audit_file(subj_cred, profile, &perms, OP_EXEC,
80390c436a6SJohn Johansen AA_MAY_ONEXEC, xname,
80493c98a48SJohn Johansen NULL, onexec, cond->uid, info, error);
80593c98a48SJohn Johansen }
80693c98a48SJohn Johansen
80793c98a48SJohn Johansen /* ensure none ns domain transitions are correctly applied with onexec */
80893c98a48SJohn 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)80990c436a6SJohn Johansen static struct aa_label *handle_onexec(const struct cred *subj_cred,
81090c436a6SJohn Johansen struct aa_label *label,
81193c98a48SJohn Johansen struct aa_label *onexec, bool stack,
81293c98a48SJohn Johansen const struct linux_binprm *bprm,
81393c98a48SJohn Johansen char *buffer, struct path_cond *cond,
81493c98a48SJohn Johansen bool *unsafe)
81593c98a48SJohn Johansen {
81693c98a48SJohn Johansen struct aa_profile *profile;
81793c98a48SJohn Johansen struct aa_label *new;
81893c98a48SJohn Johansen int error;
81993c98a48SJohn Johansen
82093c98a48SJohn Johansen AA_BUG(!label);
82193c98a48SJohn Johansen AA_BUG(!onexec);
82293c98a48SJohn Johansen AA_BUG(!bprm);
82393c98a48SJohn Johansen AA_BUG(!buffer);
82493c98a48SJohn Johansen
82593c98a48SJohn Johansen if (!stack) {
82693c98a48SJohn Johansen error = fn_for_each_in_ns(label, profile,
82790c436a6SJohn Johansen profile_onexec(subj_cred, profile, onexec, stack,
82893c98a48SJohn Johansen bprm, buffer, cond, unsafe));
82993c98a48SJohn Johansen if (error)
83093c98a48SJohn Johansen return ERR_PTR(error);
8318ac2ca32SSebastian Andrzej Siewior new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
83293c98a48SJohn Johansen aa_get_newest_label(onexec),
83390c436a6SJohn Johansen profile_transition(subj_cred, profile, bprm,
83490c436a6SJohn Johansen buffer,
83593c98a48SJohn Johansen cond, unsafe));
83693c98a48SJohn Johansen
83793c98a48SJohn Johansen } else {
838b2c2086cSZygmunt Krynicki /* TODO: determine how much we want to loosen this */
83993c98a48SJohn Johansen error = fn_for_each_in_ns(label, profile,
84090c436a6SJohn Johansen profile_onexec(subj_cred, profile, onexec, stack, bprm,
84193c98a48SJohn Johansen buffer, cond, unsafe));
84293c98a48SJohn Johansen if (error)
84393c98a48SJohn Johansen return ERR_PTR(error);
8448ac2ca32SSebastian Andrzej Siewior new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
84593c98a48SJohn Johansen aa_label_merge(&profile->label, onexec,
8468ac2ca32SSebastian Andrzej Siewior GFP_KERNEL),
84790c436a6SJohn Johansen profile_transition(subj_cred, profile, bprm,
84890c436a6SJohn Johansen buffer,
84993c98a48SJohn Johansen cond, unsafe));
85093c98a48SJohn Johansen }
85193c98a48SJohn Johansen
85293c98a48SJohn Johansen if (new)
85393c98a48SJohn Johansen return new;
85493c98a48SJohn Johansen
85593c98a48SJohn Johansen /* TODO: get rid of GLOBAL_ROOT_UID */
85693c98a48SJohn Johansen error = fn_for_each_in_ns(label, profile,
85790c436a6SJohn Johansen aa_audit_file(subj_cred, profile, &nullperms,
85890c436a6SJohn Johansen OP_CHANGE_ONEXEC,
85993c98a48SJohn Johansen AA_MAY_ONEXEC, bprm->filename, NULL,
86093c98a48SJohn Johansen onexec, GLOBAL_ROOT_UID,
86193c98a48SJohn Johansen "failed to build target label", -ENOMEM));
86293c98a48SJohn Johansen return ERR_PTR(error);
863898127c3SJohn Johansen }
864898127c3SJohn Johansen
865898127c3SJohn Johansen /**
866b8bff599SEric W. Biederman * apparmor_bprm_creds_for_exec - Update the new creds on the bprm struct
867898127c3SJohn Johansen * @bprm: binprm for the exec (NOT NULL)
868898127c3SJohn Johansen *
869898127c3SJohn Johansen * Returns: %0 or error on failure
87093c98a48SJohn Johansen *
87193c98a48SJohn Johansen * TODO: once the other paths are done see if we can't refactor into a fn
872898127c3SJohn Johansen */
apparmor_bprm_creds_for_exec(struct linux_binprm * bprm)873b8bff599SEric W. Biederman int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
874898127c3SJohn Johansen {
875f175221aSJohn Johansen struct aa_task_ctx *ctx;
87693c98a48SJohn Johansen struct aa_label *label, *new = NULL;
87790c436a6SJohn Johansen const struct cred *subj_cred;
87893c98a48SJohn Johansen struct aa_profile *profile;
879898127c3SJohn Johansen char *buffer = NULL;
88093c98a48SJohn Johansen const char *info = NULL;
88193c98a48SJohn Johansen int error = 0;
88293c98a48SJohn Johansen bool unsafe = false;
883e67fe633SChristian Brauner vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(bprm->file),
8843cee6079SChristian Brauner file_inode(bprm->file));
885898127c3SJohn Johansen struct path_cond cond = {
8865e26a01eSChristian Brauner vfsuid_into_kuid(vfsuid),
887496ad9aaSAl Viro file_inode(bprm->file)->i_mode
888898127c3SJohn Johansen };
889898127c3SJohn Johansen
89090c436a6SJohn Johansen subj_cred = current_cred();
891de62de59SJohn Johansen ctx = task_ctx(current);
892d9087c49SJohn Johansen AA_BUG(!cred_label(bprm->cred));
893f175221aSJohn Johansen AA_BUG(!ctx);
894898127c3SJohn Johansen
895d9087c49SJohn Johansen label = aa_get_newest_label(cred_label(bprm->cred));
8964227c333SJohn Johansen
8979fcf78ccSJohn Johansen /*
8989fcf78ccSJohn Johansen * Detect no new privs being set, and store the label it
8999fcf78ccSJohn Johansen * occurred under. Ideally this would happen when nnp
9009fcf78ccSJohn Johansen * is set but there isn't a good way to do that yet.
9019fcf78ccSJohn Johansen *
9029fcf78ccSJohn Johansen * Testing for unconfined must be done before the subset test
9039fcf78ccSJohn Johansen */
9049fcf78ccSJohn Johansen if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && !unconfined(label) &&
9059fcf78ccSJohn Johansen !ctx->nnp)
9069fcf78ccSJohn Johansen ctx->nnp = aa_get_label(label);
9079fcf78ccSJohn Johansen
9084227c333SJohn Johansen /* buffer freed below, name is pointer into buffer */
909341c1fdaSJohn Johansen buffer = aa_get_buffer(false);
910df323337SSebastian Andrzej Siewior if (!buffer) {
911df323337SSebastian Andrzej Siewior error = -ENOMEM;
912df323337SSebastian Andrzej Siewior goto done;
913df323337SSebastian Andrzej Siewior }
914df323337SSebastian Andrzej Siewior
91593c98a48SJohn Johansen /* Test for onexec first as onexec override other x transitions. */
916f175221aSJohn Johansen if (ctx->onexec)
91790c436a6SJohn Johansen new = handle_onexec(subj_cred, label, ctx->onexec, ctx->token,
91893c98a48SJohn Johansen bprm, buffer, &cond, &unsafe);
919898127c3SJohn Johansen else
9208ac2ca32SSebastian Andrzej Siewior new = fn_label_build(label, profile, GFP_KERNEL,
92190c436a6SJohn Johansen profile_transition(subj_cred, profile, bprm,
92290c436a6SJohn Johansen buffer,
92393c98a48SJohn Johansen &cond, &unsafe));
924898127c3SJohn Johansen
92593c98a48SJohn Johansen AA_BUG(!new);
92693c98a48SJohn Johansen if (IS_ERR(new)) {
92793c98a48SJohn Johansen error = PTR_ERR(new);
92893c98a48SJohn Johansen goto done;
92993c98a48SJohn Johansen } else if (!new) {
930898127c3SJohn Johansen error = -ENOMEM;
93193c98a48SJohn Johansen goto done;
932c29bceb3SJohn Johansen }
933c29bceb3SJohn Johansen
9349fcf78ccSJohn Johansen /* Policy has specified a domain transitions. If no_new_privs and
9359fcf78ccSJohn Johansen * confined ensure the transition is to confinement that is subset
9369fcf78ccSJohn Johansen * of the confinement when the task entered no new privs.
9379fcf78ccSJohn Johansen *
9389fcf78ccSJohn Johansen * NOTE: Domain transitions from unconfined and to stacked
9399fcf78ccSJohn Johansen * subsets are allowed even when no_new_privs is set because this
9409fcf78ccSJohn Johansen * aways results in a further reduction of permissions.
9419fcf78ccSJohn Johansen */
9429fcf78ccSJohn Johansen if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) &&
9433ed4aaa9SJohn Johansen !unconfined(label) &&
9443ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(new, ctx->nnp)) {
9459fcf78ccSJohn Johansen error = -EPERM;
9469fcf78ccSJohn Johansen info = "no new privs";
9479fcf78ccSJohn Johansen goto audit;
9489fcf78ccSJohn Johansen }
949898127c3SJohn Johansen
950898127c3SJohn Johansen if (bprm->unsafe & LSM_UNSAFE_SHARE) {
951898127c3SJohn Johansen /* FIXME: currently don't mediate shared state */
952898127c3SJohn Johansen ;
953898127c3SJohn Johansen }
954898127c3SJohn Johansen
95593c98a48SJohn Johansen if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) {
95693c98a48SJohn Johansen /* TODO: test needs to be profile of label to new */
95790c436a6SJohn Johansen error = may_change_ptraced_domain(bprm->cred, new, &info);
958f7da2de0SJohn Johansen if (error)
959898127c3SJohn Johansen goto audit;
960898127c3SJohn Johansen }
961898127c3SJohn Johansen
96293c98a48SJohn Johansen if (unsafe) {
96393c98a48SJohn Johansen if (DEBUG_ON) {
96493c98a48SJohn Johansen dbg_printk("scrubbing environment variables for %s "
96593c98a48SJohn Johansen "label=", bprm->filename);
9668ac2ca32SSebastian Andrzej Siewior aa_label_printk(new, GFP_KERNEL);
96793c98a48SJohn Johansen dbg_printk("\n");
96893c98a48SJohn Johansen }
969993b3ab0SKees Cook bprm->secureexec = 1;
970898127c3SJohn Johansen }
97193c98a48SJohn Johansen
97293c98a48SJohn Johansen if (label->proxy != new->proxy) {
97393c98a48SJohn Johansen /* when transitioning clear unsafe personality bits */
97493c98a48SJohn Johansen if (DEBUG_ON) {
97593c98a48SJohn Johansen dbg_printk("apparmor: clearing unsafe personality "
97693c98a48SJohn Johansen "bits. %s label=", bprm->filename);
9778ac2ca32SSebastian Andrzej Siewior aa_label_printk(new, GFP_KERNEL);
97893c98a48SJohn Johansen dbg_printk("\n");
97993c98a48SJohn Johansen }
980898127c3SJohn Johansen bprm->per_clear |= PER_CLEAR_ON_SETID;
98193c98a48SJohn Johansen }
982d9087c49SJohn Johansen aa_put_label(cred_label(bprm->cred));
983d9087c49SJohn Johansen /* transfer reference, released when cred is freed */
98469b5a44aSCasey Schaufler set_cred_label(bprm->cred, new);
985898127c3SJohn Johansen
98693c98a48SJohn Johansen done:
987637f688dSJohn Johansen aa_put_label(label);
988df323337SSebastian Andrzej Siewior aa_put_buffer(buffer);
989898127c3SJohn Johansen
990898127c3SJohn Johansen return error;
99193c98a48SJohn Johansen
99293c98a48SJohn Johansen audit:
99393c98a48SJohn Johansen error = fn_for_each(label, profile,
99490c436a6SJohn Johansen aa_audit_file(current_cred(), profile, &nullperms,
99590c436a6SJohn Johansen OP_EXEC, MAY_EXEC,
99693c98a48SJohn Johansen bprm->filename, NULL, new,
9975e26a01eSChristian Brauner vfsuid_into_kuid(vfsuid), info, error));
99893c98a48SJohn Johansen aa_put_label(new);
99993c98a48SJohn Johansen goto done;
1000898127c3SJohn Johansen }
1001898127c3SJohn Johansen
1002898127c3SJohn Johansen /*
1003898127c3SJohn Johansen * Functions for self directed profile change
1004898127c3SJohn Johansen */
1005898127c3SJohn Johansen
100689dbf196SJohn Johansen
100789dbf196SJohn Johansen /* helper fn for change_hat
1008898127c3SJohn Johansen *
100989dbf196SJohn Johansen * Returns: label for hat transition OR ERR_PTR. Does NOT return NULL
1010898127c3SJohn Johansen */
build_change_hat(const struct cred * subj_cred,struct aa_profile * profile,const char * name,bool sibling)101190c436a6SJohn Johansen static struct aa_label *build_change_hat(const struct cred *subj_cred,
101290c436a6SJohn Johansen struct aa_profile *profile,
101389dbf196SJohn Johansen const char *name, bool sibling)
1014898127c3SJohn Johansen {
101589dbf196SJohn Johansen struct aa_profile *root, *hat = NULL;
101689dbf196SJohn Johansen const char *info = NULL;
101789dbf196SJohn Johansen int error = 0;
101889dbf196SJohn Johansen
101989dbf196SJohn Johansen if (sibling && PROFILE_IS_HAT(profile)) {
102089dbf196SJohn Johansen root = aa_get_profile_rcu(&profile->parent);
102189dbf196SJohn Johansen } else if (!sibling && !PROFILE_IS_HAT(profile)) {
102289dbf196SJohn Johansen root = aa_get_profile(profile);
102389dbf196SJohn Johansen } else {
102489dbf196SJohn Johansen info = "conflicting target types";
102589dbf196SJohn Johansen error = -EPERM;
102689dbf196SJohn Johansen goto audit;
102789dbf196SJohn Johansen }
102889dbf196SJohn Johansen
102989dbf196SJohn Johansen hat = aa_find_child(root, name);
103089dbf196SJohn Johansen if (!hat) {
103189dbf196SJohn Johansen error = -ENOENT;
103289dbf196SJohn Johansen if (COMPLAIN_MODE(profile)) {
103358f89ce5SJohn Johansen hat = aa_new_learning_profile(profile, true, name,
103489dbf196SJohn Johansen GFP_KERNEL);
103589dbf196SJohn Johansen if (!hat) {
103689dbf196SJohn Johansen info = "failed null profile create";
103789dbf196SJohn Johansen error = -ENOMEM;
103889dbf196SJohn Johansen }
103989dbf196SJohn Johansen }
104089dbf196SJohn Johansen }
104189dbf196SJohn Johansen aa_put_profile(root);
104289dbf196SJohn Johansen
104389dbf196SJohn Johansen audit:
104490c436a6SJohn Johansen aa_audit_file(subj_cred, profile, &nullperms, OP_CHANGE_HAT,
104590c436a6SJohn Johansen AA_MAY_CHANGEHAT,
104689dbf196SJohn Johansen name, hat ? hat->base.hname : NULL,
104724b87a16SJohn Johansen hat ? &hat->label : NULL, GLOBAL_ROOT_UID, info,
104889dbf196SJohn Johansen error);
104989dbf196SJohn Johansen if (!hat || (error && error != -ENOENT))
105089dbf196SJohn Johansen return ERR_PTR(error);
105189dbf196SJohn Johansen /* if hat && error - complain mode, already audited and we adjust for
105289dbf196SJohn Johansen * complain mode allow by returning hat->label
105389dbf196SJohn Johansen */
105489dbf196SJohn Johansen return &hat->label;
105589dbf196SJohn Johansen }
105689dbf196SJohn Johansen
105789dbf196SJohn Johansen /* helper fn for changing into a hat
105889dbf196SJohn Johansen *
105989dbf196SJohn Johansen * Returns: label for hat transition or ERR_PTR. Does not return NULL
106089dbf196SJohn Johansen */
change_hat(const struct cred * subj_cred,struct aa_label * label,const char * hats[],int count,int flags)106190c436a6SJohn Johansen static struct aa_label *change_hat(const struct cred *subj_cred,
106290c436a6SJohn Johansen struct aa_label *label, const char *hats[],
106389dbf196SJohn Johansen int count, int flags)
106489dbf196SJohn Johansen {
106589dbf196SJohn Johansen struct aa_profile *profile, *root, *hat = NULL;
106689dbf196SJohn Johansen struct aa_label *new;
106789dbf196SJohn Johansen struct label_it it;
106889dbf196SJohn Johansen bool sibling = false;
106989dbf196SJohn Johansen const char *name, *info = NULL;
107089dbf196SJohn Johansen int i, error;
107189dbf196SJohn Johansen
107289dbf196SJohn Johansen AA_BUG(!label);
107389dbf196SJohn Johansen AA_BUG(!hats);
107489dbf196SJohn Johansen AA_BUG(count < 1);
107589dbf196SJohn Johansen
107689dbf196SJohn Johansen if (PROFILE_IS_HAT(labels_profile(label)))
107789dbf196SJohn Johansen sibling = true;
107889dbf196SJohn Johansen
107989dbf196SJohn Johansen /*find first matching hat */
108089dbf196SJohn Johansen for (i = 0; i < count && !hat; i++) {
108189dbf196SJohn Johansen name = hats[i];
108289dbf196SJohn Johansen label_for_each_in_ns(it, labels_ns(label), label, profile) {
108389dbf196SJohn Johansen if (sibling && PROFILE_IS_HAT(profile)) {
108489dbf196SJohn Johansen root = aa_get_profile_rcu(&profile->parent);
108589dbf196SJohn Johansen } else if (!sibling && !PROFILE_IS_HAT(profile)) {
108689dbf196SJohn Johansen root = aa_get_profile(profile);
108789dbf196SJohn Johansen } else { /* conflicting change type */
108889dbf196SJohn Johansen info = "conflicting targets types";
108989dbf196SJohn Johansen error = -EPERM;
109089dbf196SJohn Johansen goto fail;
109189dbf196SJohn Johansen }
109289dbf196SJohn Johansen hat = aa_find_child(root, name);
109389dbf196SJohn Johansen aa_put_profile(root);
109489dbf196SJohn Johansen if (!hat) {
109589dbf196SJohn Johansen if (!COMPLAIN_MODE(profile))
109689dbf196SJohn Johansen goto outer_continue;
109789dbf196SJohn Johansen /* complain mode succeed as if hat */
109889dbf196SJohn Johansen } else if (!PROFILE_IS_HAT(hat)) {
109989dbf196SJohn Johansen info = "target not hat";
110089dbf196SJohn Johansen error = -EPERM;
110189dbf196SJohn Johansen aa_put_profile(hat);
110289dbf196SJohn Johansen goto fail;
110389dbf196SJohn Johansen }
110489dbf196SJohn Johansen aa_put_profile(hat);
110589dbf196SJohn Johansen }
110689dbf196SJohn Johansen /* found a hat for all profiles in ns */
110789dbf196SJohn Johansen goto build;
110889dbf196SJohn Johansen outer_continue:
110989dbf196SJohn Johansen ;
111089dbf196SJohn Johansen }
111189dbf196SJohn Johansen /* no hats that match, find appropriate error
111289dbf196SJohn Johansen *
111389dbf196SJohn Johansen * In complain mode audit of the failure is based off of the first
111489dbf196SJohn Johansen * hat supplied. This is done due how userspace interacts with
111589dbf196SJohn Johansen * change_hat.
111689dbf196SJohn Johansen */
111789dbf196SJohn Johansen name = NULL;
111889dbf196SJohn Johansen label_for_each_in_ns(it, labels_ns(label), label, profile) {
111989dbf196SJohn Johansen if (!list_empty(&profile->base.profiles)) {
112089dbf196SJohn Johansen info = "hat not found";
112189dbf196SJohn Johansen error = -ENOENT;
112289dbf196SJohn Johansen goto fail;
112389dbf196SJohn Johansen }
112489dbf196SJohn Johansen }
112589dbf196SJohn Johansen info = "no hats defined";
112689dbf196SJohn Johansen error = -ECHILD;
112789dbf196SJohn Johansen
112889dbf196SJohn Johansen fail:
112989dbf196SJohn Johansen label_for_each_in_ns(it, labels_ns(label), label, profile) {
113089dbf196SJohn Johansen /*
113189dbf196SJohn Johansen * no target as it has failed to be found or built
113289dbf196SJohn Johansen *
113389dbf196SJohn Johansen * change_hat uses probing and should not log failures
113489dbf196SJohn Johansen * related to missing hats
113589dbf196SJohn Johansen */
113689dbf196SJohn Johansen /* TODO: get rid of GLOBAL_ROOT_UID */
113789dbf196SJohn Johansen if (count > 1 || COMPLAIN_MODE(profile)) {
113890c436a6SJohn Johansen aa_audit_file(subj_cred, profile, &nullperms,
113990c436a6SJohn Johansen OP_CHANGE_HAT,
114089dbf196SJohn Johansen AA_MAY_CHANGEHAT, name, NULL, NULL,
114189dbf196SJohn Johansen GLOBAL_ROOT_UID, info, error);
114289dbf196SJohn Johansen }
114389dbf196SJohn Johansen }
114489dbf196SJohn Johansen return ERR_PTR(error);
114589dbf196SJohn Johansen
114689dbf196SJohn Johansen build:
114789dbf196SJohn Johansen new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
114890c436a6SJohn Johansen build_change_hat(subj_cred, profile, name,
114990c436a6SJohn Johansen sibling),
115089dbf196SJohn Johansen aa_get_label(&profile->label));
115189dbf196SJohn Johansen if (!new) {
115289dbf196SJohn Johansen info = "label build failed";
115389dbf196SJohn Johansen error = -ENOMEM;
115489dbf196SJohn Johansen goto fail;
115589dbf196SJohn Johansen } /* else if (IS_ERR) build_change_hat has logged error so return new */
115689dbf196SJohn Johansen
115789dbf196SJohn Johansen return new;
1158898127c3SJohn Johansen }
1159898127c3SJohn Johansen
1160898127c3SJohn Johansen /**
1161898127c3SJohn Johansen * aa_change_hat - change hat to/from subprofile
1162898127c3SJohn Johansen * @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0)
1163898127c3SJohn Johansen * @count: number of hat names in @hats
1164898127c3SJohn Johansen * @token: magic value to validate the hat change
1165df8073c6SJohn Johansen * @flags: flags affecting behavior of the change
1166898127c3SJohn Johansen *
116789dbf196SJohn Johansen * Returns %0 on success, error otherwise.
116889dbf196SJohn Johansen *
1169898127c3SJohn Johansen * Change to the first profile specified in @hats that exists, and store
1170898127c3SJohn Johansen * the @hat_magic in the current task context. If the count == 0 and the
1171898127c3SJohn Johansen * @token matches that stored in the current task context, return to the
1172898127c3SJohn Johansen * top level profile.
1173898127c3SJohn Johansen *
117489dbf196SJohn Johansen * change_hat only applies to profiles in the current ns, and each profile
117589dbf196SJohn Johansen * in the ns must make the same transition otherwise change_hat will fail.
1176898127c3SJohn Johansen */
aa_change_hat(const char * hats[],int count,u64 token,int flags)1177df8073c6SJohn Johansen int aa_change_hat(const char *hats[], int count, u64 token, int flags)
1178898127c3SJohn Johansen {
117990c436a6SJohn Johansen const struct cred *subj_cred;
11809fcf78ccSJohn Johansen struct aa_task_ctx *ctx = task_ctx(current);
118189dbf196SJohn Johansen struct aa_label *label, *previous, *new = NULL, *target = NULL;
118289dbf196SJohn Johansen struct aa_profile *profile;
11832d679f3cSJohn Johansen struct aa_perms perms = {};
118489dbf196SJohn Johansen const char *info = NULL;
1185898127c3SJohn Johansen int error = 0;
1186898127c3SJohn Johansen
1187898127c3SJohn Johansen /* released below */
118890c436a6SJohn Johansen subj_cred = get_current_cred();
118990c436a6SJohn Johansen label = aa_get_newest_cred_label(subj_cred);
1190f175221aSJohn Johansen previous = aa_get_newest_label(ctx->previous);
1191898127c3SJohn Johansen
11929fcf78ccSJohn Johansen /*
11939fcf78ccSJohn Johansen * Detect no new privs being set, and store the label it
11949fcf78ccSJohn Johansen * occurred under. Ideally this would happen when nnp
11959fcf78ccSJohn Johansen * is set but there isn't a good way to do that yet.
11969fcf78ccSJohn Johansen *
11979fcf78ccSJohn Johansen * Testing for unconfined must be done before the subset test
11989fcf78ccSJohn Johansen */
11999fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp)
12009fcf78ccSJohn Johansen ctx->nnp = aa_get_label(label);
12019fcf78ccSJohn Johansen
1202637f688dSJohn Johansen if (unconfined(label)) {
120389dbf196SJohn Johansen info = "unconfined can not change_hat";
1204898127c3SJohn Johansen error = -EPERM;
120589dbf196SJohn Johansen goto fail;
1206898127c3SJohn Johansen }
1207898127c3SJohn Johansen
1208898127c3SJohn Johansen if (count) {
120990c436a6SJohn Johansen new = change_hat(subj_cred, label, hats, count, flags);
121089dbf196SJohn Johansen AA_BUG(!new);
121189dbf196SJohn Johansen if (IS_ERR(new)) {
121289dbf196SJohn Johansen error = PTR_ERR(new);
121389dbf196SJohn Johansen new = NULL;
121489dbf196SJohn Johansen /* already audited */
1215898127c3SJohn Johansen goto out;
1216898127c3SJohn Johansen }
1217898127c3SJohn Johansen
121890c436a6SJohn Johansen /* target cred is the same as current except new label */
121990c436a6SJohn Johansen error = may_change_ptraced_domain(subj_cred, new, &info);
122089dbf196SJohn Johansen if (error)
122189dbf196SJohn Johansen goto fail;
1222898127c3SJohn Johansen
12239fcf78ccSJohn Johansen /*
12249fcf78ccSJohn Johansen * no new privs prevents domain transitions that would
12259fcf78ccSJohn Johansen * reduce restrictions.
12269fcf78ccSJohn Johansen */
12279fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) &&
12283ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(new, ctx->nnp)) {
12299fcf78ccSJohn Johansen /* not an apparmor denial per se, so don't log it */
12309fcf78ccSJohn Johansen AA_DEBUG("no_new_privs - change_hat denied");
12319fcf78ccSJohn Johansen error = -EPERM;
12329fcf78ccSJohn Johansen goto out;
12339fcf78ccSJohn Johansen }
12349fcf78ccSJohn Johansen
123589dbf196SJohn Johansen if (flags & AA_CHANGE_TEST)
123689dbf196SJohn Johansen goto out;
1237898127c3SJohn Johansen
123889dbf196SJohn Johansen target = new;
123989dbf196SJohn Johansen error = aa_set_current_hat(new, token);
1240898127c3SJohn Johansen if (error == -EACCES)
1241898127c3SJohn Johansen /* kill task in case of brute force attacks */
124289dbf196SJohn Johansen goto kill;
124389dbf196SJohn Johansen } else if (previous && !(flags & AA_CHANGE_TEST)) {
12449fcf78ccSJohn Johansen /*
12459fcf78ccSJohn Johansen * no new privs prevents domain transitions that would
12469fcf78ccSJohn Johansen * reduce restrictions.
12479fcf78ccSJohn Johansen */
12489fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) &&
12493ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(previous, ctx->nnp)) {
12509fcf78ccSJohn Johansen /* not an apparmor denial per se, so don't log it */
12519fcf78ccSJohn Johansen AA_DEBUG("no_new_privs - change_hat denied");
12529fcf78ccSJohn Johansen error = -EPERM;
12539fcf78ccSJohn Johansen goto out;
12549fcf78ccSJohn Johansen }
12559fcf78ccSJohn Johansen
125689dbf196SJohn Johansen /* Return to saved label. Kill task if restore fails
1257898127c3SJohn Johansen * to avoid brute force attacks
1258898127c3SJohn Johansen */
125989dbf196SJohn Johansen target = previous;
1260637f688dSJohn Johansen error = aa_restore_previous_label(token);
126189dbf196SJohn Johansen if (error) {
126289dbf196SJohn Johansen if (error == -EACCES)
126389dbf196SJohn Johansen goto kill;
126489dbf196SJohn Johansen goto fail;
126589dbf196SJohn Johansen }
126689dbf196SJohn Johansen } /* else ignore @flags && restores when there is no saved profile */
1267898127c3SJohn Johansen
1268898127c3SJohn Johansen out:
126989dbf196SJohn Johansen aa_put_label(new);
127089dbf196SJohn Johansen aa_put_label(previous);
1271637f688dSJohn Johansen aa_put_label(label);
127290c436a6SJohn Johansen put_cred(subj_cred);
1273898127c3SJohn Johansen
1274898127c3SJohn Johansen return error;
127589dbf196SJohn Johansen
127689dbf196SJohn Johansen kill:
127789dbf196SJohn Johansen info = "failed token match";
127889dbf196SJohn Johansen perms.kill = AA_MAY_CHANGEHAT;
127989dbf196SJohn Johansen
128089dbf196SJohn Johansen fail:
128189dbf196SJohn Johansen fn_for_each_in_ns(label, profile,
128290c436a6SJohn Johansen aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT,
128389dbf196SJohn Johansen AA_MAY_CHANGEHAT, NULL, NULL, target,
128489dbf196SJohn Johansen GLOBAL_ROOT_UID, info, error));
128589dbf196SJohn Johansen
128689dbf196SJohn Johansen goto out;
1287898127c3SJohn Johansen }
1288898127c3SJohn Johansen
128989dbf196SJohn 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)1290e00b02bbSJohn Johansen static int change_profile_perms_wrapper(const char *op, const char *name,
129190c436a6SJohn Johansen const struct cred *subj_cred,
1292e00b02bbSJohn Johansen struct aa_profile *profile,
1293e00b02bbSJohn Johansen struct aa_label *target, bool stack,
1294e00b02bbSJohn Johansen u32 request, struct aa_perms *perms)
1295e00b02bbSJohn Johansen {
12961ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules,
12971ad22fccSJohn Johansen typeof(*rules), list);
1298e00b02bbSJohn Johansen const char *info = NULL;
1299e00b02bbSJohn Johansen int error = 0;
1300e00b02bbSJohn Johansen
1301e00b02bbSJohn Johansen if (!error)
1302e00b02bbSJohn Johansen error = change_profile_perms(profile, target, stack, request,
130398b824ffSJohn Johansen rules->file->start[AA_CLASS_FILE],
130453bdc46fSJohn Johansen perms);
1305e00b02bbSJohn Johansen if (error)
130690c436a6SJohn Johansen error = aa_audit_file(subj_cred, profile, perms, op, request,
130790c436a6SJohn Johansen name,
1308e00b02bbSJohn Johansen NULL, target, GLOBAL_ROOT_UID, info,
1309e00b02bbSJohn Johansen error);
1310e00b02bbSJohn Johansen
1311e00b02bbSJohn Johansen return error;
1312e00b02bbSJohn Johansen }
131389dbf196SJohn Johansen
1314*3c49ce0eSJohn Johansen static const char *stack_msg = "change_profile unprivileged unconfined converted to stacking";
13152d9da9b1SJohn Johansen
1316898127c3SJohn Johansen /**
1317898127c3SJohn Johansen * aa_change_profile - perform a one-way profile transition
1318aa9a39adSJohn Johansen * @fqname: name of profile may include namespace (NOT NULL)
1319df8073c6SJohn Johansen * @flags: flags affecting change behavior
1320898127c3SJohn Johansen *
1321898127c3SJohn Johansen * Change to new profile @name. Unlike with hats, there is no way
1322898127c3SJohn Johansen * to change back. If @name isn't specified the current profile name is
1323898127c3SJohn Johansen * used.
1324898127c3SJohn Johansen * If @onexec then the transition is delayed until
1325898127c3SJohn Johansen * the next exec.
1326898127c3SJohn Johansen *
1327898127c3SJohn Johansen * Returns %0 on success, error otherwise.
1328898127c3SJohn Johansen */
aa_change_profile(const char * fqname,int flags)1329df8073c6SJohn Johansen int aa_change_profile(const char *fqname, int flags)
1330898127c3SJohn Johansen {
1331e00b02bbSJohn Johansen struct aa_label *label, *new = NULL, *target = NULL;
1332e00b02bbSJohn Johansen struct aa_profile *profile;
13332d679f3cSJohn Johansen struct aa_perms perms = {};
1334e00b02bbSJohn Johansen const char *info = NULL;
1335e00b02bbSJohn Johansen const char *auditname = fqname; /* retain leading & if stack */
1336e00b02bbSJohn Johansen bool stack = flags & AA_CHANGE_STACK;
13379fcf78ccSJohn Johansen struct aa_task_ctx *ctx = task_ctx(current);
133890c436a6SJohn Johansen const struct cred *subj_cred = get_current_cred();
133947f6e5ccSJohn Johansen int error = 0;
1340e00b02bbSJohn Johansen char *op;
1341898127c3SJohn Johansen u32 request;
1342898127c3SJohn Johansen
13439fcf78ccSJohn Johansen label = aa_get_current_label();
13449fcf78ccSJohn Johansen
13459fcf78ccSJohn Johansen /*
13469fcf78ccSJohn Johansen * Detect no new privs being set, and store the label it
13479fcf78ccSJohn Johansen * occurred under. Ideally this would happen when nnp
13489fcf78ccSJohn Johansen * is set but there isn't a good way to do that yet.
13499fcf78ccSJohn Johansen *
13509fcf78ccSJohn Johansen * Testing for unconfined must be done before the subset test
13519fcf78ccSJohn Johansen */
13529fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp)
13539fcf78ccSJohn Johansen ctx->nnp = aa_get_label(label);
13549fcf78ccSJohn Johansen
1355aa9a39adSJohn Johansen if (!fqname || !*fqname) {
1356a0b845ffSXiyu Yang aa_put_label(label);
1357aa9a39adSJohn Johansen AA_DEBUG("no profile name");
1358898127c3SJohn Johansen return -EINVAL;
1359aa9a39adSJohn Johansen }
1360898127c3SJohn Johansen
1361df8073c6SJohn Johansen if (flags & AA_CHANGE_ONEXEC) {
1362898127c3SJohn Johansen request = AA_MAY_ONEXEC;
1363e00b02bbSJohn Johansen if (stack)
1364e00b02bbSJohn Johansen op = OP_STACK_ONEXEC;
1365e00b02bbSJohn Johansen else
1366898127c3SJohn Johansen op = OP_CHANGE_ONEXEC;
1367898127c3SJohn Johansen } else {
1368898127c3SJohn Johansen request = AA_MAY_CHANGE_PROFILE;
1369e00b02bbSJohn Johansen if (stack)
1370e00b02bbSJohn Johansen op = OP_STACK;
1371e00b02bbSJohn Johansen else
1372898127c3SJohn Johansen op = OP_CHANGE_PROFILE;
1373898127c3SJohn Johansen }
1374898127c3SJohn Johansen
13752d9da9b1SJohn Johansen /* This should move to a per profile test. Requires pushing build
13762d9da9b1SJohn Johansen * into callback
13772d9da9b1SJohn Johansen */
13782d9da9b1SJohn Johansen if (!stack && unconfined(label) &&
13792d9da9b1SJohn Johansen label == &labels_ns(label)->unconfined->label &&
13802d9da9b1SJohn Johansen aa_unprivileged_unconfined_restricted &&
13812d9da9b1SJohn Johansen /* TODO: refactor so this check is a fn */
13822d9da9b1SJohn Johansen cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE,
13832d9da9b1SJohn Johansen CAP_OPT_NOAUDIT)) {
13842d9da9b1SJohn Johansen /* regardless of the request in this case apparmor
13852d9da9b1SJohn Johansen * stacks against unconfined so admin set policy can't be
13862d9da9b1SJohn Johansen * by-passed
13872d9da9b1SJohn Johansen */
13882d9da9b1SJohn Johansen stack = true;
13892d9da9b1SJohn Johansen perms.audit = request;
13902d9da9b1SJohn Johansen (void) fn_for_each_in_ns(label, profile,
13912d9da9b1SJohn Johansen aa_audit_file(subj_cred, profile, &perms, op,
13922d9da9b1SJohn Johansen request, auditname, NULL, target,
13932d9da9b1SJohn Johansen GLOBAL_ROOT_UID, stack_msg, 0));
13942d9da9b1SJohn Johansen perms.audit = 0;
13952d9da9b1SJohn Johansen }
13962d9da9b1SJohn Johansen
1397e00b02bbSJohn Johansen if (*fqname == '&') {
1398e00b02bbSJohn Johansen stack = true;
1399e00b02bbSJohn Johansen /* don't have label_parse() do stacking */
1400e00b02bbSJohn Johansen fqname++;
1401c29bceb3SJohn Johansen }
1402e00b02bbSJohn Johansen target = aa_label_parse(label, fqname, GFP_KERNEL, true, false);
1403e00b02bbSJohn Johansen if (IS_ERR(target)) {
1404e00b02bbSJohn Johansen struct aa_profile *tprofile;
1405c29bceb3SJohn Johansen
1406e00b02bbSJohn Johansen info = "label not found";
1407e00b02bbSJohn Johansen error = PTR_ERR(target);
1408e00b02bbSJohn Johansen target = NULL;
1409e00b02bbSJohn Johansen /*
1410e00b02bbSJohn Johansen * TODO: fixme using labels_profile is not right - do profile
1411e00b02bbSJohn Johansen * per complain profile
1412e00b02bbSJohn Johansen */
1413df8073c6SJohn Johansen if ((flags & AA_CHANGE_TEST) ||
1414e00b02bbSJohn Johansen !COMPLAIN_MODE(labels_profile(label)))
1415898127c3SJohn Johansen goto audit;
1416898127c3SJohn Johansen /* released below */
141758f89ce5SJohn Johansen tprofile = aa_new_learning_profile(labels_profile(label), false,
1418e00b02bbSJohn Johansen fqname, GFP_KERNEL);
1419e00b02bbSJohn Johansen if (!tprofile) {
1420898127c3SJohn Johansen info = "failed null profile create";
1421898127c3SJohn Johansen error = -ENOMEM;
1422898127c3SJohn Johansen goto audit;
1423898127c3SJohn Johansen }
1424e00b02bbSJohn Johansen target = &tprofile->label;
1425e00b02bbSJohn Johansen goto check;
1426898127c3SJohn Johansen }
1427898127c3SJohn Johansen
1428e00b02bbSJohn Johansen /*
1429e00b02bbSJohn Johansen * self directed transitions only apply to current policy ns
1430e00b02bbSJohn Johansen * TODO: currently requiring perms for stacking and straight change
1431e00b02bbSJohn Johansen * stacking doesn't strictly need this. Determine how much
1432e00b02bbSJohn Johansen * we want to loosen this restriction for stacking
1433e00b02bbSJohn Johansen *
1434e00b02bbSJohn Johansen * if (!stack) {
1435e00b02bbSJohn Johansen */
1436e00b02bbSJohn Johansen error = fn_for_each_in_ns(label, profile,
1437e00b02bbSJohn Johansen change_profile_perms_wrapper(op, auditname,
143890c436a6SJohn Johansen subj_cred,
1439e00b02bbSJohn Johansen profile, target, stack,
1440e00b02bbSJohn Johansen request, &perms));
1441e00b02bbSJohn Johansen if (error)
1442e00b02bbSJohn Johansen /* auditing done in change_profile_perms_wrapper */
1443e00b02bbSJohn Johansen goto out;
1444aa9a39adSJohn Johansen
1445e00b02bbSJohn Johansen /* } */
1446e00b02bbSJohn Johansen
1447e00b02bbSJohn Johansen check:
1448898127c3SJohn Johansen /* check if tracing task is allowed to trace target domain */
144990c436a6SJohn Johansen error = may_change_ptraced_domain(subj_cred, target, &info);
1450e00b02bbSJohn Johansen if (error && !fn_for_each_in_ns(label, profile,
1451e00b02bbSJohn Johansen COMPLAIN_MODE(profile)))
1452e00b02bbSJohn Johansen goto audit;
1453e00b02bbSJohn Johansen
1454e00b02bbSJohn Johansen /* TODO: add permission check to allow this
1455e00b02bbSJohn Johansen * if ((flags & AA_CHANGE_ONEXEC) && !current_is_single_threaded()) {
1456e00b02bbSJohn Johansen * info = "not a single threaded task";
1457e00b02bbSJohn Johansen * error = -EACCES;
1458e00b02bbSJohn Johansen * goto audit;
1459e00b02bbSJohn Johansen * }
1460e00b02bbSJohn Johansen */
1461e00b02bbSJohn Johansen if (flags & AA_CHANGE_TEST)
1462e00b02bbSJohn Johansen goto out;
1463e00b02bbSJohn Johansen
14649fcf78ccSJohn Johansen /* stacking is always a subset, so only check the nonstack case */
14659fcf78ccSJohn Johansen if (!stack) {
14669fcf78ccSJohn Johansen new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
14679fcf78ccSJohn Johansen aa_get_label(target),
14689fcf78ccSJohn Johansen aa_get_label(&profile->label));
14699fcf78ccSJohn Johansen /*
14709fcf78ccSJohn Johansen * no new privs prevents domain transitions that would
14719fcf78ccSJohn Johansen * reduce restrictions.
14729fcf78ccSJohn Johansen */
14739fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) &&
14743ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(new, ctx->nnp)) {
14759fcf78ccSJohn Johansen /* not an apparmor denial per se, so don't log it */
14769fcf78ccSJohn Johansen AA_DEBUG("no_new_privs - change_hat denied");
14779fcf78ccSJohn Johansen error = -EPERM;
14789fcf78ccSJohn Johansen goto out;
14799fcf78ccSJohn Johansen }
14809fcf78ccSJohn Johansen }
14819fcf78ccSJohn Johansen
1482e00b02bbSJohn Johansen if (!(flags & AA_CHANGE_ONEXEC)) {
1483e00b02bbSJohn Johansen /* only transition profiles in the current ns */
1484e00b02bbSJohn Johansen if (stack)
1485e00b02bbSJohn Johansen new = aa_label_merge(label, target, GFP_KERNEL);
1486e00b02bbSJohn Johansen if (IS_ERR_OR_NULL(new)) {
1487e00b02bbSJohn Johansen info = "failed to build target label";
1488d6d478aeSJohn Johansen if (!new)
1489d6d478aeSJohn Johansen error = -ENOMEM;
1490d6d478aeSJohn Johansen else
1491e00b02bbSJohn Johansen error = PTR_ERR(new);
1492e00b02bbSJohn Johansen new = NULL;
1493e00b02bbSJohn Johansen perms.allow = 0;
1494898127c3SJohn Johansen goto audit;
1495898127c3SJohn Johansen }
1496e00b02bbSJohn Johansen error = aa_replace_current_label(new);
14979fcf78ccSJohn Johansen } else {
14989fcf78ccSJohn Johansen if (new) {
14999fcf78ccSJohn Johansen aa_put_label(new);
15009fcf78ccSJohn Johansen new = NULL;
15019fcf78ccSJohn Johansen }
15029fcf78ccSJohn Johansen
1503e00b02bbSJohn Johansen /* full transition will be built in exec path */
15040897fcb1SQuanfa Fu aa_set_current_onexec(target, stack);
15059fcf78ccSJohn Johansen }
1506898127c3SJohn Johansen
1507898127c3SJohn Johansen audit:
1508e00b02bbSJohn Johansen error = fn_for_each_in_ns(label, profile,
150990c436a6SJohn Johansen aa_audit_file(subj_cred,
151090c436a6SJohn Johansen profile, &perms, op, request, auditname,
1511e00b02bbSJohn Johansen NULL, new ? new : target,
1512e00b02bbSJohn Johansen GLOBAL_ROOT_UID, info, error));
1513898127c3SJohn Johansen
1514e00b02bbSJohn Johansen out:
1515e00b02bbSJohn Johansen aa_put_label(new);
1516e00b02bbSJohn Johansen aa_put_label(target);
1517637f688dSJohn Johansen aa_put_label(label);
151890c436a6SJohn Johansen put_cred(subj_cred);
1519898127c3SJohn Johansen
1520898127c3SJohn Johansen return error;
1521898127c3SJohn Johansen }
1522