xref: /linux/security/apparmor/domain.c (revision 341c1fda5e17156619fb71acfc7082b2669b4b72)
1898127c3SJohn Johansen /*
2898127c3SJohn Johansen  * AppArmor security module
3898127c3SJohn Johansen  *
4898127c3SJohn Johansen  * This file contains AppArmor policy attachment and domain transitions
5898127c3SJohn Johansen  *
6898127c3SJohn Johansen  * Copyright (C) 2002-2008 Novell/SUSE
7898127c3SJohn Johansen  * Copyright 2009-2010 Canonical Ltd.
8898127c3SJohn Johansen  *
9898127c3SJohn Johansen  * This program is free software; you can redistribute it and/or
10898127c3SJohn Johansen  * modify it under the terms of the GNU General Public License as
11898127c3SJohn Johansen  * published by the Free Software Foundation, version 2 of the
12898127c3SJohn Johansen  * License.
13898127c3SJohn Johansen  */
14898127c3SJohn Johansen 
15898127c3SJohn Johansen #include <linux/errno.h>
16898127c3SJohn Johansen #include <linux/fdtable.h>
17898127c3SJohn Johansen #include <linux/file.h>
18898127c3SJohn Johansen #include <linux/mount.h>
19898127c3SJohn Johansen #include <linux/syscalls.h>
20898127c3SJohn Johansen #include <linux/tracehook.h>
21898127c3SJohn Johansen #include <linux/personality.h>
228e51f908SMatthew Garrett #include <linux/xattr.h>
23898127c3SJohn Johansen 
24898127c3SJohn Johansen #include "include/audit.h"
25898127c3SJohn Johansen #include "include/apparmorfs.h"
26d8889d49SJohn Johansen #include "include/cred.h"
27898127c3SJohn Johansen #include "include/domain.h"
28898127c3SJohn Johansen #include "include/file.h"
29898127c3SJohn Johansen #include "include/ipc.h"
30898127c3SJohn Johansen #include "include/match.h"
31898127c3SJohn Johansen #include "include/path.h"
32898127c3SJohn Johansen #include "include/policy.h"
33cff281f6SJohn Johansen #include "include/policy_ns.h"
34898127c3SJohn Johansen 
35898127c3SJohn Johansen /**
36898127c3SJohn Johansen  * aa_free_domain_entries - free entries in a domain table
37898127c3SJohn Johansen  * @domain: the domain table to free  (MAYBE NULL)
38898127c3SJohn Johansen  */
39898127c3SJohn Johansen void aa_free_domain_entries(struct aa_domain *domain)
40898127c3SJohn Johansen {
41898127c3SJohn Johansen 	int i;
42898127c3SJohn Johansen 	if (domain) {
43898127c3SJohn Johansen 		if (!domain->table)
44898127c3SJohn Johansen 			return;
45898127c3SJohn Johansen 
46898127c3SJohn Johansen 		for (i = 0; i < domain->size; i++)
47898127c3SJohn Johansen 			kzfree(domain->table[i]);
48898127c3SJohn Johansen 		kzfree(domain->table);
49898127c3SJohn Johansen 		domain->table = NULL;
50898127c3SJohn Johansen 	}
51898127c3SJohn Johansen }
52898127c3SJohn Johansen 
53898127c3SJohn Johansen /**
54898127c3SJohn Johansen  * may_change_ptraced_domain - check if can change profile on ptraced task
55b2d09ae4SJohn Johansen  * @to_label: profile to change to  (NOT NULL)
56b2d09ae4SJohn Johansen  * @info: message if there is an error
57898127c3SJohn Johansen  *
5851775fe7SOleg Nesterov  * Check if current is ptraced and if so if the tracing task is allowed
59898127c3SJohn Johansen  * to trace the new domain
60898127c3SJohn Johansen  *
61898127c3SJohn Johansen  * Returns: %0 or error if change not allowed
62898127c3SJohn Johansen  */
63b2d09ae4SJohn Johansen static int may_change_ptraced_domain(struct aa_label *to_label,
64b2d09ae4SJohn Johansen 				     const char **info)
65898127c3SJohn Johansen {
66898127c3SJohn Johansen 	struct task_struct *tracer;
67637f688dSJohn Johansen 	struct aa_label *tracerl = NULL;
68898127c3SJohn Johansen 	int error = 0;
69898127c3SJohn Johansen 
70898127c3SJohn Johansen 	rcu_read_lock();
7151775fe7SOleg Nesterov 	tracer = ptrace_parent(current);
723cfcc19eSJohn Johansen 	if (tracer)
73898127c3SJohn Johansen 		/* released below */
74637f688dSJohn Johansen 		tracerl = aa_get_task_label(tracer);
75898127c3SJohn Johansen 
76898127c3SJohn Johansen 	/* not ptraced */
77637f688dSJohn Johansen 	if (!tracer || unconfined(tracerl))
78898127c3SJohn Johansen 		goto out;
79898127c3SJohn Johansen 
80b2d09ae4SJohn Johansen 	error = aa_may_ptrace(tracerl, to_label, PTRACE_MODE_ATTACH);
81898127c3SJohn Johansen 
82898127c3SJohn Johansen out:
8304fdc099SJohn Johansen 	rcu_read_unlock();
84637f688dSJohn Johansen 	aa_put_label(tracerl);
85898127c3SJohn Johansen 
86b2d09ae4SJohn Johansen 	if (error)
87b2d09ae4SJohn Johansen 		*info = "ptrace prevents transition";
88898127c3SJohn Johansen 	return error;
89898127c3SJohn Johansen }
90898127c3SJohn Johansen 
9193c98a48SJohn Johansen /**** TODO: dedup to aa_label_match - needs perm and dfa, merging
9293c98a48SJohn Johansen  * specifically this is an exact copy of aa_label_match except
9393c98a48SJohn Johansen  * aa_compute_perms is replaced with aa_compute_fperms
9493c98a48SJohn Johansen  * and policy.dfa with file.dfa
9593c98a48SJohn Johansen  ****/
9693c98a48SJohn Johansen /* match a profile and its associated ns component if needed
9793c98a48SJohn Johansen  * Assumes visibility test has already been done.
9893c98a48SJohn Johansen  * If a subns profile is not to be matched should be prescreened with
9993c98a48SJohn Johansen  * visibility test.
10093c98a48SJohn Johansen  */
10193c98a48SJohn Johansen static inline unsigned int match_component(struct aa_profile *profile,
10293c98a48SJohn Johansen 					   struct aa_profile *tp,
10393c98a48SJohn Johansen 					   bool stack, unsigned int state)
10493c98a48SJohn Johansen {
10593c98a48SJohn Johansen 	const char *ns_name;
10693c98a48SJohn Johansen 
10793c98a48SJohn Johansen 	if (stack)
10893c98a48SJohn Johansen 		state = aa_dfa_match(profile->file.dfa, state, "&");
10993c98a48SJohn Johansen 	if (profile->ns == tp->ns)
11093c98a48SJohn Johansen 		return aa_dfa_match(profile->file.dfa, state, tp->base.hname);
11193c98a48SJohn Johansen 
11293c98a48SJohn Johansen 	/* try matching with namespace name and then profile */
11393c98a48SJohn Johansen 	ns_name = aa_ns_name(profile->ns, tp->ns, true);
11493c98a48SJohn Johansen 	state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
11593c98a48SJohn Johansen 	state = aa_dfa_match(profile->file.dfa, state, ns_name);
11693c98a48SJohn Johansen 	state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
11793c98a48SJohn Johansen 	return aa_dfa_match(profile->file.dfa, state, tp->base.hname);
11893c98a48SJohn Johansen }
11993c98a48SJohn Johansen 
12093c98a48SJohn Johansen /**
12193c98a48SJohn Johansen  * label_compound_match - find perms for full compound label
12293c98a48SJohn Johansen  * @profile: profile to find perms for
12393c98a48SJohn Johansen  * @label: label to check access permissions for
12493c98a48SJohn Johansen  * @stack: whether this is a stacking request
12593c98a48SJohn Johansen  * @start: state to start match in
12693c98a48SJohn Johansen  * @subns: whether to do permission checks on components in a subns
12793c98a48SJohn Johansen  * @request: permissions to request
12893c98a48SJohn Johansen  * @perms: perms struct to set
12993c98a48SJohn Johansen  *
13093c98a48SJohn Johansen  * Returns: 0 on success else ERROR
13193c98a48SJohn Johansen  *
13293c98a48SJohn Johansen  * For the label A//&B//&C this does the perm match for A//&B//&C
13393c98a48SJohn Johansen  * @perms should be preinitialized with allperms OR a previous permission
13493c98a48SJohn Johansen  *        check to be stacked.
13593c98a48SJohn Johansen  */
13693c98a48SJohn Johansen static int label_compound_match(struct aa_profile *profile,
13793c98a48SJohn Johansen 				struct aa_label *label, bool stack,
13893c98a48SJohn Johansen 				unsigned int state, bool subns, u32 request,
13993c98a48SJohn Johansen 				struct aa_perms *perms)
14093c98a48SJohn Johansen {
14193c98a48SJohn Johansen 	struct aa_profile *tp;
14293c98a48SJohn Johansen 	struct label_it i;
14393c98a48SJohn Johansen 	struct path_cond cond = { };
14493c98a48SJohn Johansen 
14593c98a48SJohn Johansen 	/* find first subcomponent that is visible */
14693c98a48SJohn Johansen 	label_for_each(i, label, tp) {
14793c98a48SJohn Johansen 		if (!aa_ns_visible(profile->ns, tp->ns, subns))
14893c98a48SJohn Johansen 			continue;
14993c98a48SJohn Johansen 		state = match_component(profile, tp, stack, state);
15093c98a48SJohn Johansen 		if (!state)
15193c98a48SJohn Johansen 			goto fail;
15293c98a48SJohn Johansen 		goto next;
15393c98a48SJohn Johansen 	}
15493c98a48SJohn Johansen 
15593c98a48SJohn Johansen 	/* no component visible */
15693c98a48SJohn Johansen 	*perms = allperms;
15793c98a48SJohn Johansen 	return 0;
15893c98a48SJohn Johansen 
15993c98a48SJohn Johansen next:
16093c98a48SJohn Johansen 	label_for_each_cont(i, label, tp) {
16193c98a48SJohn Johansen 		if (!aa_ns_visible(profile->ns, tp->ns, subns))
16293c98a48SJohn Johansen 			continue;
16393c98a48SJohn Johansen 		state = aa_dfa_match(profile->file.dfa, state, "//&");
16493c98a48SJohn Johansen 		state = match_component(profile, tp, false, state);
16593c98a48SJohn Johansen 		if (!state)
16693c98a48SJohn Johansen 			goto fail;
16793c98a48SJohn Johansen 	}
16893c98a48SJohn Johansen 	*perms = aa_compute_fperms(profile->file.dfa, state, &cond);
16993c98a48SJohn Johansen 	aa_apply_modes_to_perms(profile, perms);
17093c98a48SJohn Johansen 	if ((perms->allow & request) != request)
17193c98a48SJohn Johansen 		return -EACCES;
17293c98a48SJohn Johansen 
17393c98a48SJohn Johansen 	return 0;
17493c98a48SJohn Johansen 
17593c98a48SJohn Johansen fail:
17693c98a48SJohn Johansen 	*perms = nullperms;
17793c98a48SJohn Johansen 	return -EACCES;
17893c98a48SJohn Johansen }
17993c98a48SJohn Johansen 
18093c98a48SJohn Johansen /**
18193c98a48SJohn Johansen  * label_components_match - find perms for all subcomponents of a label
18293c98a48SJohn Johansen  * @profile: profile to find perms for
18393c98a48SJohn Johansen  * @label: label to check access permissions for
18493c98a48SJohn Johansen  * @stack: whether this is a stacking request
18593c98a48SJohn Johansen  * @start: state to start match in
18693c98a48SJohn Johansen  * @subns: whether to do permission checks on components in a subns
18793c98a48SJohn Johansen  * @request: permissions to request
18893c98a48SJohn Johansen  * @perms: an initialized perms struct to add accumulation to
18993c98a48SJohn Johansen  *
19093c98a48SJohn Johansen  * Returns: 0 on success else ERROR
19193c98a48SJohn Johansen  *
19293c98a48SJohn Johansen  * For the label A//&B//&C this does the perm match for each of A and B and C
19393c98a48SJohn Johansen  * @perms should be preinitialized with allperms OR a previous permission
19493c98a48SJohn Johansen  *        check to be stacked.
19593c98a48SJohn Johansen  */
19693c98a48SJohn Johansen static int label_components_match(struct aa_profile *profile,
19793c98a48SJohn Johansen 				  struct aa_label *label, bool stack,
19893c98a48SJohn Johansen 				  unsigned int start, bool subns, u32 request,
19993c98a48SJohn Johansen 				  struct aa_perms *perms)
20093c98a48SJohn Johansen {
20193c98a48SJohn Johansen 	struct aa_profile *tp;
20293c98a48SJohn Johansen 	struct label_it i;
20393c98a48SJohn Johansen 	struct aa_perms tmp;
20493c98a48SJohn Johansen 	struct path_cond cond = { };
20593c98a48SJohn Johansen 	unsigned int state = 0;
20693c98a48SJohn Johansen 
20793c98a48SJohn Johansen 	/* find first subcomponent to test */
20893c98a48SJohn Johansen 	label_for_each(i, label, tp) {
20993c98a48SJohn Johansen 		if (!aa_ns_visible(profile->ns, tp->ns, subns))
21093c98a48SJohn Johansen 			continue;
21193c98a48SJohn Johansen 		state = match_component(profile, tp, stack, start);
21293c98a48SJohn Johansen 		if (!state)
21393c98a48SJohn Johansen 			goto fail;
21493c98a48SJohn Johansen 		goto next;
21593c98a48SJohn Johansen 	}
21693c98a48SJohn Johansen 
21793c98a48SJohn Johansen 	/* no subcomponents visible - no change in perms */
21893c98a48SJohn Johansen 	return 0;
21993c98a48SJohn Johansen 
22093c98a48SJohn Johansen next:
22193c98a48SJohn Johansen 	tmp = aa_compute_fperms(profile->file.dfa, state, &cond);
22293c98a48SJohn Johansen 	aa_apply_modes_to_perms(profile, &tmp);
22393c98a48SJohn Johansen 	aa_perms_accum(perms, &tmp);
22493c98a48SJohn Johansen 	label_for_each_cont(i, label, tp) {
22593c98a48SJohn Johansen 		if (!aa_ns_visible(profile->ns, tp->ns, subns))
22693c98a48SJohn Johansen 			continue;
22793c98a48SJohn Johansen 		state = match_component(profile, tp, stack, start);
22893c98a48SJohn Johansen 		if (!state)
22993c98a48SJohn Johansen 			goto fail;
23093c98a48SJohn Johansen 		tmp = aa_compute_fperms(profile->file.dfa, state, &cond);
23193c98a48SJohn Johansen 		aa_apply_modes_to_perms(profile, &tmp);
23293c98a48SJohn Johansen 		aa_perms_accum(perms, &tmp);
23393c98a48SJohn Johansen 	}
23493c98a48SJohn Johansen 
23593c98a48SJohn Johansen 	if ((perms->allow & request) != request)
23693c98a48SJohn Johansen 		return -EACCES;
23793c98a48SJohn Johansen 
23893c98a48SJohn Johansen 	return 0;
23993c98a48SJohn Johansen 
24093c98a48SJohn Johansen fail:
24193c98a48SJohn Johansen 	*perms = nullperms;
24293c98a48SJohn Johansen 	return -EACCES;
24393c98a48SJohn Johansen }
24493c98a48SJohn Johansen 
24593c98a48SJohn Johansen /**
24693c98a48SJohn Johansen  * label_match - do a multi-component label match
24793c98a48SJohn Johansen  * @profile: profile to match against (NOT NULL)
24893c98a48SJohn Johansen  * @label: label to match (NOT NULL)
24993c98a48SJohn Johansen  * @stack: whether this is a stacking request
25093c98a48SJohn Johansen  * @state: state to start in
25193c98a48SJohn Johansen  * @subns: whether to match subns components
25293c98a48SJohn Johansen  * @request: permission request
25393c98a48SJohn Johansen  * @perms: Returns computed perms (NOT NULL)
25493c98a48SJohn Johansen  *
25593c98a48SJohn Johansen  * Returns: the state the match finished in, may be the none matching state
25693c98a48SJohn Johansen  */
25793c98a48SJohn Johansen static int label_match(struct aa_profile *profile, struct aa_label *label,
25893c98a48SJohn Johansen 		       bool stack, unsigned int state, bool subns, u32 request,
25993c98a48SJohn Johansen 		       struct aa_perms *perms)
26093c98a48SJohn Johansen {
26193c98a48SJohn Johansen 	int error;
26293c98a48SJohn Johansen 
26393c98a48SJohn Johansen 	*perms = nullperms;
26493c98a48SJohn Johansen 	error = label_compound_match(profile, label, stack, state, subns,
26593c98a48SJohn Johansen 				     request, perms);
26693c98a48SJohn Johansen 	if (!error)
26793c98a48SJohn Johansen 		return error;
26893c98a48SJohn Johansen 
26993c98a48SJohn Johansen 	*perms = allperms;
27093c98a48SJohn Johansen 	return label_components_match(profile, label, stack, state, subns,
27193c98a48SJohn Johansen 				      request, perms);
27293c98a48SJohn Johansen }
27393c98a48SJohn Johansen 
27493c98a48SJohn Johansen /******* end TODO: dedup *****/
27593c98a48SJohn Johansen 
276898127c3SJohn Johansen /**
277898127c3SJohn Johansen  * change_profile_perms - find permissions for change_profile
278898127c3SJohn Johansen  * @profile: the current profile  (NOT NULL)
27993c98a48SJohn Johansen  * @target: label to transition to (NOT NULL)
28093c98a48SJohn Johansen  * @stack: whether this is a stacking request
281898127c3SJohn Johansen  * @request: requested perms
282898127c3SJohn Johansen  * @start: state to start matching in
283898127c3SJohn Johansen  *
28493c98a48SJohn Johansen  *
285898127c3SJohn Johansen  * Returns: permission set
28693c98a48SJohn Johansen  *
28793c98a48SJohn Johansen  * currently only matches full label A//&B//&C or individual components A, B, C
28893c98a48SJohn Johansen  * not arbitrary combinations. Eg. A//&B, C
289898127c3SJohn Johansen  */
29093c98a48SJohn Johansen static int change_profile_perms(struct aa_profile *profile,
29193c98a48SJohn Johansen 				struct aa_label *target, bool stack,
29293c98a48SJohn Johansen 				u32 request, unsigned int start,
29393c98a48SJohn Johansen 				struct aa_perms *perms)
29493c98a48SJohn Johansen {
29593c98a48SJohn Johansen 	if (profile_unconfined(profile)) {
29693c98a48SJohn Johansen 		perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
29793c98a48SJohn Johansen 		perms->audit = perms->quiet = perms->kill = 0;
29893c98a48SJohn Johansen 		return 0;
29993c98a48SJohn Johansen 	}
30093c98a48SJohn Johansen 
30193c98a48SJohn Johansen 	/* TODO: add profile in ns screening */
30293c98a48SJohn Johansen 	return label_match(profile, target, stack, start, true, request, perms);
30393c98a48SJohn Johansen }
30493c98a48SJohn Johansen 
305898127c3SJohn Johansen /**
3068e51f908SMatthew Garrett  * aa_xattrs_match - check whether a file matches the xattrs defined in profile
3078e51f908SMatthew Garrett  * @bprm: binprm struct for the process to validate
3088e51f908SMatthew Garrett  * @profile: profile to match against (NOT NULL)
30973f488cdSJohn Johansen  * @state: state to start match in
3108e51f908SMatthew Garrett  *
3118e51f908SMatthew Garrett  * Returns: number of extended attributes that matched, or < 0 on error
3128e51f908SMatthew Garrett  */
3138e51f908SMatthew Garrett static int aa_xattrs_match(const struct linux_binprm *bprm,
31473f488cdSJohn Johansen 			   struct aa_profile *profile, unsigned int state)
3158e51f908SMatthew Garrett {
3168e51f908SMatthew Garrett 	int i;
317a61ecd32SColin Ian King 	ssize_t size;
3188e51f908SMatthew Garrett 	struct dentry *d;
3198e51f908SMatthew Garrett 	char *value = NULL;
3208e51f908SMatthew Garrett 	int value_size = 0, ret = profile->xattr_count;
3218e51f908SMatthew Garrett 
3228e51f908SMatthew Garrett 	if (!bprm || !profile->xattr_count)
3238e51f908SMatthew Garrett 		return 0;
3248e51f908SMatthew Garrett 
32573f488cdSJohn Johansen 	/* transition from exec match to xattr set */
32673f488cdSJohn Johansen 	state = aa_dfa_null_transition(profile->xmatch, state);
32773f488cdSJohn Johansen 
3288e51f908SMatthew Garrett 	d = bprm->file->f_path.dentry;
3298e51f908SMatthew Garrett 
3308e51f908SMatthew Garrett 	for (i = 0; i < profile->xattr_count; i++) {
3318e51f908SMatthew Garrett 		size = vfs_getxattr_alloc(d, profile->xattrs[i], &value,
3328e51f908SMatthew Garrett 					  value_size, GFP_KERNEL);
33373f488cdSJohn Johansen 		if (size >= 0) {
33473f488cdSJohn Johansen 			u32 perm;
3358e51f908SMatthew Garrett 
3368e51f908SMatthew Garrett 			/* Check the xattr value, not just presence */
33773f488cdSJohn Johansen 			state = aa_dfa_match_len(profile->xmatch, state, value,
33873f488cdSJohn Johansen 						 size);
33973f488cdSJohn Johansen 			perm = dfa_user_allow(profile->xmatch, state);
34073f488cdSJohn Johansen 			if (!(perm & MAY_EXEC)) {
3418e51f908SMatthew Garrett 				ret = -EINVAL;
3428e51f908SMatthew Garrett 				goto out;
3438e51f908SMatthew Garrett 			}
34473f488cdSJohn Johansen 		}
34573f488cdSJohn Johansen 		/* transition to next element */
34673f488cdSJohn Johansen 		state = aa_dfa_null_transition(profile->xmatch, 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 /**
368898127c3SJohn Johansen  * __attach_match_ - find an attachment match
3698e51f908SMatthew Garrett  * @bprm - binprm structure of transitioning task
370898127c3SJohn Johansen  * @name - to match against  (NOT NULL)
371898127c3SJohn Johansen  * @head - profile list to walk  (NOT NULL)
372844b8292SJohn Johansen  * @info - info message if there was an error (NOT NULL)
373898127c3SJohn Johansen  *
374898127c3SJohn Johansen  * Do a linear search on the profiles in the list.  There is a matching
375898127c3SJohn Johansen  * preference where an exact match is preferred over a name which uses
376898127c3SJohn Johansen  * expressions to match, and matching expressions with the greatest
377898127c3SJohn Johansen  * xmatch_len are preferred.
378898127c3SJohn Johansen  *
379898127c3SJohn Johansen  * Requires: @head not be shared or have appropriate locks held
380898127c3SJohn Johansen  *
381898127c3SJohn Johansen  * Returns: profile or NULL if no match found
382898127c3SJohn Johansen  */
3838e51f908SMatthew Garrett static struct aa_profile *__attach_match(const struct linux_binprm *bprm,
3848e51f908SMatthew Garrett 					 const char *name,
385844b8292SJohn Johansen 					 struct list_head *head,
386844b8292SJohn Johansen 					 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 
39501e2b670SJohn Johansen 	list_for_each_entry_rcu(profile, head, base.list) {
39606d426d1SJohn Johansen 		if (profile->label.flags & FLAG_NULL &&
39706d426d1SJohn Johansen 		    &profile->label == ns_unconfined(profile->ns))
398898127c3SJohn Johansen 			continue;
39906d426d1SJohn Johansen 
4008e51f908SMatthew Garrett 		/* Find the "best" matching profile. Profiles must
4018e51f908SMatthew Garrett 		 * match the path and extended attributes (if any)
4028e51f908SMatthew Garrett 		 * associated with the file. A more specific path
4038e51f908SMatthew Garrett 		 * match will be preferred over a less specific one,
4048e51f908SMatthew Garrett 		 * and a match with more matching extended attributes
4058e51f908SMatthew Garrett 		 * will be preferred over one with fewer. If the best
4068e51f908SMatthew Garrett 		 * match has both the same level of path specificity
4078e51f908SMatthew Garrett 		 * and the same number of matching extended attributes
4088e51f908SMatthew Garrett 		 * as another profile, signal a conflict and refuse to
4098e51f908SMatthew Garrett 		 * match.
4108e51f908SMatthew Garrett 		 */
411844b8292SJohn Johansen 		if (profile->xmatch) {
41221f60661SJohn Johansen 			unsigned int state, count;
413844b8292SJohn Johansen 			u32 perm;
414844b8292SJohn Johansen 
41521f60661SJohn Johansen 			state = aa_dfa_leftmatch(profile->xmatch, DFA_START,
41621f60661SJohn Johansen 						 name, &count);
417844b8292SJohn Johansen 			perm = dfa_user_allow(profile->xmatch, state);
418898127c3SJohn Johansen 			/* any accepting state means a valid match. */
419898127c3SJohn Johansen 			if (perm & MAY_EXEC) {
42021f60661SJohn Johansen 				int ret;
4218e51f908SMatthew Garrett 
42221f60661SJohn Johansen 				if (count < candidate_len)
42321f60661SJohn Johansen 					continue;
42421f60661SJohn Johansen 
42521f60661SJohn Johansen 				ret = aa_xattrs_match(bprm, profile, state);
4268e51f908SMatthew Garrett 				/* Fail matching if the xattrs don't match */
4278e51f908SMatthew Garrett 				if (ret < 0)
4288e51f908SMatthew Garrett 					continue;
4298e51f908SMatthew Garrett 
43073f488cdSJohn Johansen 				/*
43173f488cdSJohn Johansen 				 * TODO: allow for more flexible best match
43273f488cdSJohn Johansen 				 *
43373f488cdSJohn Johansen 				 * The new match isn't more specific
4348e51f908SMatthew Garrett 				 * than the current best match
4358e51f908SMatthew Garrett 				 */
43621f60661SJohn Johansen 				if (count == candidate_len &&
43721f60661SJohn Johansen 				    ret <= candidate_xattrs) {
4388e51f908SMatthew Garrett 					/* Match is equivalent, so conflict */
43921f60661SJohn Johansen 					if (ret == candidate_xattrs)
4401a3881d3SMatthew Garrett 						conflict = true;
4411a3881d3SMatthew Garrett 					continue;
4421a3881d3SMatthew Garrett 				}
4438e51f908SMatthew Garrett 
4448e51f908SMatthew Garrett 				/* Either the same length with more matching
4458e51f908SMatthew Garrett 				 * xattrs, or a longer match
4468e51f908SMatthew Garrett 				 */
447898127c3SJohn Johansen 				candidate = profile;
44821f60661SJohn Johansen 				candidate_len = profile->xmatch_len;
44921f60661SJohn Johansen 				candidate_xattrs = ret;
450844b8292SJohn Johansen 				conflict = false;
451844b8292SJohn Johansen 			}
45273f488cdSJohn Johansen 		} else if (!strcmp(profile->base.name, name))
45373f488cdSJohn Johansen 			/*
45473f488cdSJohn Johansen 			 * old exact non-re match, without conditionals such
45573f488cdSJohn Johansen 			 * as xattrs. no more searching required
45673f488cdSJohn Johansen 			 */
457898127c3SJohn Johansen 			return profile;
458898127c3SJohn Johansen 	}
459898127c3SJohn Johansen 
460844b8292SJohn Johansen 	if (conflict) {
461844b8292SJohn Johansen 		*info = "conflicting profile attachments";
462844b8292SJohn Johansen 		return NULL;
463844b8292SJohn Johansen 	}
464844b8292SJohn Johansen 
465898127c3SJohn Johansen 	return candidate;
466898127c3SJohn Johansen }
467898127c3SJohn Johansen 
468898127c3SJohn Johansen /**
469898127c3SJohn Johansen  * find_attach - do attachment search for unconfined processes
4708e51f908SMatthew Garrett  * @bprm - binprm structure of transitioning task
471898127c3SJohn Johansen  * @ns: the current namespace  (NOT NULL)
472898127c3SJohn Johansen  * @list: list to search  (NOT NULL)
473898127c3SJohn Johansen  * @name: the executable name to match against  (NOT NULL)
474844b8292SJohn Johansen  * @info: info message if there was an error
475898127c3SJohn Johansen  *
47693c98a48SJohn Johansen  * Returns: label or NULL if no match found
477898127c3SJohn Johansen  */
4788e51f908SMatthew Garrett static struct aa_label *find_attach(const struct linux_binprm *bprm,
4798e51f908SMatthew Garrett 				    struct aa_ns *ns, struct list_head *list,
480844b8292SJohn Johansen 				    const char *name, const char **info)
481898127c3SJohn Johansen {
482898127c3SJohn Johansen 	struct aa_profile *profile;
483898127c3SJohn Johansen 
48401e2b670SJohn Johansen 	rcu_read_lock();
4858e51f908SMatthew Garrett 	profile = aa_get_profile(__attach_match(bprm, name, list, info));
48601e2b670SJohn Johansen 	rcu_read_unlock();
487898127c3SJohn Johansen 
48893c98a48SJohn Johansen 	return profile ? &profile->label : NULL;
489898127c3SJohn Johansen }
490898127c3SJohn Johansen 
491898127c3SJohn Johansen static const char *next_name(int xtype, const char *name)
492898127c3SJohn Johansen {
493898127c3SJohn Johansen 	return NULL;
494898127c3SJohn Johansen }
495898127c3SJohn Johansen 
496898127c3SJohn Johansen /**
497898127c3SJohn Johansen  * x_table_lookup - lookup an x transition name via transition table
498898127c3SJohn Johansen  * @profile: current profile (NOT NULL)
499898127c3SJohn Johansen  * @xindex: index into x transition table
50093c98a48SJohn Johansen  * @name: returns: name tested to find label (NOT NULL)
501898127c3SJohn Johansen  *
50293c98a48SJohn Johansen  * Returns: refcounted label, or NULL on failure (MAYBE NULL)
503898127c3SJohn Johansen  */
5042ea3ffb7SJohn Johansen struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
50593c98a48SJohn Johansen 				const char **name)
506898127c3SJohn Johansen {
50793c98a48SJohn Johansen 	struct aa_label *label = NULL;
508898127c3SJohn Johansen 	u32 xtype = xindex & AA_X_TYPE_MASK;
509898127c3SJohn Johansen 	int index = xindex & AA_X_INDEX_MASK;
51093c98a48SJohn Johansen 
51193c98a48SJohn Johansen 	AA_BUG(!name);
512898127c3SJohn Johansen 
513898127c3SJohn Johansen 	/* index is guaranteed to be in range, validated at load time */
51493c98a48SJohn Johansen 	/* TODO: move lookup parsing to unpack time so this is a straight
51593c98a48SJohn Johansen 	 *       index into the resultant label
51693c98a48SJohn Johansen 	 */
51793c98a48SJohn Johansen 	for (*name = profile->file.trans.table[index]; !label && *name;
51893c98a48SJohn Johansen 	     *name = next_name(xtype, *name)) {
519898127c3SJohn Johansen 		if (xindex & AA_X_CHILD) {
52093c98a48SJohn Johansen 			struct aa_profile *new_profile;
521898127c3SJohn Johansen 			/* release by caller */
52293c98a48SJohn Johansen 			new_profile = aa_find_child(profile, *name);
52393c98a48SJohn Johansen 			if (new_profile)
52493c98a48SJohn Johansen 				label = &new_profile->label;
525898127c3SJohn Johansen 			continue;
526898127c3SJohn Johansen 		}
5278ac2ca32SSebastian Andrzej Siewior 		label = aa_label_parse(&profile->label, *name, GFP_KERNEL,
52893c98a48SJohn Johansen 				       true, false);
52993c98a48SJohn Johansen 		if (IS_ERR(label))
53093c98a48SJohn Johansen 			label = NULL;
531898127c3SJohn Johansen 	}
532898127c3SJohn Johansen 
533898127c3SJohn Johansen 	/* released by caller */
534898127c3SJohn Johansen 
53593c98a48SJohn Johansen 	return label;
536898127c3SJohn Johansen }
537898127c3SJohn Johansen 
538898127c3SJohn Johansen /**
53993c98a48SJohn Johansen  * x_to_label - get target label for a given xindex
540898127c3SJohn Johansen  * @profile: current profile  (NOT NULL)
5418e51f908SMatthew Garrett  * @bprm: binprm structure of transitioning task
542898127c3SJohn Johansen  * @name: name to lookup (NOT NULL)
543898127c3SJohn Johansen  * @xindex: index into x transition table
54493c98a48SJohn Johansen  * @lookupname: returns: name used in lookup if one was specified (NOT NULL)
545898127c3SJohn Johansen  *
54693c98a48SJohn Johansen  * find label for a transition index
547898127c3SJohn Johansen  *
54893c98a48SJohn Johansen  * Returns: refcounted label or NULL if not found available
549898127c3SJohn Johansen  */
55093c98a48SJohn Johansen static struct aa_label *x_to_label(struct aa_profile *profile,
5518e51f908SMatthew Garrett 				   const struct linux_binprm *bprm,
55293c98a48SJohn Johansen 				   const char *name, u32 xindex,
55393c98a48SJohn Johansen 				   const char **lookupname,
55493c98a48SJohn Johansen 				   const char **info)
555898127c3SJohn Johansen {
55693c98a48SJohn Johansen 	struct aa_label *new = NULL;
55798849dffSJohn Johansen 	struct aa_ns *ns = profile->ns;
558898127c3SJohn Johansen 	u32 xtype = xindex & AA_X_TYPE_MASK;
55993c98a48SJohn Johansen 	const char *stack = NULL;
560898127c3SJohn Johansen 
561898127c3SJohn Johansen 	switch (xtype) {
562898127c3SJohn Johansen 	case AA_X_NONE:
563898127c3SJohn Johansen 		/* fail exec unless ix || ux fallback - handled by caller */
56493c98a48SJohn Johansen 		*lookupname = NULL;
56593c98a48SJohn Johansen 		break;
56693c98a48SJohn Johansen 	case AA_X_TABLE:
56793c98a48SJohn Johansen 		/* TODO: fix when perm mapping done at unload */
56893c98a48SJohn Johansen 		stack = profile->file.trans.table[xindex & AA_X_INDEX_MASK];
56993c98a48SJohn Johansen 		if (*stack != '&') {
57093c98a48SJohn Johansen 			/* released by caller */
57193c98a48SJohn Johansen 			new = x_table_lookup(profile, xindex, lookupname);
57293c98a48SJohn Johansen 			stack = NULL;
57393c98a48SJohn Johansen 			break;
57493c98a48SJohn Johansen 		}
57509186e50SGustavo A. R. Silva 		/* fall through - to X_NAME */
576898127c3SJohn Johansen 	case AA_X_NAME:
577898127c3SJohn Johansen 		if (xindex & AA_X_CHILD)
578898127c3SJohn Johansen 			/* released by caller */
5798e51f908SMatthew Garrett 			new = find_attach(bprm, ns, &profile->base.profiles,
580844b8292SJohn Johansen 					  name, info);
581898127c3SJohn Johansen 		else
582898127c3SJohn Johansen 			/* released by caller */
5838e51f908SMatthew Garrett 			new = find_attach(bprm, ns, &ns->base.profiles,
584844b8292SJohn Johansen 					  name, info);
58593c98a48SJohn Johansen 		*lookupname = name;
586898127c3SJohn Johansen 		break;
587898127c3SJohn Johansen 	}
588898127c3SJohn Johansen 
58993c98a48SJohn Johansen 	if (!new) {
59093c98a48SJohn Johansen 		if (xindex & AA_X_INHERIT) {
59193c98a48SJohn Johansen 			/* (p|c|n)ix - don't change profile but do
59293c98a48SJohn Johansen 			 * use the newest version
59393c98a48SJohn Johansen 			 */
59493c98a48SJohn Johansen 			*info = "ix fallback";
59593c98a48SJohn Johansen 			/* no profile && no error */
59693c98a48SJohn Johansen 			new = aa_get_newest_label(&profile->label);
59793c98a48SJohn Johansen 		} else if (xindex & AA_X_UNCONFINED) {
59893c98a48SJohn Johansen 			new = aa_get_newest_label(ns_unconfined(profile->ns));
59993c98a48SJohn Johansen 			*info = "ux fallback";
60093c98a48SJohn Johansen 		}
60193c98a48SJohn Johansen 	}
60293c98a48SJohn Johansen 
60393c98a48SJohn Johansen 	if (new && stack) {
60493c98a48SJohn Johansen 		/* base the stack on post domain transition */
60593c98a48SJohn Johansen 		struct aa_label *base = new;
60693c98a48SJohn Johansen 
6078ac2ca32SSebastian Andrzej Siewior 		new = aa_label_parse(base, stack, GFP_KERNEL, true, false);
60893c98a48SJohn Johansen 		if (IS_ERR(new))
60993c98a48SJohn Johansen 			new = NULL;
61093c98a48SJohn Johansen 		aa_put_label(base);
61193c98a48SJohn Johansen 	}
61293c98a48SJohn Johansen 
613898127c3SJohn Johansen 	/* released by caller */
61493c98a48SJohn Johansen 	return new;
61593c98a48SJohn Johansen }
61693c98a48SJohn Johansen 
61793c98a48SJohn Johansen static struct aa_label *profile_transition(struct aa_profile *profile,
61893c98a48SJohn Johansen 					   const struct linux_binprm *bprm,
61993c98a48SJohn Johansen 					   char *buffer, struct path_cond *cond,
62093c98a48SJohn Johansen 					   bool *secure_exec)
62193c98a48SJohn Johansen {
62293c98a48SJohn Johansen 	struct aa_label *new = NULL;
6238e51f908SMatthew Garrett 	struct aa_profile *component;
6248e51f908SMatthew Garrett 	struct label_it i;
62593c98a48SJohn Johansen 	const char *info = NULL, *name = NULL, *target = NULL;
62693c98a48SJohn Johansen 	unsigned int state = profile->file.start;
62793c98a48SJohn Johansen 	struct aa_perms perms = {};
62893c98a48SJohn Johansen 	bool nonewprivs = false;
62993c98a48SJohn Johansen 	int error = 0;
63093c98a48SJohn Johansen 
63193c98a48SJohn Johansen 	AA_BUG(!profile);
63293c98a48SJohn Johansen 	AA_BUG(!bprm);
63393c98a48SJohn Johansen 	AA_BUG(!buffer);
63493c98a48SJohn Johansen 
63593c98a48SJohn Johansen 	error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
63693c98a48SJohn Johansen 			     &name, &info, profile->disconnected);
63793c98a48SJohn Johansen 	if (error) {
63893c98a48SJohn Johansen 		if (profile_unconfined(profile) ||
63993c98a48SJohn Johansen 		    (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
64093c98a48SJohn Johansen 			AA_DEBUG("name lookup ix on error");
64193c98a48SJohn Johansen 			error = 0;
64293c98a48SJohn Johansen 			new = aa_get_newest_label(&profile->label);
64393c98a48SJohn Johansen 		}
64493c98a48SJohn Johansen 		name = bprm->filename;
64593c98a48SJohn Johansen 		goto audit;
64693c98a48SJohn Johansen 	}
64793c98a48SJohn Johansen 
64893c98a48SJohn Johansen 	if (profile_unconfined(profile)) {
6498e51f908SMatthew Garrett 		new = find_attach(bprm, profile->ns,
6508e51f908SMatthew Garrett 				  &profile->ns->base.profiles, name, &info);
65193c98a48SJohn Johansen 		if (new) {
65293c98a48SJohn Johansen 			AA_DEBUG("unconfined attached to new label");
65393c98a48SJohn Johansen 			return new;
65493c98a48SJohn Johansen 		}
65593c98a48SJohn Johansen 		AA_DEBUG("unconfined exec no attachment");
65693c98a48SJohn Johansen 		return aa_get_newest_label(&profile->label);
65793c98a48SJohn Johansen 	}
65893c98a48SJohn Johansen 
65993c98a48SJohn Johansen 	/* find exec permissions for name */
66093c98a48SJohn Johansen 	state = aa_str_perms(profile->file.dfa, state, name, cond, &perms);
66193c98a48SJohn Johansen 	if (perms.allow & MAY_EXEC) {
66293c98a48SJohn Johansen 		/* exec permission determine how to transition */
6638e51f908SMatthew Garrett 		new = x_to_label(profile, bprm, name, perms.xindex, &target,
6648e51f908SMatthew Garrett 				 &info);
66593c98a48SJohn Johansen 		if (new && new->proxy == profile->label.proxy && info) {
66693c98a48SJohn Johansen 			/* hack ix fallback - improve how this is detected */
66793c98a48SJohn Johansen 			goto audit;
66893c98a48SJohn Johansen 		} else if (!new) {
66993c98a48SJohn Johansen 			error = -EACCES;
67093c98a48SJohn Johansen 			info = "profile transition not found";
67193c98a48SJohn Johansen 			/* remove MAY_EXEC to audit as failure */
67293c98a48SJohn Johansen 			perms.allow &= ~MAY_EXEC;
6738e51f908SMatthew Garrett 		} else {
6748e51f908SMatthew Garrett 			/* verify that each component's xattr requirements are
6758e51f908SMatthew Garrett 			 * met, and fail execution otherwise
6768e51f908SMatthew Garrett 			 */
6778e51f908SMatthew Garrett 			label_for_each(i, new, component) {
67873f488cdSJohn Johansen 				if (aa_xattrs_match(bprm, component, state) <
67973f488cdSJohn Johansen 				    0) {
6808e51f908SMatthew Garrett 					error = -EACCES;
6818e51f908SMatthew Garrett 					info = "required xattrs not present";
6828e51f908SMatthew Garrett 					perms.allow &= ~MAY_EXEC;
6838e51f908SMatthew Garrett 					aa_put_label(new);
6848e51f908SMatthew Garrett 					new = NULL;
6858e51f908SMatthew Garrett 					goto audit;
6868e51f908SMatthew Garrett 				}
6878e51f908SMatthew Garrett 			}
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 
693df323337SSebastian Andrzej Siewior 		new_profile = aa_new_null_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:
72293c98a48SJohn Johansen 	aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, target, new,
72393c98a48SJohn Johansen 		      cond->uid, info, error);
72493c98a48SJohn Johansen 	if (!new || nonewprivs) {
72593c98a48SJohn Johansen 		aa_put_label(new);
72693c98a48SJohn Johansen 		return ERR_PTR(error);
72793c98a48SJohn Johansen 	}
72893c98a48SJohn Johansen 
72993c98a48SJohn Johansen 	return new;
73093c98a48SJohn Johansen }
73193c98a48SJohn Johansen 
73293c98a48SJohn Johansen static int profile_onexec(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 {
73793c98a48SJohn Johansen 	unsigned int state = profile->file.start;
73893c98a48SJohn Johansen 	struct aa_perms perms = {};
73993c98a48SJohn Johansen 	const char *xname = NULL, *info = "change_profile onexec";
74093c98a48SJohn Johansen 	int error = -EACCES;
74193c98a48SJohn Johansen 
74293c98a48SJohn Johansen 	AA_BUG(!profile);
74393c98a48SJohn Johansen 	AA_BUG(!onexec);
74493c98a48SJohn Johansen 	AA_BUG(!bprm);
74593c98a48SJohn Johansen 	AA_BUG(!buffer);
74693c98a48SJohn Johansen 
74793c98a48SJohn Johansen 	if (profile_unconfined(profile)) {
74893c98a48SJohn Johansen 		/* change_profile on exec already granted */
74993c98a48SJohn Johansen 		/*
75093c98a48SJohn Johansen 		 * NOTE: Domain transitions from unconfined are allowed
75193c98a48SJohn Johansen 		 * even when no_new_privs is set because this aways results
75293c98a48SJohn Johansen 		 * in a further reduction of permissions.
75393c98a48SJohn Johansen 		 */
75493c98a48SJohn Johansen 		return 0;
75593c98a48SJohn Johansen 	}
75693c98a48SJohn Johansen 
75793c98a48SJohn Johansen 	error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
75893c98a48SJohn Johansen 			     &xname, &info, profile->disconnected);
75993c98a48SJohn Johansen 	if (error) {
76093c98a48SJohn Johansen 		if (profile_unconfined(profile) ||
76193c98a48SJohn Johansen 		    (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
76293c98a48SJohn Johansen 			AA_DEBUG("name lookup ix on error");
76393c98a48SJohn Johansen 			error = 0;
76493c98a48SJohn Johansen 		}
76593c98a48SJohn Johansen 		xname = bprm->filename;
76693c98a48SJohn Johansen 		goto audit;
76793c98a48SJohn Johansen 	}
76893c98a48SJohn Johansen 
76993c98a48SJohn Johansen 	/* find exec permissions for name */
77093c98a48SJohn Johansen 	state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms);
77193c98a48SJohn Johansen 	if (!(perms.allow & AA_MAY_ONEXEC)) {
77293c98a48SJohn Johansen 		info = "no change_onexec valid for executable";
77393c98a48SJohn Johansen 		goto audit;
77493c98a48SJohn Johansen 	}
77593c98a48SJohn Johansen 	/* test if this exec can be paired with change_profile onexec.
77693c98a48SJohn Johansen 	 * onexec permission is linked to exec with a standard pairing
77793c98a48SJohn Johansen 	 * exec\0change_profile
77893c98a48SJohn Johansen 	 */
77993c98a48SJohn Johansen 	state = aa_dfa_null_transition(profile->file.dfa, state);
78093c98a48SJohn Johansen 	error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC,
78193c98a48SJohn Johansen 				     state, &perms);
78293c98a48SJohn Johansen 	if (error) {
78393c98a48SJohn Johansen 		perms.allow &= ~AA_MAY_ONEXEC;
78493c98a48SJohn Johansen 		goto audit;
78593c98a48SJohn Johansen 	}
78693c98a48SJohn Johansen 
78793c98a48SJohn Johansen 	if (!(perms.xindex & AA_X_UNSAFE)) {
78893c98a48SJohn Johansen 		if (DEBUG_ON) {
78993c98a48SJohn Johansen 			dbg_printk("apparmor: scrubbing environment "
79093c98a48SJohn Johansen 				   "variables for %s label=", xname);
7918ac2ca32SSebastian Andrzej Siewior 			aa_label_printk(onexec, GFP_KERNEL);
79293c98a48SJohn Johansen 			dbg_printk("\n");
79393c98a48SJohn Johansen 		}
79493c98a48SJohn Johansen 		*secure_exec = true;
79593c98a48SJohn Johansen 	}
79693c98a48SJohn Johansen 
79793c98a48SJohn Johansen audit:
79893c98a48SJohn Johansen 	return aa_audit_file(profile, &perms, OP_EXEC, AA_MAY_ONEXEC, xname,
79993c98a48SJohn Johansen 			     NULL, onexec, cond->uid, info, error);
80093c98a48SJohn Johansen }
80193c98a48SJohn Johansen 
80293c98a48SJohn Johansen /* ensure none ns domain transitions are correctly applied with onexec */
80393c98a48SJohn Johansen 
80493c98a48SJohn Johansen static struct aa_label *handle_onexec(struct aa_label *label,
80593c98a48SJohn Johansen 				      struct aa_label *onexec, bool stack,
80693c98a48SJohn Johansen 				      const struct linux_binprm *bprm,
80793c98a48SJohn Johansen 				      char *buffer, struct path_cond *cond,
80893c98a48SJohn Johansen 				      bool *unsafe)
80993c98a48SJohn Johansen {
81093c98a48SJohn Johansen 	struct aa_profile *profile;
81193c98a48SJohn Johansen 	struct aa_label *new;
81293c98a48SJohn Johansen 	int error;
81393c98a48SJohn Johansen 
81493c98a48SJohn Johansen 	AA_BUG(!label);
81593c98a48SJohn Johansen 	AA_BUG(!onexec);
81693c98a48SJohn Johansen 	AA_BUG(!bprm);
81793c98a48SJohn Johansen 	AA_BUG(!buffer);
81893c98a48SJohn Johansen 
81993c98a48SJohn Johansen 	if (!stack) {
82093c98a48SJohn Johansen 		error = fn_for_each_in_ns(label, profile,
82193c98a48SJohn Johansen 				profile_onexec(profile, onexec, stack,
82293c98a48SJohn Johansen 					       bprm, buffer, cond, unsafe));
82393c98a48SJohn Johansen 		if (error)
82493c98a48SJohn Johansen 			return ERR_PTR(error);
8258ac2ca32SSebastian Andrzej Siewior 		new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
82693c98a48SJohn Johansen 				aa_get_newest_label(onexec),
82793c98a48SJohn Johansen 				profile_transition(profile, bprm, buffer,
82893c98a48SJohn Johansen 						   cond, unsafe));
82993c98a48SJohn Johansen 
83093c98a48SJohn Johansen 	} else {
831b2c2086cSZygmunt Krynicki 		/* TODO: determine how much we want to loosen this */
83293c98a48SJohn Johansen 		error = fn_for_each_in_ns(label, profile,
83393c98a48SJohn Johansen 				profile_onexec(profile, onexec, stack, bprm,
83493c98a48SJohn Johansen 					       buffer, cond, unsafe));
83593c98a48SJohn Johansen 		if (error)
83693c98a48SJohn Johansen 			return ERR_PTR(error);
8378ac2ca32SSebastian Andrzej Siewior 		new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
83893c98a48SJohn Johansen 				aa_label_merge(&profile->label, onexec,
8398ac2ca32SSebastian Andrzej Siewior 					       GFP_KERNEL),
84093c98a48SJohn Johansen 				profile_transition(profile, bprm, buffer,
84193c98a48SJohn Johansen 						   cond, unsafe));
84293c98a48SJohn Johansen 	}
84393c98a48SJohn Johansen 
84493c98a48SJohn Johansen 	if (new)
84593c98a48SJohn Johansen 		return new;
84693c98a48SJohn Johansen 
84793c98a48SJohn Johansen 	/* TODO: get rid of GLOBAL_ROOT_UID */
84893c98a48SJohn Johansen 	error = fn_for_each_in_ns(label, profile,
84993c98a48SJohn Johansen 			aa_audit_file(profile, &nullperms, OP_CHANGE_ONEXEC,
85093c98a48SJohn Johansen 				      AA_MAY_ONEXEC, bprm->filename, NULL,
85193c98a48SJohn Johansen 				      onexec, GLOBAL_ROOT_UID,
85293c98a48SJohn Johansen 				      "failed to build target label", -ENOMEM));
85393c98a48SJohn Johansen 	return ERR_PTR(error);
854898127c3SJohn Johansen }
855898127c3SJohn Johansen 
856898127c3SJohn Johansen /**
857898127c3SJohn Johansen  * apparmor_bprm_set_creds - set the new creds on the bprm struct
858898127c3SJohn Johansen  * @bprm: binprm for the exec  (NOT NULL)
859898127c3SJohn Johansen  *
860898127c3SJohn Johansen  * Returns: %0 or error on failure
86193c98a48SJohn Johansen  *
86293c98a48SJohn Johansen  * TODO: once the other paths are done see if we can't refactor into a fn
863898127c3SJohn Johansen  */
864898127c3SJohn Johansen int apparmor_bprm_set_creds(struct linux_binprm *bprm)
865898127c3SJohn Johansen {
866f175221aSJohn Johansen 	struct aa_task_ctx *ctx;
86793c98a48SJohn Johansen 	struct aa_label *label, *new = NULL;
86893c98a48SJohn Johansen 	struct aa_profile *profile;
869898127c3SJohn Johansen 	char *buffer = NULL;
87093c98a48SJohn Johansen 	const char *info = NULL;
87193c98a48SJohn Johansen 	int error = 0;
87293c98a48SJohn Johansen 	bool unsafe = false;
873898127c3SJohn Johansen 	struct path_cond cond = {
874496ad9aaSAl Viro 		file_inode(bprm->file)->i_uid,
875496ad9aaSAl Viro 		file_inode(bprm->file)->i_mode
876898127c3SJohn Johansen 	};
877898127c3SJohn Johansen 
878ddb4a144SKees Cook 	if (bprm->called_set_creds)
879898127c3SJohn Johansen 		return 0;
880898127c3SJohn Johansen 
881de62de59SJohn Johansen 	ctx = task_ctx(current);
882d9087c49SJohn Johansen 	AA_BUG(!cred_label(bprm->cred));
883f175221aSJohn Johansen 	AA_BUG(!ctx);
884898127c3SJohn Johansen 
885d9087c49SJohn Johansen 	label = aa_get_newest_label(cred_label(bprm->cred));
8864227c333SJohn Johansen 
8879fcf78ccSJohn Johansen 	/*
8889fcf78ccSJohn Johansen 	 * Detect no new privs being set, and store the label it
8899fcf78ccSJohn Johansen 	 * occurred under. Ideally this would happen when nnp
8909fcf78ccSJohn Johansen 	 * is set but there isn't a good way to do that yet.
8919fcf78ccSJohn Johansen 	 *
8929fcf78ccSJohn Johansen 	 * Testing for unconfined must be done before the subset test
8939fcf78ccSJohn Johansen 	 */
8949fcf78ccSJohn Johansen 	if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && !unconfined(label) &&
8959fcf78ccSJohn Johansen 	    !ctx->nnp)
8969fcf78ccSJohn Johansen 		ctx->nnp = aa_get_label(label);
8979fcf78ccSJohn Johansen 
8984227c333SJohn Johansen 	/* buffer freed below, name is pointer into buffer */
899*341c1fdaSJohn Johansen 	buffer = aa_get_buffer(false);
900df323337SSebastian Andrzej Siewior 	if (!buffer) {
901df323337SSebastian Andrzej Siewior 		error = -ENOMEM;
902df323337SSebastian Andrzej Siewior 		goto done;
903df323337SSebastian Andrzej Siewior 	}
904df323337SSebastian Andrzej Siewior 
90593c98a48SJohn Johansen 	/* Test for onexec first as onexec override other x transitions. */
906f175221aSJohn Johansen 	if (ctx->onexec)
907f175221aSJohn Johansen 		new = handle_onexec(label, ctx->onexec, ctx->token,
90893c98a48SJohn Johansen 				    bprm, buffer, &cond, &unsafe);
909898127c3SJohn Johansen 	else
9108ac2ca32SSebastian Andrzej Siewior 		new = fn_label_build(label, profile, GFP_KERNEL,
91193c98a48SJohn Johansen 				profile_transition(profile, bprm, buffer,
91293c98a48SJohn Johansen 						   &cond, &unsafe));
913898127c3SJohn Johansen 
91493c98a48SJohn Johansen 	AA_BUG(!new);
91593c98a48SJohn Johansen 	if (IS_ERR(new)) {
91693c98a48SJohn Johansen 		error = PTR_ERR(new);
91793c98a48SJohn Johansen 		goto done;
91893c98a48SJohn Johansen 	} else if (!new) {
919898127c3SJohn Johansen 		error = -ENOMEM;
92093c98a48SJohn Johansen 		goto done;
921c29bceb3SJohn Johansen 	}
922c29bceb3SJohn Johansen 
9239fcf78ccSJohn Johansen 	/* Policy has specified a domain transitions. If no_new_privs and
9249fcf78ccSJohn Johansen 	 * confined ensure the transition is to confinement that is subset
9259fcf78ccSJohn Johansen 	 * of the confinement when the task entered no new privs.
9269fcf78ccSJohn Johansen 	 *
9279fcf78ccSJohn Johansen 	 * NOTE: Domain transitions from unconfined and to stacked
9289fcf78ccSJohn Johansen 	 * subsets are allowed even when no_new_privs is set because this
9299fcf78ccSJohn Johansen 	 * aways results in a further reduction of permissions.
9309fcf78ccSJohn Johansen 	 */
9319fcf78ccSJohn Johansen 	if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) &&
9329fcf78ccSJohn Johansen 	    !unconfined(label) && !aa_label_is_subset(new, ctx->nnp)) {
9339fcf78ccSJohn Johansen 		error = -EPERM;
9349fcf78ccSJohn Johansen 		info = "no new privs";
9359fcf78ccSJohn Johansen 		goto audit;
9369fcf78ccSJohn Johansen 	}
937898127c3SJohn Johansen 
938898127c3SJohn Johansen 	if (bprm->unsafe & LSM_UNSAFE_SHARE) {
939898127c3SJohn Johansen 		/* FIXME: currently don't mediate shared state */
940898127c3SJohn Johansen 		;
941898127c3SJohn Johansen 	}
942898127c3SJohn Johansen 
94393c98a48SJohn Johansen 	if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) {
94493c98a48SJohn Johansen 		/* TODO: test needs to be profile of label to new */
94593c98a48SJohn Johansen 		error = may_change_ptraced_domain(new, &info);
946f7da2de0SJohn Johansen 		if (error)
947898127c3SJohn Johansen 			goto audit;
948898127c3SJohn Johansen 	}
949898127c3SJohn Johansen 
95093c98a48SJohn Johansen 	if (unsafe) {
95193c98a48SJohn Johansen 		if (DEBUG_ON) {
95293c98a48SJohn Johansen 			dbg_printk("scrubbing environment variables for %s "
95393c98a48SJohn Johansen 				   "label=", bprm->filename);
9548ac2ca32SSebastian Andrzej Siewior 			aa_label_printk(new, GFP_KERNEL);
95593c98a48SJohn Johansen 			dbg_printk("\n");
95693c98a48SJohn Johansen 		}
957993b3ab0SKees Cook 		bprm->secureexec = 1;
958898127c3SJohn Johansen 	}
95993c98a48SJohn Johansen 
96093c98a48SJohn Johansen 	if (label->proxy != new->proxy) {
96193c98a48SJohn Johansen 		/* when transitioning clear unsafe personality bits */
96293c98a48SJohn Johansen 		if (DEBUG_ON) {
96393c98a48SJohn Johansen 			dbg_printk("apparmor: clearing unsafe personality "
96493c98a48SJohn Johansen 				   "bits. %s label=", bprm->filename);
9658ac2ca32SSebastian Andrzej Siewior 			aa_label_printk(new, GFP_KERNEL);
96693c98a48SJohn Johansen 			dbg_printk("\n");
96793c98a48SJohn Johansen 		}
968898127c3SJohn Johansen 		bprm->per_clear |= PER_CLEAR_ON_SETID;
96993c98a48SJohn Johansen 	}
970d9087c49SJohn Johansen 	aa_put_label(cred_label(bprm->cred));
971d9087c49SJohn Johansen 	/* transfer reference, released when cred is freed */
97269b5a44aSCasey Schaufler 	set_cred_label(bprm->cred, new);
973898127c3SJohn Johansen 
97493c98a48SJohn Johansen done:
975637f688dSJohn Johansen 	aa_put_label(label);
976df323337SSebastian Andrzej Siewior 	aa_put_buffer(buffer);
977898127c3SJohn Johansen 
978898127c3SJohn Johansen 	return error;
97993c98a48SJohn Johansen 
98093c98a48SJohn Johansen audit:
98193c98a48SJohn Johansen 	error = fn_for_each(label, profile,
98293c98a48SJohn Johansen 			aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC,
98393c98a48SJohn Johansen 				      bprm->filename, NULL, new,
98493c98a48SJohn Johansen 				      file_inode(bprm->file)->i_uid, info,
98593c98a48SJohn Johansen 				      error));
98693c98a48SJohn Johansen 	aa_put_label(new);
98793c98a48SJohn Johansen 	goto done;
988898127c3SJohn Johansen }
989898127c3SJohn Johansen 
990898127c3SJohn Johansen /*
991898127c3SJohn Johansen  * Functions for self directed profile change
992898127c3SJohn Johansen  */
993898127c3SJohn Johansen 
99489dbf196SJohn Johansen 
99589dbf196SJohn Johansen /* helper fn for change_hat
996898127c3SJohn Johansen  *
99789dbf196SJohn Johansen  * Returns: label for hat transition OR ERR_PTR.  Does NOT return NULL
998898127c3SJohn Johansen  */
99989dbf196SJohn Johansen static struct aa_label *build_change_hat(struct aa_profile *profile,
100089dbf196SJohn Johansen 					 const char *name, bool sibling)
1001898127c3SJohn Johansen {
100289dbf196SJohn Johansen 	struct aa_profile *root, *hat = NULL;
100389dbf196SJohn Johansen 	const char *info = NULL;
100489dbf196SJohn Johansen 	int error = 0;
100589dbf196SJohn Johansen 
100689dbf196SJohn Johansen 	if (sibling && PROFILE_IS_HAT(profile)) {
100789dbf196SJohn Johansen 		root = aa_get_profile_rcu(&profile->parent);
100889dbf196SJohn Johansen 	} else if (!sibling && !PROFILE_IS_HAT(profile)) {
100989dbf196SJohn Johansen 		root = aa_get_profile(profile);
101089dbf196SJohn Johansen 	} else {
101189dbf196SJohn Johansen 		info = "conflicting target types";
101289dbf196SJohn Johansen 		error = -EPERM;
101389dbf196SJohn Johansen 		goto audit;
101489dbf196SJohn Johansen 	}
101589dbf196SJohn Johansen 
101689dbf196SJohn Johansen 	hat = aa_find_child(root, name);
101789dbf196SJohn Johansen 	if (!hat) {
101889dbf196SJohn Johansen 		error = -ENOENT;
101989dbf196SJohn Johansen 		if (COMPLAIN_MODE(profile)) {
102089dbf196SJohn Johansen 			hat = aa_new_null_profile(profile, true, name,
102189dbf196SJohn Johansen 						  GFP_KERNEL);
102289dbf196SJohn Johansen 			if (!hat) {
102389dbf196SJohn Johansen 				info = "failed null profile create";
102489dbf196SJohn Johansen 				error = -ENOMEM;
102589dbf196SJohn Johansen 			}
102689dbf196SJohn Johansen 		}
102789dbf196SJohn Johansen 	}
102889dbf196SJohn Johansen 	aa_put_profile(root);
102989dbf196SJohn Johansen 
103089dbf196SJohn Johansen audit:
103189dbf196SJohn Johansen 	aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT,
103289dbf196SJohn Johansen 		      name, hat ? hat->base.hname : NULL,
103324b87a16SJohn Johansen 		      hat ? &hat->label : NULL, GLOBAL_ROOT_UID, info,
103489dbf196SJohn Johansen 		      error);
103589dbf196SJohn Johansen 	if (!hat || (error && error != -ENOENT))
103689dbf196SJohn Johansen 		return ERR_PTR(error);
103789dbf196SJohn Johansen 	/* if hat && error - complain mode, already audited and we adjust for
103889dbf196SJohn Johansen 	 * complain mode allow by returning hat->label
103989dbf196SJohn Johansen 	 */
104089dbf196SJohn Johansen 	return &hat->label;
104189dbf196SJohn Johansen }
104289dbf196SJohn Johansen 
104389dbf196SJohn Johansen /* helper fn for changing into a hat
104489dbf196SJohn Johansen  *
104589dbf196SJohn Johansen  * Returns: label for hat transition or ERR_PTR. Does not return NULL
104689dbf196SJohn Johansen  */
104789dbf196SJohn Johansen static struct aa_label *change_hat(struct aa_label *label, const char *hats[],
104889dbf196SJohn Johansen 				   int count, int flags)
104989dbf196SJohn Johansen {
105089dbf196SJohn Johansen 	struct aa_profile *profile, *root, *hat = NULL;
105189dbf196SJohn Johansen 	struct aa_label *new;
105289dbf196SJohn Johansen 	struct label_it it;
105389dbf196SJohn Johansen 	bool sibling = false;
105489dbf196SJohn Johansen 	const char *name, *info = NULL;
105589dbf196SJohn Johansen 	int i, error;
105689dbf196SJohn Johansen 
105789dbf196SJohn Johansen 	AA_BUG(!label);
105889dbf196SJohn Johansen 	AA_BUG(!hats);
105989dbf196SJohn Johansen 	AA_BUG(count < 1);
106089dbf196SJohn Johansen 
106189dbf196SJohn Johansen 	if (PROFILE_IS_HAT(labels_profile(label)))
106289dbf196SJohn Johansen 		sibling = true;
106389dbf196SJohn Johansen 
106489dbf196SJohn Johansen 	/*find first matching hat */
106589dbf196SJohn Johansen 	for (i = 0; i < count && !hat; i++) {
106689dbf196SJohn Johansen 		name = hats[i];
106789dbf196SJohn Johansen 		label_for_each_in_ns(it, labels_ns(label), label, profile) {
106889dbf196SJohn Johansen 			if (sibling && PROFILE_IS_HAT(profile)) {
106989dbf196SJohn Johansen 				root = aa_get_profile_rcu(&profile->parent);
107089dbf196SJohn Johansen 			} else if (!sibling && !PROFILE_IS_HAT(profile)) {
107189dbf196SJohn Johansen 				root = aa_get_profile(profile);
107289dbf196SJohn Johansen 			} else {	/* conflicting change type */
107389dbf196SJohn Johansen 				info = "conflicting targets types";
107489dbf196SJohn Johansen 				error = -EPERM;
107589dbf196SJohn Johansen 				goto fail;
107689dbf196SJohn Johansen 			}
107789dbf196SJohn Johansen 			hat = aa_find_child(root, name);
107889dbf196SJohn Johansen 			aa_put_profile(root);
107989dbf196SJohn Johansen 			if (!hat) {
108089dbf196SJohn Johansen 				if (!COMPLAIN_MODE(profile))
108189dbf196SJohn Johansen 					goto outer_continue;
108289dbf196SJohn Johansen 				/* complain mode succeed as if hat */
108389dbf196SJohn Johansen 			} else if (!PROFILE_IS_HAT(hat)) {
108489dbf196SJohn Johansen 				info = "target not hat";
108589dbf196SJohn Johansen 				error = -EPERM;
108689dbf196SJohn Johansen 				aa_put_profile(hat);
108789dbf196SJohn Johansen 				goto fail;
108889dbf196SJohn Johansen 			}
108989dbf196SJohn Johansen 			aa_put_profile(hat);
109089dbf196SJohn Johansen 		}
109189dbf196SJohn Johansen 		/* found a hat for all profiles in ns */
109289dbf196SJohn Johansen 		goto build;
109389dbf196SJohn Johansen outer_continue:
109489dbf196SJohn Johansen 	;
109589dbf196SJohn Johansen 	}
109689dbf196SJohn Johansen 	/* no hats that match, find appropriate error
109789dbf196SJohn Johansen 	 *
109889dbf196SJohn Johansen 	 * In complain mode audit of the failure is based off of the first
109989dbf196SJohn Johansen 	 * hat supplied.  This is done due how userspace interacts with
110089dbf196SJohn Johansen 	 * change_hat.
110189dbf196SJohn Johansen 	 */
110289dbf196SJohn Johansen 	name = NULL;
110389dbf196SJohn Johansen 	label_for_each_in_ns(it, labels_ns(label), label, profile) {
110489dbf196SJohn Johansen 		if (!list_empty(&profile->base.profiles)) {
110589dbf196SJohn Johansen 			info = "hat not found";
110689dbf196SJohn Johansen 			error = -ENOENT;
110789dbf196SJohn Johansen 			goto fail;
110889dbf196SJohn Johansen 		}
110989dbf196SJohn Johansen 	}
111089dbf196SJohn Johansen 	info = "no hats defined";
111189dbf196SJohn Johansen 	error = -ECHILD;
111289dbf196SJohn Johansen 
111389dbf196SJohn Johansen fail:
111489dbf196SJohn Johansen 	label_for_each_in_ns(it, labels_ns(label), label, profile) {
111589dbf196SJohn Johansen 		/*
111689dbf196SJohn Johansen 		 * no target as it has failed to be found or built
111789dbf196SJohn Johansen 		 *
111889dbf196SJohn Johansen 		 * change_hat uses probing and should not log failures
111989dbf196SJohn Johansen 		 * related to missing hats
112089dbf196SJohn Johansen 		 */
112189dbf196SJohn Johansen 		/* TODO: get rid of GLOBAL_ROOT_UID */
112289dbf196SJohn Johansen 		if (count > 1 || COMPLAIN_MODE(profile)) {
112389dbf196SJohn Johansen 			aa_audit_file(profile, &nullperms, OP_CHANGE_HAT,
112489dbf196SJohn Johansen 				      AA_MAY_CHANGEHAT, name, NULL, NULL,
112589dbf196SJohn Johansen 				      GLOBAL_ROOT_UID, info, error);
112689dbf196SJohn Johansen 		}
112789dbf196SJohn Johansen 	}
112889dbf196SJohn Johansen 	return ERR_PTR(error);
112989dbf196SJohn Johansen 
113089dbf196SJohn Johansen build:
113189dbf196SJohn Johansen 	new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
113289dbf196SJohn Johansen 				   build_change_hat(profile, name, sibling),
113389dbf196SJohn Johansen 				   aa_get_label(&profile->label));
113489dbf196SJohn Johansen 	if (!new) {
113589dbf196SJohn Johansen 		info = "label build failed";
113689dbf196SJohn Johansen 		error = -ENOMEM;
113789dbf196SJohn Johansen 		goto fail;
113889dbf196SJohn Johansen 	} /* else if (IS_ERR) build_change_hat has logged error so return new */
113989dbf196SJohn Johansen 
114089dbf196SJohn Johansen 	return new;
1141898127c3SJohn Johansen }
1142898127c3SJohn Johansen 
1143898127c3SJohn Johansen /**
1144898127c3SJohn Johansen  * aa_change_hat - change hat to/from subprofile
1145898127c3SJohn Johansen  * @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0)
1146898127c3SJohn Johansen  * @count: number of hat names in @hats
1147898127c3SJohn Johansen  * @token: magic value to validate the hat change
1148df8073c6SJohn Johansen  * @flags: flags affecting behavior of the change
1149898127c3SJohn Johansen  *
115089dbf196SJohn Johansen  * Returns %0 on success, error otherwise.
115189dbf196SJohn Johansen  *
1152898127c3SJohn Johansen  * Change to the first profile specified in @hats that exists, and store
1153898127c3SJohn Johansen  * the @hat_magic in the current task context.  If the count == 0 and the
1154898127c3SJohn Johansen  * @token matches that stored in the current task context, return to the
1155898127c3SJohn Johansen  * top level profile.
1156898127c3SJohn Johansen  *
115789dbf196SJohn Johansen  * change_hat only applies to profiles in the current ns, and each profile
115889dbf196SJohn Johansen  * in the ns must make the same transition otherwise change_hat will fail.
1159898127c3SJohn Johansen  */
1160df8073c6SJohn Johansen int aa_change_hat(const char *hats[], int count, u64 token, int flags)
1161898127c3SJohn Johansen {
1162898127c3SJohn Johansen 	const struct cred *cred;
11639fcf78ccSJohn Johansen 	struct aa_task_ctx *ctx = task_ctx(current);
116489dbf196SJohn Johansen 	struct aa_label *label, *previous, *new = NULL, *target = NULL;
116589dbf196SJohn Johansen 	struct aa_profile *profile;
11662d679f3cSJohn Johansen 	struct aa_perms perms = {};
116789dbf196SJohn Johansen 	const char *info = NULL;
1168898127c3SJohn Johansen 	int error = 0;
1169898127c3SJohn Johansen 
1170898127c3SJohn Johansen 	/* released below */
1171898127c3SJohn Johansen 	cred = get_current_cred();
1172637f688dSJohn Johansen 	label = aa_get_newest_cred_label(cred);
1173f175221aSJohn Johansen 	previous = aa_get_newest_label(ctx->previous);
1174898127c3SJohn Johansen 
11759fcf78ccSJohn Johansen 	/*
11769fcf78ccSJohn Johansen 	 * Detect no new privs being set, and store the label it
11779fcf78ccSJohn Johansen 	 * occurred under. Ideally this would happen when nnp
11789fcf78ccSJohn Johansen 	 * is set but there isn't a good way to do that yet.
11799fcf78ccSJohn Johansen 	 *
11809fcf78ccSJohn Johansen 	 * Testing for unconfined must be done before the subset test
11819fcf78ccSJohn Johansen 	 */
11829fcf78ccSJohn Johansen 	if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp)
11839fcf78ccSJohn Johansen 		ctx->nnp = aa_get_label(label);
11849fcf78ccSJohn Johansen 
1185637f688dSJohn Johansen 	if (unconfined(label)) {
118689dbf196SJohn Johansen 		info = "unconfined can not change_hat";
1187898127c3SJohn Johansen 		error = -EPERM;
118889dbf196SJohn Johansen 		goto fail;
1189898127c3SJohn Johansen 	}
1190898127c3SJohn Johansen 
1191898127c3SJohn Johansen 	if (count) {
119289dbf196SJohn Johansen 		new = change_hat(label, hats, count, flags);
119389dbf196SJohn Johansen 		AA_BUG(!new);
119489dbf196SJohn Johansen 		if (IS_ERR(new)) {
119589dbf196SJohn Johansen 			error = PTR_ERR(new);
119689dbf196SJohn Johansen 			new = NULL;
119789dbf196SJohn Johansen 			/* already audited */
1198898127c3SJohn Johansen 			goto out;
1199898127c3SJohn Johansen 		}
1200898127c3SJohn Johansen 
120189dbf196SJohn Johansen 		error = may_change_ptraced_domain(new, &info);
120289dbf196SJohn Johansen 		if (error)
120389dbf196SJohn Johansen 			goto fail;
1204898127c3SJohn Johansen 
12059fcf78ccSJohn Johansen 		/*
12069fcf78ccSJohn Johansen 		 * no new privs prevents domain transitions that would
12079fcf78ccSJohn Johansen 		 * reduce restrictions.
12089fcf78ccSJohn Johansen 		 */
12099fcf78ccSJohn Johansen 		if (task_no_new_privs(current) && !unconfined(label) &&
12109fcf78ccSJohn Johansen 		    !aa_label_is_subset(new, ctx->nnp)) {
12119fcf78ccSJohn Johansen 			/* not an apparmor denial per se, so don't log it */
12129fcf78ccSJohn Johansen 			AA_DEBUG("no_new_privs - change_hat denied");
12139fcf78ccSJohn Johansen 			error = -EPERM;
12149fcf78ccSJohn Johansen 			goto out;
12159fcf78ccSJohn Johansen 		}
12169fcf78ccSJohn Johansen 
121789dbf196SJohn Johansen 		if (flags & AA_CHANGE_TEST)
121889dbf196SJohn Johansen 			goto out;
1219898127c3SJohn Johansen 
122089dbf196SJohn Johansen 		target = new;
122189dbf196SJohn Johansen 		error = aa_set_current_hat(new, token);
1222898127c3SJohn Johansen 		if (error == -EACCES)
1223898127c3SJohn Johansen 			/* kill task in case of brute force attacks */
122489dbf196SJohn Johansen 			goto kill;
122589dbf196SJohn Johansen 	} else if (previous && !(flags & AA_CHANGE_TEST)) {
12269fcf78ccSJohn Johansen 		/*
12279fcf78ccSJohn Johansen 		 * no new privs prevents domain transitions that would
12289fcf78ccSJohn Johansen 		 * reduce restrictions.
12299fcf78ccSJohn Johansen 		 */
12309fcf78ccSJohn Johansen 		if (task_no_new_privs(current) && !unconfined(label) &&
12319fcf78ccSJohn Johansen 		    !aa_label_is_subset(previous, ctx->nnp)) {
12329fcf78ccSJohn Johansen 			/* not an apparmor denial per se, so don't log it */
12339fcf78ccSJohn Johansen 			AA_DEBUG("no_new_privs - change_hat denied");
12349fcf78ccSJohn Johansen 			error = -EPERM;
12359fcf78ccSJohn Johansen 			goto out;
12369fcf78ccSJohn Johansen 		}
12379fcf78ccSJohn Johansen 
123889dbf196SJohn Johansen 		/* Return to saved label.  Kill task if restore fails
1239898127c3SJohn Johansen 		 * to avoid brute force attacks
1240898127c3SJohn Johansen 		 */
124189dbf196SJohn Johansen 		target = previous;
1242637f688dSJohn Johansen 		error = aa_restore_previous_label(token);
124389dbf196SJohn Johansen 		if (error) {
124489dbf196SJohn Johansen 			if (error == -EACCES)
124589dbf196SJohn Johansen 				goto kill;
124689dbf196SJohn Johansen 			goto fail;
124789dbf196SJohn Johansen 		}
124889dbf196SJohn Johansen 	} /* else ignore @flags && restores when there is no saved profile */
1249898127c3SJohn Johansen 
1250898127c3SJohn Johansen out:
125189dbf196SJohn Johansen 	aa_put_label(new);
125289dbf196SJohn Johansen 	aa_put_label(previous);
1253637f688dSJohn Johansen 	aa_put_label(label);
1254898127c3SJohn Johansen 	put_cred(cred);
1255898127c3SJohn Johansen 
1256898127c3SJohn Johansen 	return error;
125789dbf196SJohn Johansen 
125889dbf196SJohn Johansen kill:
125989dbf196SJohn Johansen 	info = "failed token match";
126089dbf196SJohn Johansen 	perms.kill = AA_MAY_CHANGEHAT;
126189dbf196SJohn Johansen 
126289dbf196SJohn Johansen fail:
126389dbf196SJohn Johansen 	fn_for_each_in_ns(label, profile,
126489dbf196SJohn Johansen 		aa_audit_file(profile, &perms, OP_CHANGE_HAT,
126589dbf196SJohn Johansen 			      AA_MAY_CHANGEHAT, NULL, NULL, target,
126689dbf196SJohn Johansen 			      GLOBAL_ROOT_UID, info, error));
126789dbf196SJohn Johansen 
126889dbf196SJohn Johansen 	goto out;
1269898127c3SJohn Johansen }
1270898127c3SJohn Johansen 
127189dbf196SJohn Johansen 
1272e00b02bbSJohn Johansen static int change_profile_perms_wrapper(const char *op, const char *name,
1273e00b02bbSJohn Johansen 					struct aa_profile *profile,
1274e00b02bbSJohn Johansen 					struct aa_label *target, bool stack,
1275e00b02bbSJohn Johansen 					u32 request, struct aa_perms *perms)
1276e00b02bbSJohn Johansen {
1277e00b02bbSJohn Johansen 	const char *info = NULL;
1278e00b02bbSJohn Johansen 	int error = 0;
1279e00b02bbSJohn Johansen 
1280e00b02bbSJohn Johansen 	if (!error)
1281e00b02bbSJohn Johansen 		error = change_profile_perms(profile, target, stack, request,
1282e00b02bbSJohn Johansen 					     profile->file.start, perms);
1283e00b02bbSJohn Johansen 	if (error)
1284e00b02bbSJohn Johansen 		error = aa_audit_file(profile, perms, op, request, name,
1285e00b02bbSJohn Johansen 				      NULL, target, GLOBAL_ROOT_UID, info,
1286e00b02bbSJohn Johansen 				      error);
1287e00b02bbSJohn Johansen 
1288e00b02bbSJohn Johansen 	return error;
1289e00b02bbSJohn Johansen }
129089dbf196SJohn Johansen 
1291898127c3SJohn Johansen /**
1292898127c3SJohn Johansen  * aa_change_profile - perform a one-way profile transition
1293aa9a39adSJohn Johansen  * @fqname: name of profile may include namespace (NOT NULL)
1294898127c3SJohn Johansen  * @onexec: whether this transition is to take place immediately or at exec
1295df8073c6SJohn Johansen  * @flags: flags affecting change behavior
1296898127c3SJohn Johansen  *
1297898127c3SJohn Johansen  * Change to new profile @name.  Unlike with hats, there is no way
1298898127c3SJohn Johansen  * to change back.  If @name isn't specified the current profile name is
1299898127c3SJohn Johansen  * used.
1300898127c3SJohn Johansen  * If @onexec then the transition is delayed until
1301898127c3SJohn Johansen  * the next exec.
1302898127c3SJohn Johansen  *
1303898127c3SJohn Johansen  * Returns %0 on success, error otherwise.
1304898127c3SJohn Johansen  */
1305df8073c6SJohn Johansen int aa_change_profile(const char *fqname, int flags)
1306898127c3SJohn Johansen {
1307e00b02bbSJohn Johansen 	struct aa_label *label, *new = NULL, *target = NULL;
1308e00b02bbSJohn Johansen 	struct aa_profile *profile;
13092d679f3cSJohn Johansen 	struct aa_perms perms = {};
1310e00b02bbSJohn Johansen 	const char *info = NULL;
1311e00b02bbSJohn Johansen 	const char *auditname = fqname;		/* retain leading & if stack */
1312e00b02bbSJohn Johansen 	bool stack = flags & AA_CHANGE_STACK;
13139fcf78ccSJohn Johansen 	struct aa_task_ctx *ctx = task_ctx(current);
131447f6e5ccSJohn Johansen 	int error = 0;
1315e00b02bbSJohn Johansen 	char *op;
1316898127c3SJohn Johansen 	u32 request;
1317898127c3SJohn Johansen 
13189fcf78ccSJohn Johansen 	label = aa_get_current_label();
13199fcf78ccSJohn Johansen 
13209fcf78ccSJohn Johansen 	/*
13219fcf78ccSJohn Johansen 	 * Detect no new privs being set, and store the label it
13229fcf78ccSJohn Johansen 	 * occurred under. Ideally this would happen when nnp
13239fcf78ccSJohn Johansen 	 * is set but there isn't a good way to do that yet.
13249fcf78ccSJohn Johansen 	 *
13259fcf78ccSJohn Johansen 	 * Testing for unconfined must be done before the subset test
13269fcf78ccSJohn Johansen 	 */
13279fcf78ccSJohn Johansen 	if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp)
13289fcf78ccSJohn Johansen 		ctx->nnp = aa_get_label(label);
13299fcf78ccSJohn Johansen 
1330aa9a39adSJohn Johansen 	if (!fqname || !*fqname) {
1331aa9a39adSJohn Johansen 		AA_DEBUG("no profile name");
1332898127c3SJohn Johansen 		return -EINVAL;
1333aa9a39adSJohn Johansen 	}
1334898127c3SJohn Johansen 
1335df8073c6SJohn Johansen 	if (flags & AA_CHANGE_ONEXEC) {
1336898127c3SJohn Johansen 		request = AA_MAY_ONEXEC;
1337e00b02bbSJohn Johansen 		if (stack)
1338e00b02bbSJohn Johansen 			op = OP_STACK_ONEXEC;
1339e00b02bbSJohn Johansen 		else
1340898127c3SJohn Johansen 			op = OP_CHANGE_ONEXEC;
1341898127c3SJohn Johansen 	} else {
1342898127c3SJohn Johansen 		request = AA_MAY_CHANGE_PROFILE;
1343e00b02bbSJohn Johansen 		if (stack)
1344e00b02bbSJohn Johansen 			op = OP_STACK;
1345e00b02bbSJohn Johansen 		else
1346898127c3SJohn Johansen 			op = OP_CHANGE_PROFILE;
1347898127c3SJohn Johansen 	}
1348898127c3SJohn Johansen 
1349e00b02bbSJohn Johansen 	label = aa_get_current_label();
1350898127c3SJohn Johansen 
1351e00b02bbSJohn Johansen 	if (*fqname == '&') {
1352e00b02bbSJohn Johansen 		stack = true;
1353e00b02bbSJohn Johansen 		/* don't have label_parse() do stacking */
1354e00b02bbSJohn Johansen 		fqname++;
1355c29bceb3SJohn Johansen 	}
1356e00b02bbSJohn Johansen 	target = aa_label_parse(label, fqname, GFP_KERNEL, true, false);
1357e00b02bbSJohn Johansen 	if (IS_ERR(target)) {
1358e00b02bbSJohn Johansen 		struct aa_profile *tprofile;
1359c29bceb3SJohn Johansen 
1360e00b02bbSJohn Johansen 		info = "label not found";
1361e00b02bbSJohn Johansen 		error = PTR_ERR(target);
1362e00b02bbSJohn Johansen 		target = NULL;
1363e00b02bbSJohn Johansen 		/*
1364e00b02bbSJohn Johansen 		 * TODO: fixme using labels_profile is not right - do profile
1365e00b02bbSJohn Johansen 		 * per complain profile
1366e00b02bbSJohn Johansen 		 */
1367df8073c6SJohn Johansen 		if ((flags & AA_CHANGE_TEST) ||
1368e00b02bbSJohn Johansen 		    !COMPLAIN_MODE(labels_profile(label)))
1369898127c3SJohn Johansen 			goto audit;
1370898127c3SJohn Johansen 		/* released below */
1371e00b02bbSJohn Johansen 		tprofile = aa_new_null_profile(labels_profile(label), false,
1372e00b02bbSJohn Johansen 					       fqname, GFP_KERNEL);
1373e00b02bbSJohn Johansen 		if (!tprofile) {
1374898127c3SJohn Johansen 			info = "failed null profile create";
1375898127c3SJohn Johansen 			error = -ENOMEM;
1376898127c3SJohn Johansen 			goto audit;
1377898127c3SJohn Johansen 		}
1378e00b02bbSJohn Johansen 		target = &tprofile->label;
1379e00b02bbSJohn Johansen 		goto check;
1380898127c3SJohn Johansen 	}
1381898127c3SJohn Johansen 
1382e00b02bbSJohn Johansen 	/*
1383e00b02bbSJohn Johansen 	 * self directed transitions only apply to current policy ns
1384e00b02bbSJohn Johansen 	 * TODO: currently requiring perms for stacking and straight change
1385e00b02bbSJohn Johansen 	 *       stacking doesn't strictly need this. Determine how much
1386e00b02bbSJohn Johansen 	 *       we want to loosen this restriction for stacking
1387e00b02bbSJohn Johansen 	 *
1388e00b02bbSJohn Johansen 	 * if (!stack) {
1389e00b02bbSJohn Johansen 	 */
1390e00b02bbSJohn Johansen 	error = fn_for_each_in_ns(label, profile,
1391e00b02bbSJohn Johansen 			change_profile_perms_wrapper(op, auditname,
1392e00b02bbSJohn Johansen 						     profile, target, stack,
1393e00b02bbSJohn Johansen 						     request, &perms));
1394e00b02bbSJohn Johansen 	if (error)
1395e00b02bbSJohn Johansen 		/* auditing done in change_profile_perms_wrapper */
1396e00b02bbSJohn Johansen 		goto out;
1397aa9a39adSJohn Johansen 
1398e00b02bbSJohn Johansen 	/* } */
1399e00b02bbSJohn Johansen 
1400e00b02bbSJohn Johansen check:
1401898127c3SJohn Johansen 	/* check if tracing task is allowed to trace target domain */
1402e00b02bbSJohn Johansen 	error = may_change_ptraced_domain(target, &info);
1403e00b02bbSJohn Johansen 	if (error && !fn_for_each_in_ns(label, profile,
1404e00b02bbSJohn Johansen 					COMPLAIN_MODE(profile)))
1405e00b02bbSJohn Johansen 		goto audit;
1406e00b02bbSJohn Johansen 
1407e00b02bbSJohn Johansen 	/* TODO: add permission check to allow this
1408e00b02bbSJohn Johansen 	 * if ((flags & AA_CHANGE_ONEXEC) && !current_is_single_threaded()) {
1409e00b02bbSJohn Johansen 	 *      info = "not a single threaded task";
1410e00b02bbSJohn Johansen 	 *      error = -EACCES;
1411e00b02bbSJohn Johansen 	 *      goto audit;
1412e00b02bbSJohn Johansen 	 * }
1413e00b02bbSJohn Johansen 	 */
1414e00b02bbSJohn Johansen 	if (flags & AA_CHANGE_TEST)
1415e00b02bbSJohn Johansen 		goto out;
1416e00b02bbSJohn Johansen 
14179fcf78ccSJohn Johansen 	/* stacking is always a subset, so only check the nonstack case */
14189fcf78ccSJohn Johansen 	if (!stack) {
14199fcf78ccSJohn Johansen 		new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
14209fcf78ccSJohn Johansen 					   aa_get_label(target),
14219fcf78ccSJohn Johansen 					   aa_get_label(&profile->label));
14229fcf78ccSJohn Johansen 		/*
14239fcf78ccSJohn Johansen 		 * no new privs prevents domain transitions that would
14249fcf78ccSJohn Johansen 		 * reduce restrictions.
14259fcf78ccSJohn Johansen 		 */
14269fcf78ccSJohn Johansen 		if (task_no_new_privs(current) && !unconfined(label) &&
14279fcf78ccSJohn Johansen 		    !aa_label_is_subset(new, ctx->nnp)) {
14289fcf78ccSJohn Johansen 			/* not an apparmor denial per se, so don't log it */
14299fcf78ccSJohn Johansen 			AA_DEBUG("no_new_privs - change_hat denied");
14309fcf78ccSJohn Johansen 			error = -EPERM;
14319fcf78ccSJohn Johansen 			goto out;
14329fcf78ccSJohn Johansen 		}
14339fcf78ccSJohn Johansen 	}
14349fcf78ccSJohn Johansen 
1435e00b02bbSJohn Johansen 	if (!(flags & AA_CHANGE_ONEXEC)) {
1436e00b02bbSJohn Johansen 		/* only transition profiles in the current ns */
1437e00b02bbSJohn Johansen 		if (stack)
1438e00b02bbSJohn Johansen 			new = aa_label_merge(label, target, GFP_KERNEL);
1439e00b02bbSJohn Johansen 		if (IS_ERR_OR_NULL(new)) {
1440e00b02bbSJohn Johansen 			info = "failed to build target label";
1441d6d478aeSJohn Johansen 			if (!new)
1442d6d478aeSJohn Johansen 				error = -ENOMEM;
1443d6d478aeSJohn Johansen 			else
1444e00b02bbSJohn Johansen 				error = PTR_ERR(new);
1445e00b02bbSJohn Johansen 			new = NULL;
1446e00b02bbSJohn Johansen 			perms.allow = 0;
1447898127c3SJohn Johansen 			goto audit;
1448898127c3SJohn Johansen 		}
1449e00b02bbSJohn Johansen 		error = aa_replace_current_label(new);
14509fcf78ccSJohn Johansen 	} else {
14519fcf78ccSJohn Johansen 		if (new) {
14529fcf78ccSJohn Johansen 			aa_put_label(new);
14539fcf78ccSJohn Johansen 			new = NULL;
14549fcf78ccSJohn Johansen 		}
14559fcf78ccSJohn Johansen 
1456e00b02bbSJohn Johansen 		/* full transition will be built in exec path */
1457e00b02bbSJohn Johansen 		error = aa_set_current_onexec(target, stack);
14589fcf78ccSJohn Johansen 	}
1459898127c3SJohn Johansen 
1460898127c3SJohn Johansen audit:
1461e00b02bbSJohn Johansen 	error = fn_for_each_in_ns(label, profile,
1462e00b02bbSJohn Johansen 			aa_audit_file(profile, &perms, op, request, auditname,
1463e00b02bbSJohn Johansen 				      NULL, new ? new : target,
1464e00b02bbSJohn Johansen 				      GLOBAL_ROOT_UID, info, error));
1465898127c3SJohn Johansen 
1466e00b02bbSJohn Johansen out:
1467e00b02bbSJohn Johansen 	aa_put_label(new);
1468e00b02bbSJohn Johansen 	aa_put_label(target);
1469637f688dSJohn Johansen 	aa_put_label(label);
1470898127c3SJohn Johansen 
1471898127c3SJohn Johansen 	return error;
1472898127c3SJohn Johansen }
1473