lkml.org 
[lkml]   [2007]   [May]   [14]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    From
    Subject[AppArmor 37/45] AppArmor: Main Part
    The underlying functions by which the AppArmor LSM hooks are implemented.

    Signed-off-by: John Johansen <jjohansen@suse.de>
    Signed-off-by: Andreas Gruenbacher <agruen@suse.de>

    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-kernel" in
    the body of a message to majordomo@vger.kernel.org
    More majordomo info at http://vger.kernel.org/majordomo-info.html
    Please read the FAQ at http://www.tux.org/lkml/

    \
     
     \ /
      Last update: 2007-05-14 13:57    [W:0.092 / U:62.052 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site