apparmor/kernel-patches/for-mainline/apparmor.diff
Andreas Gruenbacher 5673ac6b2d - Fold minor cleanups into apparmor.diff.
- Add more substantial cleanups as separate patches initially.
2007-02-09 10:12:40 +00:00

5025 lines
127 KiB
Diff

Index: b/security/apparmor/Kconfig
===================================================================
--- /dev/null
+++ b/security/apparmor/Kconfig
@@ -0,0 +1,9 @@
+config SECURITY_APPARMOR
+ tristate "AppArmor support"
+ depends on SECURITY!=n
+ help
+ This enables the AppArmor security module.
+ Required userspace tools (if they are not included in your
+ distribution) and further information may be found at
+ <http://forge.novell.com/modules/xfmod/project/?apparmor>
+ If you are unsure how to answer this question, answer N.
Index: b/security/apparmor/Makefile
===================================================================
--- /dev/null
+++ b/security/apparmor/Makefile
@@ -0,0 +1,6 @@
+# Makefile for AppArmor Linux Security Module
+#
+obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
+
+apparmor-y := main.o list.o procattr.o lsm.o apparmorfs.o capabilities.o \
+ module_interface.o match.o
Index: b/security/apparmor/apparmor.h
===================================================================
--- /dev/null
+++ b/security/apparmor/apparmor.h
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 1998-2005 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 internal prototypes
+ */
+
+#ifndef __APPARMOR_H
+#define __APPARMOR_H
+
+#include <linux/fs.h> /* Include for defn of iattr */
+#include <linux/binfmts.h> /* defn of linux_binprm */
+#include <linux/rcupdate.h>
+
+#include "shared.h"
+#include "match.h"
+
+/* Control parameters (0 or 1), settable thru module/boot flags or
+ * via /sys/kernel/security/apparmor/control */
+extern int apparmor_complain;
+extern int apparmor_debug;
+extern int apparmor_audit;
+extern int apparmor_logsyscall;
+
+static inline int mediated_filesystem(struct inode *inode)
+{
+ return !(inode->i_sb->s_flags & MS_NOUSER);
+}
+
+#define PROFILE_COMPLAIN(_profile) \
+ (apparmor_complain == 1 || ((_profile) && (_profile)->flags.complain))
+
+#define SUBDOMAIN_COMPLAIN(_sd) \
+ (apparmor_complain == 1 || \
+ ((_sd) && (_sd)->active && (_sd)->active->flags.complain))
+
+#define PROFILE_AUDIT(_profile) \
+ (apparmor_audit == 1 || ((_profile) && (_profile)->flags.audit))
+
+#define SUBDOMAIN_AUDIT(_sd) \
+ (apparmor_audit == 1 || \
+ ((_sd) && (_sd)->active && (_sd)->active->flags.audit))
+
+/*
+ * DEBUG remains global (no per profile flag) since it is mostly used in sysctl
+ * which is not related to profile accesses.
+ */
+
+#define AA_DEBUG(fmt, args...) \
+ do { \
+ if (apparmor_debug) \
+ printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
+ } while (0)
+#define AA_INFO(fmt, args...) printk(KERN_INFO "AppArmor: " fmt, ##args)
+#define AA_WARN(fmt, args...) printk(KERN_WARNING "AppArmor: " fmt, ##args)
+#define AA_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args)
+
+/* basic AppArmor data structures */
+
+struct flagval {
+ int debug;
+ int complain;
+ int audit;
+};
+
+#define AA_SECURE_EXEC_NEEDED 0x00000001
+
+#define AA_EXEC_MODIFIER_MASK(mask) ((mask) & AA_EXEC_MODIFIERS)
+#define AA_EXEC_MASK(mask) ((mask) & (AA_EXEC_MODIFIERS | AA_EXEC_UNSAFE))
+
+/* struct aa_profile - basic confinement data
+ * @parent: non refcounted pointer to parent profile
+ * @name: the profiles name
+ * @file_rules: dfa containing the profiles file rules
+ * @list: list this profile is on
+ * @sub: profiles list of subprofiles (HATS)
+ * @flags: flags controlling profile behavior
+ * @null_profile: if needed per profile learning and null confinement profile
+ * @isstale: flag to indicate the profile is stale
+ * @capabilities: capabilities granted by the process
+ * @rcu: rcu head used when freeing the profile
+ * @count: reference count of the profile
+ *
+ * The AppArmor profile contains the basic confinement data. Each profile
+ * has a name and potentially a list of profile entries. The profiles are
+ * connected in a list
+ */
+struct aa_profile {
+ struct aa_profile *parent;
+ char *name;
+
+ struct aa_dfa *file_rules;
+
+ struct list_head list;
+ struct list_head sub;
+ struct flagval flags;
+ struct aa_profile *null_profile;
+ int isstale;
+
+ kernel_cap_t capabilities;
+
+ struct rcu_head rcu;
+
+ struct kref count;
+};
+
+/**
+ * struct subdomain - primary label for confined tasks
+ * @active: the current active profile
+ * @hat_magic: the magic token controling the ability to leave a hat
+ * @list: list this subdomain is on
+ * @task: task that the subdomain confines
+ *
+ * Contains the tasks current active profile (which could change due to
+ * change_hat). Plus the hat_magic needed during change_hat.
+ *
+ * N.B AppArmor's previous product name SubDomain was derived from the name
+ * of this structure/concept (changehat reducing a task into a sub-domain).
+ */
+struct subdomain {
+ struct aa_profile *active; /* The current active profile */
+ u32 hat_magic; /* used with change_hat */
+ struct list_head list; /* list of subdomains */
+ struct task_struct *task;
+};
+
+typedef int (*aa_iter) (struct subdomain *, void *);
+
+#define AA_SUBDOMAIN(sec) ((struct subdomain*)(sec))
+#define AA_PROFILE(sec) ((struct aa_profile*)(sec))
+
+/* Lock protecting access to 'struct subdomain' accesses */
+extern spinlock_t sd_lock;
+
+extern struct aa_profile *null_complain_profile;
+
+/* aa_audit - AppArmor auditing structure
+ * Structure is populated by access control code and passed to aa_audit which
+ * provides for a single point of logging.
+ */
+
+struct aa_audit {
+ unsigned short type, flags;
+ unsigned int result;
+ gfp_t gfp_mask;
+ int error_code;
+
+ const char *operation;
+ const char *name;
+ union {
+ int capability;
+ int mask;
+ };
+ union {
+ const void *pval;
+ va_list vaval;
+ };
+};
+
+/* audit types */
+#define AA_AUDITTYPE_FILE 1
+#define AA_AUDITTYPE_DIR 2
+#define AA_AUDITTYPE_ATTR 3
+#define AA_AUDITTYPE_XATTR 4
+#define AA_AUDITTYPE_LINK 5
+#define AA_AUDITTYPE_CAP 6
+#define AA_AUDITTYPE_MSG 7
+#define AA_AUDITTYPE_SYSCALL 8
+#define AA_AUDITTYPE__END 9
+
+/* audit flags */
+#define AA_AUDITFLAG_AUDITSS_SYSCALL 1 /* log syscall context */
+#define AA_AUDITFLAG_LOGERR 2 /* log operations that failed due to
+ non permission errors */
+
+#define HINT_UNKNOWN_HAT "unknown_hat"
+#define HINT_FORK "fork"
+#define HINT_MANDPROF "missing_mandatory_profile"
+#define HINT_CHGPROF "changing_profile"
+
+#define LOG_HINT(p, gfp, hint, fmt, args...) \
+ do {\
+ aa_audit_message(p, gfp, 0, \
+ "LOGPROF-HINT " hint " " fmt, ##args);\
+ } while(0)
+
+#define BASE_PROFILE(p) ((p)->parent ? (p)->parent : (p))
+#define IN_SUBPROFILE(p) ((p)->parent)
+
+/* main.c */
+extern int alloc_null_complain_profile(void);
+extern void free_null_complain_profile(void);
+extern int attach_nullprofile(struct aa_profile *profile);
+extern int aa_audit_message(struct aa_profile *active, gfp_t gfp, int,
+ const char *, ...);
+extern int aa_audit_syscallreject(struct aa_profile *active, gfp_t gfp,
+ const char *);
+extern int aa_audit(struct aa_profile *active, const struct aa_audit *);
+extern char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt);
+
+extern int aa_attr(struct aa_profile *active, struct dentry *dentry,
+ struct vfsmount *mnt, struct iattr *iattr);
+extern int aa_perm_xattr(struct aa_profile *active, struct dentry *dentry,
+ struct vfsmount *mnt, const char *operation,
+ const char *xattr_xattr, int mask);
+extern int aa_capability(struct aa_profile *active, int cap);
+extern int aa_perm(struct aa_profile *active, struct dentry *dentry,
+ struct vfsmount *mnt, int mask);
+extern int aa_perm_dir(struct aa_profile *active, struct dentry *dentry,
+ struct vfsmount *mnt, const char *operation, int mask);
+extern int aa_link(struct aa_profile *active,
+ struct dentry *link, struct vfsmount *link_mnt,
+ struct dentry *target, struct vfsmount *target_mnt);
+extern int aa_fork(struct task_struct *p);
+extern int aa_register(struct linux_binprm *bprm);
+extern void aa_release(struct task_struct *p);
+extern int aa_change_hat(const char *id, u32 hat_magic);
+extern int aa_associate_filp(struct file *filp);
+
+/* list.c */
+extern struct aa_profile *aa_profilelist_find(const char *name);
+extern int aa_profilelist_add(struct aa_profile *profile);
+extern struct aa_profile *aa_profilelist_remove(const char *name);
+extern void aa_profilelist_release(void);
+extern struct aa_profile *aa_profilelist_replace(struct aa_profile *profile);
+extern void aa_profile_dump(struct aa_profile *);
+extern void aa_profilelist_dump(void);
+extern void aa_subdomainlist_add(struct subdomain *);
+extern void aa_subdomainlist_remove(struct subdomain *);
+extern void aa_subdomainlist_iterate(aa_iter, void *);
+extern void aa_subdomainlist_iterateremove(aa_iter, void *);
+extern void aa_subdomainlist_release(void);
+
+/* module_interface.c */
+extern ssize_t aa_file_prof_add(void *, size_t);
+extern ssize_t aa_file_prof_repl(void *, size_t);
+extern ssize_t aa_file_prof_remove(const char *, size_t);
+extern void free_aa_profile(struct aa_profile *profile);
+extern void free_aa_profile_kref(struct kref *kref);
+
+/* procattr.c */
+extern size_t aa_getprocattr(struct aa_profile *active, char *str, size_t size);
+extern int aa_setprocattr_changehat(char *hatinfo, size_t infosize);
+extern int aa_setprocattr_setprofile(struct task_struct *p, char *profilename,
+ size_t profilesize);
+
+/* apparmorfs.c */
+extern int create_apparmorfs(void);
+extern void destroy_apparmorfs(void);
+
+/* capabilities.c */
+extern const char *capability_to_name(unsigned int cap);
+
+/* match.c */
+struct aa_dfa *aa_match_alloc(void);
+void aa_match_free(struct aa_dfa *dfa);
+int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size);
+int verify_dfa(struct aa_dfa *dfa);
+const char *aa_match_features(void);
+unsigned int aa_match(struct aa_dfa *dfa, const char *pathname);
+
+#endif /* __APPARMOR_H */
Index: b/security/apparmor/apparmorfs.c
===================================================================
--- /dev/null
+++ b/security/apparmor/apparmorfs.c
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2005 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 filesystem (part of securityfs)
+ */
+
+#include <linux/security.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <asm/uaccess.h>
+
+#include "apparmor.h"
+#include "inline.h"
+
+#define SECFS_AA "apparmor"
+static struct dentry *aa_fs_dentry = NULL;
+
+/* profile */
+extern struct seq_operations apparmorfs_profiles_op;
+static int aa_prof_open(struct inode *inode, struct file *file);
+static int aa_prof_release(struct inode *inode, struct file *file);
+
+static struct file_operations apparmorfs_profiles_fops = {
+ .open = aa_prof_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = aa_prof_release,
+};
+
+/* matching */
+static ssize_t aa_matching_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos);
+
+static struct file_operations apparmorfs_matching_fops = {
+ .read = aa_matching_read,
+};
+
+
+/* interface */
+static ssize_t aa_profile_load(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos);
+static ssize_t aa_profile_replace(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos);
+static ssize_t aa_profile_remove(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos);
+
+static struct file_operations apparmorfs_profile_load = {
+ .write = aa_profile_load
+};
+
+static struct file_operations apparmorfs_profile_replace = {
+ .write = aa_profile_replace
+};
+
+static struct file_operations apparmorfs_profile_remove = {
+ .write = aa_profile_remove
+};
+
+
+/* control */
+static u64 aa_control_get(void *data);
+static void aa_control_set(void *data, u64 val);
+
+DEFINE_SIMPLE_ATTRIBUTE(apparmorfs_control_fops, aa_control_get,
+ aa_control_set, "%lld\n");
+
+
+
+/* table of static entries */
+
+static struct root_entry {
+ const char *name;
+ int mode;
+ int access;
+ struct file_operations *fops;
+ void *data;
+
+ /* internal fields */
+ struct dentry *dentry;
+ int parent_index;
+} root_entries[] = {
+ /* our root, normally /sys/kernel/security/apparmor */
+ {SECFS_AA, S_IFDIR, 0550}, /* DO NOT EDIT/MOVE */
+
+ /* interface for obtaining list of profiles currently loaded */
+ {"profiles", S_IFREG, 0440, &apparmorfs_profiles_fops,
+ NULL},
+
+ /* interface for obtaining matching features supported */
+ {"matching", S_IFREG, 0440, &apparmorfs_matching_fops,
+ NULL},
+
+ /* interface for loading/removing/replacing profiles */
+ {".load", S_IFREG, 0640, &apparmorfs_profile_load,
+ NULL},
+ {".replace", S_IFREG, 0640, &apparmorfs_profile_replace,
+ NULL},
+ {".remove", S_IFREG, 0640, &apparmorfs_profile_remove,
+ NULL},
+
+ /* interface for setting binary config values */
+ {"control", S_IFDIR, 0550},
+ {"complain", S_IFREG, 0640, &apparmorfs_control_fops,
+ &apparmor_complain},
+ {"audit", S_IFREG, 0640, &apparmorfs_control_fops,
+ &apparmor_audit},
+ {"debug", S_IFREG, 0640, &apparmorfs_control_fops,
+ &apparmor_debug},
+ {"logsyscall", S_IFREG, 0640, &apparmorfs_control_fops,
+ &apparmor_logsyscall},
+ {NULL, S_IFDIR, 0},
+
+ /* root end */
+ {NULL, S_IFDIR, 0}
+};
+
+#define AA_FS_DENTRY root_entries[0].dentry
+
+static const unsigned int num_entries =
+ sizeof(root_entries) / sizeof(struct root_entry);
+
+
+
+static int aa_prof_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &apparmorfs_profiles_op);
+}
+
+
+static int aa_prof_release(struct inode *inode, struct file *file)
+{
+ return seq_release(inode, file);
+}
+
+static ssize_t aa_matching_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ const char *matching = aa_match_features();
+
+ return simple_read_from_buffer(buf, size, ppos, matching,
+ strlen(matching));
+}
+
+static char *aa_simple_write_to_buffer(const char __user *userbuf,
+ size_t alloc_size, size_t copy_size,
+ loff_t *pos, const char *msg)
+{
+ struct aa_profile *active;
+ char *data;
+
+ if (*pos != 0) {
+ /* only writes from pos 0, that is complete writes */
+ data = ERR_PTR(-ESPIPE);
+ goto out;
+ }
+
+ /* Don't allow confined processes to load/replace/remove profiles.
+ * No sane person would add rules allowing this to a profile
+ * but we enforce the restriction anyways.
+ */
+ rcu_read_lock();
+ active = get_activeptr_rcu();
+ if (active) {
+ AA_WARN("REJECTING access to profile %s (%s(%d) "
+ "profile %s active %s)\n",
+ msg, current->comm, current->pid,
+ BASE_PROFILE(active)->name, active->name);
+
+ data = ERR_PTR(-EPERM);
+ goto out;
+ }
+ rcu_read_unlock();
+
+ data = vmalloc(alloc_size);
+ if (data == NULL) {
+ data = ERR_PTR(-ENOMEM);
+ goto out;
+ }
+
+ if (copy_from_user(data, userbuf, copy_size)) {
+ vfree(data);
+ data = ERR_PTR(-EFAULT);
+ goto out;
+ }
+
+out:
+ return data;
+}
+
+static ssize_t aa_profile_load(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos)
+{
+ char *data;
+ ssize_t error;
+
+ data = aa_simple_write_to_buffer(buf, size, size, pos, "load");
+
+ if (!IS_ERR(data)) {
+ error = aa_file_prof_add(data, size);
+ vfree(data);
+ } else {
+ error = PTR_ERR(data);
+ }
+
+ return error;
+}
+
+static ssize_t aa_profile_replace(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos)
+{
+ char *data;
+ ssize_t error;
+
+ data = aa_simple_write_to_buffer(buf, size, size, pos, "replacement");
+
+ if (!IS_ERR(data)) {
+ error = aa_file_prof_repl(data, size);
+ vfree(data);
+ } else {
+ error = PTR_ERR(data);
+ }
+
+ return error;
+}
+
+static ssize_t aa_profile_remove(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos)
+{
+ char *data;
+ ssize_t error;
+
+ /* aa_file_prof_remove needs a null terminated string so 1 extra
+ * byte is allocated and null the copied data is then null terminated
+ */
+ data = aa_simple_write_to_buffer(buf, size+1, size, pos, "removal");
+
+ if (!IS_ERR(data)) {
+ data[size] = 0;
+ error = aa_file_prof_remove(data, size);
+ vfree(data);
+ } else {
+ error = PTR_ERR(data);
+ }
+
+ return error;
+}
+
+static u64 aa_control_get(void *data)
+{
+ return *(int *)data;
+}
+
+static void aa_control_set(void *data, u64 val)
+{
+ if (val > 1)
+ val = 1;
+
+ *(int*)data = (int)val;
+}
+
+static void clear_apparmorfs(void)
+{
+ unsigned int i;
+
+ for (i=0; i < num_entries;i++) {
+ unsigned int index;
+
+ if (root_entries[i].mode == S_IFDIR) {
+ if (root_entries[i].name)
+ /* defer dir free till all sub-entries freed */
+ continue;
+ else
+ /* cleanup parent */
+ index = root_entries[i].parent_index;
+ } else {
+ index = i;
+ }
+
+ if (root_entries[index].dentry) {
+ securityfs_remove(root_entries[index].dentry);
+
+ AA_DEBUG("%s: deleted apparmorfs entry name=%s "
+ "dentry=%p\n",
+ __FUNCTION__,
+ root_entries[index].name,
+ root_entries[index].dentry);
+
+ root_entries[index].dentry = NULL;
+ root_entries[index].parent_index = 0;
+ }
+ }
+}
+
+static int populate_apparmorfs(struct dentry *root)
+{
+ unsigned int i, parent_index, depth;
+
+ for (i = 0; i < num_entries; i++) {
+ root_entries[i].dentry = NULL;
+ root_entries[i].parent_index = 0;
+ }
+
+ /* 1. Verify entry 0 is valid [sanity check] */
+ if (num_entries == 0 ||
+ !root_entries[0].name ||
+ strcmp(root_entries[0].name, SECFS_AA) != 0 ||
+ root_entries[0].mode != S_IFDIR) {
+ AA_ERROR("%s: root entry 0 is not SECFS_AA/dir\n",
+ __FUNCTION__);
+ goto error;
+ }
+
+ /* 2. Build back pointers */
+ parent_index = 0;
+ depth = 1;
+
+ for (i = 1; i < num_entries; i++) {
+ root_entries[i].parent_index = parent_index;
+
+ if (root_entries[i].name &&
+ root_entries[i].mode == S_IFDIR) {
+ depth++;
+ parent_index = i;
+ } else if (!root_entries[i].name) {
+ if (root_entries[i].mode != S_IFDIR || depth == 0) {
+ AA_ERROR("%s: root_entry %d invalid (%u %d)",
+ __FUNCTION__, i,
+ root_entries[i].mode,
+ root_entries[i].parent_index);
+ goto error;
+ }
+
+ depth--;
+ parent_index = root_entries[parent_index].parent_index;
+ }
+ }
+
+ if (depth != 0) {
+ AA_ERROR("%s: root_entry table not correctly terminated\n",
+ __FUNCTION__);
+ goto error;
+ }
+
+ /* 3. Create root (parent=NULL) */
+ root_entries[0].dentry = securityfs_create_file(
+ root_entries[0].name,
+ root_entries[0].mode |
+ root_entries[0].access,
+ NULL, NULL, NULL);
+
+ if (IS_ERR(root_entries[0].dentry))
+ goto error;
+ else
+ AA_DEBUG("%s: created securityfs/apparmor [dentry=%p]\n",
+ __FUNCTION__, root_entries[0].dentry);
+
+
+ /* 4. create remaining nodes */
+ for (i = 1; i < num_entries; i++) {
+ struct dentry *parent;
+ void *data = NULL;
+ struct file_operations *fops = NULL;
+
+ /* end of directory ? */
+ if (!root_entries[i].name)
+ continue;
+
+ parent = root_entries[root_entries[i].parent_index].dentry;
+
+ if (root_entries[i].mode != S_IFDIR) {
+ data = root_entries[i].data;
+ fops = root_entries[i].fops;
+ }
+
+ root_entries[i].dentry = securityfs_create_file(
+ root_entries[i].name,
+ root_entries[i].mode |
+ root_entries[i].access,
+ parent,
+ data,
+ fops);
+
+ if (IS_ERR(root_entries[i].dentry))
+ goto cleanup_error;
+
+ AA_DEBUG("%s: added apparmorfs entry "
+ "name=%s mode=%x dentry=%p [parent %p]\n",
+ __FUNCTION__, root_entries[i].name,
+ root_entries[i].mode|root_entries[i].access,
+ root_entries[i].dentry, parent);
+ }
+
+ return 0;
+
+cleanup_error:
+ clear_apparmorfs();
+
+error:
+ return -EINVAL;
+}
+
+int create_apparmorfs(void)
+{
+ int error = 0;
+
+ if (AA_FS_DENTRY) {
+ error = -EEXIST;
+ AA_ERROR("%s: AppArmor securityfs already exists\n",
+ __FUNCTION__);
+ } else {
+ error = populate_apparmorfs(aa_fs_dentry);
+ if (error != 0) {
+ AA_ERROR("%s: Error populating AppArmor securityfs\n",
+ __FUNCTION__);
+ }
+ }
+
+ return error;
+}
+
+void destroy_apparmorfs(void)
+{
+ if (AA_FS_DENTRY)
+ clear_apparmorfs();
+}
Index: b/security/apparmor/capabilities.c
===================================================================
--- /dev/null
+++ b/security/apparmor/capabilities.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2005 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 capability definitions
+ */
+
+#include "apparmor.h"
+
+static const char *cap_names[] = {
+ "chown",
+ "dac_override",
+ "dac_read_search",
+ "fowner",
+ "fsetid",
+ "kill",
+ "setgid",
+ "setuid",
+ "setpcap",
+ "linux_immutable",
+ "net_bind_service",
+ "net_broadcast",
+ "net_admin",
+ "net_raw",
+ "ipc_lock",
+ "ipc_owner",
+ "sys_module",
+ "sys_rawio",
+ "sys_chroot",
+ "sys_ptrace",
+ "sys_pacct",
+ "sys_admin",
+ "sys_boot",
+ "sys_nice",
+ "sys_resource",
+ "sys_time",
+ "sys_tty_config",
+ "mknod",
+ "lease"
+};
+
+const char *capability_to_name(unsigned int cap)
+{
+ const char *name;
+
+ name = (cap < (sizeof(cap_names) / sizeof(char *))
+ ? cap_names[cap] : "invalid-capability");
+
+ return name;
+}
Index: b/security/apparmor/inline.h
===================================================================
--- /dev/null
+++ b/security/apparmor/inline.h
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2005 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.
+ */
+
+#ifndef __INLINE_H
+#define __INLINE_H
+
+#include <linux/sched.h>
+
+static inline int __aa_is_confined(struct subdomain *sd)
+{
+ return (sd && sd->active);
+}
+
+/**
+ * aa_is_confined
+ * Determine whether current task contains a valid profile (confined).
+ * Return %1 if confined, %0 otherwise.
+ */
+static inline int aa_is_confined(void)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
+ return __aa_is_confined(sd);
+}
+
+static inline int __aa_sub_defined(struct subdomain *sd)
+{
+ return __aa_is_confined(sd) && !list_empty(&BASE_PROFILE(sd->active)->sub);
+}
+
+/**
+ * aa_sub_defined - check to see if current task has any subprofiles
+ * Return 1 if true, 0 otherwise
+ */
+static inline int aa_sub_defined(void)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
+ return __aa_sub_defined(sd);
+}
+
+/**
+ * get_aa_profile - increment refcount on profile @p
+ * @p: profile
+ */
+static inline struct aa_profile *get_aa_profile(struct aa_profile *p)
+{
+ if (p)
+ kref_get(&(BASE_PROFILE(p)->count));
+
+ return p;
+}
+
+/**
+ * put_aa_profile - decrement refcount on profile @p
+ * @p: profile
+ */
+static inline void put_aa_profile(struct aa_profile *p)
+{
+ if (p)
+ kref_put(&BASE_PROFILE(p)->count, free_aa_profile_kref);
+}
+
+/**
+ * get_task_activeptr_rcu - get pointer to @tsk's active profile.
+ * @tsk: task to get active profile from
+ *
+ * Requires rcu_read_lock is held
+ */
+static inline struct aa_profile *get_task_activeptr_rcu(struct task_struct *tsk)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(tsk->security);
+ struct aa_profile *active = NULL;
+
+ if (sd)
+ active = (struct aa_profile *) rcu_dereference(sd->active);
+
+ return active;
+}
+
+/**
+ * get_activeptr_rcu - get pointer to current task's active profile
+ * Requires rcu_read_lock is held
+ */
+static inline struct aa_profile *get_activeptr_rcu(void)
+{
+ return get_task_activeptr_rcu(current);
+}
+
+/**
+ * get_task_active_aa_profile - get a reference to tsk's active profile.
+ * @tsk: the task to get the active profile reference for
+ */
+static inline struct aa_profile *get_task_active_aa_profile(struct task_struct *tsk)
+{
+ struct aa_profile *active;
+
+ rcu_read_lock();
+ active = get_aa_profile(get_task_activeptr_rcu(tsk));
+ rcu_read_unlock();
+
+ return active;
+}
+
+/**
+ * get_active_aa_profile - get a reference to the current tasks active profile
+ */
+static inline struct aa_profile *get_active_aa_profile(void)
+{
+ return get_task_active_aa_profile(current);
+}
+
+/**
+ * aa_switch - change subdomain to use a new profile
+ * @sd: subdomain to switch the active profile on
+ * @newactive: new active profile
+ *
+ * aa_switch handles the changing of a subdomain's active profile. The
+ * sd_lock must be held to ensure consistency against other writers.
+ * Some write paths (ex. aa_register) require sd->active not to change
+ * over several operations, so the calling function is responsible
+ * for grabing the sd_lock to meet its consistency constraints before
+ * calling aa_switch
+ */
+static inline void aa_switch(struct subdomain *sd, struct aa_profile *newactive)
+{
+ struct aa_profile *oldactive = sd->active;
+
+ /* noop if NULL */
+ rcu_assign_pointer(sd->active, get_aa_profile(newactive));
+ put_aa_profile(oldactive);
+}
+
+/**
+ * aa_switch_unconfined - change subdomain to be unconfined (no profile)
+ * @sd: subdomain to switch
+ *
+ * aa_switch_unconfined handles the removal of a subdomain's active profile.
+ * The sd_lock must be held to ensure consistency against other writers.
+ * Like aa_switch the sd_lock is used to maintain consistency.
+ */
+static inline void aa_switch_unconfined(struct subdomain *sd)
+{
+ aa_switch(sd, NULL);
+
+ /* reset magic in case we were in a subhat before */
+ sd->hat_magic = 0;
+}
+
+/**
+ * alloc_subdomain - allocate a new subdomain
+ * @tsk: task struct
+ *
+ * Allocate a new subdomain including a backpointer to it's referring task.
+ */
+static inline struct subdomain *alloc_subdomain(struct task_struct *tsk)
+{
+ struct subdomain *sd;
+
+ sd = kzalloc(sizeof(struct subdomain), GFP_KERNEL);
+ if (!sd)
+ goto out;
+
+ /* back pointer to task */
+ sd->task = tsk;
+
+ /* any readers of the list must make sure that they can handle
+ * case where sd->active is not yet set (null)
+ */
+ aa_subdomainlist_add(sd);
+
+out:
+ return sd;
+}
+
+/**
+ * free_subdomain - Free a subdomain previously allocated by alloc_subdomain
+ * @sd: subdomain
+ */
+static inline void free_subdomain(struct subdomain *sd)
+{
+ aa_subdomainlist_remove(sd);
+ kfree(sd);
+}
+
+/**
+ * alloc_aa_profile - Allocate, initialize and return a new zeroed profile.
+ * Returns NULL on failure.
+ */
+static inline struct aa_profile *alloc_aa_profile(void)
+{
+ struct aa_profile *profile;
+
+ profile = (struct aa_profile *)kzalloc(sizeof(struct aa_profile),
+ GFP_KERNEL);
+ AA_DEBUG("%s(%p)\n", __FUNCTION__, profile);
+ if (profile) {
+ INIT_LIST_HEAD(&profile->list);
+ INIT_LIST_HEAD(&profile->sub);
+ INIT_RCU_HEAD(&profile->rcu);
+ kref_init(&profile->count);
+ }
+ return profile;
+}
+
+/**
+ * aa_put_name
+ * @name: name to release.
+ *
+ * Release space (free_page) allocated to hold pathname
+ * name may be NULL (checked for by free_page)
+ */
+static inline void aa_put_name(const char *name)
+{
+ free_page((unsigned long)name);
+}
+
+/** __aa_find_profile
+ * @name: name of profile to find
+ * @head: list to search
+ *
+ * Return reference counted copy of profile. NULL if not found
+ * Caller must hold any necessary locks
+ */
+static inline struct aa_profile *__aa_find_profile(const char *name,
+ struct list_head *head)
+{
+ struct aa_profile *p;
+
+ if (!name || !head)
+ return NULL;
+
+ AA_DEBUG("%s: finding profile %s\n", __FUNCTION__, name);
+ list_for_each_entry(p, head, list) {
+ if (!strcmp(p->name, name)) {
+ /* return refcounted object */
+ p = get_aa_profile(p);
+ return p;
+ } else {
+ AA_DEBUG("%s: skipping %s\n", __FUNCTION__, p->name);
+ }
+ }
+ return NULL;
+}
+#endif /* __INLINE_H__ */
Index: b/security/apparmor/list.c
===================================================================
--- /dev/null
+++ b/security/apparmor/list.c
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 1998-2005 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 Profile List Management
+ */
+
+#include <linux/seq_file.h>
+#include "apparmor.h"
+#include "inline.h"
+
+/* list of all profiles and lock */
+static LIST_HEAD(profile_list);
+static rwlock_t profile_lock = RW_LOCK_UNLOCKED;
+
+/* list of all subdomains and lock */
+static LIST_HEAD(subdomain_list);
+static rwlock_t subdomain_lock = RW_LOCK_UNLOCKED;
+
+/**
+ * aa_profilelist_find
+ * @name: profile name (program name)
+ *
+ * Search the profile list for profile @name. Return refcounted profile on
+ * success, NULL on failure.
+ */
+struct aa_profile *aa_profilelist_find(const char *name)
+{
+ struct aa_profile *p = NULL;
+ if (name) {
+ read_lock(&profile_lock);
+ p = __aa_find_profile(name, &profile_list);
+ read_unlock(&profile_lock);
+ }
+ return p;
+}
+
+/**
+ * aa_profilelist_add - add new profile to list
+ * @profile: new profile to add to list
+ *
+ * NOTE: Caller must allocate necessary reference count that will be used
+ * by the profile_list. This is because profile allocation alloc_aa_profile()
+ * returns an unreferenced object with a initial count of %1.
+ *
+ * Return %1 on success, %0 on failure (already exists)
+ */
+int aa_profilelist_add(struct aa_profile *profile)
+{
+ struct aa_profile *old_profile;
+ int ret = 0;
+
+ if (!profile)
+ goto out;
+
+ write_lock(&profile_lock);
+ old_profile = __aa_find_profile(profile->name, &profile_list);
+ if (old_profile) {
+ put_aa_profile(old_profile);
+ goto out;
+ }
+
+ list_add(&profile->list, &profile_list);
+ ret = 1;
+ out:
+ write_unlock(&profile_lock);
+ return ret;
+}
+
+/**
+ * aa_profilelist_remove - remove a profile from the list by name
+ * @name: name of profile to be removed
+ *
+ * If the profile exists remove profile from list and return its reference.
+ * The reference count on profile is not decremented and should be decremented
+ * when the profile is no longer needed
+ */
+struct aa_profile *aa_profilelist_remove(const char *name)
+{
+ struct aa_profile *profile = NULL;
+ struct aa_profile *p, *tmp;
+
+ if (!name)
+ goto out;
+
+ write_lock(&profile_lock);
+ list_for_each_entry_safe(p, tmp, &profile_list, list) {
+ if (!strcmp(p->name, name)) {
+ list_del_init(&p->list);
+ /* mark old profile as stale */
+ p->isstale = 1;
+ profile = p;
+ break;
+ }
+ }
+ write_unlock(&profile_lock);
+
+out:
+ return profile;
+}
+
+/**
+ * aa_profilelist_replace - replace a profile on the list
+ * @profile: new profile
+ *
+ * Replace a profile on the profile list. Find the old profile by name in
+ * the list, and replace it with the new profile. NOTE: Caller must allocate
+ * necessary initial reference count for new profile as aa_profilelist_add().
+ *
+ * This is an atomic list operation. Returns the old profile (which is still
+ * refcounted) if there was one, or NULL.
+ */
+struct aa_profile *aa_profilelist_replace(struct aa_profile *profile)
+{
+ struct aa_profile *oldprofile;
+
+ write_lock(&profile_lock);
+ oldprofile = __aa_find_profile(profile->name, &profile_list);
+ if (oldprofile) {
+ list_del_init(&oldprofile->list);
+ /* mark old profile as stale */
+ oldprofile->isstale = 1;
+
+ /* __aa_find_profile incremented count, so adjust down */
+ put_aa_profile(oldprofile);
+ }
+
+ list_add(&profile->list, &profile_list);
+ write_unlock(&profile_lock);
+
+ return oldprofile;
+}
+
+/**
+ * aa_profilelist_release - Remove all profiles from profile_list
+ */
+void aa_profilelist_release(void)
+{
+ struct aa_profile *p, *tmp;
+
+ write_lock(&profile_lock);
+ list_for_each_entry_safe(p, tmp, &profile_list, list) {
+ list_del_init(&p->list);
+ put_aa_profile(p);
+ }
+ write_unlock(&profile_lock);
+}
+
+/**
+ * aa_subdomainlist_add - Add subdomain to subdomain_list
+ * @sd: new subdomain
+ */
+void aa_subdomainlist_add(struct subdomain *sd)
+{
+ unsigned long flags;
+
+ if (!sd) {
+ AA_INFO("%s: bad subdomain\n", __FUNCTION__);
+ return;
+ }
+
+ write_lock_irqsave(&subdomain_lock, flags);
+ /* new subdomains must be added to the end of the list due to a
+ * subtle interaction between fork and profile replacement.
+ */
+ list_add_tail(&sd->list, &subdomain_list);
+ write_unlock_irqrestore(&subdomain_lock, flags);
+}
+
+/**
+ * aa_subdomainlist_remove - Remove subdomain from subdomain_list
+ * @sd: subdomain to be removed
+ */
+void aa_subdomainlist_remove(struct subdomain *sd)
+{
+ unsigned long flags;
+
+ if (sd) {
+ write_lock_irqsave(&subdomain_lock, flags);
+ list_del_init(&sd->list);
+ write_unlock_irqrestore(&subdomain_lock, flags);
+ }
+}
+
+/**
+ * aa_subdomainlist_iterate - iterate over the subdomain list applying @func
+ * @func: method to be called for each element
+ * @cookie: user passed data
+ *
+ * Iterate over subdomain list applying @func, stop when @func returns
+ * non zero
+ */
+void aa_subdomainlist_iterate(aa_iter func, void *cookie)
+{
+ struct subdomain *node;
+ int ret = 0;
+ unsigned long flags;
+
+ read_lock_irqsave(&subdomain_lock, flags);
+ list_for_each_entry(node, &subdomain_list, list) {
+ ret = (*func) (node, cookie);
+ if (ret != 0)
+ break;
+ }
+ read_unlock_irqrestore(&subdomain_lock, flags);
+}
+
+/**
+ * aa_subdomainlist_release - Remove all subdomains from subdomain_list
+ */
+void aa_subdomainlist_release(void)
+{
+ struct subdomain *node, *tmp;
+ unsigned long flags;
+
+ write_lock_irqsave(&subdomain_lock, flags);
+ list_for_each_entry_safe(node, tmp, &subdomain_list, list) {
+ list_del_init(&node->list);
+ }
+ write_unlock_irqrestore(&subdomain_lock, flags);
+}
+
+/* seq_file helper routines
+ * Used by apparmorfs.c to iterate over profile_list
+ */
+static void *p_start(struct seq_file *f, loff_t *pos)
+{
+ struct aa_profile *node;
+ loff_t l = *pos;
+
+ read_lock(&profile_lock);
+ list_for_each_entry(node, &profile_list, list)
+ if (!l--)
+ return node;
+ return NULL;
+}
+
+static void *p_next(struct seq_file *f, void *p, loff_t *pos)
+{
+ struct list_head *lh = ((struct aa_profile *)p)->list.next;
+ (*pos)++;
+ return lh == &profile_list ?
+ NULL : list_entry(lh, struct aa_profile, list);
+}
+
+static void p_stop(struct seq_file *f, void *v)
+{
+ read_unlock(&profile_lock);
+}
+
+static int seq_show_profile(struct seq_file *f, void *v)
+{
+ struct aa_profile *profile = (struct aa_profile *)v;
+ seq_printf(f, "%s (%s)\n", profile->name,
+ PROFILE_COMPLAIN(profile) ? "complain" : "enforce");
+ return 0;
+}
+
+struct seq_operations apparmorfs_profiles_op = {
+ .start = p_start,
+ .next = p_next,
+ .stop = p_stop,
+ .show = seq_show_profile,
+};
Index: b/security/apparmor/lsm.c
===================================================================
--- /dev/null
+++ b/security/apparmor/lsm.c
@@ -0,0 +1,919 @@
+/*
+ * Copyright (C) 2002-2005 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.
+ *
+ * http://forge.novell.com/modules/xfmod/project/?apparmor
+ *
+ * Immunix AppArmor LSM interface
+ */
+
+#include <linux/security.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+
+#include "apparmor.h"
+#include "inline.h"
+
+/* struct subdomain write update lock (read side is RCU). */
+spinlock_t sd_lock = SPIN_LOCK_UNLOCKED;
+
+/* Flag values, also controllable via apparmorfs/control.
+ * We explicitly do not allow these to be modifiable when exported via
+ * /sys/modules/parameters, as we want to do additional mediation and
+ * don't want to add special path code. */
+
+/* Complain mode -- in complain mode access failures result in auditing only
+ * and task is allowed access. audit events are processed by userspace to
+ * generate policy. Default is 'enforce' (0).
+ * Value is also togglable per profile and referenced when global value is
+ * enforce.
+ */
+int apparmor_complain = 0;
+module_param_named(complain, apparmor_complain, int, S_IRUSR);
+MODULE_PARM_DESC(apparmor_complain, "Toggle AppArmor complain mode");
+
+/* Debug mode */
+int apparmor_debug = 0;
+module_param_named(debug, apparmor_debug, int, S_IRUSR);
+MODULE_PARM_DESC(apparmor_debug, "Toggle AppArmor debug mode");
+
+/* Audit mode */
+int apparmor_audit = 0;
+module_param_named(audit, apparmor_audit, int, S_IRUSR);
+MODULE_PARM_DESC(apparmor_audit, "Toggle AppArmor audit mode");
+
+/* Syscall logging mode */
+int apparmor_logsyscall = 0;
+module_param_named(logsyscall, apparmor_logsyscall, int, S_IRUSR);
+MODULE_PARM_DESC(apparmor_logsyscall, "Toggle AppArmor logsyscall mode");
+
+#ifndef MODULE
+static int __init aa_getopt_complain(char *str)
+{
+ get_option(&str, &apparmor_complain);
+ return 1;
+}
+__setup("apparmor_complain=", aa_getopt_complain);
+
+static int __init aa_getopt_debug(char *str)
+{
+ get_option(&str, &apparmor_debug);
+ return 1;
+}
+__setup("apparmor_debug=", aa_getopt_debug);
+
+static int __init aa_getopt_audit(char *str)
+{
+ get_option(&str, &apparmor_audit);
+ return 1;
+}
+__setup("apparmor_audit=", aa_getopt_audit);
+
+static int __init aa_getopt_logsyscall(char *str)
+{
+ get_option(&str, &apparmor_logsyscall);
+ return 1;
+}
+__setup("apparmor_logsyscall=", aa_getopt_logsyscall);
+#endif
+
+static int apparmor_ptrace(struct task_struct *parent,
+ struct task_struct *child)
+{
+ int error;
+ struct aa_profile *active;
+
+ error = cap_ptrace(parent, child);
+
+ active = get_task_active_aa_profile(parent);
+
+ if (!error && active)
+ error = aa_audit_syscallreject(active, GFP_KERNEL, "ptrace");
+
+ put_aa_profile(active);
+
+ return error;
+}
+
+static int apparmor_capget(struct task_struct *target,
+ kernel_cap_t *effective,
+ kernel_cap_t *inheritable,
+ kernel_cap_t *permitted)
+{
+ return cap_capget(target, effective, inheritable, permitted);
+}
+
+static int apparmor_capset_check(struct task_struct *target,
+ kernel_cap_t *effective,
+ kernel_cap_t *inheritable,
+ kernel_cap_t *permitted)
+{
+ return cap_capset_check(target, effective, inheritable, permitted);
+}
+
+static void apparmor_capset_set(struct task_struct *target,
+ kernel_cap_t *effective,
+ kernel_cap_t *inheritable,
+ kernel_cap_t *permitted)
+{
+ cap_capset_set(target, effective, inheritable, permitted);
+}
+
+static int apparmor_capable(struct task_struct *tsk, int cap)
+{
+ int error;
+
+ /* cap_capable returns 0 on success, else -EPERM */
+ error = cap_capable(tsk, cap);
+
+ if (!error) {
+ struct aa_profile *active;
+
+ active = get_task_active_aa_profile(tsk);
+
+ if (active)
+ error = aa_capability(active, cap);
+
+ put_aa_profile(active);
+ }
+
+ return error;
+}
+
+static int apparmor_sysctl(struct ctl_table *table, int op)
+{
+ int error = 0;
+ struct aa_profile *active;
+
+ active = get_active_aa_profile();
+
+ if ((op & 002) && active && !capable(CAP_SYS_ADMIN))
+ error = aa_audit_syscallreject(active, GFP_KERNEL,
+ "sysctl (write)");
+
+ put_aa_profile(active);
+
+ return error;
+}
+
+static int apparmor_syslog(int type)
+{
+ return cap_syslog(type);
+}
+
+static int apparmor_netlink_send(struct sock *sk, struct sk_buff *skb)
+{
+ return cap_netlink_send(sk, skb);
+}
+
+static int apparmor_netlink_recv(struct sk_buff *skb, int cap)
+{
+ return cap_netlink_recv(skb, cap);
+}
+
+static void apparmor_bprm_apply_creds(struct linux_binprm *bprm, int unsafe)
+{
+ cap_bprm_apply_creds(bprm, unsafe);
+}
+
+static int apparmor_bprm_set_security(struct linux_binprm *bprm)
+{
+ /* handle capability bits with setuid, etc */
+ cap_bprm_set_security(bprm);
+ /* already set based on script name */
+ if (bprm->sh_bang)
+ return 0;
+ return aa_register(bprm);
+}
+
+static int apparmor_bprm_secureexec(struct linux_binprm *bprm)
+{
+ int ret = cap_bprm_secureexec(bprm);
+
+ if (!ret && (unsigned long)bprm->security & AA_SECURE_EXEC_NEEDED) {
+ AA_DEBUG("%s: secureexec required for %s\n",
+ __FUNCTION__, bprm->filename);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static int apparmor_sb_mount(char *dev_name, struct nameidata *nd, char *type,
+ unsigned long flags, void *data)
+{
+ int error = 0;
+ struct aa_profile *active;
+
+ active = get_active_aa_profile();
+
+ if (active)
+ error = aa_audit_syscallreject(active, GFP_KERNEL, "mount");
+
+ put_aa_profile(active);
+
+ return error;
+}
+
+static int apparmor_umount(struct vfsmount *mnt, int flags)
+{
+ int error = 0;
+ struct aa_profile *active;
+
+ active = get_active_aa_profile();
+
+ if (active)
+ error = aa_audit_syscallreject(active, GFP_ATOMIC, "umount");
+
+ put_aa_profile(active);
+
+ return error;
+}
+
+static int apparmor_inode_mkdir(struct inode *dir, struct dentry *dentry,
+ struct vfsmount *mnt, int mask)
+{
+ struct aa_profile *active;
+ int error = 0;
+
+ if (!mnt || !mediated_filesystem(dir))
+ goto out;
+
+ active = get_active_aa_profile();
+
+ if (active)
+ error = aa_perm_dir(active, dentry, mnt, "mkdir", AA_MAY_WRITE);
+
+ put_aa_profile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_inode_rmdir(struct inode *dir, struct dentry *dentry,
+ struct vfsmount *mnt)
+{
+ struct aa_profile *active;
+ int error = 0;
+
+ if (!mnt || !mediated_filesystem(dir))
+ goto out;
+
+ active = get_active_aa_profile();
+
+ if (active)
+ error = aa_perm_dir(active, dentry, mnt, "rmdir", AA_MAY_WRITE);
+
+ put_aa_profile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_inode_create(struct inode *dir, struct dentry *dentry,
+ struct vfsmount *mnt, int mask)
+{
+ struct aa_profile *active;
+ int error = 0;
+
+ if (!mnt || !mediated_filesystem(dir))
+ goto out;
+
+ active = get_active_aa_profile();
+
+ /* At a minimum, need write perm to create */
+ if (active)
+ error = aa_perm(active, dentry, mnt, MAY_WRITE);
+
+ put_aa_profile(active);
+out:
+ return error;
+}
+
+static int apparmor_inode_link(struct dentry *old_dentry,
+ struct vfsmount *old_mnt, struct inode *dir,
+ struct dentry *new_dentry,
+ struct vfsmount *new_mnt)
+{
+ int error = 0;
+ struct aa_profile *active;
+
+ if (!old_mnt || !new_mnt || !mediated_filesystem(dir))
+ goto out;
+
+ active = get_active_aa_profile();
+
+ if (active)
+ error = aa_link(active, new_dentry, new_mnt,
+ old_dentry, old_mnt);
+
+ put_aa_profile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_inode_unlink(struct inode *dir,
+ struct dentry *dentry,
+ struct vfsmount *mnt)
+{
+ struct aa_profile *active;
+ int error = 0;
+
+ if (!mnt || !mediated_filesystem(dir))
+ goto out;
+
+ active = get_active_aa_profile();
+
+ if (active)
+ error = aa_perm(active, dentry, mnt, MAY_WRITE);
+
+ put_aa_profile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_inode_mknod(struct inode *dir, struct dentry *dentry,
+ struct vfsmount *mnt, int mode, dev_t dev)
+{
+ struct aa_profile *active;
+ int error = 0;
+
+ if (!mnt || !mediated_filesystem(dir))
+ goto out;
+
+ active = get_active_aa_profile();
+
+ if (active)
+ error = aa_perm(active, dentry, mnt, MAY_WRITE);
+
+ put_aa_profile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_inode_rename(struct inode *old_dir,
+ struct dentry *old_dentry,
+ struct vfsmount *old_mnt,
+ struct inode *new_dir,
+ struct dentry *new_dentry,
+ struct vfsmount *new_mnt)
+{
+ struct aa_profile *active;
+ int error = 0;
+
+ if ((!old_mnt && !new_mnt) || !mediated_filesystem(old_dir))
+ goto out;
+
+ active = get_active_aa_profile();
+
+ if (active) {
+ if (old_mnt)
+ error = aa_perm(active, old_dentry, old_mnt,
+ MAY_READ|MAY_WRITE);
+
+ if (!error && new_mnt)
+ error = aa_perm(active, new_dentry, new_mnt,
+ MAY_WRITE);
+ }
+
+ put_aa_profile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_inode_permission(struct inode *inode, int mask,
+ struct nameidata *nd)
+{
+ int error = 0;
+
+ /* Do not perform check on pipes or sockets
+ * Same as apparmor_file_permission
+ */
+ if (nd && mediated_filesystem(inode)) {
+ struct aa_profile *active;
+
+ active = get_active_aa_profile();
+ if (active)
+ error = aa_perm(active, nd->dentry, nd->mnt, mask);
+ put_aa_profile(active);
+ }
+
+ return error;
+}
+
+static int apparmor_inode_setattr(struct dentry *dentry, struct vfsmount *mnt,
+ struct iattr *iattr)
+{
+ int error = 0;
+
+ if (!mnt)
+ goto out;
+
+ if (mediated_filesystem(dentry->d_inode)) {
+ struct aa_profile *active;
+
+ active = get_active_aa_profile();
+ /*
+ * Mediate any attempt to change attributes of a file
+ * (chmod, chown, chgrp, etc)
+ */
+ if (active)
+ error = aa_attr(active, dentry, mnt, iattr);
+
+ put_aa_profile(active);
+ }
+
+out:
+ return error;
+}
+
+static int apparmor_inode_setxattr(struct dentry *dentry, struct vfsmount *mnt,
+ char *name, void *value, size_t size,
+ int flags)
+{
+ int error = 0;
+
+ if (!mnt)
+ goto out;
+
+ if (mediated_filesystem(dentry->d_inode)) {
+ struct aa_profile *active;
+
+ active = get_active_aa_profile();
+ if (active)
+ error = aa_perm_xattr(active, dentry, mnt, name,
+ "xattr set", AA_MAY_WRITE);
+ put_aa_profile(active);
+ }
+
+out:
+ return error;
+}
+
+static int apparmor_inode_getxattr(struct dentry *dentry, struct vfsmount *mnt,
+ char *name)
+{
+ int error = 0;
+
+ if (!mnt)
+ goto out;
+
+ if (mediated_filesystem(dentry->d_inode)) {
+ struct aa_profile *active;
+
+ active = get_active_aa_profile();
+ if (active)
+ error = aa_perm_xattr(active, dentry, mnt, name,
+ "xattr get", AA_MAY_READ);
+ put_aa_profile(active);
+ }
+
+out:
+ return error;
+}
+static int apparmor_inode_listxattr(struct dentry *dentry, struct vfsmount *mnt)
+{
+ int error = 0;
+
+ if (!mnt)
+ goto out;
+
+ if (mediated_filesystem(dentry->d_inode)) {
+ struct aa_profile *active;
+
+ active = get_active_aa_profile();
+ if (active)
+ error = aa_perm_xattr(active, dentry, mnt, NULL,
+ "xattr list", AA_MAY_READ);;
+ put_aa_profile(active);
+ }
+
+out:
+ return error;
+}
+
+static int apparmor_inode_removexattr(struct dentry *dentry,
+ struct vfsmount *mnt, char *name)
+{
+ int error = 0;
+
+ if (!mnt)
+ goto out;
+
+ if (mediated_filesystem(dentry->d_inode)) {
+ struct aa_profile *active;
+
+ active = get_active_aa_profile();
+ if (active)
+ error = aa_perm_xattr(active, dentry, mnt, name,
+ "xattr remove", AA_MAY_WRITE);
+ put_aa_profile(active);
+ }
+
+out:
+ return error;
+}
+
+static int apparmor_file_permission(struct file *file, int mask)
+{
+ struct aa_profile *active;
+ struct aa_profile *file_profile = (struct aa_profile*)file->f_security;
+ int error = 0;
+
+ /* bail out early if this isn't a mediated file */
+ if (!file_profile || !mediated_filesystem(file->f_dentry->d_inode))
+ goto out;
+
+ active = get_active_aa_profile();
+ if (active && file_profile != active) {
+ /* FIXME: get rid of revalidation. */
+ error = aa_perm(active, file->f_dentry, file->f_vfsmnt,
+ mask & (MAY_EXEC | MAY_WRITE | MAY_READ));
+ }
+ put_aa_profile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_file_alloc_security(struct file *file)
+{
+ struct aa_profile *active;
+
+ active = get_active_aa_profile();
+ if (active) {
+ /* FIXME: get rid of revalidation. */
+ file->f_security = active;
+ }
+
+ return 0;
+}
+
+static void apparmor_file_free_security(struct file *file)
+{
+ struct aa_profile *file_profile = (struct aa_profile*)file->f_security;
+
+ /* FIXME: get rid of revalidation. */
+ put_aa_profile(file_profile);
+}
+
+static inline int aa_mmap(struct file *file, unsigned long prot,
+ unsigned long flags)
+{
+ int error = 0, mask = 0;
+ struct aa_profile *active;
+
+ active = get_active_aa_profile();
+ if (!active || !file || !mediated_filesystem(file->f_dentry->d_inode))
+ goto out;
+
+ if (prot & PROT_READ)
+ mask |= MAY_READ;
+
+ /* Private mappings don't require write perms since they don't
+ * write back to the files */
+ if (prot & PROT_WRITE && !(flags & MAP_PRIVATE))
+ mask |= MAY_WRITE;
+ if (prot & PROT_EXEC)
+ mask |= AA_EXEC_MMAP;
+
+ AA_DEBUG("%s: 0x%x\n", __FUNCTION__, mask);
+
+ if (mask)
+ error = aa_perm(active, file->f_dentry, file->f_vfsmnt, mask);
+
+ put_aa_profile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_file_mmap(struct file *file, unsigned long reqprot,
+ unsigned long prot, unsigned long flags)
+{
+ return aa_mmap(file, prot, flags);
+}
+
+static int apparmor_file_mprotect(struct vm_area_struct *vma,
+ unsigned long reqprot, unsigned long prot)
+{
+ return aa_mmap(vma->vm_file, prot,
+ !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
+}
+
+static int apparmor_task_alloc_security(struct task_struct *p)
+{
+ return aa_fork(p);
+}
+
+static void apparmor_task_free_security(struct task_struct *p)
+{
+ aa_release(p);
+}
+
+static int apparmor_task_post_setuid(uid_t id0, uid_t id1, uid_t id2,
+ int flags)
+{
+ return cap_task_post_setuid(id0, id1, id2, flags);
+}
+
+static void apparmor_task_reparent_to_init(struct task_struct *p)
+{
+ cap_task_reparent_to_init(p);
+}
+
+static int apparmor_getprocattr(struct task_struct *p, char *name, void *value,
+ size_t size)
+{
+ int error;
+ struct aa_profile *active;
+ char *str = value;
+
+ /* AppArmor only supports the "current" process attribute */
+ if (strcmp(name, "current") != 0) {
+ error = -EINVAL;
+ goto out;
+ }
+
+ /* must be task querying itself or admin */
+ if (current != p && !capable(CAP_SYS_ADMIN)) {
+ error = -EPERM;
+ goto out;
+ }
+
+ active = get_task_active_aa_profile(p);
+ error = aa_getprocattr(active, str, size);
+ put_aa_profile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_setprocattr(struct task_struct *p, char *name, void *value,
+ size_t size)
+{
+ const char *cmd_changehat = "changehat ",
+ *cmd_setprofile = "setprofile ";
+
+ int error;
+ char *cmd = (char *)value;
+
+ error = -EINVAL;
+ if (strcmp(name, "current") != 0)
+ goto out;
+ error = -ERANGE;
+ if (!size)
+ goto out;
+
+ /* CHANGE HAT -- switch task into a subhat (subprofile) if defined */
+ if (size > strlen(cmd_changehat) &&
+ strncmp(cmd, cmd_changehat, strlen(cmd_changehat)) == 0) {
+ char *hatinfo = cmd + strlen(cmd_changehat);
+ size_t infosize = size - strlen(cmd_changehat);
+
+ /* Only the current process may change it's hat */
+ if (current != p) {
+ AA_WARN("%s: Attempt by foreign task %s(%d) "
+ "[user %d] to changehat of task %s(%d)\n",
+ __FUNCTION__,
+ current->comm,
+ current->pid,
+ current->uid,
+ p->comm,
+ p->pid);
+
+ error = -EACCES;
+ goto out;
+ }
+
+ error = aa_setprocattr_changehat(hatinfo, infosize);
+ if (!error)
+ error = size;
+
+ /* SET NEW PROFILE */
+ } else if (size > strlen(cmd_setprofile) &&
+ strncmp(cmd, cmd_setprofile, strlen(cmd_setprofile)) == 0) {
+ struct aa_profile *active;
+
+ /* only an unconfined process with admin capabilities
+ * may change the profile of another task
+ */
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ AA_WARN("%s: Unprivileged attempt by task %s(%d) "
+ "[user %d] to assign profile to task %s(%d)\n",
+ __FUNCTION__,
+ current->comm,
+ current->pid,
+ current->uid,
+ p->comm,
+ p->pid);
+ error = -EACCES;
+ goto out;
+ }
+
+ active = get_active_aa_profile();
+ if (!active) {
+ char *profile = cmd + strlen(cmd_setprofile);
+ size_t profilesize = size - strlen(cmd_setprofile);
+
+ error = aa_setprocattr_setprofile(p, profile, profilesize);
+ if (!error)
+ /* success,
+ * set return to #bytes in orig request
+ */
+ error = size;
+ } else {
+ AA_WARN("%s: Attempt by confined task %s(%d) "
+ "[user %d] to assign profile to task %s(%d)\n",
+ __FUNCTION__,
+ current->comm,
+ current->pid,
+ current->uid,
+ p->comm,
+ p->pid);
+
+ error = -EACCES;
+ }
+ put_aa_profile(active);
+ } else {
+ /* unknown operation */
+ AA_WARN("%s: Unknown setprocattr command '%.*s' by task %s(%d) "
+ "[user %d] for task %s(%d)\n",
+ __FUNCTION__,
+ size < 16 ? (int)size : 16,
+ cmd,
+ current->comm,
+ current->pid,
+ current->uid,
+ p->comm,
+ p->pid);
+
+ error = -EINVAL;
+ }
+
+out:
+ return error;
+}
+
+struct security_operations apparmor_ops = {
+ .ptrace = apparmor_ptrace,
+ .capget = apparmor_capget,
+ .capset_check = apparmor_capset_check,
+ .capset_set = apparmor_capset_set,
+ .sysctl = apparmor_sysctl,
+ .capable = apparmor_capable,
+ .syslog = apparmor_syslog,
+
+ .netlink_send = apparmor_netlink_send,
+ .netlink_recv = apparmor_netlink_recv,
+
+ .bprm_apply_creds = apparmor_bprm_apply_creds,
+ .bprm_set_security = apparmor_bprm_set_security,
+ .bprm_secureexec = apparmor_bprm_secureexec,
+
+ .sb_mount = apparmor_sb_mount,
+ .sb_umount = apparmor_umount,
+
+ .inode_mkdir = apparmor_inode_mkdir,
+ .inode_rmdir = apparmor_inode_rmdir,
+ .inode_create = apparmor_inode_create,
+ .inode_link = apparmor_inode_link,
+ .inode_unlink = apparmor_inode_unlink,
+ .inode_mknod = apparmor_inode_mknod,
+ .inode_rename = apparmor_inode_rename,
+ .inode_permission = apparmor_inode_permission,
+ .inode_setattr = apparmor_inode_setattr,
+ .inode_setxattr = apparmor_inode_setxattr,
+ .inode_getxattr = apparmor_inode_getxattr,
+ .inode_listxattr = apparmor_inode_listxattr,
+ .inode_removexattr = apparmor_inode_removexattr,
+ .file_permission = apparmor_file_permission,
+ .file_alloc_security = apparmor_file_alloc_security,
+ .file_free_security = apparmor_file_free_security,
+ .file_mmap = apparmor_file_mmap,
+ .file_mprotect = apparmor_file_mprotect,
+
+ .task_alloc_security = apparmor_task_alloc_security,
+ .task_free_security = apparmor_task_free_security,
+ .task_post_setuid = apparmor_task_post_setuid,
+ .task_reparent_to_init = apparmor_task_reparent_to_init,
+
+ .getprocattr = apparmor_getprocattr,
+ .setprocattr = apparmor_setprocattr,
+};
+
+static int __init apparmor_init(void)
+{
+ int error;
+ const char *complainmsg = ": complainmode enabled";
+
+ if ((error = create_apparmorfs())) {
+ AA_ERROR("Unable to activate AppArmor filesystem\n");
+ goto createfs_out;
+ }
+
+ if ((error = alloc_null_complain_profile())){
+ AA_ERROR("Unable to allocate null complain profile\n");
+ goto alloc_out;
+ }
+
+ if ((error = register_security(&apparmor_ops))) {
+ AA_ERROR("Unable to load AppArmor\n");
+ goto register_security_out;
+ }
+
+ AA_INFO("AppArmor initialized%s\n",
+ apparmor_complain ? complainmsg : "");
+ aa_audit_message(NULL, GFP_KERNEL, 0,
+ "AppArmor initialized%s\n",
+ apparmor_complain ? complainmsg : "");
+
+ return error;
+
+register_security_out:
+ free_null_complain_profile();
+
+alloc_out:
+ (void)destroy_apparmorfs();
+
+createfs_out:
+ return error;
+
+}
+
+static int apparmor_exit_removeall_iter(struct subdomain *sd, void *cookie)
+{
+ /* spin_lock(&sd_lock) held here */
+
+ if (__aa_is_confined(sd)) {
+ AA_DEBUG("%s: Dropping profiles %s(%d) "
+ "profile %s(%p) active %s(%p)\n",
+ __FUNCTION__,
+ sd->task->comm, sd->task->pid,
+ BASE_PROFILE(sd->active)->name,
+ BASE_PROFILE(sd->active),
+ sd->active->name, sd->active);
+ aa_switch_unconfined(sd);
+ }
+
+ return 0;
+}
+
+static void __exit apparmor_exit(void)
+{
+ unsigned long flags;
+
+ /* Remove profiles from the global profile list.
+ * This is just for tidyness as there is no way to reference this
+ * list once the AppArmor lsm hooks are detached (below)
+ */
+ aa_profilelist_release();
+
+ /* Remove profiles from active tasks
+ * If this is not done, if module is reloaded after being removed,
+ * old profiles (still refcounted in memory) will become 'magically'
+ * reattached
+ */
+
+ spin_lock_irqsave(&sd_lock, flags);
+ aa_subdomainlist_iterate(apparmor_exit_removeall_iter, NULL);
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+ /* Free up list of active subdomain */
+ aa_subdomainlist_release();
+
+ free_null_complain_profile();
+
+ destroy_apparmorfs();
+
+ if (unregister_security(&apparmor_ops))
+ AA_WARN("Unable to properly unregister AppArmor\n");
+
+ /* delay for an rcu cycle to make ensure that profiles pending
+ * destruction in the rcu callback are freed.
+ */
+ synchronize_rcu();
+
+ AA_INFO("AppArmor protection removed\n");
+ aa_audit_message(NULL, GFP_KERNEL, 0,
+ "AppArmor protection removed\n");
+}
+
+module_init(apparmor_init);
+module_exit(apparmor_exit);
+
+MODULE_DESCRIPTION("AppArmor process confinement");
+MODULE_AUTHOR("Tony Jones <tonyj@suse.de>");
+MODULE_LICENSE("GPL");
Index: b/security/apparmor/main.c
===================================================================
--- /dev/null
+++ b/security/apparmor/main.c
@@ -0,0 +1,1256 @@
+/*
+ * Copyright (C) 2002-2005 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 "apparmor.h"
+
+#include "inline.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;
+
+/***************************
+ * Private utility functions
+ **************************/
+
+/**
+ * aa_taskattr_access
+ * @name: name of file to check permission
+ *
+ * Check if name matches /proc/self/attr/current, with self resolved
+ * to the current pid. This file is the usermode iterface for
+ * changing one's hat.
+ */
+static inline int aa_taskattr_access(const char *name)
+{
+ unsigned long pid;
+ char *end;
+
+ if (strncmp(name, "/proc/", 6) != 0)
+ return 0;
+ pid = simple_strtoul(name + 6, &end, 10);
+ if (pid != current->pid)
+ return 0;
+ return strcmp(end, "/attr/current") == 0;
+}
+
+/**
+ * aa_file_mode - get full mode for file entry from profile
+ * @profile: profile
+ * @name: filename
+ */
+static inline int aa_file_mode(struct aa_profile *profile, const char *name)
+{
+ int perms = 0;
+
+ AA_DEBUG("%s: %s\n", __FUNCTION__, name);
+ if (!name) {
+ AA_DEBUG("%s: no name\n", __FUNCTION__);
+ goto out;
+ }
+
+ if (!profile) {
+ AA_DEBUG("%s: no profile\n", __FUNCTION__);
+ goto out;
+ }
+
+ perms = aa_match(profile->file_rules, name);
+
+out:
+ return perms;
+}
+
+/**
+ * aa_filter_mask
+ * @mask: requested mask
+ * @inode: potential directory inode
+ *
+ * This fn performs pre-verification of the requested mask
+ * We ignore append. Previously we required 'w' on a dir to add a file.
+ * No longer. Now we require 'w' on just the file itself. Traversal 'x' is
+ * also ignored for directories.
+ *
+ * Returned value of %0 indicates no need to perform a perm check.
+ */
+static inline int aa_filter_mask(int mask, struct inode *inode)
+{
+ if (mask) {
+ int elim = MAY_APPEND;
+
+ if (inode && S_ISDIR(inode->i_mode))
+ elim |= (MAY_EXEC | MAY_WRITE);
+
+ mask &= ~elim;
+ }
+
+ return mask;
+}
+
+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;
+ }
+}
+
+/*************************
+ * Main internal functions
+ ************************/
+
+/**
+ * aa_file_perm - calculate access mode for file
+ * @active: profile to check against
+ * @name: name of file to calculate mode for
+ * @mask: permission mask requested for file
+ *
+ * Search the aa_entry list in @active.
+ * Search looking to verify all permissions passed in mask.
+ * Perform the search by looking at the partitioned list of entries, one
+ * partition per permission bit.
+ *
+ * Return %0 on success, else mask of non-allowed permissions
+ */
+static int aa_file_perm(struct aa_profile *active, const char *name, int mask)
+{
+ int perms;
+
+ /* Always allow write access to /proc/self/attr/current. */
+ if (mask == MAY_WRITE && aa_taskattr_access(name))
+ return 0;
+
+ perms = aa_match(active->file_rules, name);
+
+ return mask & ~perms; /* return permissions not satisfied */
+}
+
+/**
+ * aa_link_perm - test permission to link to a file
+ * @active: profile to check against
+ * @link: name of link being created
+ * @target: name of target to be linked to
+ *
+ * Look up permission mode on both @link and @target. @link must have same
+ * permission mode as @target. At least @link must have the link bit enabled.
+ * Return %0 on success, else -EPERM
+ */
+static int aa_link_perm(struct aa_profile *active,
+ const char *link, const char *target)
+{
+ int l_mode, t_mode, ret = -EPERM;
+
+ l_mode = aa_file_mode(active, link);
+ if (l_mode & AA_MAY_LINK) {
+ /* mask off link bit */
+ l_mode &= ~AA_MAY_LINK;
+
+ t_mode = aa_file_mode(active, target);
+ t_mode &= ~AA_MAY_LINK;
+
+ if (l_mode == t_mode)
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int _aa_perm_vfsmount(struct aa_profile *active, struct dentry *dentry,
+ struct vfsmount *mnt, struct aa_audit *sa, int mask)
+{
+ int permerror, error;
+
+ sa->name = aa_get_name(dentry, mnt);
+
+ if (IS_ERR(sa->name)) {
+ permerror = PTR_ERR(sa->name);
+ sa->name = NULL;
+ } else {
+ permerror = aa_file_perm(active, sa->name, mask);
+ }
+
+ aa_permerror2result(permerror, sa);
+
+ error = aa_audit(active, sa);
+
+ aa_put_name(sa->name);
+
+ return error;
+}
+
+/**************************
+ * Global utility functions
+ *************************/
+
+/**
+ * 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)
+{
+ put_aa_profile(null_complain_profile);
+ null_complain_profile = NULL;
+}
+
+/**
+ * aa_audit_message - Log a message to the audit subsystem
+ * @active: profile to check against
+ * @gfp: allocation flags
+ * @flags: audit flags
+ * @fmt: varargs fmt
+ */
+int aa_audit_message(struct aa_profile *active, gfp_t gfp, int flags,
+ const char *fmt, ...)
+{
+ int ret;
+ struct aa_audit sa;
+
+ sa.type = AA_AUDITTYPE_MSG;
+ sa.name = fmt;
+ va_start(sa.vaval, fmt);
+ sa.flags = flags;
+ sa.gfp_mask = gfp;
+ sa.error_code = 0;
+ sa.result = 0; /* fake failure: force message to be logged */
+
+ ret = aa_audit(active, &sa);
+
+ va_end(sa.vaval);
+
+ return ret;
+}
+
+/**
+ * aa_audit_syscallreject - Log a syscall rejection to the audit subsystem
+ * @active: profile to check against
+ * @msg: string describing syscall being rejected
+ * @gfp: memory allocation flags
+ */
+int aa_audit_syscallreject(struct aa_profile *active, 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(active, &sa);
+}
+
+/**
+ * aa_audit - Log an audit event to the audit subsystem
+ * @active: profile to check against
+ * @sa: audit event
+ */
+int aa_audit(struct aa_profile *active, const struct aa_audit *sa)
+{
+ struct audit_buffer *ab = NULL;
+ struct audit_context *ctx;
+
+ const char *logcls;
+ unsigned int flags;
+ int audit = 0,
+ complain = 0,
+ error = -EINVAL,
+ opspec_error = -EACCES;
+
+ const gfp_t gfp_mask = sa->gfp_mask;
+
+ WARN_ON(sa->type >= AA_AUDITTYPE__END);
+
+ /*
+ * 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(active))) {
+ /* 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(active);
+ logcls = complain ? "PERMITTING" : "REJECTING";
+ }
+
+ /* In future extend w/ per-profile flags
+ * (flags |= sa->active->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) {
+ ctx = current->audit_context;
+ } else {
+ ctx = (flags & AA_AUDITFLAG_AUDITSS_SYSCALL) ?
+ current->audit_context : NULL;
+ }
+
+ ab = audit_log_start(ctx, 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;
+ }
+
+ /* log operation */
+
+ audit_log_format(ab, "%s ", logcls); /* REJECTING/ALLOWING/etc */
+
+ if (sa->type == 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 & AA_MAY_READ ? "r" : "",
+ perm & AA_MAY_WRITE ? "w" : "",
+ perm & AA_MAY_EXEC ? "x" : "",
+ perm & AA_MAY_LINK ? "l" : "",
+ sa->name);
+
+ opspec_error = -EPERM;
+
+ } else if (sa->type == AA_AUDITTYPE_DIR) {
+ audit_log_format(ab, "%s on %s ", sa->operation, sa->name);
+
+ } else if (sa->type == AA_AUDITTYPE_ATTR) {
+ struct iattr *iattr = (struct iattr*)sa->pval;
+
+ 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);
+
+ } else if (sa->type == AA_AUDITTYPE_XATTR) {
+ /* FIXME: how are special characters in sa->name escaped? */
+ /* FIXME: check if this can be handled on the stack
+ with an inline varargs function. */
+ audit_log_format(ab, "%s on %s ", sa->operation, sa->name);
+
+ } else if (sa->type == AA_AUDITTYPE_LINK) {
+ audit_log_format(ab,
+ "link access from %s to %s ",
+ sa->name,
+ (char*)sa->pval);
+
+ } else if (sa->type == AA_AUDITTYPE_CAP) {
+ audit_log_format(ab,
+ "access to capability '%s' ",
+ capability_to_name(sa->capability));
+
+ opspec_error = -EPERM;
+ } else if (sa->type == AA_AUDITTYPE_SYSCALL) {
+ audit_log_format(ab, "access to syscall '%s' ", sa->name);
+
+ opspec_error = -EPERM;
+ } else {
+ /* -EINVAL -- will WARN_ON above */
+ goto out;
+ }
+
+ audit_log_format(ab, "(%s(%d) profile %s active %s)",
+ current->comm, current->pid,
+ BASE_PROFILE(active)->name, active->name);
+
+ audit_log_end(ab);
+
+ if (complain)
+ error = 0;
+ else
+ error = sa->result ? 0 : opspec_error;
+
+out:
+ return error;
+}
+
+/**
+ * aa_get_name - retrieve fully qualified path name
+ * @dentry: relative path element
+ * @mnt: where in tree
+ *
+ * Returns fully qualified path name on sucess, NULL on failure.
+ * aa_put_name must be used to free allocated buffer.
+ */
+char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt)
+{
+ char *page, *name;
+
+ page = (char *)__get_free_page(GFP_KERNEL);
+ if (!page) {
+ name = ERR_PTR(-ENOMEM);
+ goto out;
+ }
+
+ name = d_path(dentry, mnt, page, PAGE_SIZE);
+ /* check for (deleted) that d_path appends to pathnames if the dentry
+ * has been removed from the cache.
+ * The size > deleted_size and strcmp checks are redundant safe guards.
+ */
+ if (IS_ERR(name)) {
+ free_page((unsigned long)page);
+ } else {
+ const char deleted_str[] = " (deleted)";
+ const size_t deleted_size = sizeof(deleted_str) - 1;
+ size_t size;
+ size = strlen(name);
+ if (!IS_ROOT(dentry) && d_unhashed(dentry) &&
+ size > deleted_size &&
+ strcmp(name + size - deleted_size, deleted_str) == 0)
+ name[size - deleted_size] = '\0';
+ AA_DEBUG("%s: full_path=%s\n", __FUNCTION__, name);
+ }
+
+out:
+ return name;
+}
+
+/***********************************
+ * Global permission check functions
+ ***********************************/
+
+/**
+ * aa_attr - check whether attribute change allowed
+ * @active: profile to check against
+ * @dentry: file to check
+ * @iattr: attribute changes requested
+ */
+int aa_attr(struct aa_profile *active, struct dentry *dentry,
+ struct vfsmount *mnt, struct iattr *iattr)
+{
+ int error;
+ struct aa_audit sa;
+
+ sa.type = AA_AUDITTYPE_ATTR;
+ sa.pval = iattr;
+ sa.flags = 0;
+ sa.gfp_mask = GFP_KERNEL;
+
+ error = _aa_perm_vfsmount(active, dentry, mnt, &sa, MAY_WRITE);
+
+ return error;
+}
+
+/**
+ * aa_perm_xattr - check whether xattr attribute change allowed
+ * @active: profile to check against
+ * @dentry: file to check
+ * @mnt: mount of file to check
+ * @operation: xattr operation being done
+ * @xattr_name: name of xattr to check
+ * @mask: access mode requested
+ */
+int aa_perm_xattr(struct aa_profile *active, struct dentry *dentry,
+ struct vfsmount *mnt, const char *operation,
+ const char *xattr_name, int mask)
+{
+ int error;
+ struct aa_audit sa;
+
+ sa.type = AA_AUDITTYPE_XATTR;
+ sa.operation = operation;
+ sa.pval = xattr_name;
+ sa.flags = 0;
+ sa.gfp_mask = GFP_KERNEL;
+
+ error = _aa_perm_vfsmount(active, dentry, mnt, &sa, mask);
+
+ return error;
+}
+
+/**
+ * aa_perm - basic apparmor permissions check
+ * @active: profile to check against
+ * @dentry: dentry
+ * @mnt: mountpoint
+ * @mask: access mode requested
+ *
+ * Determine if access (mask) for dentry is authorized by active
+ * profile. Result, %0 (success), -ve (error)
+ */
+int aa_perm(struct aa_profile *active, struct dentry *dentry,
+ struct vfsmount *mnt, int mask)
+{
+ int error = 0;
+ struct aa_audit sa;
+
+ if ((mask = aa_filter_mask(mask, dentry->d_inode)) == 0)
+ goto out;
+
+ sa.type = AA_AUDITTYPE_FILE;
+ sa.mask = mask;
+ sa.flags = 0;
+ sa.gfp_mask = GFP_KERNEL;
+ error = _aa_perm_vfsmount(active, dentry, mnt, &sa, mask);
+
+out:
+ return error;
+}
+
+/**
+ * aa_perm_dir
+ * @active: profile to check against
+ * @dentry: requested dentry
+ * @mnt: mount of file to check
+ * @operation: directory operation being performed
+ * @mask: access mode requested
+ *
+ * Determine if directory operation (make/remove) for dentry is authorized
+ * by @active profile.
+ * Result, %0 (success), -ve (error)
+ */
+int aa_perm_dir(struct aa_profile *active, struct dentry *dentry,
+ struct vfsmount *mnt, const char *operation, int mask)
+{
+ struct aa_audit sa;
+
+ sa.type = AA_AUDITTYPE_DIR;
+ sa.operation = operation;
+ sa.flags = 0;
+ sa.gfp_mask = GFP_KERNEL;
+
+ return _aa_perm_vfsmount(active, dentry, mnt, &sa, mask);
+}
+
+/**
+ * aa_capability - test permission to use capability
+ * @active: profile to check against
+ * @cap: capability to be tested
+ *
+ * Look up capability in active profile capability set.
+ * Return %0 (success), -%EPERM (error)
+ */
+int aa_capability(struct aa_profile *active, int cap)
+{
+ int error = 0;
+ struct aa_audit sa;
+
+ sa.type = AA_AUDITTYPE_CAP;
+ sa.name = NULL;
+ sa.capability = cap;
+ sa.flags = 0;
+ sa.error_code = 0;
+ sa.result = cap_raised(active->capabilities, cap);
+ sa.gfp_mask = GFP_ATOMIC;
+
+ error = aa_audit(active, &sa);
+
+ return error;
+}
+
+/**
+ * aa_link - hard link check
+ * @active: profile to check against
+ * @link: dentry for link being created
+ * @target: dentry for link target
+ * @mnt: vfsmount (-EXDEV is link and target are not on same vfsmount)
+ */
+int aa_link(struct aa_profile *active,
+ struct dentry *link, struct vfsmount *link_mnt,
+ struct dentry *target, struct vfsmount *target_mnt)
+{
+ int permerror = -EPERM, error;
+ struct aa_audit sa;
+
+ sa.name = aa_get_name(link, link_mnt);
+ sa.pval = aa_get_name(target, target_mnt);
+
+ if (IS_ERR(sa.name)) {
+ permerror = PTR_ERR(sa.name);
+ sa.name = NULL;
+ }
+ if (IS_ERR(sa.pval)) {
+ permerror = PTR_ERR(sa.pval);
+ sa.pval = NULL;
+ }
+
+ if (sa.name && sa.pval)
+ permerror = aa_link_perm(active, sa.name, sa.pval);
+
+ aa_permerror2result(permerror, &sa);
+
+ sa.type = AA_AUDITTYPE_LINK;
+ sa.flags = 0;
+ sa.gfp_mask = GFP_KERNEL;
+
+ error = aa_audit(active, &sa);
+
+ aa_put_name(sa.name);
+ aa_put_name(sa.pval);
+
+ return error;
+}
+
+/*******************************
+ * Global task related functions
+ *******************************/
+
+/**
+ * aa_fork - create a new subdomain
+ * @p: new process
+ *
+ * Create a new subdomain for newly created process @p if it's parent
+ * is already confined. Otherwise a subdomain will be lazily allocated
+ * will get one with NULL values. Return 0 on sucess.
+ * for the child if it subsequently execs (in aa_register).
+ * Return 0 on sucess.
+ *
+ * The sd_lock is used to maintain consistency against profile
+ * replacement/removal.
+ */
+
+int aa_fork(struct task_struct *p)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
+ struct subdomain *newsd = NULL;
+
+ AA_DEBUG("%s\n", __FUNCTION__);
+
+ if (__aa_is_confined(sd)) {
+ unsigned long flags;
+
+ newsd = alloc_subdomain(p);
+
+ if (!newsd)
+ return -ENOMEM;
+
+ /* Use locking here instead of getting the reference
+ * because we need both the old reference and the
+ * new reference to be consistent.
+ */
+ spin_lock_irqsave(&sd_lock, flags);
+ aa_switch(newsd, sd->active);
+ newsd->hat_magic = sd->hat_magic;
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+ if (SUBDOMAIN_COMPLAIN(sd) &&
+ sd->active == null_complain_profile)
+ LOG_HINT(sd->active, GFP_KERNEL, HINT_FORK,
+ "pid=%d child=%d\n",
+ current->pid, p->pid);
+ }
+ p->security = newsd;
+ return 0;
+}
+
+/**
+ * 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 subdomain.
+ */
+int aa_register(struct linux_binprm *bprm)
+{
+ char *filename;
+ struct file *filp = bprm->file;
+ struct aa_profile *active;
+ struct aa_profile *newprofile = NULL, unconstrained_flag;
+ int error = -ENOMEM,
+ exec_mode = 0,
+ find_profile = 0,
+ find_profile_mandatory = 0,
+ complain = 0,
+ unsafe_exec = 0;
+
+ AA_DEBUG("%s\n", __FUNCTION__);
+
+ filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt);
+ if (IS_ERR(filename)) {
+ AA_WARN("%s: Failed to get filename\n", __FUNCTION__);
+ goto out;
+ }
+
+ error = 0;
+
+ active = get_active_aa_profile();
+
+ if (!active) {
+ /* Unconfined task, load profile if it exists */
+ find_profile = 1;
+ goto find_profile;
+ }
+
+ complain = PROFILE_COMPLAIN(active);
+
+ /* Confined task, determine what mode inherit, unconstrained or
+ * mandatory to load new profile
+ */
+ exec_mode = AA_EXEC_MASK(aa_match(active->file_rules, filename));
+ unsafe_exec = exec_mode & AA_EXEC_UNSAFE;
+
+ if (exec_mode) {
+ switch (AA_EXEC_MODIFIER_MASK(exec_mode)) {
+ case AA_EXEC_INHERIT:
+ /* do nothing - setting of profile
+ * already handed in aa_fork
+ */
+ AA_DEBUG("%s: INHERIT %s\n",
+ __FUNCTION__,
+ filename);
+ break;
+
+ case AA_EXEC_UNCONSTRAINED:
+ AA_DEBUG("%s: UNCONSTRAINED %s\n",
+ __FUNCTION__,
+ filename);
+
+ /* unload profile */
+ newprofile = &unconstrained_flag;
+ break;
+
+ case AA_EXEC_PROFILE:
+ AA_DEBUG("%s: PROFILE %s\n",
+ __FUNCTION__,
+ filename);
+
+ find_profile = 1;
+ find_profile_mandatory = 1;
+ break;
+
+ case AA_MAY_EXEC:
+ /* this should not happen, entries
+ * with just EXEC only should be
+ * rejected at profile load time
+ */
+ AA_ERROR("%s: Rejecting exec(2) of image '%s'. "
+ "AA_MAY_EXEC without exec qualifier invalid "
+ "(%s(%d) profile %s active %s\n",
+ __FUNCTION__,
+ filename,
+ current->comm, current->pid,
+ BASE_PROFILE(active)->name, active->name);
+ error = -EPERM;
+ break;
+
+ default:
+ AA_ERROR("%s: Rejecting exec(2) of image '%s'. "
+ "Unknown exec qualifier %x "
+ "(%s (pid %d) profile %s active %s)\n",
+ __FUNCTION__,
+ filename,
+ exec_mode,
+ current->comm, current->pid,
+ BASE_PROFILE(active)->name, active->name);
+ error = -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).
+ */
+ newprofile = get_aa_profile(null_complain_profile);
+ unsafe_exec = 1;
+ } else {
+ AA_WARN("%s: Rejecting exec(2) of image '%s'. "
+ "Unable to determine exec qualifier "
+ "(%s (pid %d) profile %s active %s)\n",
+ __FUNCTION__,
+ filename,
+ current->comm, current->pid,
+ BASE_PROFILE(active)->name, active->name);
+ error = -EPERM;
+ }
+
+
+find_profile:
+ if (!find_profile)
+ goto apply_profile;
+
+ /* Locate new profile */
+ newprofile = aa_profilelist_find(filename);
+ if (newprofile) {
+ AA_DEBUG("%s: setting profile %s\n",
+ __FUNCTION__, newprofile->name);
+ } else if (find_profile_mandatory) {
+ /* Profile (mandatory) could not be found */
+
+ if (complain) {
+ LOG_HINT(active, GFP_KERNEL, HINT_MANDPROF,
+ "image=%s pid=%d profile=%s active=%s\n",
+ filename,
+ current->pid,
+ BASE_PROFILE(active)->name, active->name);
+
+ newprofile = get_aa_profile(null_complain_profile);
+ } else {
+ AA_WARN("REJECTING exec(2) of image '%s'. "
+ "Profile mandatory and not found "
+ "(%s(%d) profile %s active %s)\n",
+ filename,
+ current->comm, current->pid,
+ BASE_PROFILE(active)->name, active->name);
+ error = -EPERM;
+ }
+ } else {
+ /* Profile (non-mandatory) could not be found */
+
+ /* Only way we can get into this code is if task
+ * is unconstrained.
+ */
+
+ WARN_ON(active);
+
+ AA_DEBUG("%s: No profile found for exec image %s\n",
+ __FUNCTION__,
+ filename);
+ } /* newprofile */
+
+
+apply_profile:
+ /* Apply profile if necessary */
+ if (newprofile) {
+ struct subdomain *sd, *lazy_sd = NULL;
+ unsigned long flags;
+
+ if (newprofile == &unconstrained_flag)
+ newprofile = NULL;
+
+ /* grab a lock - this is to guarentee consistency against
+ * other writers of subdomain (replacement/removal)
+ *
+ * Several things may have changed since the code above
+ *
+ * - Task may be presently unconfined (have no sd). In which
+ * case we have to lazily allocate one. Note we may be raced
+ * to this allocation by a setprofile.
+ *
+ * - If we are a confined process, active is a refcounted copy
+ * of the profile that was on the subdomain at entry.
+ * This allows us to not have to hold a lock around
+ * all this code. If profile replacement has taken place
+ * our active may not equal sd->active any more.
+ * This is okay since the operation is treated as if
+ * the transition occured before replacement.
+ *
+ * - If newprofile points to an actual profile (result of
+ * aa_profilelist_find above), this profile may have been
+ * replaced. We need to fix it up. Doing this to avoid
+ * having to hold a lock around all this code.
+ */
+
+ if (!active && !(sd = AA_SUBDOMAIN(current->security))) {
+ lazy_sd = alloc_subdomain(current);
+ if (!lazy_sd) {
+ AA_ERROR("%s: Failed to allocate subdomain\n",
+ __FUNCTION__);
+ error = -ENOMEM;
+ goto cleanup;
+ }
+ }
+
+ spin_lock_irqsave(&sd_lock, flags);
+
+ sd = AA_SUBDOMAIN(current->security);
+ if (lazy_sd) {
+ if (sd) {
+ /* raced by setprofile - created sd */
+ free_subdomain(lazy_sd);
+ lazy_sd = NULL;
+ } else {
+ /* Not rcu used to get the write barrier
+ * correct */
+ rcu_assign_pointer(current->security, lazy_sd);
+ sd = lazy_sd;
+ }
+ }
+
+ /* Determine if profile we found earlier is stale.
+ * If so, reobtain it. N.B stale flag should never be
+ * set on null_complain profile.
+ */
+ if (newprofile && unlikely(newprofile->isstale)) {
+ WARN_ON(newprofile == null_complain_profile);
+
+ /* drop refcnt obtained from earlier get_aa_profile */
+ put_aa_profile(newprofile);
+
+ newprofile = aa_profilelist_find(filename);
+
+ if (!newprofile) {
+ /* Race, profile was removed, not replaced.
+ * Redo with error checking
+ */
+ spin_unlock_irqrestore(&sd_lock, flags);
+ goto find_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 (__aa_is_confined(sd) && !unsafe_exec) {
+ unsigned long bprm_flags;
+
+ bprm_flags = AA_SECURE_EXEC_NEEDED;
+ bprm->security = (void*)
+ ((unsigned long)bprm->security | bprm_flags);
+ }
+
+ aa_switch(sd, newprofile);
+ put_aa_profile(newprofile);
+
+ if (complain && newprofile == null_complain_profile)
+ LOG_HINT(newprofile, GFP_ATOMIC, HINT_CHGPROF,
+ "pid=%d\n",
+ current->pid);
+
+ spin_unlock_irqrestore(&sd_lock, flags);
+ }
+
+cleanup:
+ aa_put_name(filename);
+
+ put_aa_profile(active);
+
+out:
+ return error;
+}
+
+/**
+ * aa_release - release the task's subdomain
+ * @p: task being released
+ *
+ * This is called after a task has exited and the parent has reaped it.
+ * @p->security blob is freed.
+ *
+ * This is the one case where we don't need to hold the sd_lock before
+ * removing a profile from a subdomain. Once the subdomain has been
+ * removed from the subdomain_list, we are no longer racing other writers.
+ * There may still be other readers so we must still use aa_switch
+ * to put the subdomain's reference safely.
+ */
+void aa_release(struct task_struct *p)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(p->security);
+ if (sd) {
+ p->security = NULL;
+
+ aa_subdomainlist_remove(sd);
+ aa_switch_unconfined(sd);
+
+ kfree(sd);
+ }
+}
+
+/*****************************
+ * global subprofile functions
+ ****************************/
+
+/**
+ * do_change_hat - actually switch hats
+ * @hat_name: name of hat to swtich to
+ * @sd: current subdomain
+ *
+ * Switch to a new hat. Return %0 on success, error otherwise.
+ */
+static inline int do_change_hat(const char *hat_name, struct subdomain *sd)
+{
+ struct aa_profile *sub;
+ int error = 0;
+
+ sub = __aa_find_profile(hat_name, &BASE_PROFILE(sd->active)->sub);
+
+ if (sub) {
+ /* change hat */
+ aa_switch(sd, sub);
+ put_aa_profile(sub);
+ } else {
+ /* There is no such subprofile change to a NULL profile.
+ * The NULL profile grants no file access.
+ *
+ * This feature is used by changehat_apache.
+ *
+ * N.B from the null-profile the task can still changehat back
+ * out to the parent profile (assuming magic != NULL)
+ */
+ if (SUBDOMAIN_COMPLAIN(sd)) {
+ LOG_HINT(sd->active, GFP_ATOMIC, HINT_UNKNOWN_HAT,
+ "%s pid=%d "
+ "profile=%s active=%s\n",
+ hat_name,
+ current->pid,
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+ } else {
+ AA_DEBUG("%s: Unknown hatname '%s'. "
+ "Changing to NULL profile "
+ "(%s(%d) profile %s active %s)\n",
+ __FUNCTION__,
+ hat_name,
+ current->comm, current->pid,
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+ error = -EACCES;
+ }
+ aa_switch(sd, sd->active->null_profile);
+ }
+
+ return error;
+}
+
+/**
+ * aa_change_hat - change hat to/from subprofile
+ * @hat_name: specifies hat to change to
+ * @hat_magic: token to validate hat change
+ *
+ * Change to new @hat_name when current hat is top level profile, and store
+ * the @hat_magic in the current subdomain. If the new @hat_name is
+ * %NULL, and the @hat_magic matches that stored in the current subdomain
+ * return to original top level profile. Returns %0 on success, error
+ * otherwise.
+ */
+int aa_change_hat(const char *hat_name, u32 hat_magic)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
+ int error = 0;
+
+ AA_DEBUG("%s: %p, 0x%x (pid %d)\n",
+ __FUNCTION__,
+ hat_name, hat_magic,
+ current->pid);
+
+ /* Dump out above debugging in WARN mode if we are in AUDIT mode */
+ if (SUBDOMAIN_AUDIT(sd)) {
+ AA_WARN("%s: %s, 0x%x (pid %d)\n",
+ __FUNCTION__, hat_name ? hat_name : "NULL",
+ hat_magic, current->pid);
+ }
+
+ /* check to see if an unconfined process is doing a changehat. */
+ if (!__aa_is_confined(sd)) {
+ error = -EPERM;
+ goto out;
+ }
+
+ /* check to see if the confined process has any hats. */
+ if (list_empty(&BASE_PROFILE(sd->active)->sub) &&
+ !PROFILE_COMPLAIN(sd->active)) {
+ error = -ECHILD;
+ goto out;
+ }
+
+ /* Check whether current domain is parent
+ * or one of the sibling children
+ */
+ if (!IN_SUBPROFILE(sd->active)) {
+ /*
+ * parent
+ */
+ if (hat_name) {
+ AA_DEBUG("%s: switching to %s, 0x%x\n",
+ __FUNCTION__,
+ hat_name,
+ hat_magic);
+
+ /*
+ * N.B hat_magic == 0 has a special meaning
+ * this indicates that the task may never changehat
+ * back to it's parent, it will stay in this subhat
+ * (or null-profile, if the hat doesn't exist) until
+ * the task terminates
+ */
+ sd->hat_magic = hat_magic;
+ error = do_change_hat(hat_name, sd);
+ } else {
+ /* Got here via changehat(NULL, magic)
+ *
+ * We used to simply update the magic cookie.
+ * That's an odd behaviour, so just do nothing.
+ */
+ }
+ } else {
+ /*
+ * child -- 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 changehats
+ */
+ if (hat_magic == sd->hat_magic && sd->hat_magic) {
+ if (!hat_name) {
+ /*
+ * Got here via changehat(NULL, magic)
+ * Return from subprofile, back to parent
+ */
+ aa_switch(sd, sd->active->parent);
+
+ /* Reset hat_magic to zero.
+ * New value will be passed on next changehat
+ */
+ sd->hat_magic = 0;
+ } else {
+ /* change to another (sibling) profile */
+ error = do_change_hat(hat_name, sd);
+ }
+ } else if (sd->hat_magic) {
+ AA_ERROR("KILLING process %s(%d) "
+ "Invalid change_hat() magic# 0x%x "
+ "(hatname %s profile %s active %s)\n",
+ current->comm, current->pid,
+ hat_magic,
+ hat_name ? hat_name : "NULL",
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+
+ /* terminate current process */
+ (void)send_sig_info(SIGKILL, NULL, current);
+ } else { /* sd->hat_magic == NULL */
+ AA_ERROR("KILLING process %s(%d) "
+ "Task was confined to current subprofile "
+ "(profile %s active %s)\n",
+ current->comm, current->pid,
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+
+ /* terminate current process */
+ (void)send_sig_info(SIGKILL, NULL, current);
+ }
+
+ }
+
+out:
+ return error;
+}
Index: b/security/apparmor/module_interface.c
===================================================================
--- /dev/null
+++ b/security/apparmor/module_interface.c
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 1998-2005 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 userspace policy interface
+ */
+
+#include <asm/unaligned.h>
+
+#include "apparmor.h"
+#include "inline.h"
+#include "module_interface.h"
+
+/* aa_code defined in module_interface.h */
+
+const int aa_code_datasize[] = { 1, 2, 4, 8, 2, 2, 4, 0, 0, 0, 0, 0, 0 };
+
+struct aa_taskreplace_data {
+ struct aa_profile *old_profile;
+ struct aa_profile *new_profile;
+};
+
+/**
+ * free_aa_profile_rcu - rcu callback for free profiles
+ * @head: rcu_head struct of the profile whose reference is being put.
+ *
+ * the rcu callback routine, which delays the freeing of a profile when
+ * its last reference is put.
+ */
+static void free_aa_profile_rcu(struct rcu_head *head)
+{
+ struct aa_profile *p = container_of(head, struct aa_profile, rcu);
+ free_aa_profile(p);
+}
+
+/**
+ * task_remove - remove profile from a task's subdomain
+ * @sd: task's subdomain
+ *
+ * remove the active profile from a task's subdomain, switching the task
+ * to an unconfined state.
+ */
+static inline void task_remove(struct subdomain *sd)
+{
+ /* spin_lock(&sd_lock) held here */
+ AA_DEBUG("%s: removing profile from task %s(%d) profile %s active %s\n",
+ __FUNCTION__,
+ sd->task->comm,
+ sd->task->pid,
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+
+ aa_switch_unconfined(sd);
+}
+
+/** taskremove_iter - Iterator to unconfine subdomains which match cookie
+ * @sd: subdomain to consider for profile removal
+ * @cookie: pointer to the oldprofile which is being removed
+ *
+ * If the subdomain's active profile matches old_profile, then call
+ * task_remove() to remove the profile leaving the task (subdomain) unconfined.
+ */
+static int taskremove_iter(struct subdomain *sd, void *cookie)
+{
+ struct aa_profile *old_profile = (struct aa_profile *)cookie;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sd_lock, flags);
+
+ if (__aa_is_confined(sd) && BASE_PROFILE(sd->active) == old_profile) {
+ task_remove(sd);
+ }
+
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+ return 0;
+}
+
+/** task_replace - replace subdomain's current profile with a new profile
+ * @sd: subdomain to replace the profile on
+ * @new: new profile
+ *
+ * Replace a task's (subdomain's) active profile with a new profile. If
+ * task was in a hat then the new profile will also be in the equivalent
+ * hat in the new profile if it exists. If it doesn't exist the
+ * task will be placed in the special null_profile state.
+ */
+static inline void task_replace(struct subdomain *sd, struct aa_profile *new)
+{
+ AA_DEBUG("%s: replacing profile for task %s(%d) "
+ "profile=%s (%p) active=%s (%p)\n",
+ __FUNCTION__,
+ sd->task->comm, sd->task->pid,
+ BASE_PROFILE(sd->active)->name, BASE_PROFILE(sd->active),
+ sd->active->name, sd->active);
+
+ if (!sd->active)
+ goto out;
+
+ if (IN_SUBPROFILE(sd->active)) {
+ struct aa_profile *nactive;
+
+ /* The old profile was in a hat, check to see if the new
+ * profile has an equivalent hat */
+ nactive = __aa_find_profile(sd->active->name, &new->sub);
+
+ if (!nactive)
+ nactive = get_aa_profile(new->null_profile);
+
+ aa_switch(sd, nactive);
+ put_aa_profile(nactive);
+ } else {
+ aa_switch(sd, new);
+ }
+
+ out:
+ return;
+}
+
+/** taskreplace_iter - Iterator to replace a subdomain's profile
+ * @sd: subdomain to consider for profile replacement
+ * @cookie: pointer to the old profile which is being replaced.
+ *
+ * If the subdomain's active profile matches old_profile call
+ * task_replace() to replace with the subdomain's active profile with
+ * the new profile.
+ */
+static int taskreplace_iter(struct subdomain *sd, void *cookie)
+{
+ struct aa_taskreplace_data *data = (struct aa_taskreplace_data *)cookie;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sd_lock, flags);
+
+ if (__aa_is_confined(sd) &&
+ BASE_PROFILE(sd->active) == data->old_profile)
+ task_replace(sd, data->new_profile);
+
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+ return 0;
+}
+
+static inline int aa_inbounds(struct aa_ext *e, size_t size)
+{
+ return (e->pos + size <= e->end);
+}
+
+/**
+ * aa_convert - convert trailing values of serialized type codes
+ * @code: type code
+ * @dest: pointer to object to receive the converted value
+ * @src: pointer to value to convert
+ *
+ * for serialized type codes which have a trailing value, convert it
+ * and place it in @dest. If a code does not have a trailing value nop.
+ */
+static void aa_convert(enum aa_code code, void *dest, void *src)
+{
+ switch (code) {
+ case AA_U8:
+ *(u8 *)dest = *(u8 *) src;
+ break;
+ case AA_U16:
+ case AA_NAME:
+ case AA_DYN_STRING:
+ *(u16 *)dest = le16_to_cpu(get_unaligned((u16 *)src));
+ break;
+ case AA_U32:
+ case AA_BLOB_LOC:
+ case AA_STATIC_BLOB:
+ *(u32 *)dest = le32_to_cpu(get_unaligned((u32 *)src));
+ break;
+ case AA_U64:
+ *(u64 *)dest = le64_to_cpu(get_unaligned((u64 *)src));
+ break;
+ default:
+ /* nop - all other type codes do not have a trailing value */
+ ;
+ }
+}
+
+/**
+ * aa_is_X - check if the next element is of type X
+ * @e: serialized data extent information
+ * @code: type code
+ * @data: object located at @e->pos (of type @code) is written into @data
+ * if @data is non-null. if data is null it means skip this
+ * entry
+ * check to see if the next element in the serialized data stream is of type
+ * X and check that it is with in bounds, if so put the associated value in
+ * @data.
+ * return the size of bytes associated with the returned data
+ * for complex object like blob and string a pointer to the allocated
+ * data is returned in data, but the size of the blob or string is
+ * returned.
+ */
+static u32 aa_is_X(struct aa_ext *e, enum aa_code code, void *data)
+{
+ void *pos = e->pos;
+ int ret = 0;
+ if (!aa_inbounds(e, AA_CODE_BYTE + aa_code_datasize[code]))
+ goto fail;
+ if (code != *(u8 *)e->pos &&
+ !(code == AA_BLOB_LOC && AA_STATIC_BLOB == *(u8 *)e->pos))
+ goto out;
+ e->pos += AA_CODE_BYTE;
+ if (code == AA_NAME) {
+ u16 size;
+ /* name codes are followed by X bytes */
+ size = le16_to_cpu(get_unaligned((u16 *)e->pos));
+ if (!aa_inbounds(e, (size_t) size))
+ goto fail;
+ if (data)
+ *(u16 *)data = size;
+ e->pos += aa_code_datasize[code];
+ ret = 1 + aa_code_datasize[code];
+ } else if (code == AA_DYN_STRING) {
+ u16 size;
+ char *str;
+ /* strings codes are followed by X bytes */
+ size = le16_to_cpu(get_unaligned((u16 *)e->pos));
+ e->pos += aa_code_datasize[code];
+ if (!aa_inbounds(e, (size_t) size))
+ goto fail;
+ if (data) {
+ * (char **)data = NULL;
+ str = kmalloc(size, GFP_KERNEL);
+ if (!str)
+ goto fail;
+ memcpy(str, e->pos, (size_t) size);
+ str[size-1] = '\0';
+ * (char **)data = str;
+ }
+ e->pos += size;
+ ret = size;
+
+ } else if (code == AA_BLOB_LOC) {
+ u32 size;
+ /* blobs are followed by X bytes */
+ size = le32_to_cpu(get_unaligned((u32 *)e->pos));
+ e->pos += aa_code_datasize[AA_STATIC_BLOB];
+ if (!aa_inbounds(e, (size_t) size))
+ goto fail;
+ if (data) {
+ * (char **)data = e->pos;
+ }
+ e->pos += size;
+ ret = size;
+
+ } else if (code == AA_STATIC_BLOB) {
+ u32 size;
+ /* blobs are followed by X bytes, that can be 2^32 */
+ size = le32_to_cpu(get_unaligned((u32 *)e->pos));
+ e->pos += aa_code_datasize[code];
+ if (!aa_inbounds(e, (size_t) size))
+ goto fail;
+ if (data)
+ memcpy(data, e->pos, (size_t) size);
+ e->pos += size;
+ ret = size;
+ } else {
+ if (data)
+ aa_convert(code, data, e->pos);
+ e->pos += aa_code_datasize[code];
+ ret = 1 + aa_code_datasize[code];
+ }
+out:
+ return ret;
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+/**
+ * aa_is_nameX - check is the next element is of type X with a name of @name
+ * @e: serialized data extent information
+ * @code: type code
+ * @data: location to store deserialized data if match isX criteria
+ * @name: name to match to the serialized element.
+ *
+ * check that the next serialized data element is of type X and has a tag
+ * name @name. If the code matches and name (if specified) matches then
+ * the packed data is unpacked into *data. (Note for strings this is the
+ * size, and the next data in the stream is the string data)
+ * returns %0 if either match failes
+ */
+static int aa_is_nameX(struct aa_ext *e, enum aa_code code, void *data,
+ const char *name)
+{
+ void *pos = e->pos;
+ u16 size;
+ u32 ret;
+ /* check for presence of a tagname, and if present name size
+ * AA_NAME tag value is a u16 */
+ if (aa_is_X(e, AA_NAME, &size)) {
+ /* if a name is specified it must match. otherwise skip tag */
+ if (name && ((strlen(name) != size-1) ||
+ strncmp(name, (char *)e->pos, (size_t)size-1)))
+ goto fail;
+ e->pos += size;
+ } else if (name) {
+ goto fail;
+ }
+
+ /* now check if data actually matches */
+ ret = aa_is_X(e, code, data);
+ if (!ret)
+ goto fail;
+ return ret;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+/* macro to wrap error case to make a block of reads look nicer */
+#define AA_READ_X(E, C, D, N) \
+ do { \
+ u32 __ret; \
+ __ret = aa_is_nameX((E), (C), (D), (N)); \
+ if (!__ret) \
+ goto fail; \
+ } while (0)
+
+/**
+ * aa_activate_net_entry - unpacked serialized net entries
+ * @e: serialized data extent information
+ *
+ * Ignore/skips net entries if they are present in the serialized data
+ * stream. Network confinement rules are currently unsupported but some
+ * user side tools can generate them so they are currently ignored.
+ */
+static inline int aa_activate_net_entry(struct aa_ext *e)
+{
+ AA_READ_X(e, AA_STRUCT, NULL, "ne");
+ AA_READ_X(e, AA_U32, NULL, NULL);
+ AA_READ_X(e, AA_U32, NULL, NULL);
+ AA_READ_X(e, AA_U32, NULL, NULL);
+ AA_READ_X(e, AA_U16, NULL, NULL);
+ AA_READ_X(e, AA_U16, NULL, NULL);
+ AA_READ_X(e, AA_U32, NULL, NULL);
+ AA_READ_X(e, AA_U32, NULL, NULL);
+ AA_READ_X(e, AA_U16, NULL, NULL);
+ AA_READ_X(e, AA_U16, NULL, NULL);
+ /* interface name is optional so just ignore return code */
+ aa_is_nameX(e, AA_DYN_STRING, NULL, NULL);
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ return 1;
+fail:
+ return 0;
+}
+
+/**
+ * aa_activate_dfa - unpack a file rule dfa
+ * @e: serialized data extent information
+ *
+ * returns dfa or ERR_PTR
+ */
+struct aa_dfa *aa_activate_dfa(struct aa_ext *e)
+{
+ char *blob = NULL;
+ size_t size, error = 0;
+ struct aa_dfa *dfa = NULL;
+
+ size = aa_is_nameX(e, AA_BLOB_LOC, &blob, "aadfa");
+ if (size) {
+ dfa = aa_match_alloc();
+ if (dfa) {
+ error = unpack_dfa(dfa, blob, size);
+
+ if (!error)
+ error = verify_dfa(dfa);
+ } else {
+ error = -ENOMEM;
+ }
+
+ if (error) {
+ aa_match_free(dfa);
+ dfa = ERR_PTR(error);
+ }
+ }
+
+ return dfa;
+}
+
+/**
+ * aa_activate_profile - unpack a serialized profile
+ * @e: serialized data extent information
+ * @error: error code returned if unpacking fails
+ */
+static struct aa_profile *aa_activate_profile(struct aa_ext *e, ssize_t *error)
+{
+ struct aa_profile *profile = NULL;
+ const char *rulename = "";
+ const char *error_string = "Invalid Profile";
+
+ *error = -EPROTO;
+
+ profile = alloc_aa_profile();
+ if (!profile) {
+ error_string = "Could not allocate profile";
+ *error = -ENOMEM;
+ goto fail;
+ }
+
+ /* check that we have the right struct being passed */
+ AA_READ_X(e, AA_STRUCT, NULL, "profile");
+ AA_READ_X(e, AA_DYN_STRING, &profile->name, NULL);
+
+ error_string = "Invalid flags";
+ /* per profile debug flags (debug, complain, audit) */
+ AA_READ_X(e, AA_STRUCT, NULL, "flags");
+ AA_READ_X(e, AA_U32, &(profile->flags.debug), NULL);
+ AA_READ_X(e, AA_U32, &(profile->flags.complain), NULL);
+ AA_READ_X(e, AA_U32, &(profile->flags.audit), NULL);
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ error_string = "Invalid capabilities";
+ AA_READ_X(e, AA_U32, &(profile->capabilities), NULL);
+
+ /* get file rules */
+ profile->file_rules = aa_activate_dfa(e);
+ if (IS_ERR(profile->file_rules)) {
+ error_string = "Invalid file rule dfa\n";
+ *error = PTR_ERR(profile->file_rules);
+ profile->file_rules = NULL;
+ goto fail;
+ }
+
+ /* get the net entries */
+ if (aa_is_nameX(e, AA_LIST, NULL, "net")) {
+ error_string = "Invalid net entry";
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
+ if (!aa_activate_net_entry(e))
+ goto fail;
+ }
+ }
+ rulename = "";
+
+ /* get subprofiles */
+ if (aa_is_nameX(e, AA_LIST, NULL, "hats")) {
+ error_string = "Invalid profile hat";
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
+ struct aa_profile *subprofile;
+ subprofile = aa_activate_profile(e, error);
+ if (!subprofile)
+ goto fail;
+ subprofile->parent = profile;
+ list_add(&subprofile->list, &profile->sub);
+ }
+ }
+
+ error_string = "Invalid end of profile";
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ return profile;
+
+fail:
+ AA_WARN("%s: %s %s in profile %s\n", INTERFACE_ID, rulename,
+ error_string, profile && profile->name ? profile->name
+ : "unknown");
+
+ if (profile) {
+ free_aa_profile(profile);
+ profile = NULL;
+ }
+
+ return NULL;
+}
+
+/**
+ * aa_activate_top_profile - unpack a serialized base profile
+ * @e: serialized data extent information
+ * @error: error code returned if unpacking fails
+ *
+ * check interface version unpack a profile and all its hats and patch
+ * in any extra information that the profile needs.
+ */
+static void *aa_activate_top_profile(struct aa_ext *e, ssize_t *error)
+{
+ struct aa_profile *profile = NULL;
+
+ /* get the interface version */
+ if (!aa_is_nameX(e, AA_U32, &e->version, "version")) {
+ AA_WARN("%s: version missing\n", INTERFACE_ID);
+ *error = -EPROTONOSUPPORT;
+ goto fail;
+ }
+
+ /* check that the interface version is currently supported */
+ if (e->version != 3) {
+ AA_WARN("%s: unsupported interface version (%d)\n",
+ INTERFACE_ID, e->version);
+ *error = -EPROTONOSUPPORT;
+ goto fail;
+ }
+
+ profile = aa_activate_profile(e, error);
+ if (!profile)
+ goto fail;
+
+ if (!list_empty(&profile->sub) || profile->flags.complain) {
+ if (attach_nullprofile(profile))
+ goto fail;
+ }
+ return profile;
+
+fail:
+ free_aa_profile(profile);
+ return NULL;
+}
+
+/**
+ * aa_file_prof_add - add a new profile to the profile list
+ * @data: serialized data stream
+ * @size: size of the serialized data stream
+ *
+ * unpack and add a profile to the profile list. Return %0 or error
+ */
+ssize_t aa_file_prof_add(void *data, size_t size)
+{
+ struct aa_profile *profile = NULL;
+
+ struct aa_ext e = {
+ .start = data,
+ .end = data + size,
+ .pos = data
+ };
+ ssize_t error;
+
+ profile = aa_activate_top_profile(&e, &error);
+ if (!profile) {
+ AA_DEBUG("couldn't activate profile\n");
+ goto out;
+ }
+
+ /* aa_activate_top_profile allocates profile with initial 1 count
+ * aa_profilelist_add transfers that ref to profile list without
+ * further incrementing
+ */
+ if (aa_profilelist_add(profile)) {
+ error = size;
+ } else {
+ AA_WARN("trying to add profile (%s) that already exists.\n",
+ profile->name);
+ put_aa_profile(profile);
+ error = -EEXIST;
+ }
+
+out:
+ return error;
+}
+
+/**
+ * aa_file_prof_repl - replace a profile on the profile list
+ * @udata: serialized data stream
+ * @size: size of the serialized data stream
+ *
+ * unpack and replace a profile on the profile list and uses of that profile
+ * by any subdomain. If the profile does not exist on the profile list
+ * it is added. Return %0 or error.
+ */
+ssize_t aa_file_prof_repl(void *udata, size_t size)
+{
+ struct aa_taskreplace_data data;
+ struct aa_ext e = {
+ .start = udata,
+ .end = udata + size,
+ .pos = udata
+ };
+
+ ssize_t error;
+
+ data.new_profile = aa_activate_top_profile(&e, &error);
+ if (!data.new_profile) {
+ AA_DEBUG("couldn't activate profile\n");
+ goto out;
+ }
+
+ /* Refcount on data.new_profile is 1 (aa_activate_top_profile).
+ *
+ * This reference will be inherited by aa_profilelist_replace for it's
+ * profile list reference but this isn't sufficient.
+ *
+ * Another replace (*for-same-profile*) may race us here.
+ * Task A calls aa_profilelist_replace(new_profile) and is interrupted.
+ * Task B old_profile = aa_profilelist_replace() will return task A's
+ * new_profile with the count of 1. If task B proceeeds to put this
+ * profile it will dissapear from under task A.
+ *
+ * Grab extra reference on new_profile to prevent this
+ */
+
+ get_aa_profile(data.new_profile);
+
+ data.old_profile = aa_profilelist_replace(data.new_profile);
+
+ /* If there was an old profile, find all currently executing tasks
+ * using this profile and replace the old profile with the new.
+ */
+ if (data.old_profile) {
+ AA_DEBUG("%s: try to replace profile (%p)%s\n",
+ __FUNCTION__,
+ data.old_profile,
+ data.old_profile->name);
+
+ aa_subdomainlist_iterate(taskreplace_iter, (void *)&data);
+
+ /* it's off global list, and we are done replacing */
+ put_aa_profile(data.old_profile);
+ }
+
+ /* release extra reference obtained above (race) */
+ put_aa_profile(data.new_profile);
+
+ error = size;
+
+out:
+ return error;
+}
+
+/**
+ * aa_file_prof_remove - remove a profile from the system
+ * @name: name of the profile to remove
+ * @size: size of the name
+ *
+ * remove a profile from the profile list and all subdomain references
+ * to said profile. Return %0 on success, else error.
+ */
+ssize_t aa_file_prof_remove(const char *name, size_t size)
+{
+ struct aa_profile *old_profile;
+
+ /* if the old profile exists it will be removed from the list and
+ * a reference is returned.
+ */
+ old_profile = aa_profilelist_remove(name);
+
+ if (old_profile) {
+ /* remove profile from any tasks using it */
+ aa_subdomainlist_iterate(taskremove_iter, (void *)old_profile);
+
+ /* drop reference obtained by aa_profilelist_remove */
+ put_aa_profile(old_profile);
+ } else {
+ AA_WARN("%s: trying to remove profile (%s) that "
+ "doesn't exist - skipping.\n", __FUNCTION__, name);
+ return -ENOENT;
+ }
+
+ return size;
+}
+
+/**
+ * free_aa_profile_kref - free aa_profile by kref (called by put_aa_profile)
+ * @kr: kref callback for freeing of a profile
+ */
+void free_aa_profile_kref(struct kref *kr)
+{
+ struct aa_profile *p=container_of(kr, struct aa_profile, count);
+
+ call_rcu(&p->rcu, free_aa_profile_rcu);
+}
+
+/**
+ * free_aa_profile - free aa_profile structure
+ * @profile: the profile to free
+ *
+ * free a profile, its file entries hats and null_profile. All references
+ * to the profile, its hats and null_profile must have been put.
+ * If the profile was referenced by a subdomain free_aa_profile should be
+ * called from an rcu callback routine.
+ */
+void free_aa_profile(struct aa_profile *profile)
+{
+ struct aa_profile *p, *ptmp;
+
+ AA_DEBUG("%s(%p)\n", __FUNCTION__, profile);
+
+ if (!profile)
+ return;
+
+ /* profile is still on global profile list -- invalid */
+ if (!list_empty(&profile->list)) {
+ AA_ERROR("%s: internal error, "
+ "profile '%s' still on global list\n",
+ __FUNCTION__,
+ profile->name);
+ BUG();
+ }
+
+ aa_match_free(profile->file_rules);
+
+ /* use free_aa_profile instead of put_aa_profile to destroy the
+ * null_profile, because the null_profile use the same reference
+ * counting as hats, ie. the count goes to the base profile.
+ */
+ free_aa_profile(profile->null_profile);
+ list_for_each_entry_safe(p, ptmp, &profile->sub, list) {
+ list_del_init(&p->list);
+ p->parent = NULL;
+ put_aa_profile(p);
+ }
+
+ if (profile->name) {
+ AA_DEBUG("%s: %s\n", __FUNCTION__, profile->name);
+ kfree(profile->name);
+ }
+
+ kfree(profile);
+}
Index: b/security/apparmor/module_interface.h
===================================================================
--- /dev/null
+++ b/security/apparmor/module_interface.h
@@ -0,0 +1,38 @@
+#ifndef __MODULEINTERFACE_H
+#define __MODULEINTERFACE_H
+
+/* Codes of the types of basic structures that are understood */
+#define AA_CODE_BYTE (sizeof(u8))
+#define INTERFACE_ID "INTERFACE"
+
+#define APPARMOR_INTERFACE_VERSION 2
+
+enum aa_code {
+ AA_U8,
+ AA_U16,
+ AA_U32,
+ AA_U64,
+ AA_NAME, /* same as string except it is items name */
+ AA_DYN_STRING,
+ AA_STATIC_BLOB,
+ AA_STRUCT,
+ AA_STRUCTEND,
+ AA_LIST,
+ AA_LISTEND,
+ AA_OFFSET,
+ AA_BLOB_LOC,
+ AA_BAD
+};
+
+/* aa_ext tracks the kernel buffer and read position in it. The interface
+ * data is copied into a kernel buffer in apparmorfs and then handed off to
+ * the activate routines.
+ */
+struct aa_ext {
+ void *start;
+ void *end;
+ void *pos; /* pointer to current position in the buffer */
+ u32 version;
+};
+
+#endif /* __MODULEINTERFACE_H */
Index: b/security/apparmor/procattr.c
===================================================================
--- /dev/null
+++ b/security/apparmor/procattr.c
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2005 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 /proc/pid/attr handling
+ */
+
+/* for isspace */
+#include <linux/ctype.h>
+
+#include "apparmor.h"
+#include "inline.h"
+
+size_t aa_getprocattr(struct aa_profile *active, char *str, size_t size)
+{
+ int error = -EACCES; /* default to a perm denied */
+ size_t len;
+
+ if (active) {
+ size_t lena, lenm, lenp = 0;
+ const char *enforce_str = " (enforce)";
+ const char *complain_str = " (complain)";
+ const char *mode_str =
+ PROFILE_COMPLAIN(active) ? complain_str : enforce_str;
+
+ lenm = strlen(mode_str);
+
+ lena = strlen(active->name);
+
+ len = lena;
+ if (IN_SUBPROFILE(active)) {
+ lenp = strlen(BASE_PROFILE(active)->name);
+ len += (lenp + 1); /* +1 for ^ */
+ }
+ /* DONT null terminate strings we output via proc */
+ len += (lenm + 1); /* for \n */
+
+ if (len <= size) {
+ if (lenp) {
+ memcpy(str, BASE_PROFILE(active)->name,
+ lenp);
+ str += lenp;
+ *str++ = '^';
+ }
+
+ memcpy(str, active->name, lena);
+ str += lena;
+ memcpy(str, mode_str, lenm);
+ str += lenm;
+ *str++ = '\n';
+ error = len;
+ } else if (size == 0) {
+ error = len;
+ } else {
+ error = -ERANGE;
+ }
+ } else {
+ const char *unconstrained_str = "unconstrained\n";
+ len = strlen(unconstrained_str);
+
+ /* DONT null terminate strings we output via proc */
+ if (len <= size) {
+ memcpy(str, unconstrained_str, len);
+ error = len;
+ } else if (size == 0) {
+ error = len;
+ } else {
+ error = -ERANGE;
+ }
+ }
+
+ return error;
+
+}
+
+int aa_setprocattr_changehat(char *hatinfo, size_t infosize)
+{
+ int error = -EINVAL;
+ char *token = NULL, *hat, *smagic, *tmp;
+ u32 magic;
+ int rc, len, consumed;
+ unsigned long flags;
+
+ AA_DEBUG("%s: %p %zd\n", __FUNCTION__, hatinfo, infosize);
+
+ /* strip leading white space */
+ while (infosize && isspace(*hatinfo)) {
+ hatinfo++;
+ infosize--;
+ }
+
+ if (infosize == 0)
+ goto out;
+
+ /*
+ * Copy string to a new buffer so we can play with it
+ * It may be zero terminated but we add a trailing 0
+ * for 100% safety
+ */
+ token = kmalloc(infosize + 1, GFP_KERNEL);
+
+ if (!token) {
+ error = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(token, hatinfo, infosize);
+ token[infosize] = 0;
+
+ /* error is INVAL until we have at least parsed something */
+ error = -EINVAL;
+
+ tmp = token;
+ while (*tmp && *tmp != '^') {
+ tmp++;
+ }
+
+ if (!*tmp || tmp == token) {
+ AA_WARN("%s: Invalid input '%s'\n", __FUNCTION__, token);
+ goto out;
+ }
+
+ /* split magic and hat into two strings */
+ *tmp = 0;
+ smagic = token;
+
+ /*
+ * Initially set consumed=strlen(magic), as if sscanf
+ * consumes all input via the %x it will not process the %n
+ * directive. Otherwise, if sscanf does not consume all the
+ * input it will process the %n and update consumed.
+ */
+ consumed = len = strlen(smagic);
+
+ rc = sscanf(smagic, "%x%n", &magic, &consumed);
+
+ if (rc != 1 || consumed != len) {
+ AA_WARN("%s: Invalid hex magic %s\n",
+ __FUNCTION__,
+ smagic);
+ goto out;
+ }
+
+ hat = tmp + 1;
+
+ if (!*hat)
+ hat = NULL;
+
+ if (!hat && !magic) {
+ AA_WARN("%s: Invalid input, NULL hat and NULL magic\n",
+ __FUNCTION__);
+ goto out;
+ }
+
+ AA_DEBUG("%s: Magic 0x%x Hat '%s'\n",
+ __FUNCTION__, magic, hat ? hat : NULL);
+
+ spin_lock_irqsave(&sd_lock, flags);
+ error = aa_change_hat(hat, magic);
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+out:
+ if (token) {
+ memset(token, 0, infosize);
+ kfree(token);
+ }
+
+ return error;
+}
+
+int aa_setprocattr_setprofile(struct task_struct *p, char *profilename,
+ size_t profilesize)
+{
+ int error = -EINVAL;
+ struct aa_profile *profile = NULL;
+ struct subdomain *sd;
+ char *name = NULL;
+ unsigned long flags;
+
+ AA_DEBUG("%s: current %s(%d)\n",
+ __FUNCTION__, current->comm, current->pid);
+
+ /* strip leading white space */
+ while (profilesize && isspace(*profilename)) {
+ profilename++;
+ profilesize--;
+ }
+
+ if (profilesize == 0)
+ goto out;
+
+ /*
+ * Copy string to a new buffer so we guarantee it is zero
+ * terminated
+ */
+ name = kmalloc(profilesize + 1, GFP_KERNEL);
+
+ if (!name) {
+ error = -ENOMEM;
+ goto out;
+ }
+
+ strncpy(name, profilename, profilesize);
+ name[profilesize] = 0;
+
+ repeat:
+ if (strcmp(name, "unconstrained") != 0) {
+ profile = aa_profilelist_find(name);
+ if (!profile) {
+ AA_WARN("%s: Unable to switch task %s(%d) to profile"
+ "'%s'. No such profile.\n",
+ __FUNCTION__,
+ p->comm, p->pid,
+ name);
+
+ error = -EINVAL;
+ goto out;
+ }
+ }
+
+ spin_lock_irqsave(&sd_lock, flags);
+
+ sd = AA_SUBDOMAIN(p->security);
+
+ /* switch to unconstrained */
+ if (!profile) {
+ if (__aa_is_confined(sd)) {
+ AA_WARN("%s: Unconstraining task %s(%d) "
+ "profile %s active %s\n",
+ __FUNCTION__,
+ p->comm, p->pid,
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+
+ aa_switch_unconfined(sd);
+ } else {
+ AA_WARN("%s: task %s(%d) "
+ "is already unconstrained\n",
+ __FUNCTION__, p->comm, p->pid);
+ }
+ } else {
+ if (!sd) {
+ /* this task was created before module was
+ * loaded, allocate a subdomain
+ */
+ AA_WARN("%s: task %s(%d) has no subdomain\n",
+ __FUNCTION__, p->comm, p->pid);
+
+ /* unlock so we can safely GFP_KERNEL */
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+ sd = alloc_subdomain(p);
+ if (!sd) {
+ AA_WARN("%s: Unable to allocate subdomain for "
+ "task %s(%d). Cannot confine task to "
+ "profile %s\n",
+ __FUNCTION__,
+ p->comm, p->pid,
+ name);
+
+ error = -ENOMEM;
+ put_aa_profile(profile);
+
+ goto out;
+ }
+
+ spin_lock_irqsave(&sd_lock, flags);
+ if (!AA_SUBDOMAIN(p->security)) {
+ p->security = sd;
+ } else { /* race */
+ free_subdomain(sd);
+ sd = AA_SUBDOMAIN(p->security);
+ }
+ }
+
+ /* ensure the profile hasn't been replaced */
+
+ if (unlikely(profile->isstale)) {
+ WARN_ON(profile == null_complain_profile);
+
+ /* drop refcnt obtained from earlier get_aa_profile */
+ put_aa_profile(profile);
+ profile = aa_profilelist_find(name);
+
+ if (!profile) {
+ /* Race, profile was removed. */
+ spin_unlock_irqrestore(&sd_lock, flags);
+ goto repeat;
+ }
+ }
+
+ /* we do not do a normal task replace since we are not
+ * replacing with the same profile.
+ * If existing process is in a hat, it will be moved
+ * into the new parent profile, even if this new
+ * profile has a identical named hat.
+ */
+
+ AA_WARN("%s: Switching task %s(%d) "
+ "profile %s active %s to new profile %s\n",
+ __FUNCTION__,
+ p->comm, p->pid,
+ sd->active ? BASE_PROFILE(sd->active)->name :
+ "unconstrained",
+ sd->active ? sd->active->name : "unconstrained",
+ name);
+
+ aa_switch(sd, profile);
+
+ put_aa_profile(profile); /* drop ref we obtained above
+ * from aa_profilelist_find
+ */
+
+ /* Reset magic in case we were in a subhat before
+ * This is the only case where we zero the magic after
+ * calling aa_switch
+ */
+ sd->hat_magic = 0;
+ }
+
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+ error = 0;
+out:
+ kfree(name);
+
+ return error;
+}
Index: b/security/apparmor/shared.h
===================================================================
--- /dev/null
+++ b/security/apparmor/shared.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2000, 2001, 2004, 2005 Novell/SUSE
+ *
+ * Immunix AppArmor LSM
+ *
+ * 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.
+ */
+
+#ifndef _SHARED_H
+#define _SHARED_H
+
+/* start of system offsets */
+#define POS_AA_FILE_MIN 0
+#define POS_AA_MAY_EXEC POS_AA_FILE_MIN
+#define POS_AA_MAY_WRITE (POS_AA_MAY_EXEC + 1)
+#define POS_AA_MAY_READ (POS_AA_MAY_WRITE + 1)
+#define POS_AA_MAY_APPEND (POS_AA_MAY_READ + 1)
+/* end of system offsets */
+
+#define POS_AA_MAY_LINK (POS_AA_MAY_APPEND + 1)
+#define POS_AA_EXEC_INHERIT (POS_AA_MAY_LINK + 1)
+#define POS_AA_EXEC_UNCONSTRAINED (POS_AA_EXEC_INHERIT + 1)
+#define POS_AA_EXEC_PROFILE (POS_AA_EXEC_UNCONSTRAINED + 1)
+#define POS_AA_EXEC_MMAP (POS_AA_EXEC_PROFILE + 1)
+#define POS_AA_EXEC_UNSAFE (POS_AA_EXEC_MMAP + 1)
+#define POS_AA_FILE_MAX POS_AA_EXEC_UNSAFE
+
+/* Invalid perm permission */
+#define POS_AA_INVALID_POS 31
+
+/* Modeled after MAY_READ, MAY_WRITE, MAY_EXEC def'ns */
+#define AA_MAY_EXEC (0x01 << POS_AA_MAY_EXEC)
+#define AA_MAY_WRITE (0x01 << POS_AA_MAY_WRITE)
+#define AA_MAY_READ (0x01 << POS_AA_MAY_READ)
+#define AA_MAY_LINK (0x01 << POS_AA_MAY_LINK)
+#define AA_EXEC_INHERIT (0x01 << POS_AA_EXEC_INHERIT)
+#define AA_EXEC_UNCONSTRAINED (0x01 << POS_AA_EXEC_UNCONSTRAINED)
+#define AA_EXEC_PROFILE (0x01 << POS_AA_EXEC_PROFILE)
+#define AA_EXEC_MMAP (0x01 << POS_AA_EXEC_MMAP)
+#define AA_EXEC_UNSAFE (0x01 << POS_AA_EXEC_UNSAFE)
+#define AA_INVALID_PERM (0x01 << POS_AA_INVALID_POS)
+
+#define AA_EXEC_MODIFIERS (AA_EXEC_INHERIT | \
+ AA_EXEC_UNCONSTRAINED | \
+ AA_EXEC_PROFILE)
+#define AA_VALID_PERM_MASK ((1 << (POS_AA_FILE_MAX + 1)) - 1)
+
+#endif /* _SHARED_H */
Index: b/security/apparmor/match.c
===================================================================
--- /dev/null
+++ b/security/apparmor/match.c
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2002-2005 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.
+ *
+ * http://forge.novell.com/modules/xfmod/project/?apparmor
+ *
+ * AppArmor aa_match submodule (w/ pattern expansion).
+ *
+ */
+
+#include <asm/unaligned.h>
+#include <linux/module.h>
+#include "match.h"
+
+static const char *features="pattern=aadfa";
+
+static struct table_header *unpack_table(void *blob, size_t bsize)
+{
+ struct table_header *table = NULL;
+ struct table_header th;
+ size_t tsize;
+
+ if (bsize < sizeof(struct table_header))
+ goto out;
+
+ th.td_id = ntohs(get_unaligned((u16 *) (blob)));
+ th.td_flags = ntohs(get_unaligned((u16 *) (blob + 2)));
+ th.td_lolen = ntohl(get_unaligned((u32 *) (blob + 8)));
+ blob += sizeof(struct table_header);
+
+ if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 ||
+ th.td_flags == YYTD_DATA8))
+ goto out;
+
+ tsize = table_size(th.td_lolen, th.td_flags);
+ if (bsize < tsize)
+ goto out;
+
+ table = kmalloc(tsize, GFP_KERNEL);
+ if (table) {
+ *table = th;
+ if (th.td_flags == YYTD_DATA8)
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u8, ntohb);
+ else if (th.td_flags == YYTD_DATA16)
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u16, ntohs);
+ else
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u32, ntohl);
+ }
+
+out:
+ return table;
+}
+
+int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size)
+{
+ int i;
+ int error = -ENOMEM;
+
+ /* get dfa table set header */
+ if (size < sizeof(struct table_set_header))
+ goto fail;
+
+ dfa->th.th_magic = ntohl(get_unaligned((u32 *) (blob + 0)));
+ dfa->th.th_hsize = ntohl(get_unaligned((u32 *) (blob + 4)));
+ dfa->th.th_ssize = ntohl(get_unaligned((u32 *) (blob + 8)));
+ dfa->th.th_flags = ntohs(get_unaligned((u16 *) (blob + 12)));
+
+ if (dfa->th.th_magic != YYTH_MAGIC)
+ goto fail;
+
+ if (size < dfa->th.th_hsize)
+ goto fail;
+
+ blob += dfa->th.th_hsize;
+ size -= dfa->th.th_hsize;
+
+ while (size > 0) {
+ struct table_header *table;
+ table = unpack_table(blob, size);
+ if (!table)
+ goto fail;
+
+ switch(table->td_id) {
+ case YYTD_ID_ACCEPT:
+ case YYTD_ID_BASE:
+ dfa->tables[table->td_id - 1] = table;
+ if (table->td_flags != YYTD_DATA32)
+ goto fail_proto;
+ break;
+ case YYTD_ID_DEF:
+ case YYTD_ID_NXT:
+ case YYTD_ID_CHK:
+ dfa->tables[table->td_id - 1] = table;
+ if (table->td_flags != YYTD_DATA16)
+ goto fail_proto;
+ break;
+ case YYTD_ID_EC:
+ dfa->tables[table->td_id - 1] = table;
+ if (table->td_flags != YYTD_DATA8)
+ goto fail_proto;
+ break;
+ default:
+ kfree(table);
+ goto fail_proto;
+ }
+
+ blob += table_size(table->td_lolen, table->td_flags);
+ size -= table_size(table->td_lolen, table->td_flags);
+ }
+
+ error = 0;
+
+ return error;
+
+fail_proto:
+ error = -EPROTO;
+fail:
+ for (i = 0; i < YYTD_ID_NXT; i++) {
+ if (dfa->tables[i]) {
+ kfree(dfa->tables[i]);
+ dfa->tables[i] = NULL;
+ }
+ }
+ return error;
+}
+
+/**
+ * verify_dfa - verify that all the transitions and states in the dfa tables
+ * are in bounds.
+ * @dfa: dfa to test
+ *
+ * assumes dfa has gone through the verification done by unpacking
+ */
+int verify_dfa(struct aa_dfa *dfa)
+{
+ size_t i, state_count, trans_count;
+ int error = -EPROTO;
+
+ /* check that required tables exist */
+ if (!(dfa->tables[YYTD_ID_ACCEPT -1 ] &&
+ dfa->tables[YYTD_ID_DEF - 1] &&
+ dfa->tables[YYTD_ID_BASE - 1] &&
+ dfa->tables[YYTD_ID_NXT - 1] &&
+ dfa->tables[YYTD_ID_CHK - 1]))
+ goto out;
+
+ /* accept.size == default.size == base.size */
+ state_count = dfa->tables[YYTD_ID_BASE - 1]->td_lolen;
+ if (!(state_count == dfa->tables[YYTD_ID_DEF - 1]->td_lolen &&
+ state_count == dfa->tables[YYTD_ID_ACCEPT - 1]->td_lolen))
+ goto out;
+
+ /* next.size == chk.size */
+ trans_count = dfa->tables[YYTD_ID_NXT - 1]->td_lolen;
+ if (trans_count != dfa->tables[YYTD_ID_CHK - 1]->td_lolen)
+ goto out;
+
+ /* if equivalence classes then its table must be 256 */
+ if (dfa->tables[YYTD_ID_EC - 1] &&
+ dfa->tables[YYTD_ID_EC - 1]->td_lolen != 256)
+ goto out;
+
+ for (i = 0; i < state_count; i++) {
+ if (DEFAULT_TABLE(dfa)[i] >= state_count)
+ goto out;
+ if (BASE_TABLE(dfa)[i] >= trans_count + 256)
+ goto out;
+ }
+
+ for (i = 0; i < trans_count ; i++) {
+ if (NEXT_TABLE(dfa)[i] >= state_count)
+ goto out;
+ if (CHECK_TABLE(dfa)[i] >= state_count)
+ goto out;
+ }
+
+ error = 0;
+out:
+ return error;
+}
+
+struct aa_dfa *aa_match_alloc(void)
+{
+ return kzalloc(sizeof(struct aa_dfa), GFP_KERNEL);
+}
+
+void aa_match_free(struct aa_dfa *dfa)
+{
+ if (dfa) {
+ int i;
+ for (i = 0; i < YYTD_ID_NXT; i++) {
+ kfree(dfa->tables[i]);
+ }
+ }
+ kfree(dfa);
+}
+
+const char *aa_match_features(void)
+{
+ return features;
+}
+
+/**
+ * aadfa_label - return the permissions associated with @state
+ * @dfa: dfa to get state permission from
+ * @state: state in the dfa for which to get a label
+ *
+ * Assumes that state is a valid state of the dfa
+ *
+ * Returns the label associated with @state. 0 indicates the state
+ * is no-accepting/provides no permissions.
+ */
+inline unsigned int aadfa_label(struct aa_dfa *dfa, int state)
+{
+ return ACCEPT_TABLE(dfa)[state];
+}
+
+/**
+ * aa_dfa_match - match @path against @dfa starting in @state
+ * @dfa: the dfa to match @path against
+ * @state: the state to start matching in
+ * @path: the path to match against the dfa
+ *
+ * aa_dfa_match will match the full path length and return the state it
+ * finished matching in. The final state returned can be used to
+ * lookup the accepting label or as a starting point to continue matching
+ * with a new string if the path has been broken into multiple components.
+ */
+inline unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int state,
+ const char *path)
+{
+ u8 *s = (u8 *) path;
+ u16 *def = DEFAULT_TABLE(dfa);
+ u32 *base = BASE_TABLE(dfa);
+ u16 *next = NEXT_TABLE(dfa);
+ u16 *check = CHECK_TABLE(dfa);
+ unsigned int pos;
+
+ /* current state is <state>, matching character *s */
+ if (dfa->tables[YYTD_ID_EC - 1]) {
+ u8 *equiv = EQUIV_TABLE(dfa);
+ for ( ; *s; ++s) {
+ pos = base[state] + equiv[*s];
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
+ } else {
+ for ( ; *s; ++s) {
+ pos = base[state] + *s;
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
+ }
+ return state;
+}
+
+unsigned int aa_match(struct aa_dfa *dfa, const char *pathname)
+{
+ if (dfa)
+ return aadfa_label(dfa, aa_dfa_match(dfa, 1, pathname));
+
+ return 0;
+}
Index: b/security/apparmor/match.h
===================================================================
--- /dev/null
+++ b/security/apparmor/match.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2002-2005 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 submodule (match) prototypes
+ */
+
+#ifndef __MATCH_H
+#define __MATCH_H
+
+#define YYTH_MAGIC 0x1B5E783D
+
+struct table_set_header {
+ u32 th_magic; /* TH_MAGIC */
+ u32 th_hsize;
+ u32 th_ssize;
+ u16 th_flags;
+ char th_version[];
+};
+
+#define YYTD_ID_ACCEPT 1 /* 1 */
+#define YYTD_ID_BASE 2 /* 2 */
+#define YYTD_ID_CHK 3 /* 3 */
+#define YYTD_ID_DEF 4 /* 4 */
+#define YYTD_ID_EC 5 /* 5 */
+#define YYTD_ID_NXT 6 /* 8 */
+#define YYTD_ID_META 7 /* 6 */
+
+#define YYTD_DATA8 1
+#define YYTD_DATA16 2
+#define YYTD_DATA32 4
+
+struct table_header {
+ u16 td_id;
+ u16 td_flags;
+ u32 td_hilen;
+ u32 td_lolen;
+ char td_data[];
+};
+
+#define DEFAULT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_DEF - 1]->td_data))
+#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE - 1]->td_data))
+#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT - 1]->td_data))
+#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK - 1]->td_data))
+#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC - 1]->td_data))
+#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT - 1]->td_data))
+
+struct aa_dfa {
+ struct table_header *tables[YYTD_ID_NXT];
+
+ struct table_set_header th;
+};
+
+#define ntohb(X) (X)
+
+#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \
+ do { \
+ typeof(LEN) __i; \
+ TYPE *__t = (TYPE *) TABLE; \
+ TYPE *__b = (TYPE *) BLOB; \
+ for (__i = 0; __i < LEN; __i++) { \
+ __t[__i] = NTOHX(__b[__i]); \
+ } \
+ } while (0)
+
+static inline size_t pad64(size_t i)
+{
+ return (i + (size_t)7) & ~(size_t)7;
+}
+
+static inline size_t table_size(size_t len, size_t el_size)
+{
+ return pad64(sizeof(struct table_header) + len * el_size);
+}
+
+#endif /* __MATCH_H */