All the things that didn't nicely fit in a category on their own: kbuild code, declararions and inline functions, /sys/kernel/security/apparmor filesystem for controlling apparmor from user space, profile list functions, locking documentation, /proc/$pid/task/$tid/attr/current access. Signed-off-by: John Johansen Signed-off-by: Andreas Gruenbacher --- security/apparmor/Kconfig | 9 + security/apparmor/Makefile | 13 ++ security/apparmor/apparmor.h | 259 +++++++++++++++++++++++++++++++++++++++++ security/apparmor/apparmorfs.c | 250 +++++++++++++++++++++++++++++++++++++++ security/apparmor/inline.h | 219 ++++++++++++++++++++++++++++++++++ security/apparmor/list.c | 94 ++++++++++++++ security/apparmor/locking.txt | 59 +++++++++ security/apparmor/procattr.c | 138 +++++++++++++++++++++ 8 files changed, 1041 insertions(+) --- /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 + + If you are unsure how to answer this question, answer N. --- /dev/null +++ b/security/apparmor/Makefile @@ -0,0 +1,13 @@ +# Makefile for AppArmor Linux Security Module +# +obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o + +apparmor-y := main.o list.o procattr.o lsm.o apparmorfs.o \ + module_interface.o match.o + +quiet_cmd_make-caps = GEN $@ +cmd_make-caps = sed -n -e "/CAP_FS_MASK/d" -e "s/^\#define[ \\t]\\+CAP_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z > $@ + +$(obj)/main.o : $(obj)/capability_names.h +$(obj)/capability_names.h : $(srctree)/include/linux/capability.h + $(call cmd,make-caps) --- /dev/null +++ b/security/apparmor/apparmor.h @@ -0,0 +1,259 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor internal prototypes + */ + +#ifndef __APPARMOR_H +#define __APPARMOR_H + +#include +#include +#include +#include + +/* + * We use MAY_READ, MAY_WRITE, MAY_EXEC, and the following flags for + * profile permissions (we don't use MAY_APPEND): + */ +#define AA_MAY_LINK 0x0010 +#define AA_EXEC_INHERIT 0x0020 +#define AA_EXEC_UNCONFINED 0x0040 +#define AA_EXEC_PROFILE 0x0080 +#define AA_EXEC_MMAP 0x0100 +#define AA_EXEC_UNSAFE 0x0200 + +#define AA_EXEC_MODIFIERS (AA_EXEC_INHERIT | \ + AA_EXEC_UNCONFINED | \ + AA_EXEC_PROFILE) + +#define AA_SECURE_EXEC_NEEDED 1 + +/* 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; +extern unsigned int apparmor_path_max; + +#define PROFILE_COMPLAIN(_profile) \ + (apparmor_complain == 1 || ((_profile) && (_profile)->flags.complain)) + +#define APPARMOR_COMPLAIN(_cxt) \ + (apparmor_complain == 1 || \ + ((_cxt) && (_cxt)->profile && (_cxt)->profile->flags.complain)) + +#define PROFILE_AUDIT(_profile) \ + (apparmor_audit == 1 || ((_profile) && (_profile)->flags.audit)) + +#define APPARMOR_AUDIT(_cxt) \ + (apparmor_audit == 1 || \ + ((_cxt) && (_cxt)->profile && (_cxt)->profile->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_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args) + +/* 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 indicating if profile is stale + * @capabilities: capabilities granted by the process + * @count: reference count of the profile + * + * The AppArmor profile contains the basic confinement data. Each profile + * has a name and potentially a list of sub profile entries. All non stale + * profiles are on the profile_list. + * + * The task_contexts list and the isstale flag are protected by the + * profile lock. + * + * If a task context is moved between two profiles, we first need to grab + * both profile locks. lock_both_profiles() does that in a deadlock-safe + * way. + */ +struct aa_profile { + struct aa_profile *parent; + char *name; + struct aa_dfa *file_rules; + struct list_head list; + struct list_head sub; + struct { + int complain; + int audit; + } flags; + struct aa_profile *null_profile; + int isstale; + + kernel_cap_t capabilities; + struct kref count; + struct list_head task_contexts; + spinlock_t lock; + unsigned long int_flags; +}; + +extern struct list_head profile_list; +extern rwlock_t profile_list_lock; +extern struct mutex aa_interface_lock; + +/** + * struct aa_task_context - primary label for confined tasks + * @profile: the current profile + * @hat_magic: the magic token controling the ability to leave a hat + * @list: list this aa_task_context is on + * @task: task that the aa_task_context confines + * @rcu: rcu head used when freeing the aa_task_context + * @caps_logged: caps that have previously generated log entries + * + * Contains the task's current profile (which could change due to + * change_hat). Plus the hat_magic needed during change_hat. + */ +struct aa_task_context { + struct aa_profile *profile; /* The current profile */ + u64 hat_magic; /* used with change_hat */ + struct list_head list; + struct task_struct *task; + struct rcu_head rcu; + kernel_cap_t caps_logged; +}; + +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 *name; + char *buffer; + union { + int mask; + int capability; + struct { + const char *name2; + char *buffer2; + }; + struct iattr *iattr; + va_list vaval; + }; +}; + +/* audit types */ +#define AA_MANGLE_NAME 32 +#define AA_MANGLE_NAME2 64 +#define AA_AUDITTYPE_FILE (1 | AA_MANGLE_NAME) +#define AA_AUDITTYPE_DIR (2 | AA_MANGLE_NAME) +#define AA_AUDITTYPE_ATTR (3 | AA_MANGLE_NAME) +#define AA_AUDITTYPE_XATTR (4 | AA_MANGLE_NAME) +#define AA_AUDITTYPE_LINK (5 | AA_MANGLE_NAME | AA_MANGLE_NAME2) +#define AA_AUDITTYPE_CAP 6 +#define AA_AUDITTYPE_MSG 7 +#define AA_AUDITTYPE_SYSCALL 8 + +/* 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 */ + +/* Flags for the permission check functions */ +#define AA_CHECK_FD 1 /* coming from a file descriptor */ +#define AA_CHECK_DIR 2 /* file type is directory */ +#define AA_CHECK_MANGLE 4 /* leave extra room for name mangling */ + +/* 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 *profile, gfp_t gfp, + const char *, ...) + __attribute__ ((format (printf, 3, 4))); +extern int aa_audit_syscallreject(struct aa_profile *profile, gfp_t gfp, + const char *); +extern int aa_audit(struct aa_profile *profile, struct aa_audit *); + +extern int aa_attr(struct aa_profile *profile, struct dentry *dentry, + struct vfsmount *mnt, struct iattr *iattr); +extern int aa_perm_xattr(struct aa_profile *profile, struct dentry *dentry, + struct vfsmount *mnt, const char *operation, int mask, + int check); +extern int aa_capability(struct aa_task_context *cxt, int cap); +extern int aa_perm(struct aa_profile *profile, struct dentry *dentry, + struct vfsmount *mnt, int mask, int check); +extern int aa_perm_dir(struct aa_profile *profile, struct dentry *dentry, + struct vfsmount *mnt, const char *operation, int mask); +extern int aa_perm_path(struct aa_profile *, const char *, int); +extern int aa_link(struct aa_profile *profile, + struct dentry *link, struct vfsmount *link_mnt, + struct dentry *target, struct vfsmount *target_mnt); +extern int aa_clone(struct task_struct *task); +extern int aa_register(struct linux_binprm *bprm); +extern void aa_release(struct task_struct *task); +extern int aa_change_hat(const char *id, u64 hat_magic); +extern struct aa_profile *__aa_find_profile(const char *name, + struct list_head *list); +extern struct aa_profile *__aa_replace_profile(struct task_struct *task, + struct aa_profile *profile, + u32 hat_magic); +extern struct aa_task_context *lock_task_and_profiles(struct task_struct *task, + struct aa_profile *profile); +extern void aa_change_task_context(struct task_struct *task, + struct aa_task_context *new_cxt, + struct aa_profile *profile, u64 hat_magic); +extern int aa_may_ptrace(struct aa_task_context *cxt, + struct aa_profile *tracee); + +/* list.c */ +extern void aa_profilelist_release(void); + +/* module_interface.c */ +extern ssize_t aa_add_profile(void *, size_t); +extern ssize_t aa_replace_profile(void *, size_t); +extern ssize_t aa_remove_profile(const char *, size_t); +extern struct aa_profile *alloc_aa_profile(void); +extern void free_aa_profile(struct aa_profile *profile); +extern void free_aa_profile_kref(struct kref *kref); +extern void aa_unconfine_tasks(struct aa_profile *profile); + +/* procattr.c */ +extern int aa_getprocattr(struct aa_profile *profile, char **string, + unsigned *len); +extern int aa_setprocattr_changehat(char *args); +extern int aa_setprocattr_setprofile(struct task_struct *task, char *args); + +/* apparmorfs.c */ +extern int create_apparmorfs(void); +extern void destroy_apparmorfs(void); + +/* match.c */ +extern struct aa_dfa *aa_match_alloc(void); +extern void aa_match_free(struct aa_dfa *dfa); +extern int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size); +extern int verify_dfa(struct aa_dfa *dfa); +extern unsigned int aa_dfa_match(struct aa_dfa *dfa, const char *str); + +#endif /* __APPARMOR_H */ --- /dev/null +++ b/security/apparmor/apparmorfs.c @@ -0,0 +1,250 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor filesystem (part of securityfs) + */ + +#include +#include +#include +#include +#include + +#include "apparmor.h" +#include "inline.h" + +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 *profile; + 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. + */ + profile = aa_get_profile(current); + if (profile) { + aa_audit_message(NULL, GFP_KERNEL, "REJECTING access to " + "profile %s (%d profile %s active %s)", + msg, current->pid, profile->parent->name, + profile->name); + aa_put_profile(profile); + + data = ERR_PTR(-EPERM); + goto out; + } + + 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; +} + +/* apparmor/profiles */ +extern struct seq_operations apparmorfs_profiles_op; + +static int aa_profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &apparmorfs_profiles_op); +} + + +static int aa_profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +static struct file_operations apparmorfs_profiles_fops = { + .open = aa_profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = aa_profiles_release, +}; + +/* apparmor/matching */ +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char *matching = "pattern=aadfa"; + + return simple_read_from_buffer(buf, size, ppos, matching, + strlen(matching)); +} + +static struct file_operations apparmorfs_matching_fops = { + .read = aa_matching_read, +}; + +/* apparmor/.load */ +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"); + + error = PTR_ERR(data); + if (!IS_ERR(data)) { + error = aa_add_profile(data, size); + vfree(data); + } + + return error; +} + + +static struct file_operations apparmorfs_profile_load = { + .write = aa_profile_load +}; + +/* apparmor/.replace */ +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"); + + error = PTR_ERR(data); + if (!IS_ERR(data)) { + error = aa_replace_profile(data, size); + vfree(data); + } + + return error; +} + + +static struct file_operations apparmorfs_profile_replace = { + .write = aa_profile_replace +}; + +/* apparmor/.remove */ +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_remove_profile needs a null terminated string so 1 extra + * byte is allocated and the copied data is null terminated. + */ + data = aa_simple_write_to_buffer(buf, size + 1, size, pos, "removal"); + + error = PTR_ERR(data); + if (!IS_ERR(data)) { + data[size] = 0; + error = aa_remove_profile(data, size); + vfree(data); + } + + return error; +} + +static struct file_operations apparmorfs_profile_remove = { + .write = aa_profile_remove +}; + +static struct dentry *apparmor_dentry; + +static void aafs_remove(const char *name) +{ + struct dentry *dentry; + + dentry = lookup_one_len(name, apparmor_dentry, strlen(name)); + if (!IS_ERR(dentry)) { + securityfs_remove(dentry); + dput(dentry); + } +} + +static int aafs_create(const char *name, int mask, struct file_operations *fops) +{ + struct dentry *dentry; + + dentry = securityfs_create_file(name, S_IFREG | mask, apparmor_dentry, + NULL, fops); + + return IS_ERR(dentry) ? PTR_ERR(dentry) : 0; +} + +void destroy_apparmorfs(void) +{ + if (apparmor_dentry) { + aafs_remove(".remove"); + aafs_remove(".replace"); + aafs_remove(".load"); + aafs_remove("matching"); + aafs_remove("profiles"); + securityfs_remove(apparmor_dentry); + apparmor_dentry = NULL; + } +} + +int create_apparmorfs(void) +{ + int error; + + if (apparmor_dentry) { + AA_ERROR("%s: AppArmor securityfs already exists\n", + __FUNCTION__); + return -EEXIST; + } + + apparmor_dentry = securityfs_create_dir("apparmor", NULL); + if (IS_ERR(apparmor_dentry)) { + error = PTR_ERR(apparmor_dentry); + apparmor_dentry = NULL; + goto error; + } + error = aafs_create("profiles", 0440, &apparmorfs_profiles_fops); + if (error) + goto error; + error = aafs_create("matching", 0444, &apparmorfs_matching_fops); + if (error) + goto error; + error = aafs_create(".load", 0640, &apparmorfs_profile_load); + if (error) + goto error; + error = aafs_create(".replace", 0640, &apparmorfs_profile_replace); + if (error) + goto error; + error = aafs_create(".remove", 0640, &apparmorfs_profile_remove); + if (error) + goto error; + + return 0; + +error: + destroy_apparmorfs(); + AA_ERROR("Error creating AppArmor securityfs\n"); + return error; +} + --- /dev/null +++ b/security/apparmor/inline.h @@ -0,0 +1,219 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __INLINE_H +#define __INLINE_H + +#include + +static inline int mediated_filesystem(struct inode *inode) +{ + return !(inode->i_sb->s_flags & MS_NOUSER); +} + +static inline struct aa_task_context *aa_task_context(struct task_struct *task) +{ + return rcu_dereference((struct aa_task_context *)task->security); +} + +/** + * aa_dup_profile - increment refcount on profile @p + * @p: profile + */ +static inline struct aa_profile *aa_dup_profile(struct aa_profile *p) +{ + if (p) + kref_get(&(p->parent->count)); + + return p; +} + +/** + * aa_put_profile - decrement refcount on profile @p + * @p: profile + */ +static inline void aa_put_profile(struct aa_profile *p) +{ + if (p) + kref_put(&p->parent->count, free_aa_profile_kref); +} + +static inline struct aa_profile *aa_get_profile(struct task_struct *task) +{ + struct aa_task_context *cxt; + struct aa_profile *profile = NULL; + + rcu_read_lock(); + cxt = aa_task_context(task); + if (cxt) { + profile = cxt->profile; + aa_dup_profile(profile); + } + rcu_read_unlock(); + + return profile; +} + +static inline struct aa_profile *aa_find_profile(const char *name) +{ + struct aa_profile *profile = NULL; + + read_lock(&profile_list_lock); + profile = aa_dup_profile(__aa_find_profile(name, &profile_list)); + read_unlock(&profile_list_lock); + + return profile; +} + +static inline struct aa_task_context *aa_alloc_task_context(gfp_t flags) +{ + struct aa_task_context *cxt; + + cxt = kzalloc(sizeof(*cxt), flags); + if (cxt) { + INIT_LIST_HEAD(&cxt->list); + INIT_RCU_HEAD(&cxt->rcu); + } + + return cxt; +} + +static inline void aa_free_task_context(struct aa_task_context *cxt) +{ + if (cxt) { + aa_put_profile(cxt->profile); + kfree(cxt); + } +} + +/** + * lock_profile - lock a profile + * @profile: the profile to lock + * + * While the profile is locked, local interrupts are disabled. This also + * gives us RCU reader safety. + */ +static inline void lock_profile(struct aa_profile *profile) +{ + /* We always lock top-level profiles instead of children. */ + if (profile) + profile = profile->parent; + + /* + * Lock the profile. + * + * Need to disable interrupts here because this lock is used in + * the task_free_security hook, which may run in RCU context. + */ + if (profile) + spin_lock_irqsave(&profile->lock, profile->int_flags); +} + +/** + * unlock_profile - unlock a profile + * @profile: the profile to unlock + */ +static inline void unlock_profile(struct aa_profile *profile) +{ + /* We always lock top-level profiles instead of children. */ + if (profile) + profile = profile->parent; + + /* Unlock the profile. */ + if (profile) + spin_unlock_irqrestore(&profile->lock, profile->int_flags); +} + +/** + * lock_both_profiles - lock two profiles in a deadlock-free way + * @profile1: profile to lock (may be NULL) + * @profile2: profile to lock (may be NULL) + * + * The order in which profiles are passed into lock_both_profiles() / + * unlock_both_profiles() does not matter. + * While the profile is locked, local interrupts are disabled. This also + * gives us RCU reader safety. + */ +static inline void lock_both_profiles(struct aa_profile *profile1, + struct aa_profile *profile2) +{ + /* We always lock top-level profiles instead of children. */ + if (profile1) + profile1 = profile1->parent; + if (profile2) + profile2 = profile2->parent; + + /* + * Lock the two profiles. + * + * We need to disable interrupts because the profile locks are + * used in the task_free_security hook, which may run in RCU + * context. + * + * Do not nest spin_lock_irqsave()/spin_unlock_irqresore(): + * interrupts only need to be turned off once. + */ + if (!profile1 || profile1 == profile2) { + if (profile2) + spin_lock_irqsave(&profile2->lock, profile2->int_flags); + } else if (profile1 > profile2) { + /* profile1 cannot be NULL here. */ + spin_lock_irqsave(&profile1->lock, profile1->int_flags); + if (profile2) + spin_lock(&profile2->lock); + + } else { + /* profile2 cannot be NULL here. */ + spin_lock_irqsave(&profile2->lock, profile2->int_flags); + spin_lock(&profile1->lock); + } +} + +/** + * unlock_both_profiles - unlock two profiles in a deadlock-free way + * @profile1: profile to unlock (may be NULL) + * @profile2: profile to unlock (may be NULL) + * + * The order in which profiles are passed into lock_both_profiles() / + * unlock_both_profiles() does not matter. + * While the profile is locked, local interrupts are disabled. This also + * gives us RCU reader safety. + */ +static inline void unlock_both_profiles(struct aa_profile *profile1, + struct aa_profile *profile2) +{ + /* We always lock top-level profiles instead of children. */ + if (profile1) + profile1 = profile1->parent; + if (profile2) + profile2 = profile2->parent; + + /* Unlock the two profiles. */ + if (!profile1 || profile1 == profile2) { + if (profile2) + spin_unlock_irqrestore(&profile2->lock, + profile2->int_flags); + } else if (profile1 > profile2) { + /* profile1 cannot be NULL here. */ + if (profile2) + spin_unlock(&profile2->lock); + spin_unlock_irqrestore(&profile1->lock, profile1->int_flags); + } else { + /* profile2 cannot be NULL here. */ + spin_unlock(&profile1->lock); + spin_unlock_irqrestore(&profile2->lock, profile2->int_flags); + } +} + +static inline unsigned int aa_match(struct aa_dfa *dfa, const char *pathname) +{ + return dfa ? aa_dfa_match(dfa, pathname) : 0; +} + +#endif /* __INLINE_H__ */ --- /dev/null +++ b/security/apparmor/list.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor Profile List Management + */ + +#include +#include "apparmor.h" +#include "inline.h" + +/* list of all profiles and lock */ +LIST_HEAD(profile_list); +rwlock_t profile_list_lock = RW_LOCK_UNLOCKED; + +/** + * __aa_find_profile - look up a profile on the profile list + * @name: name of profile to find + * @head: list to search + * + * Returns a pointer to the profile on the list, or NULL if no profile + * called @name exists. The caller must hold the profile_list_lock. + */ +struct aa_profile *__aa_find_profile(const char *name, struct list_head *head) +{ + struct aa_profile *profile; + + list_for_each_entry(profile, head, list) { + if (!strcmp(profile->name, name)) + return profile; + } + + return NULL; +} + +/** + * aa_profilelist_release - Remove all profiles from profile_list + */ +void aa_profilelist_release(void) +{ + struct aa_profile *p, *tmp; + + write_lock(&profile_list_lock); + list_for_each_entry_safe(p, tmp, &profile_list, list) { + list_del_init(&p->list); + aa_put_profile(p); + } + write_unlock(&profile_list_lock); +} + +static void *p_start(struct seq_file *f, loff_t *pos) +{ + struct aa_profile *node; + loff_t l = *pos; + + read_lock(&profile_list_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_list_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; +} + +/* Used in apparmorfs.c */ +struct seq_operations apparmorfs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; --- /dev/null +++ b/security/apparmor/locking.txt @@ -0,0 +1,59 @@ +Locking in AppArmor +=================== + +Lock hierarchy: + + aa_interface_lock + profile_list_lock + aa_profile->lock + task_lock() + + +Which lock protects what? + + /-----------------------+-------------------------------\ + | Variable | Lock | + >-----------------------+-------------------------------< + | profile_list | profile_list_lock | + +-----------------------+-------------------------------+ + | aa_profile | (reference count) | + +-----------------------+-------------------------------+ + | aa_profile-> | aa_profile->lock | + | isstale, | | + | task_contexts | | + +-----------------------+-------------------------------+ + | task_struct->security | read: RCU | + | | write: task_lock() | + +-----------------------+-------------------------------+ + | aa_profile->sub | handle on the profile (list | + | | is never modified) | + \-----------------------+-------------------------------/ + +(Obviously, the list_heads embedded in data structures are always +protected with the lock that also protects the list.) + +When moving a task context from one profile to another, we grab both +profile locks with lock_both_profiles(). This ensures that both locks +are always taken in the same order, and so we won't deadlock. + +Since task_struct->security is RCU protected the aa_task_struct it +references is only guarenteed to exist for the rcu cycle. Where +aa_task_context->profile is needed in blocking operations the +profile's reference count is incremented and the profile reference +is used. + +Profiles on profile_list are never stale: when a profile becomes stale, +it is removed from profile_list at the same time (under profile_list_lock +and aa_profile->lock). + +The aa_interface_lock is taken whenever user-space modifies the profile +list, and can sleep. This ensures that profile loading/replacement/removal +won't race with itself. We release the profile_list_lock as soon as +possible to avoid stalling exec during profile loading/replacement/removal. + +lock_dep reports a false 'possible irq lock inversion dependency detected' +when the profile lock is taken in aa_release. This is due to that the +task_lock is often taken inside the profile lock but other kernel code +takes the task_lock with interrupts enabled. A deadlock will not actually +occur because apparmor does not take the task_lock in hard_irq or soft_irq +context. --- /dev/null +++ b/security/apparmor/procattr.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor /proc/pid/attr handling + */ + +#include "apparmor.h" +#include "inline.h" + +int aa_getprocattr(struct aa_profile *profile, char **string, unsigned *len) +{ + char *str; + + if (profile) { + const char *mode_str = PROFILE_COMPLAIN(profile) ? + " (complain)" : " (enforce)"; + + *len = ((profile != profile->parent) ? + strlen(profile->parent->name) + 1 : 0) + + strlen(mode_str) + strlen(profile->name) + 1; + str = kmalloc(*len, GFP_ATOMIC); + if (!str) + return -ENOMEM; + + if (profile != profile->parent) { + memcpy(str, profile->parent->name, + strlen(profile->parent->name)); + str += strlen(profile->parent->name); + *str++ = '^'; + } + memcpy(str, profile->name, strlen(profile->name)); + str += strlen(profile->name); + memcpy(str, mode_str, strlen(mode_str)); + str += strlen(mode_str); + *str++ = '\n'; + str -= *len; + } else { + const char *unconfined_str = "unconfined\n"; + + *len = strlen(unconfined_str); + str = kmalloc(*len, GFP_ATOMIC); + if (!str) + return -ENOMEM; + + memcpy(str, unconfined_str, *len); + } + *string = str; + + return 0; +} + +int aa_setprocattr_changehat(char *args) +{ + char *hat; + u64 magic; + + magic = simple_strtoull(args, &hat, 16); + if (hat == args || *hat != '^') { + AA_ERROR("change_hat: Invalid input '%s'", args); + return -EINVAL; + } + hat++; /* skip ^ */ + if (!*hat) + hat = NULL; + if (!hat && !magic) { + AA_ERROR("change_hat: Invalid input, NULL hat and NULL magic"); + return -EINVAL; + } + + AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n", + __FUNCTION__, magic, hat ? hat : NULL); + + return aa_change_hat(hat, magic); +} + +int aa_setprocattr_setprofile(struct task_struct *task, char *args) +{ + struct aa_profile *old_profile, *new_profile; + + AA_DEBUG("%s: current %d\n", + __FUNCTION__, current->pid); + +repeat: + if (strcmp(args, "unconfined") == 0) + new_profile = NULL; + else { + new_profile = aa_find_profile(args); + if (!new_profile) { + aa_audit_message(NULL, GFP_KERNEL, "Unable to switch " + "task %d to profile '%s'. No such " + "profile.", + task->pid, args); + + return -EINVAL; + } + } + + old_profile = __aa_replace_profile(task, new_profile, 0); + if (IS_ERR(old_profile)) { + int error; + + aa_put_profile(new_profile); + error = PTR_ERR(old_profile); + if (error == -ESTALE) + goto repeat; + return error; + } + + if (new_profile) { + aa_audit_message(NULL, GFP_KERNEL, "Switching task %d profile " + "%s active %s to new profile %s", + task->pid, old_profile ? + old_profile->parent->name : "unconfined", + old_profile ? old_profile->name : "unconfined", + args); + } else { + if (old_profile) { + aa_audit_message(NULL, GFP_KERNEL, "Unconfining task " + "%d profile %s active %s", + task->pid, old_profile->parent->name, + old_profile->name); + } else { + aa_audit_message(NULL, GFP_KERNEL, "task %d is already " + "unconfined", + task->pid); + } + } + + aa_put_profile(old_profile); + aa_put_profile(new_profile); + + return 0; +} -- - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/