2007-05-13 03:59:25 +00:00
|
|
|
From: John Johansen <jjohansen@suse.de>
|
|
|
|
Subject: AppArmor: Module and LSM hooks
|
|
|
|
|
|
|
|
Module parameters, LSM hooks, initialization and teardown.
|
|
|
|
|
|
|
|
Signed-off-by: John Johansen <jjohansen@suse.de>
|
|
|
|
Signed-off-by: Andreas Gruenbacher <agruen@suse.de>
|
|
|
|
|
2007-05-12 22:13:13 +00:00
|
|
|
Index: b/security/apparmor/lsm.c
|
|
|
|
===================================================================
|
2007-04-11 00:15:58 +00:00
|
|
|
--- /dev/null
|
|
|
|
+++ b/security/apparmor/lsm.c
|
2007-05-09 13:29:44 +00:00
|
|
|
@@ -0,0 +1,790 @@
|
2007-04-11 00:15:58 +00:00
|
|
|
+/*
|
|
|
|
+ * 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 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 <linux/ctype.h>
|
2007-04-27 19:02:03 +00:00
|
|
|
+#include <linux/sysctl.h>
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+#include "apparmor.h"
|
|
|
|
+#include "inline.h"
|
|
|
|
+
|
|
|
|
+static int param_set_aabool(const char *val, struct kernel_param *kp);
|
|
|
|
+static int param_get_aabool(char *buffer, struct kernel_param *kp);
|
|
|
|
+#define param_check_aabool(name, p) __param_check(name, p, int)
|
|
|
|
+
|
|
|
|
+static int param_set_aauint(const char *val, struct kernel_param *kp);
|
|
|
|
+static int param_get_aauint(char *buffer, struct kernel_param *kp);
|
|
|
|
+#define param_check_aauint(name, p) __param_check(name, p, int)
|
|
|
|
+
|
|
|
|
+/* Flag values, also controllable via /sys/module/apparmor/parameters
|
|
|
|
+ * We define special types as we want to do additional mediation.
|
|
|
|
+ *
|
|
|
|
+ * 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, aabool, S_IRUSR | S_IWUSR);
|
|
|
|
+MODULE_PARM_DESC(apparmor_complain, "Toggle AppArmor complain mode");
|
|
|
|
+
|
|
|
|
+/* Debug mode */
|
|
|
|
+int apparmor_debug = 0;
|
|
|
|
+module_param_named(debug, apparmor_debug, aabool, S_IRUSR | S_IWUSR);
|
|
|
|
+MODULE_PARM_DESC(apparmor_debug, "Toggle AppArmor debug mode");
|
|
|
|
+
|
|
|
|
+/* Audit mode */
|
|
|
|
+int apparmor_audit = 0;
|
|
|
|
+module_param_named(audit, apparmor_audit, aabool, S_IRUSR | S_IWUSR);
|
|
|
|
+MODULE_PARM_DESC(apparmor_audit, "Toggle AppArmor audit mode");
|
|
|
|
+
|
|
|
|
+/* Syscall logging mode */
|
|
|
|
+int apparmor_logsyscall = 0;
|
|
|
|
+module_param_named(logsyscall, apparmor_logsyscall, aabool, S_IRUSR | S_IWUSR);
|
|
|
|
+MODULE_PARM_DESC(apparmor_logsyscall, "Toggle AppArmor logsyscall mode");
|
|
|
|
+
|
|
|
|
+/* Maximum pathname length before accesses will start getting rejected */
|
|
|
|
+unsigned int apparmor_path_max = 2 * PATH_MAX;
|
|
|
|
+module_param_named(path_max, apparmor_path_max, aauint, S_IRUSR | S_IWUSR);
|
|
|
|
+MODULE_PARM_DESC(apparmor_path_max, "Maximum pathname length allowed");
|
|
|
|
+
|
|
|
|
+static int param_set_aabool(const char *val, struct kernel_param *kp)
|
|
|
|
+{
|
|
|
|
+ if (aa_task_context(current))
|
|
|
|
+ return -EPERM;
|
|
|
|
+ return param_set_bool(val, kp);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int param_get_aabool(char *buffer, struct kernel_param *kp)
|
|
|
|
+{
|
|
|
|
+ if (aa_task_context(current))
|
|
|
|
+ return -EPERM;
|
|
|
|
+ return param_get_bool(buffer, kp);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int param_set_aauint(const char *val, struct kernel_param *kp)
|
|
|
|
+{
|
|
|
|
+ if (aa_task_context(current))
|
|
|
|
+ return -EPERM;
|
|
|
|
+ return param_set_uint(val, kp);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int param_get_aauint(char *buffer, struct kernel_param *kp)
|
|
|
|
+{
|
|
|
|
+ if (aa_task_context(current))
|
|
|
|
+ return -EPERM;
|
|
|
|
+ return param_get_uint(buffer, kp);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int aa_reject_syscall(struct task_struct *task, gfp_t flags,
|
|
|
|
+ const char *name)
|
|
|
|
+{
|
|
|
|
+ struct aa_profile *profile = aa_get_profile(task);
|
|
|
|
+ int error = 0;
|
|
|
|
+
|
|
|
|
+ if (profile) {
|
|
|
|
+ error = aa_audit_syscallreject(profile, flags, name);
|
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_ptrace(struct task_struct *parent,
|
|
|
|
+ struct task_struct *child)
|
|
|
|
+{
|
|
|
|
+ struct aa_task_context *cxt;
|
|
|
|
+ struct aa_task_context *child_cxt;
|
|
|
|
+ struct aa_profile *child_profile;
|
|
|
|
+ int error = 0;
|
|
|
|
+
|
2007-05-12 22:13:13 +00:00
|
|
|
+ /*
|
2007-04-11 00:15:58 +00:00
|
|
|
+ * parent can ptrace child when
|
|
|
|
+ * - parent is unconfined
|
2007-04-27 11:20:11 +00:00
|
|
|
+ * - parent & child are in the same namespace &&
|
|
|
|
+ * - parent is in complain mode
|
|
|
|
+ * - parent and child are confined by the same profile
|
|
|
|
+ * - parent profile has CAP_SYS_PTRACE
|
2007-04-11 00:15:58 +00:00
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ rcu_read_lock();
|
|
|
|
+ cxt = aa_task_context(parent);
|
|
|
|
+ child_cxt = aa_task_context(child);
|
|
|
|
+ child_profile = child_cxt ? child_cxt->profile : NULL;
|
2007-04-27 11:20:11 +00:00
|
|
|
+ if (cxt && (parent->nsproxy != child->nsproxy)) {
|
2007-05-09 13:29:44 +00:00
|
|
|
+ aa_audit_message(NULL, GFP_ATOMIC, "REJECTING ptrace across "
|
|
|
|
+ "namespace of %d by %d",
|
|
|
|
+ parent->pid, child->pid);
|
2007-04-27 11:20:11 +00:00
|
|
|
+ error = -EPERM;
|
|
|
|
+ } else {
|
|
|
|
+ error = aa_may_ptrace(cxt, child_profile);
|
|
|
|
+ if (cxt && PROFILE_COMPLAIN(cxt->profile)) {
|
2007-05-09 13:29:44 +00:00
|
|
|
+ aa_audit_message(cxt->profile, GFP_ATOMIC,
|
|
|
|
+ "LOGPROF-HINT ptrace pid=%d child=%d "
|
|
|
|
+ "(%d profile %s active %s)",
|
|
|
|
+ current->pid, child->pid, current->pid,
|
|
|
|
+ cxt->profile->parent->name,
|
|
|
|
+ cxt->profile->name);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ }
|
2007-04-27 11:20:11 +00:00
|
|
|
+ }
|
2007-04-11 00:15:58 +00:00
|
|
|
+ rcu_read_unlock();
|
|
|
|
+
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_capable(struct task_struct *task, int cap)
|
|
|
|
+{
|
|
|
|
+ int error;
|
|
|
|
+
|
|
|
|
+ /* cap_capable returns 0 on success, else -EPERM */
|
|
|
|
+ error = cap_capable(task, cap);
|
|
|
|
+
|
|
|
|
+ if (!error) {
|
|
|
|
+ struct aa_task_context *cxt;
|
|
|
|
+
|
|
|
|
+ rcu_read_lock();
|
|
|
|
+ cxt = aa_task_context(task);
|
|
|
|
+ if (cxt)
|
|
|
|
+ error = aa_capability(cxt, cap);
|
|
|
|
+ rcu_read_unlock();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_sysctl(struct ctl_table *table, int op)
|
|
|
|
+{
|
2007-04-27 19:02:03 +00:00
|
|
|
+ struct aa_profile *profile = aa_get_profile(current);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ int error = 0;
|
|
|
|
+
|
2007-04-27 19:02:03 +00:00
|
|
|
+ if (profile) {
|
|
|
|
+ char *buffer, *name;
|
|
|
|
+ int mask;
|
|
|
|
+
|
|
|
|
+ mask = 0;
|
|
|
|
+ if (op & 4)
|
|
|
|
+ mask |= MAY_READ;
|
|
|
|
+ if (op & 2)
|
|
|
|
+ mask |= MAY_WRITE;
|
|
|
|
+
|
|
|
|
+ error = -ENOMEM;
|
|
|
|
+ buffer = (char*)__get_free_page(GFP_KERNEL);
|
|
|
|
+ if (!buffer)
|
|
|
|
+ goto out;
|
|
|
|
+ name = sysctl_pathname(table, buffer, PAGE_SIZE);
|
|
|
|
+ if (name && name - buffer >= 5) {
|
|
|
|
+ name -= 5;
|
|
|
|
+ memcpy(name, "/proc", 5);
|
|
|
|
+ error = aa_perm_path(profile, name, mask);
|
|
|
|
+ }
|
|
|
|
+ free_page((unsigned long)buffer);
|
|
|
|
+ }
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
2007-04-27 19:02:03 +00:00
|
|
|
+out:
|
2007-04-11 00:15:58 +00:00
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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)
|
|
|
|
+{
|
|
|
|
+ return aa_reject_syscall(current, GFP_KERNEL, "mount");
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_umount(struct vfsmount *mnt, int flags)
|
|
|
|
+{
|
|
|
|
+ return aa_reject_syscall(current, GFP_KERNEL, "umount");
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_inode_mkdir(struct inode *dir, struct dentry *dentry,
|
|
|
|
+ struct vfsmount *mnt, int mask)
|
|
|
|
+{
|
|
|
|
+ struct aa_profile *profile;
|
|
|
|
+ int error = 0;
|
|
|
|
+
|
|
|
|
+ if (!mnt || !mediated_filesystem(dir))
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ profile = aa_get_profile(current);
|
|
|
|
+
|
|
|
|
+ if (profile)
|
|
|
|
+ error = aa_perm_dir(profile, dentry, mnt, "mkdir", MAY_WRITE);
|
|
|
|
+
|
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_inode_rmdir(struct inode *dir, struct dentry *dentry,
|
|
|
|
+ struct vfsmount *mnt)
|
|
|
|
+{
|
|
|
|
+ struct aa_profile *profile;
|
|
|
|
+ int error = 0;
|
|
|
|
+
|
|
|
|
+ if (!mnt || !mediated_filesystem(dir))
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ profile = aa_get_profile(current);
|
|
|
|
+
|
|
|
|
+ if (profile)
|
|
|
|
+ error = aa_perm_dir(profile, dentry, mnt, "rmdir", MAY_WRITE);
|
|
|
|
+
|
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int aa_permission(struct inode *inode, struct dentry *dentry,
|
|
|
|
+ struct vfsmount *mnt, int mask, int check)
|
|
|
|
+{
|
|
|
|
+ int error = 0;
|
|
|
|
+
|
|
|
|
+ if (mnt && mediated_filesystem(inode)) {
|
|
|
|
+ struct aa_profile *profile;
|
|
|
|
+
|
|
|
|
+ profile = aa_get_profile(current);
|
|
|
|
+ if (profile)
|
|
|
|
+ error = aa_perm(profile, dentry, mnt, mask, check);
|
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+ }
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_inode_create(struct inode *dir, struct dentry *dentry,
|
|
|
|
+ struct vfsmount *mnt, int mask)
|
|
|
|
+{
|
2007-05-09 13:29:44 +00:00
|
|
|
+ return aa_permission(dir, dentry, mnt, MAY_WRITE, 0);
|
2007-04-11 00:15:58 +00:00
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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 *profile;
|
|
|
|
+
|
|
|
|
+ if (!old_mnt || !new_mnt || !mediated_filesystem(dir))
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ profile = aa_get_profile(current);
|
|
|
|
+
|
|
|
|
+ if (profile)
|
|
|
|
+ error = aa_link(profile, new_dentry, new_mnt,
|
|
|
|
+ old_dentry, old_mnt);
|
|
|
|
+
|
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_inode_unlink(struct inode *dir, struct dentry *dentry,
|
|
|
|
+ struct vfsmount *mnt)
|
|
|
|
+{
|
2007-05-09 13:29:44 +00:00
|
|
|
+ int check = 0;
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ if (S_ISDIR(dentry->d_inode->i_mode))
|
|
|
|
+ check |= AA_CHECK_DIR;
|
|
|
|
+ return aa_permission(dir, dentry, mnt, MAY_WRITE, check);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_inode_symlink(struct inode *dir, struct dentry *dentry,
|
|
|
|
+ struct vfsmount *mnt, const char *old_name)
|
|
|
|
+{
|
2007-05-09 13:29:44 +00:00
|
|
|
+ return aa_permission(dir, dentry, mnt, MAY_WRITE, 0);
|
2007-04-11 00:15:58 +00:00
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_inode_mknod(struct inode *dir, struct dentry *dentry,
|
|
|
|
+ struct vfsmount *mnt, int mode, dev_t dev)
|
|
|
|
+{
|
2007-05-09 13:29:44 +00:00
|
|
|
+ return aa_permission(dir, dentry, mnt, MAY_WRITE, 0);
|
2007-04-11 00:15:58 +00:00
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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 *profile;
|
|
|
|
+ int error = 0;
|
|
|
|
+
|
|
|
|
+ if ((!old_mnt && !new_mnt) || !mediated_filesystem(old_dir))
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ profile = aa_get_profile(current);
|
|
|
|
+
|
|
|
|
+ if (profile) {
|
|
|
|
+ struct inode *inode = old_dentry->d_inode;
|
2007-05-09 13:29:44 +00:00
|
|
|
+ int check = 0;
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ if (inode && S_ISDIR(inode->i_mode))
|
|
|
|
+ check |= AA_CHECK_DIR;
|
|
|
|
+ if (old_mnt)
|
|
|
|
+ error = aa_perm(profile, old_dentry, old_mnt,
|
|
|
|
+ MAY_READ | MAY_WRITE, check);
|
|
|
|
+
|
|
|
|
+ if (!error && new_mnt) {
|
|
|
|
+ error = aa_perm(profile, new_dentry, new_mnt,
|
|
|
|
+ MAY_WRITE, check);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_inode_permission(struct inode *inode, int mask,
|
|
|
|
+ struct nameidata *nd)
|
|
|
|
+{
|
|
|
|
+ int check = 0;
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+ if (!nd || nd->flags & (LOOKUP_PARENT | LOOKUP_CONTINUE))
|
2007-04-11 00:15:58 +00:00
|
|
|
+ return 0;
|
|
|
|
+ mask &= (MAY_READ | MAY_WRITE | MAY_EXEC);
|
2007-05-09 13:29:44 +00:00
|
|
|
+ if (S_ISDIR(inode->i_mode)) {
|
|
|
|
+ check |= AA_CHECK_DIR;
|
|
|
|
+ /* allow traverse accesses to directories */
|
|
|
|
+ mask &= ~MAY_EXEC;
|
|
|
|
+ }
|
2007-04-11 00:15:58 +00:00
|
|
|
+ return aa_permission(inode, nd->dentry, nd->mnt, mask, check);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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 *profile;
|
|
|
|
+
|
|
|
|
+ profile = aa_get_profile(current);
|
|
|
|
+ /*
|
|
|
|
+ * Mediate any attempt to change attributes of a file
|
|
|
|
+ * (chmod, chown, chgrp, etc)
|
|
|
|
+ */
|
|
|
|
+ if (profile)
|
|
|
|
+ error = aa_attr(profile, dentry, mnt, iattr);
|
|
|
|
+
|
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int aa_xattr_permission(struct dentry *dentry, struct vfsmount *mnt,
|
2007-04-27 11:20:11 +00:00
|
|
|
+ const char *operation, int mask,
|
|
|
|
+ struct file *file)
|
2007-04-11 00:15:58 +00:00
|
|
|
+{
|
|
|
|
+ int error = 0;
|
|
|
|
+
|
|
|
|
+ if (mnt && mediated_filesystem(dentry->d_inode)) {
|
|
|
|
+ struct aa_profile *profile = aa_get_profile(current);
|
|
|
|
+ int check = file ? AA_CHECK_FD : 0;
|
|
|
|
+
|
|
|
|
+ if (profile)
|
2007-04-27 11:20:11 +00:00
|
|
|
+ error = aa_perm_xattr(profile, dentry, mnt, operation,
|
|
|
|
+ mask, check);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_inode_setxattr(struct dentry *dentry, struct vfsmount *mnt,
|
|
|
|
+ char *name, void *value, size_t size,
|
|
|
|
+ int flags, struct file *file)
|
|
|
|
+{
|
2007-04-27 11:20:11 +00:00
|
|
|
+ return aa_xattr_permission(dentry, mnt, "xattr set", MAY_WRITE, file);
|
2007-04-11 00:15:58 +00:00
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_inode_getxattr(struct dentry *dentry, struct vfsmount *mnt,
|
|
|
|
+ char *name, struct file *file)
|
|
|
|
+{
|
2007-04-27 11:20:11 +00:00
|
|
|
+ return aa_xattr_permission(dentry, mnt, "xattr get", MAY_READ, file);
|
2007-04-11 00:15:58 +00:00
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_inode_listxattr(struct dentry *dentry, struct vfsmount *mnt,
|
|
|
|
+ struct file *file)
|
|
|
|
+{
|
2007-04-27 11:20:11 +00:00
|
|
|
+ return aa_xattr_permission(dentry, mnt, "xattr list", MAY_READ, file);
|
2007-04-11 00:15:58 +00:00
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_inode_removexattr(struct dentry *dentry,
|
|
|
|
+ struct vfsmount *mnt, char *name,
|
|
|
|
+ struct file *file)
|
|
|
|
+{
|
2007-04-27 11:20:11 +00:00
|
|
|
+ return aa_xattr_permission(dentry, mnt, "xattr remove", MAY_WRITE,
|
|
|
|
+ file);
|
2007-04-11 00:15:58 +00:00
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_file_permission(struct file *file, int mask)
|
|
|
|
+{
|
|
|
|
+ struct aa_profile *profile;
|
|
|
|
+ struct aa_profile *file_profile = (struct aa_profile*)file->f_security;
|
|
|
|
+ int error = 0;
|
|
|
|
+
|
|
|
|
+ if (!file_profile)
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * If this file was opened under a different profile, we
|
|
|
|
+ * revalidate the access against the current profile.
|
|
|
|
+ */
|
|
|
|
+ profile = aa_get_profile(current);
|
|
|
|
+ if (profile && file_profile != profile) {
|
|
|
|
+ struct dentry *dentry = file->f_dentry;
|
|
|
|
+ struct vfsmount *mnt = file->f_vfsmnt;
|
|
|
|
+ struct inode *inode = dentry->d_inode;
|
2007-05-09 13:29:44 +00:00
|
|
|
+ int check = AA_CHECK_FD;
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * FIXME: We should remember which profiles we revalidated
|
|
|
|
+ * against.
|
|
|
|
+ */
|
|
|
|
+ if (S_ISDIR(inode->i_mode))
|
|
|
|
+ check |= AA_CHECK_DIR;
|
|
|
|
+ mask &= (MAY_READ | MAY_WRITE | MAY_EXEC);
|
|
|
|
+ error = aa_permission(inode, dentry, mnt, mask, check);
|
|
|
|
+ }
|
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_file_alloc_security(struct file *file)
|
|
|
|
+{
|
|
|
|
+ struct aa_profile *profile;
|
|
|
|
+
|
|
|
|
+ profile = aa_get_profile(current);
|
|
|
|
+ if (profile)
|
|
|
|
+ file->f_security = profile;
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void apparmor_file_free_security(struct file *file)
|
|
|
|
+{
|
|
|
|
+ struct aa_profile *file_profile = (struct aa_profile*)file->f_security;
|
|
|
|
+
|
|
|
|
+ aa_put_profile(file_profile);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static inline int aa_mmap(struct file *file, unsigned long prot,
|
|
|
|
+ unsigned long flags)
|
|
|
|
+{
|
|
|
|
+ struct dentry *dentry;
|
|
|
|
+ int mask = 0;
|
|
|
|
+
|
|
|
|
+ if (!file || !file->f_security)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ 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;
|
|
|
|
+
|
|
|
|
+ dentry = file->f_dentry;
|
|
|
|
+ return aa_permission(dentry->d_inode, dentry, file->f_vfsmnt, mask,
|
2007-05-09 13:29:44 +00:00
|
|
|
+ AA_CHECK_FD);
|
2007-04-11 00:15:58 +00:00
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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 *task)
|
|
|
|
+{
|
|
|
|
+ return aa_clone(task);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Called from IRQ context from RCU callback.
|
|
|
|
+ */
|
|
|
|
+static void apparmor_task_free_security(struct task_struct *task)
|
|
|
|
+{
|
|
|
|
+ aa_release(task);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_getprocattr(struct task_struct *task, char *name,
|
|
|
|
+ char **value)
|
|
|
|
+{
|
|
|
|
+ unsigned len;
|
|
|
|
+ int error;
|
|
|
|
+ struct aa_profile *profile;
|
|
|
|
+
|
|
|
|
+ /* AppArmor only supports the "current" process attribute */
|
|
|
|
+ if (strcmp(name, "current") != 0)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ /* must be task querying itself or admin */
|
|
|
|
+ if (current != task && !capable(CAP_SYS_ADMIN))
|
|
|
|
+ return -EPERM;
|
|
|
|
+
|
|
|
|
+ profile = aa_get_profile(task);
|
|
|
|
+ error = aa_getprocattr(profile, value, &len);
|
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+ if (!error)
|
|
|
|
+ error = len;
|
|
|
|
+
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int apparmor_setprocattr(struct task_struct *task, char *name,
|
|
|
|
+ void *value, size_t size)
|
|
|
|
+{
|
|
|
|
+ char *command, *args;
|
|
|
|
+ int error;
|
|
|
|
+
|
|
|
|
+ if (strcmp(name, "current") != 0 || size == 0 || size >= PAGE_SIZE)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ args = value;
|
|
|
|
+ args[size] = '\0';
|
|
|
|
+ args = strstrip(args);
|
|
|
|
+ command = strsep(&args, " ");
|
|
|
|
+ if (!args)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ while (isspace(*args))
|
|
|
|
+ args++;
|
|
|
|
+ if (!*args)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ if (strcmp(command, "changehat") == 0) {
|
|
|
|
+ if (current != task)
|
|
|
|
+ return -EACCES;
|
|
|
|
+ error = aa_setprocattr_changehat(args);
|
|
|
|
+ } else if (strcmp(command, "setprofile")) {
|
|
|
|
+ struct aa_profile *profile;
|
|
|
|
+
|
|
|
|
+ /* Only an unconfined process with admin capabilities
|
|
|
|
+ * may change the profile of another task.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ if (!capable(CAP_SYS_ADMIN))
|
|
|
|
+ return -EACCES;
|
|
|
|
+
|
|
|
|
+ profile = aa_get_profile(current);
|
|
|
|
+ if (profile) {
|
|
|
|
+ aa_put_profile(profile);
|
2007-05-09 13:29:44 +00:00
|
|
|
+ aa_audit_message(NULL, GFP_KERNEL, "Attempt by "
|
|
|
|
+ "confined task %d [user %d] to "
|
|
|
|
+ "assign profile to task %d",
|
|
|
|
+ current->pid, current->uid,
|
|
|
|
+ task->pid);
|
2007-04-11 00:15:58 +00:00
|
|
|
+ return -EACCES;
|
|
|
|
+ }
|
|
|
|
+ error = aa_setprocattr_setprofile(task, args);
|
|
|
|
+ } else {
|
|
|
|
+ AA_ERROR("Unknown setprocattr command '%.*s' "
|
2007-05-09 13:29:44 +00:00
|
|
|
+ "by task %d [user %d] for task %d",
|
2007-04-11 00:15:58 +00:00
|
|
|
+ size < 16 ? (int)size : 16,
|
|
|
|
+ command,
|
|
|
|
+ current->pid,
|
|
|
|
+ current->uid,
|
|
|
|
+ task->pid);
|
|
|
|
+ error = -EINVAL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!error)
|
|
|
|
+ error = size;
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+struct security_operations apparmor_ops = {
|
|
|
|
+ .ptrace = apparmor_ptrace,
|
2007-04-27 11:20:11 +00:00
|
|
|
+ .capget = cap_capget,
|
|
|
|
+ .capset_check = cap_capset_check,
|
|
|
|
+ .capset_set = cap_capset_set,
|
2007-04-11 00:15:58 +00:00
|
|
|
+ .sysctl = apparmor_sysctl,
|
|
|
|
+ .capable = apparmor_capable,
|
2007-04-27 11:20:11 +00:00
|
|
|
+ .syslog = cap_syslog,
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
2007-04-27 11:20:11 +00:00
|
|
|
+ .netlink_send = cap_netlink_send,
|
|
|
|
+ .netlink_recv = cap_netlink_recv,
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
2007-04-27 11:20:11 +00:00
|
|
|
+ .bprm_apply_creds = cap_bprm_apply_creds,
|
2007-04-11 00:15:58 +00:00
|
|
|
+ .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_symlink = apparmor_inode_symlink,
|
|
|
|
+ .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,
|
2007-04-27 11:20:11 +00:00
|
|
|
+ .task_post_setuid = cap_task_post_setuid,
|
|
|
|
+ .task_reparent_to_init = cap_task_reparent_to_init,
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ .getprocattr = apparmor_getprocattr,
|
|
|
|
+ .setprocattr = apparmor_setprocattr,
|
|
|
|
+};
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+static void info_message(const char *str)
|
|
|
|
+{
|
|
|
|
+ printk(KERN_INFO "AppArmor: %s", str);
|
|
|
|
+ aa_audit_message(NULL, GFP_KERNEL, "%s", str);
|
|
|
|
+}
|
|
|
|
+
|
2007-04-11 00:15:58 +00:00
|
|
|
+static int __init apparmor_init(void)
|
|
|
|
+{
|
|
|
|
+ int error;
|
|
|
|
+
|
|
|
|
+ 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;
|
|
|
|
+ }
|
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+ if (apparmor_complain)
|
|
|
|
+ info_message("AppArmor initialized: complainmode enabled");
|
|
|
|
+ else
|
|
|
|
+ info_message("AppArmor initialized");
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
|
|
|
+ return error;
|
|
|
|
+
|
|
|
|
+register_security_out:
|
|
|
|
+ free_null_complain_profile();
|
|
|
|
+
|
|
|
|
+alloc_out:
|
|
|
|
+ destroy_apparmorfs();
|
|
|
|
+
|
|
|
|
+createfs_out:
|
|
|
|
+ return error;
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void __exit apparmor_exit(void)
|
|
|
|
+{
|
|
|
|
+ /* Remove and release all the profiles on the profile list. */
|
|
|
|
+ mutex_lock(&aa_interface_lock);
|
|
|
|
+ write_lock(&profile_list_lock);
|
|
|
|
+ while (!list_empty(&profile_list)) {
|
|
|
|
+ struct aa_profile *profile =
|
|
|
|
+ list_entry(profile_list.next, struct aa_profile, list);
|
|
|
|
+
|
|
|
|
+ /* Remove the profile from each task context it is on. */
|
|
|
|
+ lock_profile(profile);
|
|
|
|
+ profile->isstale = 1;
|
|
|
|
+ aa_unconfine_tasks(profile);
|
|
|
|
+ unlock_profile(profile);
|
|
|
|
+
|
|
|
|
+ /* Release the profile itself. */
|
|
|
|
+ list_del_init(&profile->list);
|
|
|
|
+ aa_put_profile(profile);
|
|
|
|
+ }
|
|
|
|
+ write_unlock(&profile_list_lock);
|
|
|
|
+
|
|
|
|
+ /* FIXME: cleanup profiles references on files */
|
|
|
|
+
|
|
|
|
+ free_null_complain_profile();
|
|
|
|
+
|
2007-05-12 22:13:13 +00:00
|
|
|
+ /*
|
2007-04-11 00:15:58 +00:00
|
|
|
+ * Delay for an rcu cycle to make sure that all active task
|
|
|
|
+ * context readers have finished, and all profiles have been
|
|
|
|
+ * freed by their rcu callbacks.
|
|
|
|
+ */
|
|
|
|
+ synchronize_rcu();
|
|
|
|
+
|
|
|
|
+ destroy_apparmorfs();
|
|
|
|
+ mutex_unlock(&aa_interface_lock);
|
|
|
|
+
|
|
|
|
+ if (unregister_security(&apparmor_ops))
|
2007-05-09 13:29:44 +00:00
|
|
|
+ info_message("Unable to properly unregister AppArmor");
|
2007-04-11 00:15:58 +00:00
|
|
|
+
|
2007-05-09 13:29:44 +00:00
|
|
|
+ info_message("AppArmor protection removed");
|
2007-04-11 00:15:58 +00:00
|
|
|
+}
|
|
|
|
+
|
|
|
|
+module_init(apparmor_init);
|
|
|
|
+module_exit(apparmor_exit);
|
|
|
|
+
|
|
|
|
+MODULE_DESCRIPTION("AppArmor process confinement");
|
|
|
|
+MODULE_AUTHOR("Novell/Immunix, http://bugs.opensuse.org");
|
|
|
|
+MODULE_LICENSE("GPL");
|