The underlying functions by which the AppArmor LSM hooks are implemented. Signed-off-by: John Johansen <jjohansen@xxxxxxx> Signed-off-by: Andreas Gruenbacher <agruen@xxxxxxx> Index: b/security/apparmor/main.c =================================================================== --- /dev/null +++ b/security/apparmor/main.c @@ -0,0 +1,1399 @@ +/* + * Copyright (C) 2002-2007 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor Core + */ + +#include <linux/security.h> +#include <linux/namei.h> +#include <linux/audit.h> +#include <linux/mount.h> +#include <linux/ptrace.h> + +#include "apparmor.h" + +#include "inline.h" + +/* + * Table of capability names: we generate it from capabilities.h. + */ +static const char *capability_names[] = { +#include "capability_names.h" +}; + +/* NULL complain profile + * + * Used when in complain mode, to emit Permitting messages for non-existant + * profiles and hats. This is necessary because of selective mode, in which + * case we need a complain null_profile and enforce null_profile + * + * The null_complain_profile cannot be statically allocated, because it + * can be associated to files which keep their reference even if apparmor is + * unloaded + */ +struct aa_profile *null_complain_profile; + +static inline void aa_permerror2result(int perm_result, struct aa_audit *sa) +{ + if (perm_result == 0) { /* success */ + sa->result = 1; + sa->error_code = 0; + } else { /* -ve internal error code or +ve mask of denied perms */ + sa->result = 0; + sa->error_code = perm_result; + } +} + +/** + * aa_file_denied - check for @mask access on a file + * @profile: profile to check against + * @name: pathname of file + * @mask: permission mask requested for file + * + * Return %0 on success, or else the permissions in @mask that the + * profile denies. + */ +static int aa_file_denied(struct aa_profile *profile, const char *name, + int mask) +{ + return (mask & ~aa_match(profile->file_rules, name)); +} + +/** + * aa_link_denied - check for permission to link a file + * @profile: profile to check against + * @link: pathname of link being created + * @target: pathname of target to be linked to + * + * Return %0 on success, or else the permissions that the profile denies. + */ +static int aa_link_denied(struct aa_profile *profile, const char *link, + const char *target) +{ + int l_mode, t_mode; + + l_mode = aa_match(profile->file_rules, link); + t_mode = aa_match(profile->file_rules, target); + + /* Link always requires 'l' on the link, a subset of the + * target's 'r', 'w', 'x', and 'm' permissions on the link, and + * if the link has 'x', an exact match of all the execute flags + * ('i', 'u', 'U', 'p', 'P'). + */ +#define RWXM (MAY_READ | MAY_WRITE | MAY_EXEC | AA_EXEC_MMAP) + if ((l_mode & AA_MAY_LINK) && + (l_mode & RWXM) && !(l_mode & ~t_mode & RWXM) && + (!(l_mode & MAY_EXEC) || + ((l_mode & AA_EXEC_MODIFIERS) == (t_mode & AA_EXEC_MODIFIERS) && + (l_mode & AA_EXEC_UNSAFE) == (t_mode & AA_EXEC_UNSAFE)))) + return 0; +#undef RWXM + /* FIXME: There currenly is no way to report which permissions + * we expect in t_mode, so linking could fail even after learning + * the required l_mode. + */ + return AA_MAY_LINK; +} + +/** + * mangle -- escape special characters in str + * @str: string to escape + * @buffer: buffer containing str + * + * Escape special characters in @str, which is contained in @buffer. @str must + * be aligned to the end of the buffer, and the space between @buffer and @str + * may be used for escaping. + * + * Returns @str if no escaping was necessary, a pointer to the beginning of the + * escaped string, or NULL if there was not enough space in @buffer. When + * called with a NULL buffer, the return value tells whether any escaping is + * necessary. + */ +static const char *mangle(const char *str, char *buffer) +{ + static const char c_escape[] = { + ['\a'] = 'a', ['\b'] = 'b', + ['\f'] = 'f', ['\n'] = 'n', + ['\r'] = 'r', ['\t'] = 't', + ['\v'] = 'v', + [' '] = ' ', ['\\'] = '\\', + }; + const char *s; + char *t, c; + +#define mangle_escape(c) \ + unlikely((unsigned char)(c) < ARRAY_SIZE(c_escape) && \ + c_escape[(unsigned char)c]) + + for (s = (char *)str; (c = *s) != '\0'; s++) + if (mangle_escape(c)) + goto escape; + return str; + +escape: + if (!buffer) + return NULL; + for (s = str, t = buffer; (c = *s) != '\0'; s++) { + if (mangle_escape(c)) { + if (t == s) + return NULL; + *t++ = '\\'; + *t++ = c_escape[(unsigned char)c]; + } else + *t++ = c; + } + *t++ = '\0'; + +#undef mangle_escape + + return buffer; +} + +/** + * aa_get_name - compute the pathname of a file + * @dentry: dentry of the file + * @mnt: vfsmount of the file + * @buffer: buffer that aa_get_name() allocated + * @check: AA_CHECK_DIR is set if the file is a directory + * + * Returns a pointer to the beginning of the pathname (which usually differs + * from the beginning of the buffer), or an error code. + * + * We need @check to indicate whether the file is a directory or not because + * the file may not yet exist, and so we cannot check the inode's file type. + */ +static char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt, + char **buffer, int check) +{ + char *name; + int is_dir, size = 256; + + is_dir = (check & AA_CHECK_DIR) ? 1 : 0; + + for (;;) { + char *buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + name = d_namespace_path(dentry, mnt, buf, size - is_dir); + + /* Make sure we have enough space for name mangling. */ + if (!IS_ERR(name) && + (check & AA_CHECK_MANGLE) && name - buf <= size / 2) + name = ERR_PTR(-ENAMETOOLONG); + + if (!IS_ERR(name)) { + if (name[0] != '/') { + /* + * This dentry is not connected to the + * namespace root -- reject access. + */ + kfree(buf); + return ERR_PTR(-ENOENT); + } + if (is_dir && name[1] != '\0') { + /* + * Append "/" to the pathname. The root + * directory is a special case; it already + * ends in slash. + */ + buf[size - 2] = '/'; + buf[size - 1] = '\0'; + } + + *buffer = buf; + return name; + } + if (PTR_ERR(name) != -ENAMETOOLONG) + return name; + + kfree(buf); + size <<= 1; + if (size > apparmor_path_max) + return ERR_PTR(-ENAMETOOLONG); + } +} + +static inline void aa_put_name_buffer(char *buffer) +{ + kfree(buffer); +} + +/** + * aa_perm_dentry - check if @profile allows @mask for a file + * @profile: profile to check against + * @dentry: dentry of the file + * @mnt: vfsmount o the file + * @sa: audit context + * @mask: requested profile permissions + * @check: kind of check to perform + * + * Returns 0 upon success, or else an error code. + * + * @check indicates the file type, and whether the file was accessed through + * an open file descriptor (AA_CHECK_FD) or not. + */ +static int aa_perm_dentry(struct aa_profile *profile, struct dentry *dentry, + struct vfsmount *mnt, struct aa_audit *sa, int mask, + int check) +{ + int denied_mask, error; + +again: + sa->buffer = NULL; + sa->name = aa_get_name(dentry, mnt, &sa->buffer, check); + + if (IS_ERR(sa->name)) { + /* + * deleted files are given a pass on permission checks when + * accessed through a file descriptor. + */ + if (PTR_ERR(sa->name) == -ENOENT && (check & AA_CHECK_FD)) + denied_mask = 0; + else + denied_mask = PTR_ERR(sa->name); + sa->name = NULL; + } else + denied_mask = aa_file_denied(profile, sa->name, mask); + + aa_permerror2result(denied_mask, sa); + + error = aa_audit(profile, sa); + + aa_put_name_buffer(sa->buffer); + if (error == -ENAMETOOLONG) { + BUG_ON(check & AA_CHECK_MANGLE); + check |= AA_CHECK_MANGLE; + goto again; + } + + return error; +} + +/** + * attach_nullprofile - allocate and attach a null_profile hat to profile + * @profile: profile to attach a null_profile hat to. + * + * Return %0 (success) or error (-%ENOMEM) + */ +int attach_nullprofile(struct aa_profile *profile) +{ + struct aa_profile *hat = NULL; + char *hatname = NULL; + + hat = alloc_aa_profile(); + if (!hat) + goto fail; + if (profile->flags.complain) + hatname = kstrdup("null-complain-profile", GFP_KERNEL); + else + hatname = kstrdup("null-profile", GFP_KERNEL); + if (!hatname) + goto fail; + + hat->flags.complain = profile->flags.complain; + hat->name = hatname; + hat->parent = profile; + + profile->null_profile = hat; + + return 0; + +fail: + kfree(hatname); + free_aa_profile(hat); + + return -ENOMEM; +} + +/** + * alloc_null_complain_profile - Allocate the global null_complain_profile. + * + * Return %0 (success) or error (-%ENOMEM) + */ +int alloc_null_complain_profile(void) +{ + null_complain_profile = alloc_aa_profile(); + if (!null_complain_profile) + goto fail; + + null_complain_profile->name = + kstrdup("null-complain-profile", GFP_KERNEL); + + if (!null_complain_profile->name) + goto fail; + + null_complain_profile->flags.complain = 1; + if (attach_nullprofile(null_complain_profile)) + goto fail; + + return 0; + +fail: + /* free_aa_profile is safe for freeing partially constructed objects */ + free_aa_profile(null_complain_profile); + null_complain_profile = NULL; + + return -ENOMEM; +} + +/** + * free_null_complain_profile - Free null profiles + */ +void free_null_complain_profile(void) +{ + aa_put_profile(null_complain_profile); + null_complain_profile = NULL; +} + +/** + * aa_audit_message - Log a message to the audit subsystem + * @profile: profile to check against + * @gfp: allocation flags + * @flags: audit flags + * @fmt: varargs fmt + */ +int aa_audit_message(struct aa_profile *profile, gfp_t gfp, const char *fmt, + ...) +{ + int ret; + struct aa_audit sa; + + sa.type = AA_AUDITTYPE_MSG; + sa.name = fmt; + va_start(sa.vaval, fmt); + sa.flags = 0; + sa.gfp_mask = gfp; + sa.error_code = 0; + sa.result = 0; /* fake failure: force message to be logged */ + + ret = aa_audit(profile, &sa); + + va_end(sa.vaval); + + return ret; +} + +/** + * aa_audit_syscallreject - Log a syscall rejection to the audit subsystem + * @profile: profile to check against + * @msg: string describing syscall being rejected + * @gfp: memory allocation flags + */ +int aa_audit_syscallreject(struct aa_profile *profile, gfp_t gfp, + const char *msg) +{ + struct aa_audit sa; + + sa.type = AA_AUDITTYPE_SYSCALL; + sa.name = msg; + sa.flags = 0; + sa.gfp_mask = gfp; + sa.error_code = 0; + sa.result = 0; /* failure */ + + return aa_audit(profile, &sa); +} + +/** + * aa_audit - Log an audit event to the audit subsystem + * @profile: profile to check against + * @sa: audit event + */ +int aa_audit(struct aa_profile *profile, struct aa_audit *sa) +{ + struct audit_buffer *ab = NULL; + struct audit_context *audit_cxt; + + const char *logcls; + unsigned int flags; + int audit = 0, + complain = 0, + error = -EINVAL, + opspec_error = -EACCES; + + const gfp_t gfp_mask = sa->gfp_mask; + + /* + * sa->result: 1 success, 0 failure + * sa->error_code: success: 0 + * failure: +ve mask of failed permissions or -ve + * system error + */ + + if (likely(sa->result)) { + if (likely(!PROFILE_AUDIT(profile))) { + /* nothing to log */ + error = 0; + goto out; + } else { + audit = 1; + logcls = "AUDITING"; + } + } else if (sa->error_code < 0) { + audit_log(current->audit_context, gfp_mask, AUDIT_APPARMOR, + "Internal error auditing event type %d (error %d)", + sa->type, sa->error_code); + AA_ERROR("Internal error auditing event type %d (error %d)\n", + sa->type, sa->error_code); + error = sa->error_code; + goto out; + } else if (sa->type == AA_AUDITTYPE_SYSCALL) { + /* Currently AA_AUDITTYPE_SYSCALL is for rejects only. + * Values set by aa_audit_syscallreject will get us here. + */ + logcls = "REJECTING"; + } else { + complain = PROFILE_COMPLAIN(profile); + logcls = complain ? "PERMITTING" : "REJECTING"; + } + + /* In future extend w/ per-profile flags + * (flags |= sa->profile->flags) + */ + flags = sa->flags; + if (apparmor_logsyscall) + flags |= AA_AUDITFLAG_AUDITSS_SYSCALL; + + + /* Force full audit syscall logging regardless of global setting if + * we are rejecting a syscall + */ + if (sa->type == AA_AUDITTYPE_SYSCALL) { + audit_cxt = current->audit_context; + } else { + audit_cxt = (flags & AA_AUDITFLAG_AUDITSS_SYSCALL) ? + current->audit_context : NULL; + } + + ab = audit_log_start(audit_cxt, gfp_mask, AUDIT_APPARMOR); + + if (!ab) { + AA_ERROR("Unable to log event (%d) to audit subsys\n", + sa->type); + if (complain) + error = 0; + goto out; + } + + /* messages get special handling */ + if (sa->type == AA_AUDITTYPE_MSG) { + audit_log_vformat(ab, sa->name, sa->vaval); + audit_log_end(ab); + error = 0; + goto out; + } + + if (sa->type & AA_MANGLE_NAME) { + sa->name = mangle(sa->name, sa->buffer); + if (!sa->name) + return -ENAMETOOLONG; + } + if (sa->type & AA_MANGLE_NAME2) { + sa->name2 = mangle(sa->name2, sa->buffer2); + if (!sa->name2) + return -ENAMETOOLONG; + + } + + /* log operation */ + + audit_log_format(ab, "%s ", logcls); /* REJECTING/ALLOWING/etc */ + +#define NOFLAGS(x) ((x) & ~(AA_MANGLE_NAME | AA_MANGLE_NAME2)) + + switch(NOFLAGS(sa->type)) { + case NOFLAGS(AA_AUDITTYPE_FILE): { + int perm = audit ? sa->mask : sa->error_code; + + audit_log_format(ab, "%s%s%s%s%s access to %s ", + perm & AA_EXEC_MMAP ? "m" : "", + perm & MAY_READ ? "r" : "", + perm & MAY_WRITE ? "w" : "", + perm & MAY_EXEC ? "x" : "", + perm & AA_MAY_LINK ? "l" : "", + sa->name); + opspec_error = -EPERM; + break; + } + case NOFLAGS(AA_AUDITTYPE_DIR): + audit_log_format(ab, "%s on %s ", sa->name2, sa->name); + break; + case NOFLAGS(AA_AUDITTYPE_ATTR): { + struct iattr *iattr = sa->iattr; + + audit_log_format(ab, + "attribute (%s%s%s%s%s%s%s) change to %s ", + iattr->ia_valid & ATTR_MODE ? "mode," : "", + iattr->ia_valid & ATTR_UID ? "uid," : "", + iattr->ia_valid & ATTR_GID ? "gid," : "", + iattr->ia_valid & ATTR_SIZE ? "size," : "", + ((iattr->ia_valid & ATTR_ATIME_SET) || + (iattr->ia_valid & ATTR_ATIME)) ? "atime," : "", + ((iattr->ia_valid & ATTR_MTIME_SET) || + (iattr->ia_valid & ATTR_MTIME)) ? "mtime," : "", + iattr->ia_valid & ATTR_CTIME ? "ctime," : "", + sa->name); + break; + } + case NOFLAGS(AA_AUDITTYPE_XATTR): + audit_log_format(ab, "%s on %s ", sa->name2, sa->name); + break; + case NOFLAGS(AA_AUDITTYPE_LINK): + audit_log_format(ab, "link access from %s to %s ", sa->name, + sa->name2); + break; + case NOFLAGS(AA_AUDITTYPE_CAP): + audit_log_format(ab, "access to capability '%s' ", + capability_names[sa->capability]); + opspec_error = -EPERM; + break; + case NOFLAGS(AA_AUDITTYPE_SYSCALL): + audit_log_format(ab, "access to syscall '%s' ", sa->name); + opspec_error = -EPERM; + break; + default: + WARN_ON(1); + return error; + } + +#undef NOFLAGS + + audit_log_format(ab, "(%d profile %s active %s)", + current->pid, profile->parent->name, profile->name); + + audit_log_end(ab); + + if (complain) + error = 0; + else + error = sa->result ? 0 : opspec_error; +out: + return error; +} + +/** + * aa_attr - check if attribute change is allowed + * @profile: profile to check against + * @dentry: dentry of the file to check + * @mnt: vfsmount of the file to check + * @iattr: attribute changes requested + */ +int aa_attr(struct aa_profile *profile, struct dentry *dentry, + struct vfsmount *mnt, struct iattr *iattr) +{ + struct inode *inode = dentry->d_inode; + int error, check; + struct aa_audit sa; + + sa.type = AA_AUDITTYPE_ATTR; + sa.iattr = iattr; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + check = 0; + if (inode && S_ISDIR(inode->i_mode)) + check |= AA_CHECK_DIR; + if (iattr->ia_valid & ATTR_FILE) + check |= AA_CHECK_FD; + + error = aa_perm_dentry(profile, dentry, mnt, &sa, MAY_WRITE, check); + + return error; +} + +/** + * aa_perm_xattr - check if xattr attribute change is allowed + * @profile: profile to check against + * @dentry: dentry of the file to check + * @mnt: vfsmount of the file to check + * @operation: xattr operation being done + * @mask: access mode requested + * @check: kind of check to perform + */ +int aa_perm_xattr(struct aa_profile *profile, struct dentry *dentry, + struct vfsmount *mnt, const char *operation, + int mask, int check) +{ + struct inode *inode = dentry->d_inode; + int error; + struct aa_audit sa; + + sa.type = AA_AUDITTYPE_XATTR; + sa.name2 = operation; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + if (inode && S_ISDIR(inode->i_mode)) + check |= AA_CHECK_DIR; + + error = aa_perm_dentry(profile, dentry, mnt, &sa, mask, check); + + return error; +} + +/** + * aa_perm - basic apparmor permissions check + * @profile: profile to check against + * @dentry: dentry of the file to check + * @mnt: vfsmount of the file to check + * @mask: access mode requested + * @check: kind of check to perform + * + * Determine if access @mask for the file is authorized by @profile. + * Returns 0 on success, or else an error code. + */ +int aa_perm(struct aa_profile *profile, struct dentry *dentry, + struct vfsmount *mnt, int mask, int check) +{ + struct aa_audit sa; + int error = 0; + + if (mask == 0) + goto out; + + sa.type = AA_AUDITTYPE_FILE; + sa.mask = mask; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + error = aa_perm_dentry(profile, dentry, mnt, &sa, mask, check); + +out: + return error; +} + +/** + * aa_perm_dir + * @profile: profile to check against + * @dentry: dentry of directory to check + * @mnt: vfsmount of directory to check + * @operation: directory operation being performed + * @mask: access mode requested + * + * Determine if directory operation (make/remove) for dentry is authorized + * by @profile. + * Returns 0 on success, or else an error code. + */ +int aa_perm_dir(struct aa_profile *profile, struct dentry *dentry, + struct vfsmount *mnt, const char *operation, int mask) +{ + struct aa_audit sa; + + sa.type = AA_AUDITTYPE_DIR; + sa.name2 = operation; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + return aa_perm_dentry(profile, dentry, mnt, &sa, mask, AA_CHECK_DIR); +} + +int aa_perm_path(struct aa_profile *profile, const char *name, int mask) +{ + struct aa_audit sa; + int denied_mask; + + sa.type = AA_AUDITTYPE_FILE; + sa.mask = mask; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + sa.name = name; + + denied_mask = aa_file_denied(profile, name, mask); + aa_permerror2result(denied_mask, &sa); + + return aa_audit(profile, &sa); +} + +/** + * aa_capability - test permission to use capability + * @cxt: aa_task_context with profile to check against + * @cap: capability to be tested + * + * Look up capability in profile capability set. + * Returns 0 on success, or else an error code. + */ +int aa_capability(struct aa_task_context *cxt, int cap) +{ + int error = cap_raised(cxt->profile->capabilities, cap) ? 0 : -EPERM; + struct aa_audit sa; + + /* test if cap has alread been logged */ + if (cap_raised(cxt->caps_logged, cap)) { + if (PROFILE_COMPLAIN(cxt->profile)) + error = 0; + return error; + } else + /* don't worry about rcu replacement of the cxt here. + * caps_logged is a cache to reduce the occurance of + * duplicate messages in the log. The worst that can + * happen is duplicate capability messages shows up in + * the audit log + */ + cap_raise(cxt->caps_logged, cap); + + sa.type = AA_AUDITTYPE_CAP; + sa.name = NULL; + sa.capability = cap; + sa.flags = 0; + sa.error_code = 0; + sa.result = !error; + sa.gfp_mask = GFP_ATOMIC; + + error = aa_audit(cxt->profile, &sa); + + return error; +} + +/* must be used inside rcu_read_lock or task_lock */ +int aa_may_ptrace(struct aa_task_context *cxt, struct aa_profile *tracee) +{ + if (!cxt || cxt->profile == tracee) + return 0; + return aa_capability(cxt, CAP_SYS_PTRACE); +} + +/** + * aa_link - hard link check + * @profile: profile to check against + * @link: dentry of link being created + * @link_mnt: vfsmount of link being created + * @target: dentry of link target + * @target_mnt: vfsmunt of link target + * + * Returns 0 on success, or else an error code. + */ +int aa_link(struct aa_profile *profile, + struct dentry *link, struct vfsmount *link_mnt, + struct dentry *target, struct vfsmount *target_mnt) +{ + int denied_mask = -EPERM, error, check = 0; + struct aa_audit sa; + +again: + sa.buffer = NULL; + sa.name = aa_get_name(link, link_mnt, &sa.buffer, check); + sa.buffer2 = NULL; + sa.name2 = aa_get_name(target, target_mnt, &sa.buffer2, check); + + if (IS_ERR(sa.name)) { + denied_mask = PTR_ERR(sa.name); + sa.name = NULL; + } + if (IS_ERR(sa.name2)) { + denied_mask = PTR_ERR(sa.name2); + sa.name2 = NULL; + } + + if (sa.name && sa.name2) + denied_mask = aa_link_denied(profile, sa.name, sa.name2); + + aa_permerror2result(denied_mask, &sa); + + sa.type = AA_AUDITTYPE_LINK; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + error = aa_audit(profile, &sa); + + aa_put_name_buffer(sa.buffer); + aa_put_name_buffer(sa.buffer2); + if (error == -ENAMETOOLONG) { + BUG_ON(check & AA_CHECK_MANGLE); + check |= AA_CHECK_MANGLE; + goto again; + } + + return error; +} + +/******************************* + * Global task related functions + *******************************/ + +/** + * aa_clone - initialize the task context for a new task + * @child: task that is being created + * + * Returns 0 on success, or else an error code. + */ +int aa_clone(struct task_struct *child) +{ + struct aa_task_context *cxt, *child_cxt; + struct aa_profile *profile; + + if (!aa_task_context(current)) + return 0; + child_cxt = aa_alloc_task_context(GFP_KERNEL); + if (!child_cxt) + return -ENOMEM; + +repeat: + profile = aa_get_profile(current); + if (profile) { + lock_profile(profile); + cxt = aa_task_context(current); + if (unlikely(profile->isstale || !cxt || + cxt->profile != profile)) { + /** + * Race with profile replacement or removal, or with + * task context removal. + */ + unlock_profile(profile); + aa_put_profile(profile); + goto repeat; + } + + /* No need to grab the child's task lock here. */ + aa_change_task_context(child, child_cxt, profile, + cxt->hat_magic); + unlock_profile(profile); + + if (APPARMOR_COMPLAIN(child_cxt) && + profile == null_complain_profile) + aa_audit_message(profile, GFP_KERNEL, + "LOGPROF-HINT fork child=%d " + "(%d profile %s active %s)", + child->pid, current->pid, + profile->parent->name, profile->name); + aa_put_profile(profile); + } else + aa_free_task_context(child_cxt); + + return 0; +} + +static struct aa_profile * +aa_register_find(struct aa_profile *profile, const char *name, char *buffer, + int mandatory, int complain) +{ + struct aa_profile *new_profile; + + /* Locate new profile */ + new_profile = aa_find_profile(name); + if (new_profile) { + AA_DEBUG("%s: setting profile %s\n", + __FUNCTION__, new_profile->name); + } else if (mandatory && profile) { + name = mangle(name, buffer); + if (complain) { + aa_audit_message(profile, GFP_KERNEL, "LOGPROF-HINT " + "missing_mandatory_profile image '%s' " + "(%d profile %s active %s)", + name, current->pid, + profile->parent->name, profile->name); + profile = aa_dup_profile(null_complain_profile); + } else { + aa_audit_message(profile, GFP_KERNEL, "REJECTING " + "exec(2) of image '%s'. Profile " + "mandatory and not found. " + "(%d profile %s active %s)", + name, current->pid, + profile->parent->name, profile->name); + return ERR_PTR(-EPERM); + } + } else { + /* Only way we can get into this code is if task + * is unconfined. + */ + AA_DEBUG("%s: No profile found for exec image '%s'\n", + __FUNCTION__, + name); + } + return new_profile; +} + +/** + * aa_register - register a new program + * @bprm: binprm of program being registered + * + * Try to register a new program during execve(). This should give the + * new program a valid aa_task_context if confined. + */ +int aa_register(struct linux_binprm *bprm) +{ + const char *filename; + char *buffer = NULL; + struct file *filp = bprm->file; + struct aa_profile *profile, *old_profile, *new_profile = NULL; + int exec_mode = AA_EXEC_UNSAFE, complain = 0; + + AA_DEBUG("%s\n", __FUNCTION__); + + filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt, &buffer, + AA_CHECK_MANGLE); + if (IS_ERR(filename)) { + AA_ERROR("%s: Failed to get filename", __FUNCTION__); + return -ENOENT; + } + +repeat: + profile = aa_get_profile(current); + if (profile) { + complain = PROFILE_COMPLAIN(profile); + + /* Confined task, determine what mode inherit, unconfined or + * mandatory to load new profile + */ + exec_mode = aa_match(profile->file_rules, filename); + + if (exec_mode & (MAY_EXEC | AA_EXEC_MODIFIERS)) { + switch (exec_mode & (MAY_EXEC | AA_EXEC_MODIFIERS)) { + case MAY_EXEC | AA_EXEC_INHERIT: + AA_DEBUG("%s: INHERIT %s\n", + __FUNCTION__, + filename); + /* nothing to be done here */ + goto cleanup; + + case MAY_EXEC | AA_EXEC_UNCONFINED: + AA_DEBUG("%s: UNCONFINED %s\n", + __FUNCTION__, + filename); + + /* detach current profile */ + new_profile = NULL; + break; + + case MAY_EXEC | AA_EXEC_PROFILE: + AA_DEBUG("%s: PROFILE %s\n", + __FUNCTION__, + filename); + new_profile = aa_register_find(profile, + filename, + buffer, 1, + complain); + break; + + default: + AA_ERROR("Rejecting exec(2) of image '%s'. " + "Unknown exec qualifier %x " + "(%d profile %s active %s)\n", + filename, + exec_mode & AA_EXEC_MODIFIERS, + current->pid, + profile->parent->name, + profile->name); + new_profile = ERR_PTR(-EPERM); + break; + } + + } else if (complain) { + /* There was no entry in calling profile + * describing mode to execute image in. + * Drop into null-profile (disabling secure exec). + */ + new_profile = aa_dup_profile(null_complain_profile); + exec_mode |= AA_EXEC_UNSAFE; + } else { + filename = mangle(filename, buffer); + aa_audit_message(profile, GFP_KERNEL, "REJECTING " + "exec(2) of image '%s'. Unable to " + "determine exec qualifier. " + "(%d profile %s active %s)", + filename, current->pid, + profile->parent->name, profile->name); + new_profile = ERR_PTR(-EPERM); + } + } else { + /* Unconfined task, load profile if it exists */ + new_profile = aa_register_find(NULL, filename, buffer, 0, 0); + if (new_profile == NULL) + goto cleanup; + } + + if (IS_ERR(new_profile)) + goto cleanup; + + old_profile = __aa_replace_profile(current, new_profile, 0); + if (IS_ERR(old_profile)) { + aa_put_profile(new_profile); + aa_put_profile(profile); + if (PTR_ERR(old_profile) == -ESTALE) + goto repeat; + if (PTR_ERR(old_profile) == -EPERM) { + filename = mangle(filename, buffer); + aa_audit_message(profile, GFP_KERNEL, + "REJECTING exec(2) of image '%s'. " + "Unable to change profile, ptraced by " + "%d. (%d profile %s active %s)", + filename, current->parent->pid, + current->pid, + profile->parent->name, profile->name); + } + new_profile = old_profile; + goto cleanup; + } + aa_put_profile(old_profile); + aa_put_profile(profile); + + /* Handle confined exec. + * Can be at this point for the following reasons: + * 1. unconfined switching to confined + * 2. confined switching to different confinement + * 3. confined switching to unconfined + * + * Cases 2 and 3 are marked as requiring secure exec + * (unless policy specified "unsafe exec") + */ + if (!(exec_mode & AA_EXEC_UNSAFE)) { + unsigned long bprm_flags; + + bprm_flags = AA_SECURE_EXEC_NEEDED; + bprm->security = (void*) + ((unsigned long)bprm->security | bprm_flags); + } + + if (complain && new_profile == null_complain_profile) + aa_audit_message(new_profile, GFP_ATOMIC, + "LOGPROF-HINT changing_profile " + "(%d profile %s active %s)", + current->pid, + new_profile->parent->name, new_profile->name); +cleanup: + aa_put_name_buffer(buffer); + if (IS_ERR(new_profile)) + return PTR_ERR(new_profile); + aa_put_profile(new_profile); + return 0; +} + +/** + * aa_release - release a task context + * @task: task being released + * + * This is called after a task has exited and the parent has reaped it. + */ +void aa_release(struct task_struct *task) +{ + struct aa_task_context *cxt; + struct aa_profile *profile; + /* + * While the task context is still on a profile's task context + * list, another process could replace the profile under us, + * leaving us with a locked profile that is no longer attached + * to this task. So after locking the profile, we check that + * the profile is still attached. The profile lock is + * sufficient to prevent the replacement race so we do not lock + * the task. + * + * lock_dep reports a false 'possible irq lock inversion dependency' + * between the profile lock and the task_lock. + * + * We also avoid taking the task_lock here because lock_dep + * would report another false {softirq-on-W} potential irq_lock + * inversion. + * + * If the task does not have a profile attached we are safe; + * nothing can race with us at this point. + */ + +repeat: + profile = aa_get_profile(task); + if (profile) { + lock_profile(profile); + cxt = aa_task_context(task); + if (unlikely(!cxt || cxt->profile != profile)) { + unlock_profile(profile); + aa_put_profile(profile); + goto repeat; + } + aa_change_task_context(task, NULL, NULL, 0); + unlock_profile(profile); + aa_put_profile(profile); + } +} + +/** + * do_change_hat - actually switch hats + * @hat_name: name of hat to switch to + * @new_cxt: new aa_task_context to use on profile change + * @hat_magic: new magic value to use + * + * Switch to a new hat. Returns %0 on success, error otherwise. + */ +static int do_change_hat(const char *hat_name, + struct aa_task_context *new_cxt, u64 hat_magic) +{ + struct aa_task_context *cxt = aa_task_context(current); + struct aa_profile *sub; + int error = 0; + + /* + * Note: the profile and sub-profiles cannot go away under us here; + * no need to grab an additional reference count. + */ + sub = __aa_find_profile(hat_name, &cxt->profile->parent->sub); + + if ((current->ptrace & PT_PTRACED) && aa_may_ptrace(cxt, sub)) + return -EPERM; + + if (sub) { + /* change hat */ + aa_change_task_context(current, new_cxt, sub, hat_magic); + } else { + struct aa_profile *profile = cxt->profile; + + if (APPARMOR_COMPLAIN(cxt)) { + aa_audit_message(profile, GFP_ATOMIC, + "LOGPROF-HINT unknown_hat %s " + "(%d profile %s active %s)", + hat_name, current->pid, + profile->parent->name, profile->name); + } else { + AA_DEBUG("%s: Unknown hatname '%s'. " + "Changing to NULL profile " + "(%d profile %s active %s)\n", + __FUNCTION__, + hat_name, + current->pid, + profile->parent->name, + profile->name); + error = -EACCES; + } + /* + * Switch to the NULL profile: it grants no accesses, so in + * learning mode all accesses will get logged, and in enforce + * mode all accesses will be denied. + * + * In learning mode, this allows us to learn about new hats. + */ + aa_change_task_context(current, new_cxt, + cxt->profile->null_profile, hat_magic); + } + + return error; +} + +/** + * aa_change_hat - change hat to/from subprofile + * @hat_name: hat to change to + * @hat_magic: magic cookie to validate the hat change + * + * Change to new @hat_name, and store the @hat_magic in the current task + * context. If the new @hat_name is %NULL and the @hat_magic matches that + * stored in the current task context and is not 0, return to the top level + * profile. + * Returns %0 on success, error otherwise. + */ +int aa_change_hat(const char *hat_name, u64 hat_magic) +{ + struct aa_task_context *cxt, *new_cxt; + struct aa_profile *profile = NULL; + int error = 0; + + /* Dump out above debugging in WARN mode if we are in AUDIT mode */ + if (APPARMOR_AUDIT(aa_task_context(current))) { + aa_audit_message(NULL, GFP_KERNEL, "change_hat %s, 0x%llx " + "(pid %d)", + hat_name ? hat_name : "NULL", hat_magic, + current->pid); + } + + new_cxt = aa_alloc_task_context(GFP_KERNEL); + if (!new_cxt) + return -ENOMEM; + + cxt = lock_task_and_profiles(current, NULL); + if (!cxt) { + /* An unconfined process cannot change_hat(). */ + error = -EPERM; + goto out; + } + + /* No need to get reference count: we do not sleep. */ + profile = cxt->profile; + + /* check to see if the confined process has any hats. */ + if (list_empty(&profile->parent->sub) && !PROFILE_COMPLAIN(profile)) { + error = -ECHILD; + goto out; + } + + if (profile == profile->parent) { + /* We are in the parent profile. */ + if (hat_name) { + AA_DEBUG("%s: switching to %s, 0x%llx\n", + __FUNCTION__, + hat_name, + hat_magic); + error = do_change_hat(hat_name, new_cxt, hat_magic); + } + } else { + /* + * We are in a child profile. + * + * Check to make sure magic is same as what was passed when + * we switched into this profile. Handle special casing of + * NULL magic which confines task to subprofile and prohibits + * further change_hats. + */ + if (hat_magic && hat_magic == cxt->hat_magic) { + if (!hat_name) { + /* Return from subprofile back to parent. */ + aa_change_task_context(current, new_cxt, + profile->parent, 0); + } else { + /* + * Change to another (sibling) profile, and + * stick with the same hat_magic. + */ + error = do_change_hat(hat_name, new_cxt, + cxt->hat_magic); + } + } else if (cxt->hat_magic) { + AA_ERROR("KILLING process %d " + "Invalid change_hat() magic# 0x%llx " + "(hatname %s profile %s active %s)\n", + current->pid, hat_magic, + hat_name ? hat_name : "NULL", + profile->parent->name, + profile->name); + + /* terminate current process */ + (void)send_sig_info(SIGKILL, NULL, current); + } else { /* cxt->hat_magic == 0 */ + AA_ERROR("KILLING process %d " + "Task was confined to current subprofile " + "(profile %s active %s)\n", + current->pid, + profile->parent->name, + profile->name); + + /* terminate current process */ + (void)send_sig_info(SIGKILL, NULL, current); + } + + } + +out: + if (aa_task_context(current) != new_cxt) + aa_free_task_context(new_cxt); + task_unlock(current); + unlock_profile(profile); + return error; +} + +/** + * __aa_replace_profile - replace a task's profile + * @task: task to switch the profile of + * @profile: profile to switch to + * @hat_magic: magic cookie to switch to + * + * Returns a handle to the previous profile upon success, or else an + * error code. + */ +struct aa_profile *__aa_replace_profile(struct task_struct *task, + struct aa_profile *profile, + u32 hat_magic) +{ + struct aa_task_context *cxt, *new_cxt = NULL; + struct aa_profile *old_profile = NULL; + + if (profile) { + new_cxt = aa_alloc_task_context(GFP_KERNEL); + if (!new_cxt) + return ERR_PTR(-ENOMEM); + } + + cxt = lock_task_and_profiles(task, profile); + if (unlikely(profile && profile->isstale)) { + task_unlock(task); + unlock_both_profiles(profile, cxt ? cxt->profile : NULL); + aa_free_task_context(new_cxt); + return ERR_PTR(-ESTALE); + } + + if ((current->ptrace & PT_PTRACED) && aa_may_ptrace(cxt, profile)) { + task_unlock(task); + unlock_both_profiles(profile, cxt ? cxt->profile : NULL); + aa_free_task_context(new_cxt); + return ERR_PTR(-EPERM); + } + + if (cxt) { + old_profile = aa_dup_profile(cxt->profile); + aa_change_task_context(task, new_cxt, profile, cxt->hat_magic); + } else + aa_change_task_context(task, new_cxt, profile, 0); + + task_unlock(task); + unlock_both_profiles(profile, old_profile); + return old_profile; +} + +/** + * lock_task_and_profile - lock the task and confining profiles and @profile + * @task - task to lock + * @profile - extra profile to lock in addition to the current profile + * + * Handle the spinning on locking to make sure the task context and + * profile are consistent once all locks are aquired. + * + * return the aa_task_context currently confining the task. The task lock + * will be held whether or not the task is confined. + */ +struct aa_task_context * +lock_task_and_profiles(struct task_struct *task, struct aa_profile *profile) +{ + struct aa_task_context *cxt; + struct aa_profile *old_profile = NULL; + + rcu_read_lock(); +repeat: + cxt = aa_task_context(task); + if (cxt) + old_profile = cxt->profile; + lock_both_profiles(profile, old_profile); + task_lock(task); + + /* check for race with profile transition, replacement or removal */ + if (unlikely(cxt != aa_task_context(task))) { + task_unlock(task); + unlock_both_profiles(profile, old_profile); + goto repeat; + } + rcu_read_unlock(); + return cxt; +} + +static void free_aa_task_context_rcu_callback(struct rcu_head *head) +{ + struct aa_task_context *cxt; + + cxt = container_of(head, struct aa_task_context, rcu); + aa_free_task_context(cxt); +} + +/** + * aa_change_task_context - switch a task to use a new context and profile + * @task: task that is having its task context changed + * @new_cxt: new task context to use after the switch + * @profile: new profile to use after the switch + * @hat_magic: hat value to switch to (0 for no hat) + */ +void aa_change_task_context(struct task_struct *task, + struct aa_task_context *new_cxt, + struct aa_profile *profile, u64 hat_magic) +{ + struct aa_task_context *old_cxt = aa_task_context(task); + + if (old_cxt) { + list_del_init(&old_cxt->list); + call_rcu(&old_cxt->rcu, free_aa_task_context_rcu_callback); + } + if (new_cxt) { + /* clear the caps_logged cache, so that new profile/hat has + * chance to emit its own set of cap messages */ + new_cxt->caps_logged = CAP_EMPTY_SET; + new_cxt->hat_magic = hat_magic; + new_cxt->task = task; + new_cxt->profile = aa_dup_profile(profile); + list_move(&new_cxt->list, &profile->parent->task_contexts); + } + rcu_assign_pointer(task->security, new_cxt); +} -- - To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html