1663 lines · 45.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * AppArmor security module * * This file contains AppArmor policy attachment and domain transitions * * Copyright (C) 2002-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. */ #include <linux/errno.h>#include <linux/fs.h>#include <linux/file.h>#include <linux/mount.h>#include <linux/mutex.h>#include <linux/syscalls.h>#include <linux/personality.h>#include <linux/xattr.h>#include <linux/user_namespace.h> #include "include/audit.h"#include "include/apparmorfs.h"#include "include/cred.h"#include "include/domain.h"#include "include/file.h"#include "include/ipc.h"#include "include/lib.h"#include "include/match.h"#include "include/path.h"#include "include/policy.h"#include "include/policy_ns.h" static const char * const CONFLICTING_ATTACH_STR = "conflicting profile attachments";static const char * const CONFLICTING_ATTACH_STR_IX =	"conflicting profile attachments - ix fallback";static const char * const CONFLICTING_ATTACH_STR_UX =	"conflicting profile attachments - ux fallback"; /** * may_change_ptraced_domain - check if can change profile on ptraced task * @to_cred: cred of task changing domain * @to_label: profile to change to  (NOT NULL) * @info: message if there is an error * * Check if current is ptraced and if so if the tracing task is allowed * to trace the new domain * * Returns: %0 or error if change not allowed */static int may_change_ptraced_domain(const struct cred *to_cred,				     struct aa_label *to_label,				     const char **info){	struct task_struct *tracer;	struct aa_label *tracerl = NULL;	const struct cred *tracer_cred = NULL; 	int error = 0; 	rcu_read_lock();	tracer = ptrace_parent(current);	if (tracer) {		/* released below */		tracerl = aa_get_task_label(tracer);		tracer_cred = get_task_cred(tracer);	}	/* not ptraced */	if (!tracer || unconfined(tracerl))		goto out; 	error = aa_may_ptrace(tracer_cred, tracerl, to_cred, to_label,			      PTRACE_MODE_ATTACH); out:	rcu_read_unlock();	aa_put_label(tracerl);	put_cred(tracer_cred); 	if (error)		*info = "ptrace prevents transition";	return error;} /**** TODO: dedup to aa_label_match - needs perm and dfa, merging * specifically this is an exact copy of aa_label_match except * aa_compute_perms is replaced with aa_compute_fperms * and policy->dfa with file->dfa ****//* match a profile and its associated ns component if needed * Assumes visibility test has already been done. * If a subns profile is not to be matched should be prescreened with * visibility test. */static inline aa_state_t match_component(const struct aa_profile *profile,					 const struct aa_profile *tp,					 bool stack, aa_state_t state){	struct aa_ruleset *rules = profile->label.rules[0];	const char *ns_name; 	if (stack)		state = aa_dfa_match(rules->file->dfa, state, "&");	if (profile->ns == tp->ns)		return aa_dfa_match(rules->file->dfa, state, tp->base.hname); 	/* try matching with namespace name and then profile */	ns_name = aa_ns_name(profile->ns, tp->ns, true);	state = aa_dfa_match_len(rules->file->dfa, state, ":", 1);	state = aa_dfa_match(rules->file->dfa, state, ns_name);	state = aa_dfa_match_len(rules->file->dfa, state, ":", 1);	return aa_dfa_match(rules->file->dfa, state, tp->base.hname);} /** * label_compound_match - find perms for full compound label * @profile: profile to find perms for * @label: label to check access permissions for * @stack: whether this is a stacking request * @state: state to start match in * @inview: whether to match labels in view or only in scope * @request: permissions to request * @perms: perms struct to set * * Returns: 0 on success else ERROR * * For the label A//&B//&C this does the perm match for A//&B//&C * @perms should be preinitialized with allperms OR a previous permission *        check to be stacked. */static int label_compound_match(const struct aa_profile *profile,				struct aa_label *label, bool stack,				aa_state_t state, bool inview, u32 request,				struct aa_perms *perms){	struct aa_ruleset *rules = profile->label.rules[0];	struct aa_profile *tp;	struct label_it i;	struct path_cond cond = { }; 	/* find first subcomponent that is in view and going to be interacted with */	label_for_each(i, label, tp) {		if (!aa_ns_visible(profile->ns, tp->ns, inview))			continue;		state = match_component(profile, tp, stack, state);		if (!state)			goto fail;		goto next;	} 	/* no component visible */	*perms = allperms;	return 0; next:	label_for_each_cont(i, label, tp) {		if (!aa_ns_visible(profile->ns, tp->ns, inview))			continue;		state = aa_dfa_match(rules->file->dfa, state, "//&");		state = match_component(profile, tp, false, state);		if (!state)			goto fail;	}	*perms = *(aa_lookup_condperms(current_fsuid(), rules->file, state,				       &cond));	aa_apply_modes_to_perms(profile, perms);	if ((perms->allow & request) != request)		return -EACCES; 	return 0; fail:	*perms = nullperms;	return -EACCES;} /** * label_components_match - find perms for all subcomponents of a label * @profile: profile to find perms for * @label: label to check access permissions for * @stack: whether this is a stacking request * @start: state to start match in * @inview: whether to match labels in view or only in scope * @request: permissions to request * @perms: an initialized perms struct to add accumulation to * * Returns: 0 on success else ERROR * * For the label A//&B//&C this does the perm match for each of A and B and C * @perms should be preinitialized with allperms OR a previous permission *        check to be stacked. */static int label_components_match(const struct aa_profile *profile,				  struct aa_label *label, bool stack,				  aa_state_t start, bool inview, u32 request,				  struct aa_perms *perms){	struct aa_ruleset *rules = profile->label.rules[0];	struct aa_profile *tp;	struct label_it i;	struct aa_perms tmp;	struct path_cond cond = { };	aa_state_t state = 0; 	/* find first subcomponent to test */	label_for_each(i, label, tp) {		if (!aa_ns_visible(profile->ns, tp->ns, inview))			continue;		state = match_component(profile, tp, stack, start);		if (!state)			goto fail;		goto next;	} 	/* no subcomponents visible - no change in perms */	return 0; next:	tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state,				    &cond));	aa_apply_modes_to_perms(profile, &tmp);	aa_perms_accum(perms, &tmp);	label_for_each_cont(i, label, tp) {		if (!aa_ns_visible(profile->ns, tp->ns, inview))			continue;		state = match_component(profile, tp, stack, start);		if (!state)			goto fail;		tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state,					    &cond));		aa_apply_modes_to_perms(profile, &tmp);		aa_perms_accum(perms, &tmp);	} 	if ((perms->allow & request) != request)		return -EACCES; 	return 0; fail:	*perms = nullperms;	return -EACCES;} /** * label_match - do a multi-component label match * @profile: profile to match against (NOT NULL) * @label: label to match (NOT NULL) * @stack: whether this is a stacking request * @state: state to start in * @inview: whether to match labels in view or only in scope * @request: permission request * @perms: Returns computed perms (NOT NULL) * * Returns: the state the match finished in, may be the none matching state */static int label_match(const struct aa_profile *profile, struct aa_label *label,		       bool stack, aa_state_t state, bool inview, u32 request,		       struct aa_perms *perms){	int error; 	*perms = nullperms;	error = label_compound_match(profile, label, stack, state, inview,				     request, perms);	if (!error)		return error; 	*perms = allperms;	return label_components_match(profile, label, stack, state, inview,				      request, perms);} /******* end TODO: dedup *****/ /** * change_profile_perms - find permissions for change_profile * @profile: the current profile  (NOT NULL) * @target: label to transition to (NOT NULL) * @stack: whether this is a stacking request * @request: requested perms * @start: state to start matching in * @perms: Returns computed perms (NOT NULL) * * * Returns: permission set * * currently only matches full label A//&B//&C or individual components A, B, C * not arbitrary combinations. Eg. A//&B, C */static int change_profile_perms(const struct aa_profile *profile,				struct aa_label *target, bool stack,				u32 request, aa_state_t start,				struct aa_perms *perms){	if (profile_unconfined(profile)) {		perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;		perms->audit = perms->quiet = perms->kill = 0;		return 0;	} 	/* TODO: add profile in ns screening */	return label_match(profile, target, stack, start, true, request, perms);} /** * aa_xattrs_match - check whether a file matches the xattrs defined in profile * @path: path for file being matched (NOT NULL) * @profile: profile to match against (NOT NULL) * @state: state to start match in * * Returns: number of extended attributes that matched, or < 0 on error */static int aa_xattrs_match(const struct path *path,			   const struct aa_profile *profile, aa_state_t state){	AA_BUG(!path);	AA_BUG(!profile); 	int i;	struct dentry *d;	char *value = NULL;	const struct aa_attachment *attach = &profile->attach;	int size, value_size = 0, ret = attach->xattr_count; 	if (!attach->xattr_count)		return 0;	might_sleep(); 	/* transition from exec match to xattr set */	state = aa_dfa_outofband_transition(attach->xmatch->dfa, state);	d = path->dentry; 	for (i = 0; i < attach->xattr_count; i++) {		size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i],					  &value, value_size, GFP_KERNEL);		if (size >= 0) {			struct aa_perms *perms; 			/*			 * Check the xattr presence before value. This ensure			 * that not present xattr can be distinguished from a 0			 * length value or rule that matches any value			 */			state = aa_dfa_null_transition(attach->xmatch->dfa,						       state);			/* Check xattr value */			state = aa_dfa_match_len(attach->xmatch->dfa, state,						 value, size);			perms = aa_lookup_perms(attach->xmatch, state);			if (!(perms->allow & MAY_EXEC)) {				ret = -EINVAL;				goto out;			}		}		/* transition to next element */		state = aa_dfa_outofband_transition(attach->xmatch->dfa, state);		if (size < 0) {			/*			 * No xattr match, so verify if transition to			 * next element was valid. IFF so the xattr			 * was optional.			 */			if (!state) {				ret = -EINVAL;				goto out;			}			/* don't count missing optional xattr as matched */			ret--;		}	} out:	kfree(value);	return ret;} /** * find_attach - do attachment search for unconfined processes * @path: path of file in question (NOT NULL) * @ns: the current namespace  (NOT NULL) * @head: profile list to walk  (NOT NULL) * @name: to match against  (NOT NULL) * @info: info message if there was an error (NOT NULL) * * Do a linear search on the profiles in the list.  There is a matching * preference where an exact match is preferred over a name which uses * expressions to match, and matching expressions with the greatest * xmatch_len are preferred. * * Requires: @head not be shared or have appropriate locks held * * Returns: label or NULL if no match found */static struct aa_label *find_attach(const struct path *path,				    struct aa_ns *ns, struct list_head *head,				    const char *name, const char **info){	int candidate_len = 0, candidate_xattrs = 0;	bool conflict = false;	struct aa_profile *profile, *candidate = NULL; 	AA_BUG(!path);	AA_BUG(!name);	AA_BUG(!head); 	rcu_read_lock();restart:	list_for_each_entry_rcu(profile, head, base.list) {		struct aa_attachment *attach = &profile->attach; 		if (profile->label.flags & FLAG_NULL &&		    &profile->label == ns_unconfined(profile->ns))			continue; 		/* Find the "best" matching profile. Profiles must		 * match the path and extended attributes (if any)		 * associated with the file. A more specific path		 * match will be preferred over a less specific one,		 * and a match with more matching extended attributes		 * will be preferred over one with fewer. If the best		 * match has both the same level of path specificity		 * and the same number of matching extended attributes		 * as another profile, signal a conflict and refuse to		 * match.		 */		if (attach->xmatch->dfa) {			unsigned int count;			aa_state_t state;			struct aa_perms *perms; 			state = aa_dfa_leftmatch(attach->xmatch->dfa,					attach->xmatch->start[AA_CLASS_XMATCH],					name, &count);			perms = aa_lookup_perms(attach->xmatch, state);			/* any accepting state means a valid match. */			if (perms->allow & MAY_EXEC) {				int ret = 0; 				if (count < candidate_len)					continue; 				if (attach->xattr_count) {					long rev = READ_ONCE(ns->revision); 					if (!aa_get_profile_not0(profile))						goto restart;					rcu_read_unlock();					ret = aa_xattrs_match(path, profile,							      state);					rcu_read_lock();					aa_put_profile(profile);					if (rev !=					    READ_ONCE(ns->revision))						/* policy changed */						goto restart;					/*					 * Fail matching if the xattrs don't					 * match					 */					if (ret < 0)						continue;				}				/*				 * TODO: allow for more flexible best match				 *				 * The new match isn't more specific				 * than the current best match				 */				if (count == candidate_len &&				    ret <= candidate_xattrs) {					/* Match is equivalent, so conflict */					if (ret == candidate_xattrs)						conflict = true;					continue;				} 				/* Either the same length with more matching				 * xattrs, or a longer match				 */				candidate = profile;				candidate_len = max(count, attach->xmatch_len);				candidate_xattrs = ret;				conflict = false;			}		} else if (!strcmp(profile->base.name, name)) {			/*			 * old exact non-re match, without conditionals such			 * as xattrs. no more searching required			 */			candidate = profile;			goto out;		}	} 	if (!candidate || conflict) {		if (conflict)			*info = CONFLICTING_ATTACH_STR;		rcu_read_unlock();		return NULL;	}

Lines 1–500 of 1663.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t