2007-05-12 22:13:13 +00:00
|
|
|
Index: b/security/apparmor/main.c
|
|
|
|
===================================================================
|
2007-04-11 00:15:58 +00:00
|
|
|
--- /dev/null
|
|
|
|
+++ b/security/apparmor/main.c
|
2007-05-12 22:13:13 +00:00
|
|
|
@@ -0,0 +1,1399 @@
|
2007-04-11 00:15:58 +00:00
|
|
|
+/*
|
|
|
|
+ * 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)
|
|
|
|
+{
|
2007-04-27 11:20:11 +00:00
|
|
|
+ return (mask & ~aa_match(profile->file_rules, name));
|
2007-04-11 00:15:58 +00:00
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 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);
|
|
|
|
+
|
2007-05-12 22:13:13 +00:00
|
|
|
+ /* Link always requires 'l' on the link, a subset of the
|
2007-04-11 00:15:58 +00:00
|
|
|
+ * 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
|
2007-05-12 22:13:13 +00:00
|
|
|
+ /* FIXME: There currenly is no way to report which permissions
|
2007-04-11 00:15:58 +00:00
|
|
|
+ * we expect in t_mode, so linking could fail even after learning
|
|
|
|
+ * the required l_mode.
|
|
|
|
+ */
|
|
|
|
+ return AA_MAY_LINK;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
2007-04-27 11:20:11 +00:00
|
|
|
+ * mangle -- escape special characters in str
|
|
|
|
+ * @str: string to escape
|
|
|
|
+ * @buffer: buffer containing str
|
2007-05-09 13:29:44 +00:00
|
|
|
+ *
|
|
|
|
+ * 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.
|
2007-04-27 11:20:11 +00:00
|
|
|
+ */
|
2007-05-09 13:29:44 +00:00
|
|
|
+static const char *mangle(const char *str, char *buffer)
|
2007-04-27 11:20:11 +00:00
|
|
|
+{
|
|
|
|
+ static const char c_escape[] = {
|
|
|
|
+ ['\a'] = 'a', ['\b'] = 'b',
|
|
|
|
+ ['\f'] = 'f', ['\n'] = 'n',
|
|
|
|
+ ['\r'] = 'r', ['\t'] = 't',
|
|
|
|
+ ['\v'] = 'v',
|
|
|
|
+ [' '] = ' ', ['\\'] = '\\',
|
|
|
|
+ };
|
2007-05-09 13:29:44 +00:00
|
|
|
+ const char *s;
|
|
|
|
+ char *t, c;
|
2007-04-27 11:20:11 +00:00
|
|
|
+
|
|
|
|
+#define mangle_escape(c) \
|
|
|
|
+ unlikely((unsigned char)(c) < ARRAY_SIZE(c_escape) && \
|
|
|
|
+ c_escape[(unsigned char)c])
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+ for (s = (char *)str; (c = *s) != '\0'; s++)
|
2007-04-27 11:20:11 +00:00
|
|
|
+ if (mangle_escape(c))
|
|
|
|
+ goto escape;
|
|
|
|
+ return str;
|
|
|
|
+
|
|
|
|
+escape:
|
2007-05-09 13:29:44 +00:00
|
|
|
+ if (!buffer)
|
|
|
|
+ return NULL;
|
2007-04-27 11:20:11 +00:00
|
|
|
+ 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
|
2007-05-09 13:29:44 +00:00
|
|
|
+
|
2007-04-27 11:20:11 +00:00
|
|
|
+ return buffer;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
2007-04-11 00:15:58 +00:00
|
|
|
+ * 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);
|
2007-05-09 13:29:44 +00:00
|
|
|
+
|
|
|
|
+ /* 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);
|
|
|
|
+
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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;
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+again:
|
|
|
|
+ sa->buffer = NULL;
|
|
|
|
+ sa->name = aa_get_name(dentry, mnt, &sa->buffer, check);
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ 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;
|
2007-04-13 00:18:23 +00:00
|
|
|
+ } else
|
2007-04-11 00:15:58 +00:00
|
|
|
+ denied_mask = aa_file_denied(profile, sa->name, mask);
|
|
|
|
+
|
|
|
|
+ aa_permerror2result(denied_mask, sa);
|
|
|
|
+
|
|
|
|
+ error = aa_audit(profile, sa);
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+ aa_put_name_buffer(sa->buffer);
|
|
|
|
+ if (error == -ENAMETOOLONG) {
|
|
|
|
+ BUG_ON(check & AA_CHECK_MANGLE);
|
|
|
|
+ check |= AA_CHECK_MANGLE;
|
|
|
|
+ goto again;
|
|
|
|
+ }
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ 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
|
|
|
|
+ */
|
2007-05-09 13:29:44 +00:00
|
|
|
+int aa_audit_message(struct aa_profile *profile, gfp_t gfp, const char *fmt,
|
|
|
|
+ ...)
|
2007-04-11 00:15:58 +00:00
|
|
|
+{
|
|
|
|
+ int ret;
|
|
|
|
+ struct aa_audit sa;
|
|
|
|
+
|
|
|
|
+ sa.type = AA_AUDITTYPE_MSG;
|
|
|
|
+ sa.name = fmt;
|
|
|
|
+ va_start(sa.vaval, fmt);
|
2007-05-09 13:29:44 +00:00
|
|
|
+ sa.flags = 0;
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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
|
|
|
|
+ */
|
2007-05-09 13:29:44 +00:00
|
|
|
+int aa_audit(struct aa_profile *profile, struct aa_audit *sa)
|
2007-04-11 00:15:58 +00:00
|
|
|
+{
|
|
|
|
+ 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;
|
|
|
|
+ }
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+ 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;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
2007-04-11 00:15:58 +00:00
|
|
|
+ /* log operation */
|
|
|
|
+
|
|
|
|
+ audit_log_format(ab, "%s ", logcls); /* REJECTING/ALLOWING/etc */
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+#define NOFLAGS(x) ((x) & ~(AA_MANGLE_NAME | AA_MANGLE_NAME2))
|
|
|
|
+
|
|
|
|
+ switch(NOFLAGS(sa->type)) {
|
|
|
|
+ case NOFLAGS(AA_AUDITTYPE_FILE): {
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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;
|
2007-04-27 11:20:11 +00:00
|
|
|
+ break;
|
|
|
|
+ }
|
2007-05-09 13:29:44 +00:00
|
|
|
+ case NOFLAGS(AA_AUDITTYPE_DIR):
|
2007-04-27 11:20:11 +00:00
|
|
|
+ audit_log_format(ab, "%s on %s ", sa->name2, sa->name);
|
|
|
|
+ break;
|
2007-05-09 13:29:44 +00:00
|
|
|
+ case NOFLAGS(AA_AUDITTYPE_ATTR): {
|
2007-04-27 11:20:11 +00:00
|
|
|
+ struct iattr *iattr = sa->iattr;
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ 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);
|
2007-04-27 11:20:11 +00:00
|
|
|
+ break;
|
|
|
|
+ }
|
2007-05-09 13:29:44 +00:00
|
|
|
+ case NOFLAGS(AA_AUDITTYPE_XATTR):
|
2007-04-27 11:20:11 +00:00
|
|
|
+ audit_log_format(ab, "%s on %s ", sa->name2, sa->name);
|
|
|
|
+ break;
|
2007-05-09 13:29:44 +00:00
|
|
|
+ case NOFLAGS(AA_AUDITTYPE_LINK):
|
2007-04-27 11:20:11 +00:00
|
|
|
+ audit_log_format(ab, "link access from %s to %s ", sa->name,
|
2007-05-09 13:29:44 +00:00
|
|
|
+ sa->name2);
|
2007-04-27 11:20:11 +00:00
|
|
|
+ break;
|
2007-05-09 13:29:44 +00:00
|
|
|
+ case NOFLAGS(AA_AUDITTYPE_CAP):
|
2007-04-27 11:20:11 +00:00
|
|
|
+ audit_log_format(ab, "access to capability '%s' ",
|
2007-04-11 00:15:58 +00:00
|
|
|
+ capability_names[sa->capability]);
|
|
|
|
+ opspec_error = -EPERM;
|
2007-04-27 11:20:11 +00:00
|
|
|
+ break;
|
2007-05-09 13:29:44 +00:00
|
|
|
+ case NOFLAGS(AA_AUDITTYPE_SYSCALL):
|
2007-04-11 00:15:58 +00:00
|
|
|
+ audit_log_format(ab, "access to syscall '%s' ", sa->name);
|
|
|
|
+ opspec_error = -EPERM;
|
2007-04-27 11:20:11 +00:00
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ WARN_ON(1);
|
|
|
|
+ return error;
|
2007-04-11 00:15:58 +00:00
|
|
|
+ }
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+#undef NOFLAGS
|
2007-04-27 11:20:11 +00:00
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+ audit_log_format(ab, "(%d profile %s active %s)",
|
|
|
|
+ current->pid, profile->parent->name, profile->name);
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ 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;
|
2007-04-27 11:20:11 +00:00
|
|
|
+ sa.iattr = iattr;
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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,
|
2007-04-27 11:20:11 +00:00
|
|
|
+ int mask, int check)
|
2007-04-11 00:15:58 +00:00
|
|
|
+{
|
|
|
|
+ struct inode *inode = dentry->d_inode;
|
|
|
|
+ int error;
|
|
|
|
+ struct aa_audit sa;
|
|
|
|
+
|
|
|
|
+ sa.type = AA_AUDITTYPE_XATTR;
|
2007-04-27 11:20:11 +00:00
|
|
|
+ sa.name2 = operation;
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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;
|
2007-04-27 11:20:11 +00:00
|
|
|
+ sa.name2 = operation;
|
2007-04-11 00:15:58 +00:00
|
|
|
+ sa.flags = 0;
|
|
|
|
+ sa.gfp_mask = GFP_KERNEL;
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+ return aa_perm_dentry(profile, dentry, mnt, &sa, mask, AA_CHECK_DIR);
|
2007-04-11 00:15:58 +00:00
|
|
|
+}
|
|
|
|
+
|
2007-04-27 19:02:03 +00:00
|
|
|
+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);
|
|
|
|
+}
|
|
|
|
+
|
2007-04-11 00:15:58 +00:00
|
|
|
+/**
|
|
|
|
+ * 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)
|
|
|
|
+{
|
2007-05-09 13:29:44 +00:00
|
|
|
+ int denied_mask = -EPERM, error, check = 0;
|
2007-04-11 00:15:58 +00:00
|
|
|
+ struct aa_audit sa;
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+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);
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ if (IS_ERR(sa.name)) {
|
|
|
|
+ denied_mask = PTR_ERR(sa.name);
|
|
|
|
+ sa.name = NULL;
|
|
|
|
+ }
|
2007-04-27 11:20:11 +00:00
|
|
|
+ if (IS_ERR(sa.name2)) {
|
|
|
|
+ denied_mask = PTR_ERR(sa.name2);
|
|
|
|
+ sa.name2 = NULL;
|
2007-04-11 00:15:58 +00:00
|
|
|
+ }
|
|
|
|
+
|
2007-04-27 11:20:11 +00:00
|
|
|
+ if (sa.name && sa.name2)
|
|
|
|
+ denied_mask = aa_link_denied(profile, sa.name, sa.name2);
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ aa_permerror2result(denied_mask, &sa);
|
|
|
|
+
|
|
|
|
+ sa.type = AA_AUDITTYPE_LINK;
|
|
|
|
+ sa.flags = 0;
|
|
|
|
+ sa.gfp_mask = GFP_KERNEL;
|
|
|
|
+
|
|
|
|
+ error = aa_audit(profile, &sa);
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+ 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;
|
|
|
|
+ }
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ 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)
|
2007-05-09 13:29:44 +00:00
|
|
|
+ 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);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+ } else
|
|
|
|
+ aa_free_task_context(child_cxt);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static struct aa_profile *
|
2007-05-09 13:29:44 +00:00
|
|
|
+aa_register_find(struct aa_profile *profile, const char *name, char *buffer,
|
|
|
|
+ int mandatory, int complain)
|
2007-04-11 00:15:58 +00:00
|
|
|
+{
|
|
|
|
+ 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) {
|
2007-05-09 13:29:44 +00:00
|
|
|
+ name = mangle(name, buffer);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ if (complain) {
|
2007-05-09 13:29:44 +00:00
|
|
|
+ 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);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ profile = aa_dup_profile(null_complain_profile);
|
|
|
|
+ } else {
|
2007-05-09 13:29:44 +00:00
|
|
|
+ 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);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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)
|
|
|
|
+{
|
2007-05-09 13:29:44 +00:00
|
|
|
+ const char *filename;
|
|
|
|
+ char *buffer = NULL;
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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__);
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+ filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt, &buffer,
|
|
|
|
+ AA_CHECK_MANGLE);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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,
|
2007-05-09 13:29:44 +00:00
|
|
|
+ filename,
|
|
|
|
+ buffer, 1,
|
2007-04-11 00:15:58 +00:00
|
|
|
+ complain);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ AA_ERROR("Rejecting exec(2) of image '%s'. "
|
|
|
|
+ "Unknown exec qualifier %x "
|
2007-05-09 13:29:44 +00:00
|
|
|
+ "(%d profile %s active %s)\n",
|
2007-04-11 00:15:58 +00:00
|
|
|
+ filename,
|
|
|
|
+ exec_mode & AA_EXEC_MODIFIERS,
|
2007-05-09 13:29:44 +00:00
|
|
|
+ current->pid,
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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 {
|
2007-05-09 13:29:44 +00:00
|
|
|
+ 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);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ new_profile = ERR_PTR(-EPERM);
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ /* Unconfined task, load profile if it exists */
|
2007-05-09 13:29:44 +00:00
|
|
|
+ new_profile = aa_register_find(NULL, filename, buffer, 0, 0);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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;
|
2007-05-09 13:29:44 +00:00
|
|
|
+ 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);
|
|
|
|
+ }
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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)
|
2007-05-09 13:29:44 +00:00
|
|
|
+ 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);
|
2007-04-11 00:15:58 +00:00
|
|
|
+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)) {
|
2007-05-09 13:29:44 +00:00
|
|
|
+ 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);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ } else {
|
|
|
|
+ AA_DEBUG("%s: Unknown hatname '%s'. "
|
|
|
|
+ "Changing to NULL profile "
|
2007-05-09 13:29:44 +00:00
|
|
|
+ "(%d profile %s active %s)\n",
|
2007-04-11 00:15:58 +00:00
|
|
|
+ __FUNCTION__,
|
|
|
|
+ hat_name,
|
2007-05-09 13:29:44 +00:00
|
|
|
+ current->pid,
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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))) {
|
2007-05-09 13:29:44 +00:00
|
|
|
+ aa_audit_message(NULL, GFP_KERNEL, "change_hat %s, 0x%llx "
|
|
|
|
+ "(pid %d)",
|
|
|
|
+ hat_name ? hat_name : "NULL", hat_magic,
|
|
|
|
+ current->pid);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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) {
|
2007-05-09 13:29:44 +00:00
|
|
|
+ AA_ERROR("KILLING process %d "
|
2007-04-11 00:15:58 +00:00
|
|
|
+ "Invalid change_hat() magic# 0x%llx "
|
|
|
|
+ "(hatname %s profile %s active %s)\n",
|
2007-05-09 13:29:44 +00:00
|
|
|
+ current->pid, hat_magic,
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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 */
|
2007-05-09 13:29:44 +00:00
|
|
|
+ AA_ERROR("KILLING process %d "
|
2007-04-11 00:15:58 +00:00
|
|
|
+ "Task was confined to current subprofile "
|
|
|
|
+ "(profile %s active %s)\n",
|
2007-05-09 13:29:44 +00:00
|
|
|
+ current->pid,
|
2007-04-11 00:15:58 +00:00
|
|
|
+ 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);
|
|
|
|
+}
|