162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * AppArmor security module
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * This file contains AppArmor policy attachment and domain transitions
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Copyright (C) 2002-2008 Novell/SUSE
862306a36Sopenharmony_ci * Copyright 2009-2010 Canonical Ltd.
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/errno.h>
1262306a36Sopenharmony_ci#include <linux/fdtable.h>
1362306a36Sopenharmony_ci#include <linux/fs.h>
1462306a36Sopenharmony_ci#include <linux/file.h>
1562306a36Sopenharmony_ci#include <linux/mount.h>
1662306a36Sopenharmony_ci#include <linux/syscalls.h>
1762306a36Sopenharmony_ci#include <linux/personality.h>
1862306a36Sopenharmony_ci#include <linux/xattr.h>
1962306a36Sopenharmony_ci#include <linux/user_namespace.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include "include/audit.h"
2262306a36Sopenharmony_ci#include "include/apparmorfs.h"
2362306a36Sopenharmony_ci#include "include/cred.h"
2462306a36Sopenharmony_ci#include "include/domain.h"
2562306a36Sopenharmony_ci#include "include/file.h"
2662306a36Sopenharmony_ci#include "include/ipc.h"
2762306a36Sopenharmony_ci#include "include/match.h"
2862306a36Sopenharmony_ci#include "include/path.h"
2962306a36Sopenharmony_ci#include "include/policy.h"
3062306a36Sopenharmony_ci#include "include/policy_ns.h"
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci/**
3362306a36Sopenharmony_ci * may_change_ptraced_domain - check if can change profile on ptraced task
3462306a36Sopenharmony_ci * @cred: cred of task changing domain
3562306a36Sopenharmony_ci * @to_label: profile to change to  (NOT NULL)
3662306a36Sopenharmony_ci * @info: message if there is an error
3762306a36Sopenharmony_ci *
3862306a36Sopenharmony_ci * Check if current is ptraced and if so if the tracing task is allowed
3962306a36Sopenharmony_ci * to trace the new domain
4062306a36Sopenharmony_ci *
4162306a36Sopenharmony_ci * Returns: %0 or error if change not allowed
4262306a36Sopenharmony_ci */
4362306a36Sopenharmony_cistatic int may_change_ptraced_domain(const struct cred *to_cred,
4462306a36Sopenharmony_ci				     struct aa_label *to_label,
4562306a36Sopenharmony_ci				     const char **info)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	struct task_struct *tracer;
4862306a36Sopenharmony_ci	struct aa_label *tracerl = NULL;
4962306a36Sopenharmony_ci	const struct cred *tracer_cred = NULL;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	int error = 0;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	rcu_read_lock();
5462306a36Sopenharmony_ci	tracer = ptrace_parent(current);
5562306a36Sopenharmony_ci	if (tracer) {
5662306a36Sopenharmony_ci		/* released below */
5762306a36Sopenharmony_ci		tracerl = aa_get_task_label(tracer);
5862306a36Sopenharmony_ci		tracer_cred = get_task_cred(tracer);
5962306a36Sopenharmony_ci	}
6062306a36Sopenharmony_ci	/* not ptraced */
6162306a36Sopenharmony_ci	if (!tracer || unconfined(tracerl))
6262306a36Sopenharmony_ci		goto out;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	error = aa_may_ptrace(tracer_cred, tracerl, to_cred, to_label,
6562306a36Sopenharmony_ci			      PTRACE_MODE_ATTACH);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ciout:
6862306a36Sopenharmony_ci	rcu_read_unlock();
6962306a36Sopenharmony_ci	aa_put_label(tracerl);
7062306a36Sopenharmony_ci	put_cred(tracer_cred);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	if (error)
7362306a36Sopenharmony_ci		*info = "ptrace prevents transition";
7462306a36Sopenharmony_ci	return error;
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci/**** TODO: dedup to aa_label_match - needs perm and dfa, merging
7862306a36Sopenharmony_ci * specifically this is an exact copy of aa_label_match except
7962306a36Sopenharmony_ci * aa_compute_perms is replaced with aa_compute_fperms
8062306a36Sopenharmony_ci * and policy.dfa with file.dfa
8162306a36Sopenharmony_ci ****/
8262306a36Sopenharmony_ci/* match a profile and its associated ns component if needed
8362306a36Sopenharmony_ci * Assumes visibility test has already been done.
8462306a36Sopenharmony_ci * If a subns profile is not to be matched should be prescreened with
8562306a36Sopenharmony_ci * visibility test.
8662306a36Sopenharmony_ci */
8762306a36Sopenharmony_cistatic inline aa_state_t match_component(struct aa_profile *profile,
8862306a36Sopenharmony_ci					 struct aa_profile *tp,
8962306a36Sopenharmony_ci					 bool stack, aa_state_t state)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct aa_ruleset *rules = list_first_entry(&profile->rules,
9262306a36Sopenharmony_ci						    typeof(*rules), list);
9362306a36Sopenharmony_ci	const char *ns_name;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (stack)
9662306a36Sopenharmony_ci		state = aa_dfa_match(rules->file.dfa, state, "&");
9762306a36Sopenharmony_ci	if (profile->ns == tp->ns)
9862306a36Sopenharmony_ci		return aa_dfa_match(rules->file.dfa, state, tp->base.hname);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	/* try matching with namespace name and then profile */
10162306a36Sopenharmony_ci	ns_name = aa_ns_name(profile->ns, tp->ns, true);
10262306a36Sopenharmony_ci	state = aa_dfa_match_len(rules->file.dfa, state, ":", 1);
10362306a36Sopenharmony_ci	state = aa_dfa_match(rules->file.dfa, state, ns_name);
10462306a36Sopenharmony_ci	state = aa_dfa_match_len(rules->file.dfa, state, ":", 1);
10562306a36Sopenharmony_ci	return aa_dfa_match(rules->file.dfa, state, tp->base.hname);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/**
10962306a36Sopenharmony_ci * label_compound_match - find perms for full compound label
11062306a36Sopenharmony_ci * @profile: profile to find perms for
11162306a36Sopenharmony_ci * @label: label to check access permissions for
11262306a36Sopenharmony_ci * @stack: whether this is a stacking request
11362306a36Sopenharmony_ci * @state: state to start match in
11462306a36Sopenharmony_ci * @subns: whether to do permission checks on components in a subns
11562306a36Sopenharmony_ci * @request: permissions to request
11662306a36Sopenharmony_ci * @perms: perms struct to set
11762306a36Sopenharmony_ci *
11862306a36Sopenharmony_ci * Returns: 0 on success else ERROR
11962306a36Sopenharmony_ci *
12062306a36Sopenharmony_ci * For the label A//&B//&C this does the perm match for A//&B//&C
12162306a36Sopenharmony_ci * @perms should be preinitialized with allperms OR a previous permission
12262306a36Sopenharmony_ci *        check to be stacked.
12362306a36Sopenharmony_ci */
12462306a36Sopenharmony_cistatic int label_compound_match(struct aa_profile *profile,
12562306a36Sopenharmony_ci				struct aa_label *label, bool stack,
12662306a36Sopenharmony_ci				aa_state_t state, bool subns, u32 request,
12762306a36Sopenharmony_ci				struct aa_perms *perms)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	struct aa_ruleset *rules = list_first_entry(&profile->rules,
13062306a36Sopenharmony_ci						    typeof(*rules), list);
13162306a36Sopenharmony_ci	struct aa_profile *tp;
13262306a36Sopenharmony_ci	struct label_it i;
13362306a36Sopenharmony_ci	struct path_cond cond = { };
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	/* find first subcomponent that is visible */
13662306a36Sopenharmony_ci	label_for_each(i, label, tp) {
13762306a36Sopenharmony_ci		if (!aa_ns_visible(profile->ns, tp->ns, subns))
13862306a36Sopenharmony_ci			continue;
13962306a36Sopenharmony_ci		state = match_component(profile, tp, stack, state);
14062306a36Sopenharmony_ci		if (!state)
14162306a36Sopenharmony_ci			goto fail;
14262306a36Sopenharmony_ci		goto next;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	/* no component visible */
14662306a36Sopenharmony_ci	*perms = allperms;
14762306a36Sopenharmony_ci	return 0;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cinext:
15062306a36Sopenharmony_ci	label_for_each_cont(i, label, tp) {
15162306a36Sopenharmony_ci		if (!aa_ns_visible(profile->ns, tp->ns, subns))
15262306a36Sopenharmony_ci			continue;
15362306a36Sopenharmony_ci		state = aa_dfa_match(rules->file.dfa, state, "//&");
15462306a36Sopenharmony_ci		state = match_component(profile, tp, false, state);
15562306a36Sopenharmony_ci		if (!state)
15662306a36Sopenharmony_ci			goto fail;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci	*perms = *(aa_lookup_fperms(&(rules->file), state, &cond));
15962306a36Sopenharmony_ci	aa_apply_modes_to_perms(profile, perms);
16062306a36Sopenharmony_ci	if ((perms->allow & request) != request)
16162306a36Sopenharmony_ci		return -EACCES;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	return 0;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cifail:
16662306a36Sopenharmony_ci	*perms = nullperms;
16762306a36Sopenharmony_ci	return -EACCES;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci/**
17162306a36Sopenharmony_ci * label_components_match - find perms for all subcomponents of a label
17262306a36Sopenharmony_ci * @profile: profile to find perms for
17362306a36Sopenharmony_ci * @label: label to check access permissions for
17462306a36Sopenharmony_ci * @stack: whether this is a stacking request
17562306a36Sopenharmony_ci * @start: state to start match in
17662306a36Sopenharmony_ci * @subns: whether to do permission checks on components in a subns
17762306a36Sopenharmony_ci * @request: permissions to request
17862306a36Sopenharmony_ci * @perms: an initialized perms struct to add accumulation to
17962306a36Sopenharmony_ci *
18062306a36Sopenharmony_ci * Returns: 0 on success else ERROR
18162306a36Sopenharmony_ci *
18262306a36Sopenharmony_ci * For the label A//&B//&C this does the perm match for each of A and B and C
18362306a36Sopenharmony_ci * @perms should be preinitialized with allperms OR a previous permission
18462306a36Sopenharmony_ci *        check to be stacked.
18562306a36Sopenharmony_ci */
18662306a36Sopenharmony_cistatic int label_components_match(struct aa_profile *profile,
18762306a36Sopenharmony_ci				  struct aa_label *label, bool stack,
18862306a36Sopenharmony_ci				  aa_state_t start, bool subns, u32 request,
18962306a36Sopenharmony_ci				  struct aa_perms *perms)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	struct aa_ruleset *rules = list_first_entry(&profile->rules,
19262306a36Sopenharmony_ci						    typeof(*rules), list);
19362306a36Sopenharmony_ci	struct aa_profile *tp;
19462306a36Sopenharmony_ci	struct label_it i;
19562306a36Sopenharmony_ci	struct aa_perms tmp;
19662306a36Sopenharmony_ci	struct path_cond cond = { };
19762306a36Sopenharmony_ci	aa_state_t state = 0;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	/* find first subcomponent to test */
20062306a36Sopenharmony_ci	label_for_each(i, label, tp) {
20162306a36Sopenharmony_ci		if (!aa_ns_visible(profile->ns, tp->ns, subns))
20262306a36Sopenharmony_ci			continue;
20362306a36Sopenharmony_ci		state = match_component(profile, tp, stack, start);
20462306a36Sopenharmony_ci		if (!state)
20562306a36Sopenharmony_ci			goto fail;
20662306a36Sopenharmony_ci		goto next;
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	/* no subcomponents visible - no change in perms */
21062306a36Sopenharmony_ci	return 0;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cinext:
21362306a36Sopenharmony_ci	tmp = *(aa_lookup_fperms(&(rules->file), state, &cond));
21462306a36Sopenharmony_ci	aa_apply_modes_to_perms(profile, &tmp);
21562306a36Sopenharmony_ci	aa_perms_accum(perms, &tmp);
21662306a36Sopenharmony_ci	label_for_each_cont(i, label, tp) {
21762306a36Sopenharmony_ci		if (!aa_ns_visible(profile->ns, tp->ns, subns))
21862306a36Sopenharmony_ci			continue;
21962306a36Sopenharmony_ci		state = match_component(profile, tp, stack, start);
22062306a36Sopenharmony_ci		if (!state)
22162306a36Sopenharmony_ci			goto fail;
22262306a36Sopenharmony_ci		tmp = *(aa_lookup_fperms(&(rules->file), state, &cond));
22362306a36Sopenharmony_ci		aa_apply_modes_to_perms(profile, &tmp);
22462306a36Sopenharmony_ci		aa_perms_accum(perms, &tmp);
22562306a36Sopenharmony_ci	}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	if ((perms->allow & request) != request)
22862306a36Sopenharmony_ci		return -EACCES;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	return 0;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cifail:
23362306a36Sopenharmony_ci	*perms = nullperms;
23462306a36Sopenharmony_ci	return -EACCES;
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci/**
23862306a36Sopenharmony_ci * label_match - do a multi-component label match
23962306a36Sopenharmony_ci * @profile: profile to match against (NOT NULL)
24062306a36Sopenharmony_ci * @label: label to match (NOT NULL)
24162306a36Sopenharmony_ci * @stack: whether this is a stacking request
24262306a36Sopenharmony_ci * @state: state to start in
24362306a36Sopenharmony_ci * @subns: whether to match subns components
24462306a36Sopenharmony_ci * @request: permission request
24562306a36Sopenharmony_ci * @perms: Returns computed perms (NOT NULL)
24662306a36Sopenharmony_ci *
24762306a36Sopenharmony_ci * Returns: the state the match finished in, may be the none matching state
24862306a36Sopenharmony_ci */
24962306a36Sopenharmony_cistatic int label_match(struct aa_profile *profile, struct aa_label *label,
25062306a36Sopenharmony_ci		       bool stack, aa_state_t state, bool subns, u32 request,
25162306a36Sopenharmony_ci		       struct aa_perms *perms)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	int error;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	*perms = nullperms;
25662306a36Sopenharmony_ci	error = label_compound_match(profile, label, stack, state, subns,
25762306a36Sopenharmony_ci				     request, perms);
25862306a36Sopenharmony_ci	if (!error)
25962306a36Sopenharmony_ci		return error;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	*perms = allperms;
26262306a36Sopenharmony_ci	return label_components_match(profile, label, stack, state, subns,
26362306a36Sopenharmony_ci				      request, perms);
26462306a36Sopenharmony_ci}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci/******* end TODO: dedup *****/
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci/**
26962306a36Sopenharmony_ci * change_profile_perms - find permissions for change_profile
27062306a36Sopenharmony_ci * @profile: the current profile  (NOT NULL)
27162306a36Sopenharmony_ci * @target: label to transition to (NOT NULL)
27262306a36Sopenharmony_ci * @stack: whether this is a stacking request
27362306a36Sopenharmony_ci * @request: requested perms
27462306a36Sopenharmony_ci * @start: state to start matching in
27562306a36Sopenharmony_ci *
27662306a36Sopenharmony_ci *
27762306a36Sopenharmony_ci * Returns: permission set
27862306a36Sopenharmony_ci *
27962306a36Sopenharmony_ci * currently only matches full label A//&B//&C or individual components A, B, C
28062306a36Sopenharmony_ci * not arbitrary combinations. Eg. A//&B, C
28162306a36Sopenharmony_ci */
28262306a36Sopenharmony_cistatic int change_profile_perms(struct aa_profile *profile,
28362306a36Sopenharmony_ci				struct aa_label *target, bool stack,
28462306a36Sopenharmony_ci				u32 request, aa_state_t start,
28562306a36Sopenharmony_ci				struct aa_perms *perms)
28662306a36Sopenharmony_ci{
28762306a36Sopenharmony_ci	if (profile_unconfined(profile)) {
28862306a36Sopenharmony_ci		perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
28962306a36Sopenharmony_ci		perms->audit = perms->quiet = perms->kill = 0;
29062306a36Sopenharmony_ci		return 0;
29162306a36Sopenharmony_ci	}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	/* TODO: add profile in ns screening */
29462306a36Sopenharmony_ci	return label_match(profile, target, stack, start, true, request, perms);
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci/**
29862306a36Sopenharmony_ci * aa_xattrs_match - check whether a file matches the xattrs defined in profile
29962306a36Sopenharmony_ci * @bprm: binprm struct for the process to validate
30062306a36Sopenharmony_ci * @profile: profile to match against (NOT NULL)
30162306a36Sopenharmony_ci * @state: state to start match in
30262306a36Sopenharmony_ci *
30362306a36Sopenharmony_ci * Returns: number of extended attributes that matched, or < 0 on error
30462306a36Sopenharmony_ci */
30562306a36Sopenharmony_cistatic int aa_xattrs_match(const struct linux_binprm *bprm,
30662306a36Sopenharmony_ci			   struct aa_profile *profile, aa_state_t state)
30762306a36Sopenharmony_ci{
30862306a36Sopenharmony_ci	int i;
30962306a36Sopenharmony_ci	struct dentry *d;
31062306a36Sopenharmony_ci	char *value = NULL;
31162306a36Sopenharmony_ci	struct aa_attachment *attach = &profile->attach;
31262306a36Sopenharmony_ci	int size, value_size = 0, ret = attach->xattr_count;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	if (!bprm || !attach->xattr_count)
31562306a36Sopenharmony_ci		return 0;
31662306a36Sopenharmony_ci	might_sleep();
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	/* transition from exec match to xattr set */
31962306a36Sopenharmony_ci	state = aa_dfa_outofband_transition(attach->xmatch.dfa, state);
32062306a36Sopenharmony_ci	d = bprm->file->f_path.dentry;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	for (i = 0; i < attach->xattr_count; i++) {
32362306a36Sopenharmony_ci		size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i],
32462306a36Sopenharmony_ci					  &value, value_size, GFP_KERNEL);
32562306a36Sopenharmony_ci		if (size >= 0) {
32662306a36Sopenharmony_ci			u32 index, perm;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci			/*
32962306a36Sopenharmony_ci			 * Check the xattr presence before value. This ensure
33062306a36Sopenharmony_ci			 * that not present xattr can be distinguished from a 0
33162306a36Sopenharmony_ci			 * length value or rule that matches any value
33262306a36Sopenharmony_ci			 */
33362306a36Sopenharmony_ci			state = aa_dfa_null_transition(attach->xmatch.dfa,
33462306a36Sopenharmony_ci						       state);
33562306a36Sopenharmony_ci			/* Check xattr value */
33662306a36Sopenharmony_ci			state = aa_dfa_match_len(attach->xmatch.dfa, state,
33762306a36Sopenharmony_ci						 value, size);
33862306a36Sopenharmony_ci			index = ACCEPT_TABLE(attach->xmatch.dfa)[state];
33962306a36Sopenharmony_ci			perm = attach->xmatch.perms[index].allow;
34062306a36Sopenharmony_ci			if (!(perm & MAY_EXEC)) {
34162306a36Sopenharmony_ci				ret = -EINVAL;
34262306a36Sopenharmony_ci				goto out;
34362306a36Sopenharmony_ci			}
34462306a36Sopenharmony_ci		}
34562306a36Sopenharmony_ci		/* transition to next element */
34662306a36Sopenharmony_ci		state = aa_dfa_outofband_transition(attach->xmatch.dfa, state);
34762306a36Sopenharmony_ci		if (size < 0) {
34862306a36Sopenharmony_ci			/*
34962306a36Sopenharmony_ci			 * No xattr match, so verify if transition to
35062306a36Sopenharmony_ci			 * next element was valid. IFF so the xattr
35162306a36Sopenharmony_ci			 * was optional.
35262306a36Sopenharmony_ci			 */
35362306a36Sopenharmony_ci			if (!state) {
35462306a36Sopenharmony_ci				ret = -EINVAL;
35562306a36Sopenharmony_ci				goto out;
35662306a36Sopenharmony_ci			}
35762306a36Sopenharmony_ci			/* don't count missing optional xattr as matched */
35862306a36Sopenharmony_ci			ret--;
35962306a36Sopenharmony_ci		}
36062306a36Sopenharmony_ci	}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ciout:
36362306a36Sopenharmony_ci	kfree(value);
36462306a36Sopenharmony_ci	return ret;
36562306a36Sopenharmony_ci}
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci/**
36862306a36Sopenharmony_ci * find_attach - do attachment search for unconfined processes
36962306a36Sopenharmony_ci * @bprm - binprm structure of transitioning task
37062306a36Sopenharmony_ci * @ns: the current namespace  (NOT NULL)
37162306a36Sopenharmony_ci * @head - profile list to walk  (NOT NULL)
37262306a36Sopenharmony_ci * @name - to match against  (NOT NULL)
37362306a36Sopenharmony_ci * @info - info message if there was an error (NOT NULL)
37462306a36Sopenharmony_ci *
37562306a36Sopenharmony_ci * Do a linear search on the profiles in the list.  There is a matching
37662306a36Sopenharmony_ci * preference where an exact match is preferred over a name which uses
37762306a36Sopenharmony_ci * expressions to match, and matching expressions with the greatest
37862306a36Sopenharmony_ci * xmatch_len are preferred.
37962306a36Sopenharmony_ci *
38062306a36Sopenharmony_ci * Requires: @head not be shared or have appropriate locks held
38162306a36Sopenharmony_ci *
38262306a36Sopenharmony_ci * Returns: label or NULL if no match found
38362306a36Sopenharmony_ci */
38462306a36Sopenharmony_cistatic struct aa_label *find_attach(const struct linux_binprm *bprm,
38562306a36Sopenharmony_ci				    struct aa_ns *ns, struct list_head *head,
38662306a36Sopenharmony_ci				    const char *name, const char **info)
38762306a36Sopenharmony_ci{
38862306a36Sopenharmony_ci	int candidate_len = 0, candidate_xattrs = 0;
38962306a36Sopenharmony_ci	bool conflict = false;
39062306a36Sopenharmony_ci	struct aa_profile *profile, *candidate = NULL;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	AA_BUG(!name);
39362306a36Sopenharmony_ci	AA_BUG(!head);
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	rcu_read_lock();
39662306a36Sopenharmony_cirestart:
39762306a36Sopenharmony_ci	list_for_each_entry_rcu(profile, head, base.list) {
39862306a36Sopenharmony_ci		struct aa_attachment *attach = &profile->attach;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci		if (profile->label.flags & FLAG_NULL &&
40162306a36Sopenharmony_ci		    &profile->label == ns_unconfined(profile->ns))
40262306a36Sopenharmony_ci			continue;
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci		/* Find the "best" matching profile. Profiles must
40562306a36Sopenharmony_ci		 * match the path and extended attributes (if any)
40662306a36Sopenharmony_ci		 * associated with the file. A more specific path
40762306a36Sopenharmony_ci		 * match will be preferred over a less specific one,
40862306a36Sopenharmony_ci		 * and a match with more matching extended attributes
40962306a36Sopenharmony_ci		 * will be preferred over one with fewer. If the best
41062306a36Sopenharmony_ci		 * match has both the same level of path specificity
41162306a36Sopenharmony_ci		 * and the same number of matching extended attributes
41262306a36Sopenharmony_ci		 * as another profile, signal a conflict and refuse to
41362306a36Sopenharmony_ci		 * match.
41462306a36Sopenharmony_ci		 */
41562306a36Sopenharmony_ci		if (attach->xmatch.dfa) {
41662306a36Sopenharmony_ci			unsigned int count;
41762306a36Sopenharmony_ci			aa_state_t state;
41862306a36Sopenharmony_ci			u32 index, perm;
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci			state = aa_dfa_leftmatch(attach->xmatch.dfa,
42162306a36Sopenharmony_ci					attach->xmatch.start[AA_CLASS_XMATCH],
42262306a36Sopenharmony_ci					name, &count);
42362306a36Sopenharmony_ci			index = ACCEPT_TABLE(attach->xmatch.dfa)[state];
42462306a36Sopenharmony_ci			perm = attach->xmatch.perms[index].allow;
42562306a36Sopenharmony_ci			/* any accepting state means a valid match. */
42662306a36Sopenharmony_ci			if (perm & MAY_EXEC) {
42762306a36Sopenharmony_ci				int ret = 0;
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci				if (count < candidate_len)
43062306a36Sopenharmony_ci					continue;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci				if (bprm && attach->xattr_count) {
43362306a36Sopenharmony_ci					long rev = READ_ONCE(ns->revision);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci					if (!aa_get_profile_not0(profile))
43662306a36Sopenharmony_ci						goto restart;
43762306a36Sopenharmony_ci					rcu_read_unlock();
43862306a36Sopenharmony_ci					ret = aa_xattrs_match(bprm, profile,
43962306a36Sopenharmony_ci							      state);
44062306a36Sopenharmony_ci					rcu_read_lock();
44162306a36Sopenharmony_ci					aa_put_profile(profile);
44262306a36Sopenharmony_ci					if (rev !=
44362306a36Sopenharmony_ci					    READ_ONCE(ns->revision))
44462306a36Sopenharmony_ci						/* policy changed */
44562306a36Sopenharmony_ci						goto restart;
44662306a36Sopenharmony_ci					/*
44762306a36Sopenharmony_ci					 * Fail matching if the xattrs don't
44862306a36Sopenharmony_ci					 * match
44962306a36Sopenharmony_ci					 */
45062306a36Sopenharmony_ci					if (ret < 0)
45162306a36Sopenharmony_ci						continue;
45262306a36Sopenharmony_ci				}
45362306a36Sopenharmony_ci				/*
45462306a36Sopenharmony_ci				 * TODO: allow for more flexible best match
45562306a36Sopenharmony_ci				 *
45662306a36Sopenharmony_ci				 * The new match isn't more specific
45762306a36Sopenharmony_ci				 * than the current best match
45862306a36Sopenharmony_ci				 */
45962306a36Sopenharmony_ci				if (count == candidate_len &&
46062306a36Sopenharmony_ci				    ret <= candidate_xattrs) {
46162306a36Sopenharmony_ci					/* Match is equivalent, so conflict */
46262306a36Sopenharmony_ci					if (ret == candidate_xattrs)
46362306a36Sopenharmony_ci						conflict = true;
46462306a36Sopenharmony_ci					continue;
46562306a36Sopenharmony_ci				}
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci				/* Either the same length with more matching
46862306a36Sopenharmony_ci				 * xattrs, or a longer match
46962306a36Sopenharmony_ci				 */
47062306a36Sopenharmony_ci				candidate = profile;
47162306a36Sopenharmony_ci				candidate_len = max(count, attach->xmatch_len);
47262306a36Sopenharmony_ci				candidate_xattrs = ret;
47362306a36Sopenharmony_ci				conflict = false;
47462306a36Sopenharmony_ci			}
47562306a36Sopenharmony_ci		} else if (!strcmp(profile->base.name, name)) {
47662306a36Sopenharmony_ci			/*
47762306a36Sopenharmony_ci			 * old exact non-re match, without conditionals such
47862306a36Sopenharmony_ci			 * as xattrs. no more searching required
47962306a36Sopenharmony_ci			 */
48062306a36Sopenharmony_ci			candidate = profile;
48162306a36Sopenharmony_ci			goto out;
48262306a36Sopenharmony_ci		}
48362306a36Sopenharmony_ci	}
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	if (!candidate || conflict) {
48662306a36Sopenharmony_ci		if (conflict)
48762306a36Sopenharmony_ci			*info = "conflicting profile attachments";
48862306a36Sopenharmony_ci		rcu_read_unlock();
48962306a36Sopenharmony_ci		return NULL;
49062306a36Sopenharmony_ci	}
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ciout:
49362306a36Sopenharmony_ci	candidate = aa_get_newest_profile(candidate);
49462306a36Sopenharmony_ci	rcu_read_unlock();
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	return &candidate->label;
49762306a36Sopenharmony_ci}
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_cistatic const char *next_name(int xtype, const char *name)
50062306a36Sopenharmony_ci{
50162306a36Sopenharmony_ci	return NULL;
50262306a36Sopenharmony_ci}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci/**
50562306a36Sopenharmony_ci * x_table_lookup - lookup an x transition name via transition table
50662306a36Sopenharmony_ci * @profile: current profile (NOT NULL)
50762306a36Sopenharmony_ci * @xindex: index into x transition table
50862306a36Sopenharmony_ci * @name: returns: name tested to find label (NOT NULL)
50962306a36Sopenharmony_ci *
51062306a36Sopenharmony_ci * Returns: refcounted label, or NULL on failure (MAYBE NULL)
51162306a36Sopenharmony_ci */
51262306a36Sopenharmony_cistruct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
51362306a36Sopenharmony_ci				const char **name)
51462306a36Sopenharmony_ci{
51562306a36Sopenharmony_ci	struct aa_ruleset *rules = list_first_entry(&profile->rules,
51662306a36Sopenharmony_ci						    typeof(*rules), list);
51762306a36Sopenharmony_ci	struct aa_label *label = NULL;
51862306a36Sopenharmony_ci	u32 xtype = xindex & AA_X_TYPE_MASK;
51962306a36Sopenharmony_ci	int index = xindex & AA_X_INDEX_MASK;
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	AA_BUG(!name);
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	/* index is guaranteed to be in range, validated at load time */
52462306a36Sopenharmony_ci	/* TODO: move lookup parsing to unpack time so this is a straight
52562306a36Sopenharmony_ci	 *       index into the resultant label
52662306a36Sopenharmony_ci	 */
52762306a36Sopenharmony_ci	for (*name = rules->file.trans.table[index]; !label && *name;
52862306a36Sopenharmony_ci	     *name = next_name(xtype, *name)) {
52962306a36Sopenharmony_ci		if (xindex & AA_X_CHILD) {
53062306a36Sopenharmony_ci			struct aa_profile *new_profile;
53162306a36Sopenharmony_ci			/* release by caller */
53262306a36Sopenharmony_ci			new_profile = aa_find_child(profile, *name);
53362306a36Sopenharmony_ci			if (new_profile)
53462306a36Sopenharmony_ci				label = &new_profile->label;
53562306a36Sopenharmony_ci			continue;
53662306a36Sopenharmony_ci		}
53762306a36Sopenharmony_ci		label = aa_label_parse(&profile->label, *name, GFP_KERNEL,
53862306a36Sopenharmony_ci				       true, false);
53962306a36Sopenharmony_ci		if (IS_ERR(label))
54062306a36Sopenharmony_ci			label = NULL;
54162306a36Sopenharmony_ci	}
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	/* released by caller */
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	return label;
54662306a36Sopenharmony_ci}
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci/**
54962306a36Sopenharmony_ci * x_to_label - get target label for a given xindex
55062306a36Sopenharmony_ci * @profile: current profile  (NOT NULL)
55162306a36Sopenharmony_ci * @bprm: binprm structure of transitioning task
55262306a36Sopenharmony_ci * @name: name to lookup (NOT NULL)
55362306a36Sopenharmony_ci * @xindex: index into x transition table
55462306a36Sopenharmony_ci * @lookupname: returns: name used in lookup if one was specified (NOT NULL)
55562306a36Sopenharmony_ci *
55662306a36Sopenharmony_ci * find label for a transition index
55762306a36Sopenharmony_ci *
55862306a36Sopenharmony_ci * Returns: refcounted label or NULL if not found available
55962306a36Sopenharmony_ci */
56062306a36Sopenharmony_cistatic struct aa_label *x_to_label(struct aa_profile *profile,
56162306a36Sopenharmony_ci				   const struct linux_binprm *bprm,
56262306a36Sopenharmony_ci				   const char *name, u32 xindex,
56362306a36Sopenharmony_ci				   const char **lookupname,
56462306a36Sopenharmony_ci				   const char **info)
56562306a36Sopenharmony_ci{
56662306a36Sopenharmony_ci	struct aa_ruleset *rules = list_first_entry(&profile->rules,
56762306a36Sopenharmony_ci						    typeof(*rules), list);
56862306a36Sopenharmony_ci	struct aa_label *new = NULL;
56962306a36Sopenharmony_ci	struct aa_ns *ns = profile->ns;
57062306a36Sopenharmony_ci	u32 xtype = xindex & AA_X_TYPE_MASK;
57162306a36Sopenharmony_ci	const char *stack = NULL;
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	switch (xtype) {
57462306a36Sopenharmony_ci	case AA_X_NONE:
57562306a36Sopenharmony_ci		/* fail exec unless ix || ux fallback - handled by caller */
57662306a36Sopenharmony_ci		*lookupname = NULL;
57762306a36Sopenharmony_ci		break;
57862306a36Sopenharmony_ci	case AA_X_TABLE:
57962306a36Sopenharmony_ci		/* TODO: fix when perm mapping done at unload */
58062306a36Sopenharmony_ci		stack = rules->file.trans.table[xindex & AA_X_INDEX_MASK];
58162306a36Sopenharmony_ci		if (*stack != '&') {
58262306a36Sopenharmony_ci			/* released by caller */
58362306a36Sopenharmony_ci			new = x_table_lookup(profile, xindex, lookupname);
58462306a36Sopenharmony_ci			stack = NULL;
58562306a36Sopenharmony_ci			break;
58662306a36Sopenharmony_ci		}
58762306a36Sopenharmony_ci		fallthrough;	/* to X_NAME */
58862306a36Sopenharmony_ci	case AA_X_NAME:
58962306a36Sopenharmony_ci		if (xindex & AA_X_CHILD)
59062306a36Sopenharmony_ci			/* released by caller */
59162306a36Sopenharmony_ci			new = find_attach(bprm, ns, &profile->base.profiles,
59262306a36Sopenharmony_ci					  name, info);
59362306a36Sopenharmony_ci		else
59462306a36Sopenharmony_ci			/* released by caller */
59562306a36Sopenharmony_ci			new = find_attach(bprm, ns, &ns->base.profiles,
59662306a36Sopenharmony_ci					  name, info);
59762306a36Sopenharmony_ci		*lookupname = name;
59862306a36Sopenharmony_ci		break;
59962306a36Sopenharmony_ci	}
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	if (!new) {
60262306a36Sopenharmony_ci		if (xindex & AA_X_INHERIT) {
60362306a36Sopenharmony_ci			/* (p|c|n)ix - don't change profile but do
60462306a36Sopenharmony_ci			 * use the newest version
60562306a36Sopenharmony_ci			 */
60662306a36Sopenharmony_ci			*info = "ix fallback";
60762306a36Sopenharmony_ci			/* no profile && no error */
60862306a36Sopenharmony_ci			new = aa_get_newest_label(&profile->label);
60962306a36Sopenharmony_ci		} else if (xindex & AA_X_UNCONFINED) {
61062306a36Sopenharmony_ci			new = aa_get_newest_label(ns_unconfined(profile->ns));
61162306a36Sopenharmony_ci			*info = "ux fallback";
61262306a36Sopenharmony_ci		}
61362306a36Sopenharmony_ci	}
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci	if (new && stack) {
61662306a36Sopenharmony_ci		/* base the stack on post domain transition */
61762306a36Sopenharmony_ci		struct aa_label *base = new;
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_ci		new = aa_label_parse(base, stack, GFP_KERNEL, true, false);
62062306a36Sopenharmony_ci		if (IS_ERR(new))
62162306a36Sopenharmony_ci			new = NULL;
62262306a36Sopenharmony_ci		aa_put_label(base);
62362306a36Sopenharmony_ci	}
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	/* released by caller */
62662306a36Sopenharmony_ci	return new;
62762306a36Sopenharmony_ci}
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_cistatic struct aa_label *profile_transition(const struct cred *subj_cred,
63062306a36Sopenharmony_ci					   struct aa_profile *profile,
63162306a36Sopenharmony_ci					   const struct linux_binprm *bprm,
63262306a36Sopenharmony_ci					   char *buffer, struct path_cond *cond,
63362306a36Sopenharmony_ci					   bool *secure_exec)
63462306a36Sopenharmony_ci{
63562306a36Sopenharmony_ci	struct aa_ruleset *rules = list_first_entry(&profile->rules,
63662306a36Sopenharmony_ci						    typeof(*rules), list);
63762306a36Sopenharmony_ci	struct aa_label *new = NULL;
63862306a36Sopenharmony_ci	const char *info = NULL, *name = NULL, *target = NULL;
63962306a36Sopenharmony_ci	aa_state_t state = rules->file.start[AA_CLASS_FILE];
64062306a36Sopenharmony_ci	struct aa_perms perms = {};
64162306a36Sopenharmony_ci	bool nonewprivs = false;
64262306a36Sopenharmony_ci	int error = 0;
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci	AA_BUG(!profile);
64562306a36Sopenharmony_ci	AA_BUG(!bprm);
64662306a36Sopenharmony_ci	AA_BUG(!buffer);
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
64962306a36Sopenharmony_ci			     &name, &info, profile->disconnected);
65062306a36Sopenharmony_ci	if (error) {
65162306a36Sopenharmony_ci		if (profile_unconfined(profile) ||
65262306a36Sopenharmony_ci		    (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
65362306a36Sopenharmony_ci			AA_DEBUG("name lookup ix on error");
65462306a36Sopenharmony_ci			error = 0;
65562306a36Sopenharmony_ci			new = aa_get_newest_label(&profile->label);
65662306a36Sopenharmony_ci		}
65762306a36Sopenharmony_ci		name = bprm->filename;
65862306a36Sopenharmony_ci		goto audit;
65962306a36Sopenharmony_ci	}
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	if (profile_unconfined(profile)) {
66262306a36Sopenharmony_ci		new = find_attach(bprm, profile->ns,
66362306a36Sopenharmony_ci				  &profile->ns->base.profiles, name, &info);
66462306a36Sopenharmony_ci		if (new) {
66562306a36Sopenharmony_ci			AA_DEBUG("unconfined attached to new label");
66662306a36Sopenharmony_ci			return new;
66762306a36Sopenharmony_ci		}
66862306a36Sopenharmony_ci		AA_DEBUG("unconfined exec no attachment");
66962306a36Sopenharmony_ci		return aa_get_newest_label(&profile->label);
67062306a36Sopenharmony_ci	}
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	/* find exec permissions for name */
67362306a36Sopenharmony_ci	state = aa_str_perms(&(rules->file), state, name, cond, &perms);
67462306a36Sopenharmony_ci	if (perms.allow & MAY_EXEC) {
67562306a36Sopenharmony_ci		/* exec permission determine how to transition */
67662306a36Sopenharmony_ci		new = x_to_label(profile, bprm, name, perms.xindex, &target,
67762306a36Sopenharmony_ci				 &info);
67862306a36Sopenharmony_ci		if (new && new->proxy == profile->label.proxy && info) {
67962306a36Sopenharmony_ci			/* hack ix fallback - improve how this is detected */
68062306a36Sopenharmony_ci			goto audit;
68162306a36Sopenharmony_ci		} else if (!new) {
68262306a36Sopenharmony_ci			error = -EACCES;
68362306a36Sopenharmony_ci			info = "profile transition not found";
68462306a36Sopenharmony_ci			/* remove MAY_EXEC to audit as failure */
68562306a36Sopenharmony_ci			perms.allow &= ~MAY_EXEC;
68662306a36Sopenharmony_ci		}
68762306a36Sopenharmony_ci	} else if (COMPLAIN_MODE(profile)) {
68862306a36Sopenharmony_ci		/* no exec permission - learning mode */
68962306a36Sopenharmony_ci		struct aa_profile *new_profile = NULL;
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci		new_profile = aa_new_learning_profile(profile, false, name,
69262306a36Sopenharmony_ci						      GFP_KERNEL);
69362306a36Sopenharmony_ci		if (!new_profile) {
69462306a36Sopenharmony_ci			error = -ENOMEM;
69562306a36Sopenharmony_ci			info = "could not create null profile";
69662306a36Sopenharmony_ci		} else {
69762306a36Sopenharmony_ci			error = -EACCES;
69862306a36Sopenharmony_ci			new = &new_profile->label;
69962306a36Sopenharmony_ci		}
70062306a36Sopenharmony_ci		perms.xindex |= AA_X_UNSAFE;
70162306a36Sopenharmony_ci	} else
70262306a36Sopenharmony_ci		/* fail exec */
70362306a36Sopenharmony_ci		error = -EACCES;
70462306a36Sopenharmony_ci
70562306a36Sopenharmony_ci	if (!new)
70662306a36Sopenharmony_ci		goto audit;
70762306a36Sopenharmony_ci
70862306a36Sopenharmony_ci
70962306a36Sopenharmony_ci	if (!(perms.xindex & AA_X_UNSAFE)) {
71062306a36Sopenharmony_ci		if (DEBUG_ON) {
71162306a36Sopenharmony_ci			dbg_printk("apparmor: scrubbing environment variables"
71262306a36Sopenharmony_ci				   " for %s profile=", name);
71362306a36Sopenharmony_ci			aa_label_printk(new, GFP_KERNEL);
71462306a36Sopenharmony_ci			dbg_printk("\n");
71562306a36Sopenharmony_ci		}
71662306a36Sopenharmony_ci		*secure_exec = true;
71762306a36Sopenharmony_ci	}
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_ciaudit:
72062306a36Sopenharmony_ci	aa_audit_file(subj_cred, profile, &perms, OP_EXEC, MAY_EXEC, name,
72162306a36Sopenharmony_ci		      target, new,
72262306a36Sopenharmony_ci		      cond->uid, info, error);
72362306a36Sopenharmony_ci	if (!new || nonewprivs) {
72462306a36Sopenharmony_ci		aa_put_label(new);
72562306a36Sopenharmony_ci		return ERR_PTR(error);
72662306a36Sopenharmony_ci	}
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci	return new;
72962306a36Sopenharmony_ci}
73062306a36Sopenharmony_ci
73162306a36Sopenharmony_cistatic int profile_onexec(const struct cred *subj_cred,
73262306a36Sopenharmony_ci			  struct aa_profile *profile, struct aa_label *onexec,
73362306a36Sopenharmony_ci			  bool stack, const struct linux_binprm *bprm,
73462306a36Sopenharmony_ci			  char *buffer, struct path_cond *cond,
73562306a36Sopenharmony_ci			  bool *secure_exec)
73662306a36Sopenharmony_ci{
73762306a36Sopenharmony_ci	struct aa_ruleset *rules = list_first_entry(&profile->rules,
73862306a36Sopenharmony_ci						    typeof(*rules), list);
73962306a36Sopenharmony_ci	aa_state_t state = rules->file.start[AA_CLASS_FILE];
74062306a36Sopenharmony_ci	struct aa_perms perms = {};
74162306a36Sopenharmony_ci	const char *xname = NULL, *info = "change_profile onexec";
74262306a36Sopenharmony_ci	int error = -EACCES;
74362306a36Sopenharmony_ci
74462306a36Sopenharmony_ci	AA_BUG(!profile);
74562306a36Sopenharmony_ci	AA_BUG(!onexec);
74662306a36Sopenharmony_ci	AA_BUG(!bprm);
74762306a36Sopenharmony_ci	AA_BUG(!buffer);
74862306a36Sopenharmony_ci
74962306a36Sopenharmony_ci	if (profile_unconfined(profile)) {
75062306a36Sopenharmony_ci		/* change_profile on exec already granted */
75162306a36Sopenharmony_ci		/*
75262306a36Sopenharmony_ci		 * NOTE: Domain transitions from unconfined are allowed
75362306a36Sopenharmony_ci		 * even when no_new_privs is set because this aways results
75462306a36Sopenharmony_ci		 * in a further reduction of permissions.
75562306a36Sopenharmony_ci		 */
75662306a36Sopenharmony_ci		return 0;
75762306a36Sopenharmony_ci	}
75862306a36Sopenharmony_ci
75962306a36Sopenharmony_ci	error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
76062306a36Sopenharmony_ci			     &xname, &info, profile->disconnected);
76162306a36Sopenharmony_ci	if (error) {
76262306a36Sopenharmony_ci		if (profile_unconfined(profile) ||
76362306a36Sopenharmony_ci		    (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
76462306a36Sopenharmony_ci			AA_DEBUG("name lookup ix on error");
76562306a36Sopenharmony_ci			error = 0;
76662306a36Sopenharmony_ci		}
76762306a36Sopenharmony_ci		xname = bprm->filename;
76862306a36Sopenharmony_ci		goto audit;
76962306a36Sopenharmony_ci	}
77062306a36Sopenharmony_ci
77162306a36Sopenharmony_ci	/* find exec permissions for name */
77262306a36Sopenharmony_ci	state = aa_str_perms(&(rules->file), state, xname, cond, &perms);
77362306a36Sopenharmony_ci	if (!(perms.allow & AA_MAY_ONEXEC)) {
77462306a36Sopenharmony_ci		info = "no change_onexec valid for executable";
77562306a36Sopenharmony_ci		goto audit;
77662306a36Sopenharmony_ci	}
77762306a36Sopenharmony_ci	/* test if this exec can be paired with change_profile onexec.
77862306a36Sopenharmony_ci	 * onexec permission is linked to exec with a standard pairing
77962306a36Sopenharmony_ci	 * exec\0change_profile
78062306a36Sopenharmony_ci	 */
78162306a36Sopenharmony_ci	state = aa_dfa_null_transition(rules->file.dfa, state);
78262306a36Sopenharmony_ci	error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC,
78362306a36Sopenharmony_ci				     state, &perms);
78462306a36Sopenharmony_ci	if (error) {
78562306a36Sopenharmony_ci		perms.allow &= ~AA_MAY_ONEXEC;
78662306a36Sopenharmony_ci		goto audit;
78762306a36Sopenharmony_ci	}
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	if (!(perms.xindex & AA_X_UNSAFE)) {
79062306a36Sopenharmony_ci		if (DEBUG_ON) {
79162306a36Sopenharmony_ci			dbg_printk("apparmor: scrubbing environment "
79262306a36Sopenharmony_ci				   "variables for %s label=", xname);
79362306a36Sopenharmony_ci			aa_label_printk(onexec, GFP_KERNEL);
79462306a36Sopenharmony_ci			dbg_printk("\n");
79562306a36Sopenharmony_ci		}
79662306a36Sopenharmony_ci		*secure_exec = true;
79762306a36Sopenharmony_ci	}
79862306a36Sopenharmony_ci
79962306a36Sopenharmony_ciaudit:
80062306a36Sopenharmony_ci	return aa_audit_file(subj_cred, profile, &perms, OP_EXEC,
80162306a36Sopenharmony_ci			     AA_MAY_ONEXEC, xname,
80262306a36Sopenharmony_ci			     NULL, onexec, cond->uid, info, error);
80362306a36Sopenharmony_ci}
80462306a36Sopenharmony_ci
80562306a36Sopenharmony_ci/* ensure none ns domain transitions are correctly applied with onexec */
80662306a36Sopenharmony_ci
80762306a36Sopenharmony_cistatic struct aa_label *handle_onexec(const struct cred *subj_cred,
80862306a36Sopenharmony_ci				      struct aa_label *label,
80962306a36Sopenharmony_ci				      struct aa_label *onexec, bool stack,
81062306a36Sopenharmony_ci				      const struct linux_binprm *bprm,
81162306a36Sopenharmony_ci				      char *buffer, struct path_cond *cond,
81262306a36Sopenharmony_ci				      bool *unsafe)
81362306a36Sopenharmony_ci{
81462306a36Sopenharmony_ci	struct aa_profile *profile;
81562306a36Sopenharmony_ci	struct aa_label *new;
81662306a36Sopenharmony_ci	int error;
81762306a36Sopenharmony_ci
81862306a36Sopenharmony_ci	AA_BUG(!label);
81962306a36Sopenharmony_ci	AA_BUG(!onexec);
82062306a36Sopenharmony_ci	AA_BUG(!bprm);
82162306a36Sopenharmony_ci	AA_BUG(!buffer);
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_ci	if (!stack) {
82462306a36Sopenharmony_ci		error = fn_for_each_in_ns(label, profile,
82562306a36Sopenharmony_ci				profile_onexec(subj_cred, profile, onexec, stack,
82662306a36Sopenharmony_ci					       bprm, buffer, cond, unsafe));
82762306a36Sopenharmony_ci		if (error)
82862306a36Sopenharmony_ci			return ERR_PTR(error);
82962306a36Sopenharmony_ci		new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
83062306a36Sopenharmony_ci				aa_get_newest_label(onexec),
83162306a36Sopenharmony_ci				profile_transition(subj_cred, profile, bprm,
83262306a36Sopenharmony_ci						   buffer,
83362306a36Sopenharmony_ci						   cond, unsafe));
83462306a36Sopenharmony_ci
83562306a36Sopenharmony_ci	} else {
83662306a36Sopenharmony_ci		/* TODO: determine how much we want to loosen this */
83762306a36Sopenharmony_ci		error = fn_for_each_in_ns(label, profile,
83862306a36Sopenharmony_ci				profile_onexec(subj_cred, profile, onexec, stack, bprm,
83962306a36Sopenharmony_ci					       buffer, cond, unsafe));
84062306a36Sopenharmony_ci		if (error)
84162306a36Sopenharmony_ci			return ERR_PTR(error);
84262306a36Sopenharmony_ci		new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
84362306a36Sopenharmony_ci				aa_label_merge(&profile->label, onexec,
84462306a36Sopenharmony_ci					       GFP_KERNEL),
84562306a36Sopenharmony_ci				profile_transition(subj_cred, profile, bprm,
84662306a36Sopenharmony_ci						   buffer,
84762306a36Sopenharmony_ci						   cond, unsafe));
84862306a36Sopenharmony_ci	}
84962306a36Sopenharmony_ci
85062306a36Sopenharmony_ci	if (new)
85162306a36Sopenharmony_ci		return new;
85262306a36Sopenharmony_ci
85362306a36Sopenharmony_ci	/* TODO: get rid of GLOBAL_ROOT_UID */
85462306a36Sopenharmony_ci	error = fn_for_each_in_ns(label, profile,
85562306a36Sopenharmony_ci			aa_audit_file(subj_cred, profile, &nullperms,
85662306a36Sopenharmony_ci				      OP_CHANGE_ONEXEC,
85762306a36Sopenharmony_ci				      AA_MAY_ONEXEC, bprm->filename, NULL,
85862306a36Sopenharmony_ci				      onexec, GLOBAL_ROOT_UID,
85962306a36Sopenharmony_ci				      "failed to build target label", -ENOMEM));
86062306a36Sopenharmony_ci	return ERR_PTR(error);
86162306a36Sopenharmony_ci}
86262306a36Sopenharmony_ci
86362306a36Sopenharmony_ci/**
86462306a36Sopenharmony_ci * apparmor_bprm_creds_for_exec - Update the new creds on the bprm struct
86562306a36Sopenharmony_ci * @bprm: binprm for the exec  (NOT NULL)
86662306a36Sopenharmony_ci *
86762306a36Sopenharmony_ci * Returns: %0 or error on failure
86862306a36Sopenharmony_ci *
86962306a36Sopenharmony_ci * TODO: once the other paths are done see if we can't refactor into a fn
87062306a36Sopenharmony_ci */
87162306a36Sopenharmony_ciint apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
87262306a36Sopenharmony_ci{
87362306a36Sopenharmony_ci	struct aa_task_ctx *ctx;
87462306a36Sopenharmony_ci	struct aa_label *label, *new = NULL;
87562306a36Sopenharmony_ci	const struct cred *subj_cred;
87662306a36Sopenharmony_ci	struct aa_profile *profile;
87762306a36Sopenharmony_ci	char *buffer = NULL;
87862306a36Sopenharmony_ci	const char *info = NULL;
87962306a36Sopenharmony_ci	int error = 0;
88062306a36Sopenharmony_ci	bool unsafe = false;
88162306a36Sopenharmony_ci	vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(bprm->file),
88262306a36Sopenharmony_ci					    file_inode(bprm->file));
88362306a36Sopenharmony_ci	struct path_cond cond = {
88462306a36Sopenharmony_ci		vfsuid_into_kuid(vfsuid),
88562306a36Sopenharmony_ci		file_inode(bprm->file)->i_mode
88662306a36Sopenharmony_ci	};
88762306a36Sopenharmony_ci
88862306a36Sopenharmony_ci	subj_cred = current_cred();
88962306a36Sopenharmony_ci	ctx = task_ctx(current);
89062306a36Sopenharmony_ci	AA_BUG(!cred_label(bprm->cred));
89162306a36Sopenharmony_ci	AA_BUG(!ctx);
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_ci	label = aa_get_newest_label(cred_label(bprm->cred));
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_ci	/*
89662306a36Sopenharmony_ci	 * Detect no new privs being set, and store the label it
89762306a36Sopenharmony_ci	 * occurred under. Ideally this would happen when nnp
89862306a36Sopenharmony_ci	 * is set but there isn't a good way to do that yet.
89962306a36Sopenharmony_ci	 *
90062306a36Sopenharmony_ci	 * Testing for unconfined must be done before the subset test
90162306a36Sopenharmony_ci	 */
90262306a36Sopenharmony_ci	if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && !unconfined(label) &&
90362306a36Sopenharmony_ci	    !ctx->nnp)
90462306a36Sopenharmony_ci		ctx->nnp = aa_get_label(label);
90562306a36Sopenharmony_ci
90662306a36Sopenharmony_ci	/* buffer freed below, name is pointer into buffer */
90762306a36Sopenharmony_ci	buffer = aa_get_buffer(false);
90862306a36Sopenharmony_ci	if (!buffer) {
90962306a36Sopenharmony_ci		error = -ENOMEM;
91062306a36Sopenharmony_ci		goto done;
91162306a36Sopenharmony_ci	}
91262306a36Sopenharmony_ci
91362306a36Sopenharmony_ci	/* Test for onexec first as onexec override other x transitions. */
91462306a36Sopenharmony_ci	if (ctx->onexec)
91562306a36Sopenharmony_ci		new = handle_onexec(subj_cred, label, ctx->onexec, ctx->token,
91662306a36Sopenharmony_ci				    bprm, buffer, &cond, &unsafe);
91762306a36Sopenharmony_ci	else
91862306a36Sopenharmony_ci		new = fn_label_build(label, profile, GFP_KERNEL,
91962306a36Sopenharmony_ci				profile_transition(subj_cred, profile, bprm,
92062306a36Sopenharmony_ci						   buffer,
92162306a36Sopenharmony_ci						   &cond, &unsafe));
92262306a36Sopenharmony_ci
92362306a36Sopenharmony_ci	AA_BUG(!new);
92462306a36Sopenharmony_ci	if (IS_ERR(new)) {
92562306a36Sopenharmony_ci		error = PTR_ERR(new);
92662306a36Sopenharmony_ci		goto done;
92762306a36Sopenharmony_ci	} else if (!new) {
92862306a36Sopenharmony_ci		error = -ENOMEM;
92962306a36Sopenharmony_ci		goto done;
93062306a36Sopenharmony_ci	}
93162306a36Sopenharmony_ci
93262306a36Sopenharmony_ci	/* Policy has specified a domain transitions. If no_new_privs and
93362306a36Sopenharmony_ci	 * confined ensure the transition is to confinement that is subset
93462306a36Sopenharmony_ci	 * of the confinement when the task entered no new privs.
93562306a36Sopenharmony_ci	 *
93662306a36Sopenharmony_ci	 * NOTE: Domain transitions from unconfined and to stacked
93762306a36Sopenharmony_ci	 * subsets are allowed even when no_new_privs is set because this
93862306a36Sopenharmony_ci	 * aways results in a further reduction of permissions.
93962306a36Sopenharmony_ci	 */
94062306a36Sopenharmony_ci	if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) &&
94162306a36Sopenharmony_ci	    !unconfined(label) &&
94262306a36Sopenharmony_ci	    !aa_label_is_unconfined_subset(new, ctx->nnp)) {
94362306a36Sopenharmony_ci		error = -EPERM;
94462306a36Sopenharmony_ci		info = "no new privs";
94562306a36Sopenharmony_ci		goto audit;
94662306a36Sopenharmony_ci	}
94762306a36Sopenharmony_ci
94862306a36Sopenharmony_ci	if (bprm->unsafe & LSM_UNSAFE_SHARE) {
94962306a36Sopenharmony_ci		/* FIXME: currently don't mediate shared state */
95062306a36Sopenharmony_ci		;
95162306a36Sopenharmony_ci	}
95262306a36Sopenharmony_ci
95362306a36Sopenharmony_ci	if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) {
95462306a36Sopenharmony_ci		/* TODO: test needs to be profile of label to new */
95562306a36Sopenharmony_ci		error = may_change_ptraced_domain(bprm->cred, new, &info);
95662306a36Sopenharmony_ci		if (error)
95762306a36Sopenharmony_ci			goto audit;
95862306a36Sopenharmony_ci	}
95962306a36Sopenharmony_ci
96062306a36Sopenharmony_ci	if (unsafe) {
96162306a36Sopenharmony_ci		if (DEBUG_ON) {
96262306a36Sopenharmony_ci			dbg_printk("scrubbing environment variables for %s "
96362306a36Sopenharmony_ci				   "label=", bprm->filename);
96462306a36Sopenharmony_ci			aa_label_printk(new, GFP_KERNEL);
96562306a36Sopenharmony_ci			dbg_printk("\n");
96662306a36Sopenharmony_ci		}
96762306a36Sopenharmony_ci		bprm->secureexec = 1;
96862306a36Sopenharmony_ci	}
96962306a36Sopenharmony_ci
97062306a36Sopenharmony_ci	if (label->proxy != new->proxy) {
97162306a36Sopenharmony_ci		/* when transitioning clear unsafe personality bits */
97262306a36Sopenharmony_ci		if (DEBUG_ON) {
97362306a36Sopenharmony_ci			dbg_printk("apparmor: clearing unsafe personality "
97462306a36Sopenharmony_ci				   "bits. %s label=", bprm->filename);
97562306a36Sopenharmony_ci			aa_label_printk(new, GFP_KERNEL);
97662306a36Sopenharmony_ci			dbg_printk("\n");
97762306a36Sopenharmony_ci		}
97862306a36Sopenharmony_ci		bprm->per_clear |= PER_CLEAR_ON_SETID;
97962306a36Sopenharmony_ci	}
98062306a36Sopenharmony_ci	aa_put_label(cred_label(bprm->cred));
98162306a36Sopenharmony_ci	/* transfer reference, released when cred is freed */
98262306a36Sopenharmony_ci	set_cred_label(bprm->cred, new);
98362306a36Sopenharmony_ci
98462306a36Sopenharmony_cidone:
98562306a36Sopenharmony_ci	aa_put_label(label);
98662306a36Sopenharmony_ci	aa_put_buffer(buffer);
98762306a36Sopenharmony_ci
98862306a36Sopenharmony_ci	return error;
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_ciaudit:
99162306a36Sopenharmony_ci	error = fn_for_each(label, profile,
99262306a36Sopenharmony_ci			aa_audit_file(current_cred(), profile, &nullperms,
99362306a36Sopenharmony_ci				      OP_EXEC, MAY_EXEC,
99462306a36Sopenharmony_ci				      bprm->filename, NULL, new,
99562306a36Sopenharmony_ci				      vfsuid_into_kuid(vfsuid), info, error));
99662306a36Sopenharmony_ci	aa_put_label(new);
99762306a36Sopenharmony_ci	goto done;
99862306a36Sopenharmony_ci}
99962306a36Sopenharmony_ci
100062306a36Sopenharmony_ci/*
100162306a36Sopenharmony_ci * Functions for self directed profile change
100262306a36Sopenharmony_ci */
100362306a36Sopenharmony_ci
100462306a36Sopenharmony_ci
100562306a36Sopenharmony_ci/* helper fn for change_hat
100662306a36Sopenharmony_ci *
100762306a36Sopenharmony_ci * Returns: label for hat transition OR ERR_PTR.  Does NOT return NULL
100862306a36Sopenharmony_ci */
100962306a36Sopenharmony_cistatic struct aa_label *build_change_hat(const struct cred *subj_cred,
101062306a36Sopenharmony_ci					 struct aa_profile *profile,
101162306a36Sopenharmony_ci					 const char *name, bool sibling)
101262306a36Sopenharmony_ci{
101362306a36Sopenharmony_ci	struct aa_profile *root, *hat = NULL;
101462306a36Sopenharmony_ci	const char *info = NULL;
101562306a36Sopenharmony_ci	int error = 0;
101662306a36Sopenharmony_ci
101762306a36Sopenharmony_ci	if (sibling && PROFILE_IS_HAT(profile)) {
101862306a36Sopenharmony_ci		root = aa_get_profile_rcu(&profile->parent);
101962306a36Sopenharmony_ci	} else if (!sibling && !PROFILE_IS_HAT(profile)) {
102062306a36Sopenharmony_ci		root = aa_get_profile(profile);
102162306a36Sopenharmony_ci	} else {
102262306a36Sopenharmony_ci		info = "conflicting target types";
102362306a36Sopenharmony_ci		error = -EPERM;
102462306a36Sopenharmony_ci		goto audit;
102562306a36Sopenharmony_ci	}
102662306a36Sopenharmony_ci
102762306a36Sopenharmony_ci	hat = aa_find_child(root, name);
102862306a36Sopenharmony_ci	if (!hat) {
102962306a36Sopenharmony_ci		error = -ENOENT;
103062306a36Sopenharmony_ci		if (COMPLAIN_MODE(profile)) {
103162306a36Sopenharmony_ci			hat = aa_new_learning_profile(profile, true, name,
103262306a36Sopenharmony_ci						      GFP_KERNEL);
103362306a36Sopenharmony_ci			if (!hat) {
103462306a36Sopenharmony_ci				info = "failed null profile create";
103562306a36Sopenharmony_ci				error = -ENOMEM;
103662306a36Sopenharmony_ci			}
103762306a36Sopenharmony_ci		}
103862306a36Sopenharmony_ci	}
103962306a36Sopenharmony_ci	aa_put_profile(root);
104062306a36Sopenharmony_ci
104162306a36Sopenharmony_ciaudit:
104262306a36Sopenharmony_ci	aa_audit_file(subj_cred, profile, &nullperms, OP_CHANGE_HAT,
104362306a36Sopenharmony_ci		      AA_MAY_CHANGEHAT,
104462306a36Sopenharmony_ci		      name, hat ? hat->base.hname : NULL,
104562306a36Sopenharmony_ci		      hat ? &hat->label : NULL, GLOBAL_ROOT_UID, info,
104662306a36Sopenharmony_ci		      error);
104762306a36Sopenharmony_ci	if (!hat || (error && error != -ENOENT))
104862306a36Sopenharmony_ci		return ERR_PTR(error);
104962306a36Sopenharmony_ci	/* if hat && error - complain mode, already audited and we adjust for
105062306a36Sopenharmony_ci	 * complain mode allow by returning hat->label
105162306a36Sopenharmony_ci	 */
105262306a36Sopenharmony_ci	return &hat->label;
105362306a36Sopenharmony_ci}
105462306a36Sopenharmony_ci
105562306a36Sopenharmony_ci/* helper fn for changing into a hat
105662306a36Sopenharmony_ci *
105762306a36Sopenharmony_ci * Returns: label for hat transition or ERR_PTR. Does not return NULL
105862306a36Sopenharmony_ci */
105962306a36Sopenharmony_cistatic struct aa_label *change_hat(const struct cred *subj_cred,
106062306a36Sopenharmony_ci				   struct aa_label *label, const char *hats[],
106162306a36Sopenharmony_ci				   int count, int flags)
106262306a36Sopenharmony_ci{
106362306a36Sopenharmony_ci	struct aa_profile *profile, *root, *hat = NULL;
106462306a36Sopenharmony_ci	struct aa_label *new;
106562306a36Sopenharmony_ci	struct label_it it;
106662306a36Sopenharmony_ci	bool sibling = false;
106762306a36Sopenharmony_ci	const char *name, *info = NULL;
106862306a36Sopenharmony_ci	int i, error;
106962306a36Sopenharmony_ci
107062306a36Sopenharmony_ci	AA_BUG(!label);
107162306a36Sopenharmony_ci	AA_BUG(!hats);
107262306a36Sopenharmony_ci	AA_BUG(count < 1);
107362306a36Sopenharmony_ci
107462306a36Sopenharmony_ci	if (PROFILE_IS_HAT(labels_profile(label)))
107562306a36Sopenharmony_ci		sibling = true;
107662306a36Sopenharmony_ci
107762306a36Sopenharmony_ci	/*find first matching hat */
107862306a36Sopenharmony_ci	for (i = 0; i < count && !hat; i++) {
107962306a36Sopenharmony_ci		name = hats[i];
108062306a36Sopenharmony_ci		label_for_each_in_ns(it, labels_ns(label), label, profile) {
108162306a36Sopenharmony_ci			if (sibling && PROFILE_IS_HAT(profile)) {
108262306a36Sopenharmony_ci				root = aa_get_profile_rcu(&profile->parent);
108362306a36Sopenharmony_ci			} else if (!sibling && !PROFILE_IS_HAT(profile)) {
108462306a36Sopenharmony_ci				root = aa_get_profile(profile);
108562306a36Sopenharmony_ci			} else {	/* conflicting change type */
108662306a36Sopenharmony_ci				info = "conflicting targets types";
108762306a36Sopenharmony_ci				error = -EPERM;
108862306a36Sopenharmony_ci				goto fail;
108962306a36Sopenharmony_ci			}
109062306a36Sopenharmony_ci			hat = aa_find_child(root, name);
109162306a36Sopenharmony_ci			aa_put_profile(root);
109262306a36Sopenharmony_ci			if (!hat) {
109362306a36Sopenharmony_ci				if (!COMPLAIN_MODE(profile))
109462306a36Sopenharmony_ci					goto outer_continue;
109562306a36Sopenharmony_ci				/* complain mode succeed as if hat */
109662306a36Sopenharmony_ci			} else if (!PROFILE_IS_HAT(hat)) {
109762306a36Sopenharmony_ci				info = "target not hat";
109862306a36Sopenharmony_ci				error = -EPERM;
109962306a36Sopenharmony_ci				aa_put_profile(hat);
110062306a36Sopenharmony_ci				goto fail;
110162306a36Sopenharmony_ci			}
110262306a36Sopenharmony_ci			aa_put_profile(hat);
110362306a36Sopenharmony_ci		}
110462306a36Sopenharmony_ci		/* found a hat for all profiles in ns */
110562306a36Sopenharmony_ci		goto build;
110662306a36Sopenharmony_ciouter_continue:
110762306a36Sopenharmony_ci	;
110862306a36Sopenharmony_ci	}
110962306a36Sopenharmony_ci	/* no hats that match, find appropriate error
111062306a36Sopenharmony_ci	 *
111162306a36Sopenharmony_ci	 * In complain mode audit of the failure is based off of the first
111262306a36Sopenharmony_ci	 * hat supplied.  This is done due how userspace interacts with
111362306a36Sopenharmony_ci	 * change_hat.
111462306a36Sopenharmony_ci	 */
111562306a36Sopenharmony_ci	name = NULL;
111662306a36Sopenharmony_ci	label_for_each_in_ns(it, labels_ns(label), label, profile) {
111762306a36Sopenharmony_ci		if (!list_empty(&profile->base.profiles)) {
111862306a36Sopenharmony_ci			info = "hat not found";
111962306a36Sopenharmony_ci			error = -ENOENT;
112062306a36Sopenharmony_ci			goto fail;
112162306a36Sopenharmony_ci		}
112262306a36Sopenharmony_ci	}
112362306a36Sopenharmony_ci	info = "no hats defined";
112462306a36Sopenharmony_ci	error = -ECHILD;
112562306a36Sopenharmony_ci
112662306a36Sopenharmony_cifail:
112762306a36Sopenharmony_ci	label_for_each_in_ns(it, labels_ns(label), label, profile) {
112862306a36Sopenharmony_ci		/*
112962306a36Sopenharmony_ci		 * no target as it has failed to be found or built
113062306a36Sopenharmony_ci		 *
113162306a36Sopenharmony_ci		 * change_hat uses probing and should not log failures
113262306a36Sopenharmony_ci		 * related to missing hats
113362306a36Sopenharmony_ci		 */
113462306a36Sopenharmony_ci		/* TODO: get rid of GLOBAL_ROOT_UID */
113562306a36Sopenharmony_ci		if (count > 1 || COMPLAIN_MODE(profile)) {
113662306a36Sopenharmony_ci			aa_audit_file(subj_cred, profile, &nullperms,
113762306a36Sopenharmony_ci				      OP_CHANGE_HAT,
113862306a36Sopenharmony_ci				      AA_MAY_CHANGEHAT, name, NULL, NULL,
113962306a36Sopenharmony_ci				      GLOBAL_ROOT_UID, info, error);
114062306a36Sopenharmony_ci		}
114162306a36Sopenharmony_ci	}
114262306a36Sopenharmony_ci	return ERR_PTR(error);
114362306a36Sopenharmony_ci
114462306a36Sopenharmony_cibuild:
114562306a36Sopenharmony_ci	new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
114662306a36Sopenharmony_ci				   build_change_hat(subj_cred, profile, name,
114762306a36Sopenharmony_ci						    sibling),
114862306a36Sopenharmony_ci				   aa_get_label(&profile->label));
114962306a36Sopenharmony_ci	if (!new) {
115062306a36Sopenharmony_ci		info = "label build failed";
115162306a36Sopenharmony_ci		error = -ENOMEM;
115262306a36Sopenharmony_ci		goto fail;
115362306a36Sopenharmony_ci	} /* else if (IS_ERR) build_change_hat has logged error so return new */
115462306a36Sopenharmony_ci
115562306a36Sopenharmony_ci	return new;
115662306a36Sopenharmony_ci}
115762306a36Sopenharmony_ci
115862306a36Sopenharmony_ci/**
115962306a36Sopenharmony_ci * aa_change_hat - change hat to/from subprofile
116062306a36Sopenharmony_ci * @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0)
116162306a36Sopenharmony_ci * @count: number of hat names in @hats
116262306a36Sopenharmony_ci * @token: magic value to validate the hat change
116362306a36Sopenharmony_ci * @flags: flags affecting behavior of the change
116462306a36Sopenharmony_ci *
116562306a36Sopenharmony_ci * Returns %0 on success, error otherwise.
116662306a36Sopenharmony_ci *
116762306a36Sopenharmony_ci * Change to the first profile specified in @hats that exists, and store
116862306a36Sopenharmony_ci * the @hat_magic in the current task context.  If the count == 0 and the
116962306a36Sopenharmony_ci * @token matches that stored in the current task context, return to the
117062306a36Sopenharmony_ci * top level profile.
117162306a36Sopenharmony_ci *
117262306a36Sopenharmony_ci * change_hat only applies to profiles in the current ns, and each profile
117362306a36Sopenharmony_ci * in the ns must make the same transition otherwise change_hat will fail.
117462306a36Sopenharmony_ci */
117562306a36Sopenharmony_ciint aa_change_hat(const char *hats[], int count, u64 token, int flags)
117662306a36Sopenharmony_ci{
117762306a36Sopenharmony_ci	const struct cred *subj_cred;
117862306a36Sopenharmony_ci	struct aa_task_ctx *ctx = task_ctx(current);
117962306a36Sopenharmony_ci	struct aa_label *label, *previous, *new = NULL, *target = NULL;
118062306a36Sopenharmony_ci	struct aa_profile *profile;
118162306a36Sopenharmony_ci	struct aa_perms perms = {};
118262306a36Sopenharmony_ci	const char *info = NULL;
118362306a36Sopenharmony_ci	int error = 0;
118462306a36Sopenharmony_ci
118562306a36Sopenharmony_ci	/* released below */
118662306a36Sopenharmony_ci	subj_cred = get_current_cred();
118762306a36Sopenharmony_ci	label = aa_get_newest_cred_label(subj_cred);
118862306a36Sopenharmony_ci	previous = aa_get_newest_label(ctx->previous);
118962306a36Sopenharmony_ci
119062306a36Sopenharmony_ci	/*
119162306a36Sopenharmony_ci	 * Detect no new privs being set, and store the label it
119262306a36Sopenharmony_ci	 * occurred under. Ideally this would happen when nnp
119362306a36Sopenharmony_ci	 * is set but there isn't a good way to do that yet.
119462306a36Sopenharmony_ci	 *
119562306a36Sopenharmony_ci	 * Testing for unconfined must be done before the subset test
119662306a36Sopenharmony_ci	 */
119762306a36Sopenharmony_ci	if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp)
119862306a36Sopenharmony_ci		ctx->nnp = aa_get_label(label);
119962306a36Sopenharmony_ci
120062306a36Sopenharmony_ci	if (unconfined(label)) {
120162306a36Sopenharmony_ci		info = "unconfined can not change_hat";
120262306a36Sopenharmony_ci		error = -EPERM;
120362306a36Sopenharmony_ci		goto fail;
120462306a36Sopenharmony_ci	}
120562306a36Sopenharmony_ci
120662306a36Sopenharmony_ci	if (count) {
120762306a36Sopenharmony_ci		new = change_hat(subj_cred, label, hats, count, flags);
120862306a36Sopenharmony_ci		AA_BUG(!new);
120962306a36Sopenharmony_ci		if (IS_ERR(new)) {
121062306a36Sopenharmony_ci			error = PTR_ERR(new);
121162306a36Sopenharmony_ci			new = NULL;
121262306a36Sopenharmony_ci			/* already audited */
121362306a36Sopenharmony_ci			goto out;
121462306a36Sopenharmony_ci		}
121562306a36Sopenharmony_ci
121662306a36Sopenharmony_ci		/* target cred is the same as current except new label */
121762306a36Sopenharmony_ci		error = may_change_ptraced_domain(subj_cred, new, &info);
121862306a36Sopenharmony_ci		if (error)
121962306a36Sopenharmony_ci			goto fail;
122062306a36Sopenharmony_ci
122162306a36Sopenharmony_ci		/*
122262306a36Sopenharmony_ci		 * no new privs prevents domain transitions that would
122362306a36Sopenharmony_ci		 * reduce restrictions.
122462306a36Sopenharmony_ci		 */
122562306a36Sopenharmony_ci		if (task_no_new_privs(current) && !unconfined(label) &&
122662306a36Sopenharmony_ci		    !aa_label_is_unconfined_subset(new, ctx->nnp)) {
122762306a36Sopenharmony_ci			/* not an apparmor denial per se, so don't log it */
122862306a36Sopenharmony_ci			AA_DEBUG("no_new_privs - change_hat denied");
122962306a36Sopenharmony_ci			error = -EPERM;
123062306a36Sopenharmony_ci			goto out;
123162306a36Sopenharmony_ci		}
123262306a36Sopenharmony_ci
123362306a36Sopenharmony_ci		if (flags & AA_CHANGE_TEST)
123462306a36Sopenharmony_ci			goto out;
123562306a36Sopenharmony_ci
123662306a36Sopenharmony_ci		target = new;
123762306a36Sopenharmony_ci		error = aa_set_current_hat(new, token);
123862306a36Sopenharmony_ci		if (error == -EACCES)
123962306a36Sopenharmony_ci			/* kill task in case of brute force attacks */
124062306a36Sopenharmony_ci			goto kill;
124162306a36Sopenharmony_ci	} else if (previous && !(flags & AA_CHANGE_TEST)) {
124262306a36Sopenharmony_ci		/*
124362306a36Sopenharmony_ci		 * no new privs prevents domain transitions that would
124462306a36Sopenharmony_ci		 * reduce restrictions.
124562306a36Sopenharmony_ci		 */
124662306a36Sopenharmony_ci		if (task_no_new_privs(current) && !unconfined(label) &&
124762306a36Sopenharmony_ci		    !aa_label_is_unconfined_subset(previous, ctx->nnp)) {
124862306a36Sopenharmony_ci			/* not an apparmor denial per se, so don't log it */
124962306a36Sopenharmony_ci			AA_DEBUG("no_new_privs - change_hat denied");
125062306a36Sopenharmony_ci			error = -EPERM;
125162306a36Sopenharmony_ci			goto out;
125262306a36Sopenharmony_ci		}
125362306a36Sopenharmony_ci
125462306a36Sopenharmony_ci		/* Return to saved label.  Kill task if restore fails
125562306a36Sopenharmony_ci		 * to avoid brute force attacks
125662306a36Sopenharmony_ci		 */
125762306a36Sopenharmony_ci		target = previous;
125862306a36Sopenharmony_ci		error = aa_restore_previous_label(token);
125962306a36Sopenharmony_ci		if (error) {
126062306a36Sopenharmony_ci			if (error == -EACCES)
126162306a36Sopenharmony_ci				goto kill;
126262306a36Sopenharmony_ci			goto fail;
126362306a36Sopenharmony_ci		}
126462306a36Sopenharmony_ci	} /* else ignore @flags && restores when there is no saved profile */
126562306a36Sopenharmony_ci
126662306a36Sopenharmony_ciout:
126762306a36Sopenharmony_ci	aa_put_label(new);
126862306a36Sopenharmony_ci	aa_put_label(previous);
126962306a36Sopenharmony_ci	aa_put_label(label);
127062306a36Sopenharmony_ci	put_cred(subj_cred);
127162306a36Sopenharmony_ci
127262306a36Sopenharmony_ci	return error;
127362306a36Sopenharmony_ci
127462306a36Sopenharmony_cikill:
127562306a36Sopenharmony_ci	info = "failed token match";
127662306a36Sopenharmony_ci	perms.kill = AA_MAY_CHANGEHAT;
127762306a36Sopenharmony_ci
127862306a36Sopenharmony_cifail:
127962306a36Sopenharmony_ci	fn_for_each_in_ns(label, profile,
128062306a36Sopenharmony_ci		aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT,
128162306a36Sopenharmony_ci			      AA_MAY_CHANGEHAT, NULL, NULL, target,
128262306a36Sopenharmony_ci			      GLOBAL_ROOT_UID, info, error));
128362306a36Sopenharmony_ci
128462306a36Sopenharmony_ci	goto out;
128562306a36Sopenharmony_ci}
128662306a36Sopenharmony_ci
128762306a36Sopenharmony_ci
128862306a36Sopenharmony_cistatic int change_profile_perms_wrapper(const char *op, const char *name,
128962306a36Sopenharmony_ci					const struct cred *subj_cred,
129062306a36Sopenharmony_ci					struct aa_profile *profile,
129162306a36Sopenharmony_ci					struct aa_label *target, bool stack,
129262306a36Sopenharmony_ci					u32 request, struct aa_perms *perms)
129362306a36Sopenharmony_ci{
129462306a36Sopenharmony_ci	struct aa_ruleset *rules = list_first_entry(&profile->rules,
129562306a36Sopenharmony_ci						    typeof(*rules), list);
129662306a36Sopenharmony_ci	const char *info = NULL;
129762306a36Sopenharmony_ci	int error = 0;
129862306a36Sopenharmony_ci
129962306a36Sopenharmony_ci	if (!error)
130062306a36Sopenharmony_ci		error = change_profile_perms(profile, target, stack, request,
130162306a36Sopenharmony_ci					     rules->file.start[AA_CLASS_FILE],
130262306a36Sopenharmony_ci					     perms);
130362306a36Sopenharmony_ci	if (error)
130462306a36Sopenharmony_ci		error = aa_audit_file(subj_cred, profile, perms, op, request,
130562306a36Sopenharmony_ci				      name,
130662306a36Sopenharmony_ci				      NULL, target, GLOBAL_ROOT_UID, info,
130762306a36Sopenharmony_ci				      error);
130862306a36Sopenharmony_ci
130962306a36Sopenharmony_ci	return error;
131062306a36Sopenharmony_ci}
131162306a36Sopenharmony_ci
131262306a36Sopenharmony_ci/**
131362306a36Sopenharmony_ci * aa_change_profile - perform a one-way profile transition
131462306a36Sopenharmony_ci * @fqname: name of profile may include namespace (NOT NULL)
131562306a36Sopenharmony_ci * @flags: flags affecting change behavior
131662306a36Sopenharmony_ci *
131762306a36Sopenharmony_ci * Change to new profile @name.  Unlike with hats, there is no way
131862306a36Sopenharmony_ci * to change back.  If @name isn't specified the current profile name is
131962306a36Sopenharmony_ci * used.
132062306a36Sopenharmony_ci * If @onexec then the transition is delayed until
132162306a36Sopenharmony_ci * the next exec.
132262306a36Sopenharmony_ci *
132362306a36Sopenharmony_ci * Returns %0 on success, error otherwise.
132462306a36Sopenharmony_ci */
132562306a36Sopenharmony_ciint aa_change_profile(const char *fqname, int flags)
132662306a36Sopenharmony_ci{
132762306a36Sopenharmony_ci	struct aa_label *label, *new = NULL, *target = NULL;
132862306a36Sopenharmony_ci	struct aa_profile *profile;
132962306a36Sopenharmony_ci	struct aa_perms perms = {};
133062306a36Sopenharmony_ci	const char *info = NULL;
133162306a36Sopenharmony_ci	const char *auditname = fqname;		/* retain leading & if stack */
133262306a36Sopenharmony_ci	bool stack = flags & AA_CHANGE_STACK;
133362306a36Sopenharmony_ci	struct aa_task_ctx *ctx = task_ctx(current);
133462306a36Sopenharmony_ci	const struct cred *subj_cred = get_current_cred();
133562306a36Sopenharmony_ci	int error = 0;
133662306a36Sopenharmony_ci	char *op;
133762306a36Sopenharmony_ci	u32 request;
133862306a36Sopenharmony_ci
133962306a36Sopenharmony_ci	label = aa_get_current_label();
134062306a36Sopenharmony_ci
134162306a36Sopenharmony_ci	/*
134262306a36Sopenharmony_ci	 * Detect no new privs being set, and store the label it
134362306a36Sopenharmony_ci	 * occurred under. Ideally this would happen when nnp
134462306a36Sopenharmony_ci	 * is set but there isn't a good way to do that yet.
134562306a36Sopenharmony_ci	 *
134662306a36Sopenharmony_ci	 * Testing for unconfined must be done before the subset test
134762306a36Sopenharmony_ci	 */
134862306a36Sopenharmony_ci	if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp)
134962306a36Sopenharmony_ci		ctx->nnp = aa_get_label(label);
135062306a36Sopenharmony_ci
135162306a36Sopenharmony_ci	if (!fqname || !*fqname) {
135262306a36Sopenharmony_ci		aa_put_label(label);
135362306a36Sopenharmony_ci		AA_DEBUG("no profile name");
135462306a36Sopenharmony_ci		return -EINVAL;
135562306a36Sopenharmony_ci	}
135662306a36Sopenharmony_ci
135762306a36Sopenharmony_ci	if (flags & AA_CHANGE_ONEXEC) {
135862306a36Sopenharmony_ci		request = AA_MAY_ONEXEC;
135962306a36Sopenharmony_ci		if (stack)
136062306a36Sopenharmony_ci			op = OP_STACK_ONEXEC;
136162306a36Sopenharmony_ci		else
136262306a36Sopenharmony_ci			op = OP_CHANGE_ONEXEC;
136362306a36Sopenharmony_ci	} else {
136462306a36Sopenharmony_ci		request = AA_MAY_CHANGE_PROFILE;
136562306a36Sopenharmony_ci		if (stack)
136662306a36Sopenharmony_ci			op = OP_STACK;
136762306a36Sopenharmony_ci		else
136862306a36Sopenharmony_ci			op = OP_CHANGE_PROFILE;
136962306a36Sopenharmony_ci	}
137062306a36Sopenharmony_ci
137162306a36Sopenharmony_ci	if (*fqname == '&') {
137262306a36Sopenharmony_ci		stack = true;
137362306a36Sopenharmony_ci		/* don't have label_parse() do stacking */
137462306a36Sopenharmony_ci		fqname++;
137562306a36Sopenharmony_ci	}
137662306a36Sopenharmony_ci	target = aa_label_parse(label, fqname, GFP_KERNEL, true, false);
137762306a36Sopenharmony_ci	if (IS_ERR(target)) {
137862306a36Sopenharmony_ci		struct aa_profile *tprofile;
137962306a36Sopenharmony_ci
138062306a36Sopenharmony_ci		info = "label not found";
138162306a36Sopenharmony_ci		error = PTR_ERR(target);
138262306a36Sopenharmony_ci		target = NULL;
138362306a36Sopenharmony_ci		/*
138462306a36Sopenharmony_ci		 * TODO: fixme using labels_profile is not right - do profile
138562306a36Sopenharmony_ci		 * per complain profile
138662306a36Sopenharmony_ci		 */
138762306a36Sopenharmony_ci		if ((flags & AA_CHANGE_TEST) ||
138862306a36Sopenharmony_ci		    !COMPLAIN_MODE(labels_profile(label)))
138962306a36Sopenharmony_ci			goto audit;
139062306a36Sopenharmony_ci		/* released below */
139162306a36Sopenharmony_ci		tprofile = aa_new_learning_profile(labels_profile(label), false,
139262306a36Sopenharmony_ci						   fqname, GFP_KERNEL);
139362306a36Sopenharmony_ci		if (!tprofile) {
139462306a36Sopenharmony_ci			info = "failed null profile create";
139562306a36Sopenharmony_ci			error = -ENOMEM;
139662306a36Sopenharmony_ci			goto audit;
139762306a36Sopenharmony_ci		}
139862306a36Sopenharmony_ci		target = &tprofile->label;
139962306a36Sopenharmony_ci		goto check;
140062306a36Sopenharmony_ci	}
140162306a36Sopenharmony_ci
140262306a36Sopenharmony_ci	/*
140362306a36Sopenharmony_ci	 * self directed transitions only apply to current policy ns
140462306a36Sopenharmony_ci	 * TODO: currently requiring perms for stacking and straight change
140562306a36Sopenharmony_ci	 *       stacking doesn't strictly need this. Determine how much
140662306a36Sopenharmony_ci	 *       we want to loosen this restriction for stacking
140762306a36Sopenharmony_ci	 *
140862306a36Sopenharmony_ci	 * if (!stack) {
140962306a36Sopenharmony_ci	 */
141062306a36Sopenharmony_ci	error = fn_for_each_in_ns(label, profile,
141162306a36Sopenharmony_ci			change_profile_perms_wrapper(op, auditname,
141262306a36Sopenharmony_ci						     subj_cred,
141362306a36Sopenharmony_ci						     profile, target, stack,
141462306a36Sopenharmony_ci						     request, &perms));
141562306a36Sopenharmony_ci	if (error)
141662306a36Sopenharmony_ci		/* auditing done in change_profile_perms_wrapper */
141762306a36Sopenharmony_ci		goto out;
141862306a36Sopenharmony_ci
141962306a36Sopenharmony_ci	/* } */
142062306a36Sopenharmony_ci
142162306a36Sopenharmony_cicheck:
142262306a36Sopenharmony_ci	/* check if tracing task is allowed to trace target domain */
142362306a36Sopenharmony_ci	error = may_change_ptraced_domain(subj_cred, target, &info);
142462306a36Sopenharmony_ci	if (error && !fn_for_each_in_ns(label, profile,
142562306a36Sopenharmony_ci					COMPLAIN_MODE(profile)))
142662306a36Sopenharmony_ci		goto audit;
142762306a36Sopenharmony_ci
142862306a36Sopenharmony_ci	/* TODO: add permission check to allow this
142962306a36Sopenharmony_ci	 * if ((flags & AA_CHANGE_ONEXEC) && !current_is_single_threaded()) {
143062306a36Sopenharmony_ci	 *      info = "not a single threaded task";
143162306a36Sopenharmony_ci	 *      error = -EACCES;
143262306a36Sopenharmony_ci	 *      goto audit;
143362306a36Sopenharmony_ci	 * }
143462306a36Sopenharmony_ci	 */
143562306a36Sopenharmony_ci	if (flags & AA_CHANGE_TEST)
143662306a36Sopenharmony_ci		goto out;
143762306a36Sopenharmony_ci
143862306a36Sopenharmony_ci	/* stacking is always a subset, so only check the nonstack case */
143962306a36Sopenharmony_ci	if (!stack) {
144062306a36Sopenharmony_ci		new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
144162306a36Sopenharmony_ci					   aa_get_label(target),
144262306a36Sopenharmony_ci					   aa_get_label(&profile->label));
144362306a36Sopenharmony_ci		/*
144462306a36Sopenharmony_ci		 * no new privs prevents domain transitions that would
144562306a36Sopenharmony_ci		 * reduce restrictions.
144662306a36Sopenharmony_ci		 */
144762306a36Sopenharmony_ci		if (task_no_new_privs(current) && !unconfined(label) &&
144862306a36Sopenharmony_ci		    !aa_label_is_unconfined_subset(new, ctx->nnp)) {
144962306a36Sopenharmony_ci			/* not an apparmor denial per se, so don't log it */
145062306a36Sopenharmony_ci			AA_DEBUG("no_new_privs - change_hat denied");
145162306a36Sopenharmony_ci			error = -EPERM;
145262306a36Sopenharmony_ci			goto out;
145362306a36Sopenharmony_ci		}
145462306a36Sopenharmony_ci	}
145562306a36Sopenharmony_ci
145662306a36Sopenharmony_ci	if (!(flags & AA_CHANGE_ONEXEC)) {
145762306a36Sopenharmony_ci		/* only transition profiles in the current ns */
145862306a36Sopenharmony_ci		if (stack)
145962306a36Sopenharmony_ci			new = aa_label_merge(label, target, GFP_KERNEL);
146062306a36Sopenharmony_ci		if (IS_ERR_OR_NULL(new)) {
146162306a36Sopenharmony_ci			info = "failed to build target label";
146262306a36Sopenharmony_ci			if (!new)
146362306a36Sopenharmony_ci				error = -ENOMEM;
146462306a36Sopenharmony_ci			else
146562306a36Sopenharmony_ci				error = PTR_ERR(new);
146662306a36Sopenharmony_ci			new = NULL;
146762306a36Sopenharmony_ci			perms.allow = 0;
146862306a36Sopenharmony_ci			goto audit;
146962306a36Sopenharmony_ci		}
147062306a36Sopenharmony_ci		error = aa_replace_current_label(new);
147162306a36Sopenharmony_ci	} else {
147262306a36Sopenharmony_ci		if (new) {
147362306a36Sopenharmony_ci			aa_put_label(new);
147462306a36Sopenharmony_ci			new = NULL;
147562306a36Sopenharmony_ci		}
147662306a36Sopenharmony_ci
147762306a36Sopenharmony_ci		/* full transition will be built in exec path */
147862306a36Sopenharmony_ci		error = aa_set_current_onexec(target, stack);
147962306a36Sopenharmony_ci	}
148062306a36Sopenharmony_ci
148162306a36Sopenharmony_ciaudit:
148262306a36Sopenharmony_ci	error = fn_for_each_in_ns(label, profile,
148362306a36Sopenharmony_ci			aa_audit_file(subj_cred,
148462306a36Sopenharmony_ci				      profile, &perms, op, request, auditname,
148562306a36Sopenharmony_ci				      NULL, new ? new : target,
148662306a36Sopenharmony_ci				      GLOBAL_ROOT_UID, info, error));
148762306a36Sopenharmony_ci
148862306a36Sopenharmony_ciout:
148962306a36Sopenharmony_ci	aa_put_label(new);
149062306a36Sopenharmony_ci	aa_put_label(target);
149162306a36Sopenharmony_ci	aa_put_label(label);
149262306a36Sopenharmony_ci	put_cred(subj_cred);
149362306a36Sopenharmony_ci
149462306a36Sopenharmony_ci	return error;
149562306a36Sopenharmony_ci}
1496