754 lines · 20.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * AppArmor security module * * This file contains AppArmor mediation of files * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. */ #include <linux/tty.h>#include <linux/fdtable.h>#include <linux/file.h>#include <linux/fs.h>#include <linux/mount.h> #include "include/af_unix.h"#include "include/apparmor.h"#include "include/audit.h"#include "include/cred.h"#include "include/file.h"#include "include/match.h"#include "include/net.h"#include "include/path.h"#include "include/policy.h"#include "include/label.h" static u32 map_mask_to_chr_mask(u32 mask){	u32 m = mask & PERMS_CHRS_MASK; 	if (mask & AA_MAY_GETATTR)		m |= MAY_READ;	if (mask & (AA_MAY_SETATTR | AA_MAY_CHMOD | AA_MAY_CHOWN))		m |= MAY_WRITE; 	return m;} /** * file_audit_cb - call back for file specific audit fields * @ab: audit_buffer  (NOT NULL) * @va: audit struct to audit values of  (NOT NULL) */static void file_audit_cb(struct audit_buffer *ab, void *va){	struct common_audit_data *sa = va;	struct apparmor_audit_data *ad = aad(sa);	kuid_t fsuid = ad->subj_cred ? ad->subj_cred->fsuid : current_fsuid();	char str[10]; 	if (ad->request & AA_AUDIT_FILE_MASK) {		aa_perm_mask_to_str(str, sizeof(str), aa_file_perm_chrs,				    map_mask_to_chr_mask(ad->request));		audit_log_format(ab, " requested_mask=\"%s\"", str);	}	if (ad->denied & AA_AUDIT_FILE_MASK) {		aa_perm_mask_to_str(str, sizeof(str), aa_file_perm_chrs,				    map_mask_to_chr_mask(ad->denied));		audit_log_format(ab, " denied_mask=\"%s\"", str);	}	if (ad->request & AA_AUDIT_FILE_MASK) {		audit_log_format(ab, " fsuid=%d",				 from_kuid(&init_user_ns, fsuid));		audit_log_format(ab, " ouid=%d",				 from_kuid(&init_user_ns, ad->fs.ouid));	} 	if (ad->peer) {		audit_log_format(ab, " target=");		aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer,				FLAG_VIEW_SUBNS, GFP_KERNEL);	} else if (ad->fs.target) {		audit_log_format(ab, " target=");		audit_log_untrustedstring(ab, ad->fs.target);	}} /** * aa_audit_file - handle the auditing of file operations * @subj_cred: cred of the subject * @profile: the profile being enforced  (NOT NULL) * @perms: the permissions computed for the request (NOT NULL) * @op: operation being mediated * @request: permissions requested * @name: name of object being mediated (MAYBE NULL) * @target: name of target (MAYBE NULL) * @tlabel: target label (MAY BE NULL) * @ouid: object uid * @info: extra information message (MAYBE NULL) * @error: 0 if operation allowed else failure error code * * Returns: %0 or error on failure */int aa_audit_file(const struct cred *subj_cred,		  struct aa_profile *profile, const struct aa_perms *perms,		  const char *op, u32 request, const char *name,		  const char *target, struct aa_label *tlabel,		  kuid_t ouid, const char *info, int error){	u32 quiet = perms->quiet;	u32 complain = perms->complain;	int type = AUDIT_APPARMOR_AUTO;	DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_FILE, op); 	ad.subj_cred = subj_cred;	ad.request = request;	ad.tags = perms->tag;	ad.name = name;	ad.fs.target = target;	ad.peer = tlabel;	ad.fs.ouid = ouid;	ad.info = info;	ad.error = error;	ad.common.u.tsk = NULL; 	if (COMPLAIN_MODE(profile))		complain |= ~(perms->allow | perms->deny);	if (likely(!ad.error)) {		u32 mask = perms->audit; 		if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))			mask = 0xffff; 		/* mask off perms that are not being force audited */		ad.request &= mask; 		if (likely(!ad.request))			return 0;		type = AUDIT_APPARMOR_AUDIT;	} else {		/* only report permissions that were denied */		ad.request = ad.request & ~perms->allow;		AA_BUG(!ad.request); 		if (ad.request & perms->kill)			type = AUDIT_APPARMOR_KILL; 		if (AUDIT_MODE(profile) == AUDIT_QUIET_ALLOWED)			quiet |= complain | perms->allow; 		/* quiet known rejects, assumes quiet and kill do not overlap */		if ((ad.request & quiet) &&		    AUDIT_MODE(profile) != AUDIT_NOQUIET &&		    AUDIT_MODE(profile) != AUDIT_ALL)			ad.request &= ~quiet; 		if (!ad.request)			return ad.error;	} 	ad.denied = ad.request & ~perms->allow;	return aa_audit(type, profile, &ad, file_audit_cb);} static int path_name(const char *op, const struct cred *subj_cred,		     struct aa_label *label,		     const struct path *path, int flags, char *buffer,		     const char **name, struct path_cond *cond, u32 request){	struct aa_profile *profile;	const char *info = NULL;	int error; 	/* don't reaudit files closed during inheritance */	if (unlikely(path->dentry == aa_null.dentry))		return -EACCES; 	error = aa_path_name(path, flags, buffer, name, &info,			     labels_profile(label)->disconnected);	if (error) {		fn_for_each_confined(label, profile,			aa_audit_file(subj_cred,				      profile, &nullperms, op, request, *name,				      NULL, NULL, cond->uid, info, error));		return error;	} 	return 0;} struct aa_perms default_perms = {};/** * aa_lookup_condperms - convert dfa compressed perms to internal perms * @subj_uid: uid to use for subject owner test * @rules: the aa_policydb to lookup perms for  (NOT NULL) * @state: state in dfa * @cond:  conditions to consider  (NOT NULL) * * TODO: convert from dfa + state to permission entry * * Returns: a pointer to a file permission set */struct aa_perms *aa_lookup_condperms(kuid_t subj_uid, struct aa_policydb *rules,				     aa_state_t state, struct path_cond *cond){	unsigned int index = ACCEPT_TABLE(rules->dfa)[state]; 	if (!(rules->perms))		return &default_perms; 	if ((ACCEPT_TABLE2(rules->dfa)[state] & ACCEPT_FLAG_OWNER)) {		if (uid_eq(subj_uid, cond->uid))			return &(rules->perms[index]);		return &(rules->perms[index + 1]);	} 	return &(rules->perms[index]);} /** * aa_str_perms - find permission that match @name * @file_rules: the aa_policydb to match against  (NOT NULL) * @start: state to start matching in * @name: string to match against dfa  (NOT NULL) * @cond: conditions to consider for permission set computation  (NOT NULL) * @perms: Returns - the permissions found when matching @name * * Returns: the final state in @dfa when beginning @start and walking @name */aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start,			const char *name, struct path_cond *cond,			struct aa_perms *perms){	aa_state_t state;	state = aa_dfa_match(file_rules->dfa, start, name);	*perms = *(aa_lookup_condperms(current_fsuid(), file_rules, state,				       cond)); 	return state;} int __aa_path_perm(const char *op, const struct cred *subj_cred,		   struct aa_profile *profile, const char *name,		   u32 request, struct path_cond *cond, int flags,		   struct aa_perms *perms){	struct aa_ruleset *rules = profile->label.rules[0];	int e = 0; 	if (profile_unconfined(profile) ||	    ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_UNIX(rules)))		return 0;	aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE],		     name, cond, perms);	if (request & ~perms->allow)		e = -EACCES;	return aa_audit_file(subj_cred,			     profile, perms, op, request, name, NULL, NULL,			     cond->uid, NULL, e);}  static int profile_path_perm(const char *op, const struct cred *subj_cred,			     struct aa_profile *profile,			     const struct path *path, char *buffer, u32 request,			     struct path_cond *cond, int flags,			     struct aa_perms *perms){	const char *name = NULL;	int error; 	if (profile_unconfined(profile))		return 0; 	error = path_name(op, subj_cred, &profile->label, path,			  flags | profile->path_flags, buffer, &name, cond,			  request);	if (error)		return error;	return __aa_path_perm(op, subj_cred, profile, name, request, cond,			      flags, perms);} /** * aa_path_perm - do permissions check & audit for @path * @op: operation being checked * @subj_cred: subject cred * @label: profile being enforced  (NOT NULL) * @path: path to check permissions of  (NOT NULL) * @flags: any additional path flags beyond what the profile specifies * @request: requested permissions * @cond: conditional info for this request  (NOT NULL) * * Returns: %0 else error if access denied or other error */int aa_path_perm(const char *op, const struct cred *subj_cred,		 struct aa_label *label,		 const struct path *path, int flags, u32 request,		 struct path_cond *cond){	struct aa_perms perms = {};	struct aa_profile *profile;	char *buffer = NULL;	int error; 	flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR :								0);	buffer = aa_get_buffer(false);	if (!buffer)		return -ENOMEM;	error = fn_for_each_confined(label, profile,			profile_path_perm(op, subj_cred, profile, path, buffer,					  request, cond, flags, &perms)); 	aa_put_buffer(buffer); 	return error;} /** * xindex_is_subset - helper for aa_path_link * @link: link permission set * @target: target permission set * * test target x permissions are equal OR a subset of link x permissions * this is done as part of the subset test, where a hardlink must have * a subset of permissions that the target has. * * Returns: true if subset else false */static inline bool xindex_is_subset(u32 link, u32 target){	if (((link & ~AA_X_UNSAFE) != (target & ~AA_X_UNSAFE)) ||	    ((link & AA_X_UNSAFE) && !(target & AA_X_UNSAFE)))		return false; 	return true;} static int profile_path_link(const struct cred *subj_cred,			     struct aa_profile *profile,			     const struct path *link, char *buffer,			     const struct path *target, char *buffer2,			     struct path_cond *cond){	struct aa_ruleset *rules = profile->label.rules[0];	const char *lname = NULL, *tname = NULL;	struct aa_perms lperms = {}, perms;	const char *info = NULL;	u32 request = AA_MAY_LINK;	aa_state_t state;	int error; 	error = path_name(OP_LINK, subj_cred, &profile->label, link,			  profile->path_flags,			  buffer, &lname, cond, AA_MAY_LINK);	if (error)		goto audit; 	/* buffer2 freed below, tname is pointer in buffer2 */	error = path_name(OP_LINK, subj_cred, &profile->label, target,			  profile->path_flags,			  buffer2, &tname, cond, AA_MAY_LINK);	if (error)		goto audit; 	error = -EACCES;	/* aa_str_perms - handles the case of the dfa being NULL */	state = aa_str_perms(rules->file,			     rules->file->start[AA_CLASS_FILE], lname,			     cond, &lperms); 	if (!(lperms.allow & AA_MAY_LINK))		goto audit; 	/* test to see if target can be paired with link */	state = aa_dfa_null_transition(rules->file->dfa, state);	aa_str_perms(rules->file, state, tname, cond, &perms); 	/* force audit/quiet masks for link are stored in the second entry	 * in the link pair.	 */	lperms.audit = perms.audit;	lperms.quiet = perms.quiet;	lperms.kill = perms.kill; 	if (!(perms.allow & AA_MAY_LINK)) {		info = "target restricted";		lperms = perms;		goto audit;	} 	/* done if link subset test is not required */	if (!(perms.allow & AA_LINK_SUBSET))		goto done_tests; 	/* Do link perm subset test requiring allowed permission on link are	 * a subset of the allowed permissions on target.	 */	aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE],		     tname, cond, &perms); 	/* AA_MAY_LINK is not considered in the subset test */	request = lperms.allow & ~AA_MAY_LINK;	lperms.allow &= perms.allow | AA_MAY_LINK; 	request |= AA_AUDIT_FILE_MASK & (lperms.allow & ~perms.allow);	if (request & ~lperms.allow) {		goto audit;	} else if ((lperms.allow & MAY_EXEC) &&		   !xindex_is_subset(lperms.xindex, perms.xindex)) {		lperms.allow &= ~MAY_EXEC;		request |= MAY_EXEC;		info = "link not subset of target";		goto audit;	} done_tests:	error = 0; audit:	return aa_audit_file(subj_cred,			     profile, &lperms, OP_LINK, request, lname, tname,			     NULL, cond->uid, info, error);} /** * aa_path_link - Handle hard link permission check * @subj_cred: subject cred * @label: the label being enforced  (NOT NULL) * @old_dentry: the target dentry  (NOT NULL) * @new_dir: directory the new link will be created in  (NOT NULL) * @new_dentry: the link being created  (NOT NULL) * * Handle the permission test for a link & target pair.  Permission * is encoded as a pair where the link permission is determined * first, and if allowed, the target is tested.  The target test * is done from the point of the link match (not start of DFA) * making the target permission dependent on the link permission match. * * The subset test if required forces that permissions granted * on link are a subset of the permission granted to target. * * Returns: %0 if allowed else error */int aa_path_link(const struct cred *subj_cred,		 struct aa_label *label, struct dentry *old_dentry,		 const struct path *new_dir, struct dentry *new_dentry){	struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry };	struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry };	struct inode *inode = d_backing_inode(old_dentry);	vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(target.mnt), inode);	struct path_cond cond = {		.uid = vfsuid_into_kuid(vfsuid),		.mode = inode->i_mode,	};	char *buffer = NULL, *buffer2 = NULL;	struct aa_profile *profile;	int error; 	/* buffer freed below, lname is pointer in buffer */	buffer = aa_get_buffer(false);	buffer2 = aa_get_buffer(false);	error = -ENOMEM;	if (!buffer || !buffer2)		goto out; 	error = fn_for_each_confined(label, profile,			profile_path_link(subj_cred, profile, &link, buffer,					  &target, buffer2, &cond));out:	aa_put_buffer(buffer);	aa_put_buffer(buffer2);	return error;} static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label,			    u32 request){	struct aa_label *l, *old; 	/* update caching of label on file_ctx */	spin_lock(&fctx->lock);	old = rcu_dereference_protected(fctx->label,					lockdep_is_held(&fctx->lock));	l = aa_label_merge(old, label, GFP_ATOMIC);	if (l) {		if (l != old) {			rcu_assign_pointer(fctx->label, l);			aa_put_label(old);		} else			aa_put_label(l);		fctx->allow |= request;	}	spin_unlock(&fctx->lock);} static int __file_path_perm(const char *op, const struct cred *subj_cred,			    struct aa_label *label,			    struct aa_label *flabel, struct file *file,			    u32 request, u32 denied, bool in_atomic){	struct aa_profile *profile;	struct aa_perms perms = {};	vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(file),					    file_inode(file));	struct path_cond cond = {		.uid = vfsuid_into_kuid(vfsuid),

Lines 1–500 of 754.