mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-06 17:31:01 +01:00
1114 lines
31 KiB
Diff
1114 lines
31 KiB
Diff
From: John Johansen <jjohansen@suse.de>
|
|
Subject: AppArmor: all the rest
|
|
|
|
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 <jjohansen@suse.de>
|
|
Signed-off-by: Andreas Gruenbacher <agruen@suse.de>
|
|
|
|
---
|
|
security/apparmor/Kconfig | 9 +
|
|
security/apparmor/Makefile | 13 ++
|
|
security/apparmor/apparmor.h | 265 +++++++++++++++++++++++++++++++++++++++++
|
|
security/apparmor/apparmorfs.c | 252 ++++++++++++++++++++++++++++++++++++++
|
|
security/apparmor/inline.h | 211 ++++++++++++++++++++++++++++++++
|
|
security/apparmor/list.c | 94 ++++++++++++++
|
|
security/apparmor/locking.txt | 68 ++++++++++
|
|
security/apparmor/procattr.c | 155 +++++++++++++++++++++++
|
|
8 files changed, 1067 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
|
|
+ <http://forge.novell.com/modules/xfmod/project/?apparmor>
|
|
+ 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,265 @@
|
|
+/*
|
|
+ * 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 <linux/sched.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/binfmts.h>
|
|
+#include <linux/rcupdate.h>
|
|
+
|
|
+/*
|
|
+ * 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_CHANGE_PROFILE 0x40000000
|
|
+
|
|
+#define AA_EXEC_MODIFIERS (AA_EXEC_INHERIT | \
|
|
+ AA_EXEC_UNCONFINED | \
|
|
+ AA_EXEC_PROFILE)
|
|
+
|
|
+#define AA_VALID_PERM_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | \
|
|
+ AA_MAY_LINK | AA_EXEC_MODIFIERS | \
|
|
+ AA_EXEC_MMAP | AA_EXEC_UNSAFE | \
|
|
+ AA_CHANGE_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
|
|
+ * @name: the profiles name
|
|
+ * @file_rules: dfa containing the profiles file rules
|
|
+ * @list: list this profile is on
|
|
+ * @flags: flags controlling profile behavior
|
|
+ * @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 {
|
|
+ char *name;
|
|
+ struct aa_dfa *file_rules;
|
|
+ struct list_head list;
|
|
+ struct {
|
|
+ int complain;
|
|
+ int audit;
|
|
+ } flags;
|
|
+ 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
|
|
+ * @previous_profile: profile the task may return to
|
|
+ * @cookie: magic value the task must know for returning to @previous_profile
|
|
+ * @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;
|
|
+ struct aa_profile *previous_profile;
|
|
+ u64 cookie;
|
|
+ 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 {
|
|
+ const char *operation;
|
|
+ gfp_t gfp_mask;
|
|
+ const char *info;
|
|
+ const char *name;
|
|
+ const char *name2;
|
|
+ int requested_mask, denied_mask;
|
|
+ struct iattr *iattr;
|
|
+ pid_t task, parent;
|
|
+ u64 cookie;
|
|
+ int error_code;
|
|
+};
|
|
+
|
|
+/* 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
|
|
+
|
|
+/* 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 */
|
|
+
|
|
+/* lock subtypes so lockdep does not raise false dependencies */
|
|
+enum aa_lock_class {
|
|
+ aa_lock_normal,
|
|
+ aa_lock_nested,
|
|
+ aa_lock_task_release
|
|
+};
|
|
+
|
|
+/* 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, struct aa_audit *sa,
|
|
+ int type);
|
|
+void aa_audit_hint(struct aa_profile *profile, struct aa_audit *sa);
|
|
+void aa_audit_status(struct aa_profile *profile, struct aa_audit *sa);
|
|
+int aa_audit_reject(struct aa_profile *profile, struct aa_audit *sa);
|
|
+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, const char *operation,
|
|
+ struct dentry *dentry, struct vfsmount *mnt,
|
|
+ int mask, int check);
|
|
+extern int aa_capability(struct aa_task_context *cxt, int cap);
|
|
+extern int aa_perm(struct aa_profile *profile, const char *operation,
|
|
+ struct dentry *dentry, struct vfsmount *mnt, int mask,
|
|
+ int check);
|
|
+extern int aa_perm_dir(struct aa_profile *profile, const char *operation,
|
|
+ struct dentry *dentry, struct vfsmount *mnt,
|
|
+ int mask);
|
|
+extern int aa_perm_path(struct aa_profile *, const char *operation,
|
|
+ const char *name, 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 int aa_change_profile(const char *name, u64 cookie);
|
|
+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);
|
|
+extern struct aa_task_context *lock_task_and_profiles(struct task_struct *task,
|
|
+ struct aa_profile *profile);
|
|
+extern void unlock_task_and_profiles(struct task_struct *task,
|
|
+ struct aa_task_context *cxt,
|
|
+ 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 cookie,
|
|
+ struct aa_profile *previous_profile);
|
|
+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_changeprofile(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,252 @@
|
|
+/*
|
|
+ * 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 <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"
|
|
+
|
|
+static char *aa_simple_write_to_buffer(const char __user *userbuf,
|
|
+ size_t alloc_size, size_t copy_size,
|
|
+ loff_t *pos, const char *operation)
|
|
+{
|
|
+ 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) {
|
|
+ struct aa_audit sa;
|
|
+ memset(&sa, 0, sizeof(sa));
|
|
+ sa.operation = operation;
|
|
+ sa.gfp_mask = GFP_KERNEL;
|
|
+ sa.error_code = -EACCES;
|
|
+ data = ERR_PTR(aa_audit_reject(profile, &sa));
|
|
+ aa_put_profile(profile);
|
|
+ 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, "profile_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,
|
|
+ "profile_replace");
|
|
+
|
|
+ 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,
|
|
+ "profile_remove");
|
|
+
|
|
+ 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,211 @@
|
|
+/*
|
|
+ * 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 <linux/sched.h>
|
|
+
|
|
+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->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->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);
|
|
+ aa_put_profile(cxt->previous_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_nested(struct aa_profile *profile,
|
|
+ enum aa_lock_class lock_class)
|
|
+{
|
|
+ /*
|
|
+ * 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_nested(&profile->lock, profile->int_flags,
|
|
+ lock_class);
|
|
+}
|
|
+
|
|
+static inline void lock_profile(struct aa_profile *profile)
|
|
+{
|
|
+ lock_profile_nested(profile, aa_lock_normal);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * unlock_profile - unlock a profile
|
|
+ * @profile: the profile to unlock
|
|
+ */
|
|
+static inline void unlock_profile(struct aa_profile *profile)
|
|
+{
|
|
+ /* 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)
|
|
+{
|
|
+ /*
|
|
+ * 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_nested(&profile2->lock,
|
|
+ profile2->int_flags,
|
|
+ aa_lock_normal);
|
|
+ } else if (profile1 > profile2) {
|
|
+ /* profile1 cannot be NULL here. */
|
|
+ spin_lock_irqsave_nested(&profile1->lock, profile1->int_flags,
|
|
+ aa_lock_normal);
|
|
+ if (profile2)
|
|
+ spin_lock_nested(&profile2->lock, aa_lock_nested);
|
|
+
|
|
+ } else {
|
|
+ /* profile2 cannot be NULL here. */
|
|
+ spin_lock_irqsave_nested(&profile2->lock, profile2->int_flags,
|
|
+ aa_lock_normal);
|
|
+ spin_lock_nested(&profile1->lock, aa_lock_nested);
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * 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)
|
|
+{
|
|
+ /* 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 <linux/seq_file.h>
|
|
+#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,68 @@
|
|
+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.
|
|
+
|
|
+AppArmor uses lock subtyping to avoid false positives from lockdep. The
|
|
+profile lock is often taken nested, but it is guaranteed to be in a lock
|
|
+safe order and not the same lock when done, so it is safe.
|
|
+
|
|
+A third lock type (aa_lock_task_release) is given to the profile lock
|
|
+when it is taken in soft irq context during task release (aa_release).
|
|
+This is to avoid a false positive between the task lock and the profile
|
|
+lock. In task context the profile lock wraps the task lock with irqs
|
|
+off, but the kernel takes the task lock with irqs enabled. This won't
|
|
+result in a deadlock because for a deadlock to occur the kernel must
|
|
+take dead task A's lock (irqs on), the rcu callback hook freeing
|
|
+dead task A must be run and AppArmor must be changing the profile on
|
|
+dead task A. The kernel should not be taking a dead task's task_lock
|
|
+at the same time the task is being freed by task rcu cleanup other wise
|
|
+the task would not be out of its quiescent period.
|
|
--- /dev/null
|
|
+++ b/security/apparmor/procattr.c
|
|
@@ -0,0 +1,155 @@
|
|
+/*
|
|
+ * 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)";
|
|
+ int mode_len, name_len;
|
|
+
|
|
+ mode_len = strlen(mode_str);
|
|
+ name_len = strlen(profile->name);
|
|
+ *len = mode_len + name_len + 1;
|
|
+ str = kmalloc(*len, GFP_ATOMIC);
|
|
+ if (!str)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ memcpy(str, profile->name, name_len);
|
|
+ str += name_len;
|
|
+ memcpy(str, mode_str, mode_len);
|
|
+ str += mode_len;
|
|
+ *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;
|
|
+}
|
|
+
|
|
+static char *split_token_from_name(const char *op, char *args, u64 *cookie)
|
|
+{
|
|
+ char *name;
|
|
+
|
|
+ *cookie = simple_strtoull(args, &name, 16);
|
|
+ if ((name == args) || *name != '^') {
|
|
+ AA_ERROR("%s: Invalid input '%s'", op, args);
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ }
|
|
+
|
|
+ name++; /* skip ^ */
|
|
+ if (!*name)
|
|
+ name = NULL;
|
|
+ return name;
|
|
+}
|
|
+
|
|
+int aa_setprocattr_changehat(char *args)
|
|
+{
|
|
+ char *hat;
|
|
+ u64 cookie;
|
|
+
|
|
+ hat = split_token_from_name("change_hat", args, &cookie);
|
|
+ if (IS_ERR(hat))
|
|
+ return PTR_ERR(hat);
|
|
+
|
|
+ if (!hat && !cookie) {
|
|
+ AA_ERROR("change_hat: Invalid input, NULL hat and NULL magic");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n",
|
|
+ __FUNCTION__, cookie, hat ? hat : NULL);
|
|
+
|
|
+ return aa_change_hat(hat, cookie);
|
|
+}
|
|
+
|
|
+int aa_setprocattr_changeprofile(char *args)
|
|
+{
|
|
+ char *name;
|
|
+ u64 cookie;
|
|
+
|
|
+ name = split_token_from_name("change_profile", args, &cookie);
|
|
+ if (IS_ERR(name))
|
|
+ return PTR_ERR(name);
|
|
+
|
|
+ return aa_change_profile(name, cookie);
|
|
+}
|
|
+
|
|
+int aa_setprocattr_setprofile(struct task_struct *task, char *args)
|
|
+{
|
|
+ struct aa_profile *old_profile, *new_profile;
|
|
+ struct aa_audit sa;
|
|
+
|
|
+ memset(&sa, 0, sizeof(sa));
|
|
+ sa.operation = "profile_set";
|
|
+ sa.gfp_mask = GFP_KERNEL;
|
|
+ sa.task = task->pid;
|
|
+
|
|
+ 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) {
|
|
+ sa.name = args;
|
|
+ sa.info = "unknown profile";
|
|
+ aa_audit_reject(NULL, &sa);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ old_profile = __aa_replace_profile(task, new_profile);
|
|
+ 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) {
|
|
+ sa.name = args;
|
|
+ sa.name2 = old_profile ? old_profile->name :
|
|
+ "unconfined";
|
|
+ aa_audit_status(NULL, &sa);
|
|
+ } else {
|
|
+ if (old_profile) {
|
|
+ sa.name = "unconfined";
|
|
+ sa.name2 = old_profile->name;
|
|
+ aa_audit_status(NULL, &sa);
|
|
+ } else {
|
|
+ sa.info = "task is unconfined";
|
|
+ aa_audit_status(NULL, &sa);
|
|
+ }
|
|
+ }
|
|
+ aa_put_profile(old_profile);
|
|
+ aa_put_profile(new_profile);
|
|
+ return 0;
|
|
+}
|